<commit_before>// Built-in Lints
#![deny(warnings, missing_debug_implementations, missing_copy_implementations)]
// Clippy lints
#![allow(
    clippy::option_map_unwrap_or_else,
    clippy::option_map_unwrap_or,
    clippy::match_same_arms,
    clippy::type_complexity
)]
#![warn(
    clippy::option_unwrap_used,
    clippy::result_unwrap_used,
    clippy::print_stdout,
    clippy::wrong_pub_self_convention,
    clippy::mut_mut,
    clippy::non_ascii_literal,
    clippy::similar_names,
    clippy::unicode_not_nfc,
    clippy::enum_glob_use,
    clippy::if_not_else,
    clippy::items_after_statements,
    clippy::used_underscore_binding
)]
#![cfg_attr(test, allow(clippy::option_unwrap_used, clippy::result_unwrap_used))]
//! Provides functions for maintaining database schema.
//!
//! A database migration always provides procedures to update the schema, as well as to revert
//! itself. Diesel's migrations are versioned, and run in order. Diesel also takes care of tracking
//! which migrations have already been run automatically. Your migrations don't need to be
//! idempotent, as Diesel will ensure no migration is run twice unless it has been reverted.
//!
//! Migrations should be placed in a `/migrations` directory at the root of your project (the same
//! directory as `Cargo.toml`). When any of these functions are run, Diesel will search for the
//! migrations directory in the current directory and its parents, stopping when it finds the
//! directory containing `Cargo.toml`.
//!
//! Individual migrations should be a folder containing exactly two files, `up.sql` and `down.sql`.
//! `up.sql` will be used to run the migration, while `down.sql` will be used for reverting it. The
//! folder itself should have the structure `{version}_{migration_name}`. It is recommended that
//! you use the timestamp of creation for the version.
//!
//! Migrations can either be run with the CLI or embedded into the compiled application
//! and executed with code, for example right after establishing a database connection.
//! For more information, consult the [`embed_migrations!`](../macro.embed_migrations.html) macro.
//!
//! ## Example
//!
//! ```text
//! # Directory Structure
//! - 20151219180527_create_users
//!     - up.sql
//!     - down.sql
//! - 20160107082941_create_posts
//!     - up.sql
//!     - down.sql
//! ```
//!
//! ```sql
//! -- 20151219180527_create_users/up.sql
//! CREATE TABLE users (
//!   id SERIAL PRIMARY KEY,
//!   name VARCHAR NOT NULL,
//!   hair_color VARCHAR
//! );
//! ```
//!
//! ```sql
//! -- 20151219180527_create_users/down.sql
//! DROP TABLE users;
//! ```
//!
//! ```sql
//! -- 20160107082941_create_posts/up.sql
//! CREATE TABLE posts (
//!   id SERIAL PRIMARY KEY,
//!   user_id INTEGER NOT NULL,
//!   title VARCHAR NOT NULL,
//!   body TEXT
//! );
//! ```
//!
//! ```sql
//! -- 20160107082941_create_posts/down.sql
//! DROP TABLE posts;
//! ```
#[macro_use]
extern crate diesel;

#[cfg(feature = "barrel")]
extern crate barrel;

#[doc(hidden)]
pub mod connection;
pub mod migration;
#[doc(hidden)]
pub mod schema;

#[doc(inline)]
pub use self::connection::MigrationConnection;
#[doc(inline)]
pub use self::migration::*;
pub use diesel::migration::*;

use std::fs::DirEntry;
use std::io::{stdout, Write};

use self::schema::__diesel_schema_migrations::dsl::*;
use diesel::expression_methods::*;
use diesel::{Connection, QueryDsl, QueryResult, RunQueryDsl};

use std::env;
use std::path::{Path, PathBuf};

pub static TIMESTAMP_FORMAT: &str = "%Y-%m-%d-%H%M%S";

/// Runs all migrations that have not yet been run. This function will print all progress to
/// stdout. This function will return an `Err` if some error occurs reading the migrations, or if
/// any migration fails to run. Each migration is run in its own transaction, so some migrations
/// may be committed, even if a later migration fails to run.
///
/// It should be noted that this runs all migrations that have not already been run, regardless of
/// whether or not their version is later than the latest run migration. This is generally not a
/// problem, and eases the more common case of two developers generating independent migrations on
/// a branch. Whoever created the second one will eventually need to run the first when both
/// branches are merged.
///
/// See the [module level documentation](index.html) for information on how migrations should be
/// structured, and where Diesel will look for them by default.
pub fn run_pending_migrations<Conn>(conn: &Conn) -> Result<(), RunMigrationsError>
where
    Conn: MigrationConnection,
{
    let migrations_dir = find_migrations_directory()?;
    run_pending_migrations_in_directory(conn, &migrations_dir, &mut stdout())
}

#[doc(hidden)]
pub fn run_pending_migrations_in_directory<Conn>(
    conn: &Conn,
    migrations_dir: &Path,
    output: &mut dyn Write,
) -> Result<(), RunMigrationsError>
where
    Conn: MigrationConnection,
{
    let all_migrations = migrations_in_directory(migrations_dir)?;
    run_migrations(conn, all_migrations, output)
}

/// Compares migrations found in `migrations_dir` to those that have been applied.
/// Returns a list of pathbufs and whether they have been applied.
pub fn mark_migrations_in_directory<Conn>(
    conn: &Conn,
    migrations_dir: &Path,
) -> Result<Vec<(Box<dyn Migration>, bool)>, RunMigrationsError>
where
    Conn: MigrationConnection,
{
    let migrations = migrations_in_directory(migrations_dir)?;
    setup_database(conn)?;
    let already_run = conn.previously_run_migration_versions()?;
    let migrations = migrations
        .into_iter()
        .map(|m| {
            let applied = already_run.contains(&m.version().to_string());
            (m, applied)
        })
        .collect();
    Ok(migrations)
}

// Returns true if there are outstanding migrations in the migrations directory, otherwise
// returns false. Returns an `Err` if there are problems with migration setup.
///
/// See the [module level documentation](index.html) for information on how migrations should be
/// structured, and where Diesel will look for them by default.
pub fn any_pending_migrations<Conn>(conn: &Conn) -> Result<bool, RunMigrationsError>
where
    Conn: MigrationConnection,
{
    let migrations_dir = find_migrations_directory()?;
    any_pending_migrations_in_directory(conn, &migrations_dir)
}

pub fn any_pending_migrations_in_directory<Conn>(
    conn: &Conn,
    migrations_dir: &Path,
) -> Result<bool, RunMigrationsError>
where
    Conn: MigrationConnection,
{
    let all_migrations = migrations_in_directory(&migrations_dir)?;
    setup_database(conn)?;
    let already_run = conn.previously_run_migration_versions()?;

    let pending = all_migrations
        .into_iter()
        .any(|m| !already_run.contains(&m.version().to_string()));

    Ok(pending)
}

/// Reverts the last migration that was run. Returns the version that was reverted. Returns an
/// `Err` if no migrations have ever been run.
///
/// See the [module level documentation](index.html) for information on how migrations should be
/// structured, and where Diesel will look for them by default.
pub fn revert_latest_migration<Conn>(conn: &Conn) -> Result<String, RunMigrationsError>
where
    Conn: MigrationConnection,
{
    let migrations_dir = find_migrations_directory()?;
    revert_latest_migration_in_directory(conn, &migrations_dir)
}

pub fn revert_latest_migration_in_directory<Conn>(
    conn: &Conn,
    path: &Path,
) -> Result<String, RunMigrationsError>
where
    Conn: MigrationConnection,
{
    setup_database(conn)?;
    let latest_migration_version = conn
        .latest_run_migration_version()?
        .ok_or_else(|| RunMigrationsError::MigrationError(MigrationError::NoMigrationRun))?;
    revert_migration_with_version(conn, path, &latest_migration_version, &mut stdout())
        .map(|_| latest_migration_version)
}

#[doc(hidden)]
pub fn revert_migration_with_version<Conn: Connection>(
    conn: &Conn,
    migrations_dir: &Path,
    ver: &str,
    output: &mut dyn Write,
) -> Result<(), RunMigrationsError> {
    migration_with_version(migrations_dir, ver)
        .map_err(Into::into)
        .and_then(|m| revert_migration(conn, &m, output))
}

#[doc(hidden)]
pub fn run_migration_with_version<Conn>(
    conn: &Conn,
    migrations_dir: &Path,
    ver: &str,
    output: &mut dyn Write,
) -> Result<(), RunMigrationsError>
where
    Conn: MigrationConnection,
{
    migration_with_version(migrations_dir, ver)
        .map_err(Into::into)
        .and_then(|m| run_migration(conn, &*m, output))
}

fn migration_with_version(
    migrations_dir: &Path,
    ver: &str,
) -> Result<Box<dyn Migration>, MigrationError> {
    let all_migrations = migrations_in_directory(migrations_dir)?;
    let migration = all_migrations.into_iter().find(|m| m.version() == ver);
    match migration {
        Some(m) => Ok(m),
        None => Err(MigrationError::UnknownMigrationVersion(ver.into())),
    }
}

#[doc(hidden)]
pub fn setup_database<Conn: MigrationConnection>(conn: &Conn) -> QueryResult<usize> {
    conn.setup()
}

#[doc(hidden)]
pub fn migration_paths_in_directory(path: &Path) -> Result<Vec<DirEntry>, MigrationError> {
    path.read_dir()?
        .filter_map(|entry| {
            let entry = match entry {
                Ok(e) => e,
                Err(e) => return Some(Err(e.into())),
            };
            if entry.file_name().to_string_lossy().starts_with('.') {
                None
            } else {
                Some(Ok(entry))
            }
        })
        .collect()
}

fn migrations_in_directory(path: &Path) -> Result<Vec<Box<dyn Migration>>, MigrationError> {
    migration_paths_in_directory(path)?
        .iter()
        .map(|e| migration_from(e.path()))
        .collect()
}

/// Run all pending migrations in the given list. Apps should likely be calling
/// `run_pending_migrations` or `run_pending_migrations_in_directory` instead.
pub fn run_migrations<Conn, List>(
    conn: &Conn,
    migrations: List,
    output: &mut dyn Write,
) -> Result<(), RunMigrationsError>
where
    Conn: MigrationConnection,
    List: IntoIterator,
    List::Item: Migration,
{
    setup_database(conn)?;
    let already_run = conn.previously_run_migration_versions()?;
    let mut pending_migrations: Vec<_> = migrations
        .into_iter()
        .filter(|m| !already_run.contains(&m.version().to_string()))
        .collect();

    pending_migrations.sort_by(|a, b| a.version().cmp(b.version()));
    for migration in pending_migrations {
        run_migration(conn, &migration, output)?;
    }
    Ok(())
}

fn run_migration<Conn>(
    conn: &Conn,
    migration: &dyn Migration,
    output: &mut dyn Write,
) -> Result<(), RunMigrationsError>
where
    Conn: MigrationConnection,
{
    conn.transaction(|| {
        if migration.version() != "00000000000000" {
            writeln!(output, "Running migration {}", name(&migration))?;
        }
        if let Err(e) = migration.run(conn) {
            writeln!(
                output,
                "Executing migration script {}",
                file_name(&migration, "up.sql")
            )?;
            return Err(e);
        }
        conn.insert_new_migration(migration.version())?;
        Ok(())
    })
}

fn revert_migration<Conn: Connection>(
    conn: &Conn,
    migration: &dyn Migration,
    output: &mut dyn Write,
) -> Result<(), RunMigrationsError> {
    conn.transaction(|| {
        writeln!(output, "Rolling back migration {}", name(&migration))?;
        if let Err(e) = migration.revert(conn) {
            writeln!(
                output,
                "Executing migration script {}",
                file_name(&migration, "down.sql")
            )?;
            return Err(e);
        }
        let target = __diesel_schema_migrations.filter(version.eq(migration.version()));
        ::diesel::delete(target).execute(conn)?;
        Ok(())
    })
}

/// Returns the directory containing migrations. Will look at for
/// $PWD/migrations. If it is not found, it will search the parents of the
/// current directory, until it reaches the root directory.  Returns
/// `MigrationError::MigrationDirectoryNotFound` if no directory is found.
pub fn find_migrations_directory() -> Result<PathBuf, MigrationError> {
    search_for_migrations_directory(&env::current_dir()?)
}

/// Searches for the migrations directory relative to the given path. See
/// `find_migrations_directory` for more details.
pub fn search_for_migrations_directory(path: &Path) -> Result<PathBuf, MigrationError> {
    let migration_path = path.join("migrations");
    if migration_path.is_dir() {
        Ok(migration_path)
    } else {
        path.parent()
            .map(|p| search_for_migrations_directory(p))
            .unwrap_or_else(|| Err(MigrationError::MigrationDirectoryNotFound(path.into())))
            .map_err(|_| MigrationError::MigrationDirectoryNotFound(path.into()))
    }
}

#[cfg(test)]
mod tests {
    extern crate tempfile;

    use super::*;

    use self::tempfile::Builder;
    use std::fs;

    #[test]
    fn migration_directory_not_found_if_no_migration_dir_exists() {
        let dir = Builder::new().prefix("diesel").tempdir().unwrap();

        assert_eq!(
            Err(MigrationError::MigrationDirectoryNotFound),
            search_for_migrations_directory(dir.path())
        );
    }

    #[test]
    fn migration_directory_defaults_to_pwd_slash_migrations() {
        let dir = Builder::new().prefix("diesel").tempdir().unwrap();
        let temp_path = dir.path().canonicalize().unwrap();
        let migrations_path = temp_path.join("migrations");

        fs::create_dir(&migrations_path).unwrap();

        assert_eq!(
            Ok(migrations_path),
            search_for_migrations_directory(&temp_path)
        );
    }

    #[test]
    fn migration_directory_checks_parents() {
        let dir = Builder::new().prefix("diesel").tempdir().unwrap();
        let temp_path = dir.path().canonicalize().unwrap();
        let migrations_path = temp_path.join("migrations");
        let child_path = temp_path.join("child");

        fs::create_dir(&child_path).unwrap();
        fs::create_dir(&migrations_path).unwrap();

        assert_eq!(
            Ok(migrations_path),
            search_for_migrations_directory(&child_path)
        );
    }
}
<commit_msg>Fix broken test<commit_after>// Built-in Lints
#![deny(warnings, missing_debug_implementations, missing_copy_implementations)]
// Clippy lints
#![allow(
    clippy::option_map_unwrap_or_else,
    clippy::option_map_unwrap_or,
    clippy::match_same_arms,
    clippy::type_complexity
)]
#![warn(
    clippy::option_unwrap_used,
    clippy::result_unwrap_used,
    clippy::print_stdout,
    clippy::wrong_pub_self_convention,
    clippy::mut_mut,
    clippy::non_ascii_literal,
    clippy::similar_names,
    clippy::unicode_not_nfc,
    clippy::enum_glob_use,
    clippy::if_not_else,
    clippy::items_after_statements,
    clippy::used_underscore_binding
)]
#![cfg_attr(test, allow(clippy::option_unwrap_used, clippy::result_unwrap_used))]
//! Provides functions for maintaining database schema.
//!
//! A database migration always provides procedures to update the schema, as well as to revert
//! itself. Diesel's migrations are versioned, and run in order. Diesel also takes care of tracking
//! which migrations have already been run automatically. Your migrations don't need to be
//! idempotent, as Diesel will ensure no migration is run twice unless it has been reverted.
//!
//! Migrations should be placed in a `/migrations` directory at the root of your project (the same
//! directory as `Cargo.toml`). When any of these functions are run, Diesel will search for the
//! migrations directory in the current directory and its parents, stopping when it finds the
//! directory containing `Cargo.toml`.
//!
//! Individual migrations should be a folder containing exactly two files, `up.sql` and `down.sql`.
//! `up.sql` will be used to run the migration, while `down.sql` will be used for reverting it. The
//! folder itself should have the structure `{version}_{migration_name}`. It is recommended that
//! you use the timestamp of creation for the version.
//!
//! Migrations can either be run with the CLI or embedded into the compiled application
//! and executed with code, for example right after establishing a database connection.
//! For more information, consult the [`embed_migrations!`](../macro.embed_migrations.html) macro.
//!
//! ## Example
//!
//! ```text
//! # Directory Structure
//! - 20151219180527_create_users
//!     - up.sql
//!     - down.sql
//! - 20160107082941_create_posts
//!     - up.sql
//!     - down.sql
//! ```
//!
//! ```sql
//! -- 20151219180527_create_users/up.sql
//! CREATE TABLE users (
//!   id SERIAL PRIMARY KEY,
//!   name VARCHAR NOT NULL,
//!   hair_color VARCHAR
//! );
//! ```
//!
//! ```sql
//! -- 20151219180527_create_users/down.sql
//! DROP TABLE users;
//! ```
//!
//! ```sql
//! -- 20160107082941_create_posts/up.sql
//! CREATE TABLE posts (
//!   id SERIAL PRIMARY KEY,
//!   user_id INTEGER NOT NULL,
//!   title VARCHAR NOT NULL,
//!   body TEXT
//! );
//! ```
//!
//! ```sql
//! -- 20160107082941_create_posts/down.sql
//! DROP TABLE posts;
//! ```
#[macro_use]
extern crate diesel;

#[cfg(feature = "barrel")]
extern crate barrel;

#[doc(hidden)]
pub mod connection;
pub mod migration;
#[doc(hidden)]
pub mod schema;

#[doc(inline)]
pub use self::connection::MigrationConnection;
#[doc(inline)]
pub use self::migration::*;
pub use diesel::migration::*;

use std::fs::DirEntry;
use std::io::{stdout, Write};

use self::schema::__diesel_schema_migrations::dsl::*;
use diesel::expression_methods::*;
use diesel::{Connection, QueryDsl, QueryResult, RunQueryDsl};

use std::env;
use std::path::{Path, PathBuf};

pub static TIMESTAMP_FORMAT: &str = "%Y-%m-%d-%H%M%S";

/// Runs all migrations that have not yet been run. This function will print all progress to
/// stdout. This function will return an `Err` if some error occurs reading the migrations, or if
/// any migration fails to run. Each migration is run in its own transaction, so some migrations
/// may be committed, even if a later migration fails to run.
///
/// It should be noted that this runs all migrations that have not already been run, regardless of
/// whether or not their version is later than the latest run migration. This is generally not a
/// problem, and eases the more common case of two developers generating independent migrations on
/// a branch. Whoever created the second one will eventually need to run the first when both
/// branches are merged.
///
/// See the [module level documentation](index.html) for information on how migrations should be
/// structured, and where Diesel will look for them by default.
pub fn run_pending_migrations<Conn>(conn: &Conn) -> Result<(), RunMigrationsError>
where
    Conn: MigrationConnection,
{
    let migrations_dir = find_migrations_directory()?;
    run_pending_migrations_in_directory(conn, &migrations_dir, &mut stdout())
}

#[doc(hidden)]
pub fn run_pending_migrations_in_directory<Conn>(
    conn: &Conn,
    migrations_dir: &Path,
    output: &mut dyn Write,
) -> Result<(), RunMigrationsError>
where
    Conn: MigrationConnection,
{
    let all_migrations = migrations_in_directory(migrations_dir)?;
    run_migrations(conn, all_migrations, output)
}

/// Compares migrations found in `migrations_dir` to those that have been applied.
/// Returns a list of pathbufs and whether they have been applied.
pub fn mark_migrations_in_directory<Conn>(
    conn: &Conn,
    migrations_dir: &Path,
) -> Result<Vec<(Box<dyn Migration>, bool)>, RunMigrationsError>
where
    Conn: MigrationConnection,
{
    let migrations = migrations_in_directory(migrations_dir)?;
    setup_database(conn)?;
    let already_run = conn.previously_run_migration_versions()?;
    let migrations = migrations
        .into_iter()
        .map(|m| {
            let applied = already_run.contains(&m.version().to_string());
            (m, applied)
        })
        .collect();
    Ok(migrations)
}

// Returns true if there are outstanding migrations in the migrations directory, otherwise
// returns false. Returns an `Err` if there are problems with migration setup.
///
/// See the [module level documentation](index.html) for information on how migrations should be
/// structured, and where Diesel will look for them by default.
pub fn any_pending_migrations<Conn>(conn: &Conn) -> Result<bool, RunMigrationsError>
where
    Conn: MigrationConnection,
{
    let migrations_dir = find_migrations_directory()?;
    any_pending_migrations_in_directory(conn, &migrations_dir)
}

pub fn any_pending_migrations_in_directory<Conn>(
    conn: &Conn,
    migrations_dir: &Path,
) -> Result<bool, RunMigrationsError>
where
    Conn: MigrationConnection,
{
    let all_migrations = migrations_in_directory(&migrations_dir)?;
    setup_database(conn)?;
    let already_run = conn.previously_run_migration_versions()?;

    let pending = all_migrations
        .into_iter()
        .any(|m| !already_run.contains(&m.version().to_string()));

    Ok(pending)
}

/// Reverts the last migration that was run. Returns the version that was reverted. Returns an
/// `Err` if no migrations have ever been run.
///
/// See the [module level documentation](index.html) for information on how migrations should be
/// structured, and where Diesel will look for them by default.
pub fn revert_latest_migration<Conn>(conn: &Conn) -> Result<String, RunMigrationsError>
where
    Conn: MigrationConnection,
{
    let migrations_dir = find_migrations_directory()?;
    revert_latest_migration_in_directory(conn, &migrations_dir)
}

pub fn revert_latest_migration_in_directory<Conn>(
    conn: &Conn,
    path: &Path,
) -> Result<String, RunMigrationsError>
where
    Conn: MigrationConnection,
{
    setup_database(conn)?;
    let latest_migration_version = conn
        .latest_run_migration_version()?
        .ok_or_else(|| RunMigrationsError::MigrationError(MigrationError::NoMigrationRun))?;
    revert_migration_with_version(conn, path, &latest_migration_version, &mut stdout())
        .map(|_| latest_migration_version)
}

#[doc(hidden)]
pub fn revert_migration_with_version<Conn: Connection>(
    conn: &Conn,
    migrations_dir: &Path,
    ver: &str,
    output: &mut dyn Write,
) -> Result<(), RunMigrationsError> {
    migration_with_version(migrations_dir, ver)
        .map_err(Into::into)
        .and_then(|m| revert_migration(conn, &m, output))
}

#[doc(hidden)]
pub fn run_migration_with_version<Conn>(
    conn: &Conn,
    migrations_dir: &Path,
    ver: &str,
    output: &mut dyn Write,
) -> Result<(), RunMigrationsError>
where
    Conn: MigrationConnection,
{
    migration_with_version(migrations_dir, ver)
        .map_err(Into::into)
        .and_then(|m| run_migration(conn, &*m, output))
}

fn migration_with_version(
    migrations_dir: &Path,
    ver: &str,
) -> Result<Box<dyn Migration>, MigrationError> {
    let all_migrations = migrations_in_directory(migrations_dir)?;
    let migration = all_migrations.into_iter().find(|m| m.version() == ver);
    match migration {
        Some(m) => Ok(m),
        None => Err(MigrationError::UnknownMigrationVersion(ver.into())),
    }
}

#[doc(hidden)]
pub fn setup_database<Conn: MigrationConnection>(conn: &Conn) -> QueryResult<usize> {
    conn.setup()
}

#[doc(hidden)]
pub fn migration_paths_in_directory(path: &Path) -> Result<Vec<DirEntry>, MigrationError> {
    path.read_dir()?
        .filter_map(|entry| {
            let entry = match entry {
                Ok(e) => e,
                Err(e) => return Some(Err(e.into())),
            };
            if entry.file_name().to_string_lossy().starts_with('.') {
                None
            } else {
                Some(Ok(entry))
            }
        })
        .collect()
}

fn migrations_in_directory(path: &Path) -> Result<Vec<Box<dyn Migration>>, MigrationError> {
    migration_paths_in_directory(path)?
        .iter()
        .map(|e| migration_from(e.path()))
        .collect()
}

/// Run all pending migrations in the given list. Apps should likely be calling
/// `run_pending_migrations` or `run_pending_migrations_in_directory` instead.
pub fn run_migrations<Conn, List>(
    conn: &Conn,
    migrations: List,
    output: &mut dyn Write,
) -> Result<(), RunMigrationsError>
where
    Conn: MigrationConnection,
    List: IntoIterator,
    List::Item: Migration,
{
    setup_database(conn)?;
    let already_run = conn.previously_run_migration_versions()?;
    let mut pending_migrations: Vec<_> = migrations
        .into_iter()
        .filter(|m| !already_run.contains(&m.version().to_string()))
        .collect();

    pending_migrations.sort_by(|a, b| a.version().cmp(b.version()));
    for migration in pending_migrations {
        run_migration(conn, &migration, output)?;
    }
    Ok(())
}

fn run_migration<Conn>(
    conn: &Conn,
    migration: &dyn Migration,
    output: &mut dyn Write,
) -> Result<(), RunMigrationsError>
where
    Conn: MigrationConnection,
{
    conn.transaction(|| {
        if migration.version() != "00000000000000" {
            writeln!(output, "Running migration {}", name(&migration))?;
        }
        if let Err(e) = migration.run(conn) {
            writeln!(
                output,
                "Executing migration script {}",
                file_name(&migration, "up.sql")
            )?;
            return Err(e);
        }
        conn.insert_new_migration(migration.version())?;
        Ok(())
    })
}

fn revert_migration<Conn: Connection>(
    conn: &Conn,
    migration: &dyn Migration,
    output: &mut dyn Write,
) -> Result<(), RunMigrationsError> {
    conn.transaction(|| {
        writeln!(output, "Rolling back migration {}", name(&migration))?;
        if let Err(e) = migration.revert(conn) {
            writeln!(
                output,
                "Executing migration script {}",
                file_name(&migration, "down.sql")
            )?;
            return Err(e);
        }
        let target = __diesel_schema_migrations.filter(version.eq(migration.version()));
        ::diesel::delete(target).execute(conn)?;
        Ok(())
    })
}

/// Returns the directory containing migrations. Will look at for
/// $PWD/migrations. If it is not found, it will search the parents of the
/// current directory, until it reaches the root directory.  Returns
/// `MigrationError::MigrationDirectoryNotFound` if no directory is found.
pub fn find_migrations_directory() -> Result<PathBuf, MigrationError> {
    search_for_migrations_directory(&env::current_dir()?)
}

/// Searches for the migrations directory relative to the given path. See
/// `find_migrations_directory` for more details.
pub fn search_for_migrations_directory(path: &Path) -> Result<PathBuf, MigrationError> {
    let migration_path = path.join("migrations");
    if migration_path.is_dir() {
        Ok(migration_path)
    } else {
        path.parent()
            .map(|p| search_for_migrations_directory(p))
            .unwrap_or_else(|| Err(MigrationError::MigrationDirectoryNotFound(path.into())))
            .map_err(|_| MigrationError::MigrationDirectoryNotFound(path.into()))
    }
}

#[cfg(test)]
mod tests {
    extern crate tempfile;

    use super::*;

    use self::tempfile::Builder;
    use std::fs;

    #[test]
    fn migration_directory_not_found_if_no_migration_dir_exists() {
        let dir = Builder::new().prefix("diesel").tempdir().unwrap();

        assert_eq!(
            Err(MigrationError::MigrationDirectoryNotFound(dir.path().into())),
            search_for_migrations_directory(dir.path())
        );
    }

    #[test]
    fn migration_directory_defaults_to_pwd_slash_migrations() {
        let dir = Builder::new().prefix("diesel").tempdir().unwrap();
        let temp_path = dir.path().canonicalize().unwrap();
        let migrations_path = temp_path.join("migrations");

        fs::create_dir(&migrations_path).unwrap();

        assert_eq!(
            Ok(migrations_path),
            search_for_migrations_directory(&temp_path)
        );
    }

    #[test]
    fn migration_directory_checks_parents() {
        let dir = Builder::new().prefix("diesel").tempdir().unwrap();
        let temp_path = dir.path().canonicalize().unwrap();
        let migrations_path = temp_path.join("migrations");
        let child_path = temp_path.join("child");

        fs::create_dir(&child_path).unwrap();
        fs::create_dir(&migrations_path).unwrap();

        assert_eq!(
            Ok(migrations_path),
            search_for_migrations_directory(&child_path)
        );
    }
}
<|endoftext|>


<commit_before>// run-pass
#![feature(const_discriminant)]

use std::mem::{discriminant, Discriminant};

enum Test {
    A(u8),
    B,
    C { a: u8, b: u8 },
}

const TEST_A: Discriminant<Test> = discriminant(&Test::A(5));
const TEST_A_OTHER: Discriminant<Test> = discriminant(&Test::A(17));
const TEST_B: Discriminant<Test> = discriminant(&Test::B);

fn main() {
    assert_eq!(TEST_A, TEST_A_OTHER);
    assert_eq!(TEST_A, discriminant(&Test::A(17)));
    assert_eq!(TEST_B, discriminant(&Test::B));
    assert_ne!(TEST_A, TEST_B);
    assert_ne!(TEST_B, discriminant(&Test::C { a: 42, b: 7 }));
}
<commit_msg>prevent potential promotion in const_discriminant<commit_after>// run-pass
#![feature(const_discriminant)]

use std::mem::{discriminant, Discriminant};

fn identity<T>(x: T) -> T { x }

enum Test {
    A(u8),
    B,
    C { a: u8, b: u8 },
}

const TEST_A: Discriminant<Test> = discriminant(&Test::A(5));
const TEST_A_OTHER: Discriminant<Test> = discriminant(&Test::A(17));
const TEST_B: Discriminant<Test> = discriminant(&Test::B);

fn main() {
    assert_eq!(TEST_A, TEST_A_OTHER);
    assert_eq!(TEST_A, discriminant(identity(&Test::A(17))));
    assert_eq!(TEST_B, discriminant(identity(&Test::B)));
    assert_ne!(TEST_A, TEST_B);
    assert_ne!(TEST_B, discriminant(identity(&Test::C { a: 42, b: 7 })));
}
<|endoftext|>


<commit_before>// run-pass
#![feature(const_discriminant)]
#![allow(dead_code)]

use std::mem::{discriminant, Discriminant};

#[inline(never)]
fn identity<T>(x: T) -> T { x }

enum Test {
    A(u8),
    B,
    C { a: u8, b: u8 },
}

const TEST_A: Discriminant<Test> = discriminant(&Test::A(5));
const TEST_A_OTHER: Discriminant<Test> = discriminant(&Test::A(17));
const TEST_B: Discriminant<Test> = discriminant(&Test::B);

enum Void {}

enum SingleVariant {
    V,
    Never(Void),
}

const TEST_V: Discriminant<SingleVariant> = discriminant(&SingleVariant::V);

fn main() {
    assert_eq!(TEST_A, TEST_A_OTHER);
    assert_eq!(TEST_A, discriminant(identity(&Test::A(17))));
    assert_eq!(TEST_B, discriminant(identity(&Test::B)));
    assert_ne!(TEST_A, TEST_B);
    assert_ne!(TEST_B, discriminant(identity(&Test::C { a: 42, b: 7 })));

    assert_eq!(TEST_V, discriminant(identity(&SingleVariant::V)));
}
<commit_msg>explain the use of a custom identity function<commit_after>// run-pass
#![feature(const_discriminant)]
#![allow(dead_code)]

use std::mem::{discriminant, Discriminant};

// `discriminant(const_expr)` may get const-propagated.
// As we want to check that const-eval is equal to ordinary exection,
// we wrap `const_expr` with a function which is not const to prevent this.
#[inline(never)]
fn identity<T>(x: T) -> T { x }

enum Test {
    A(u8),
    B,
    C { a: u8, b: u8 },
}

const TEST_A: Discriminant<Test> = discriminant(&Test::A(5));
const TEST_A_OTHER: Discriminant<Test> = discriminant(&Test::A(17));
const TEST_B: Discriminant<Test> = discriminant(&Test::B);

enum Void {}

enum SingleVariant {
    V,
    Never(Void),
}

const TEST_V: Discriminant<SingleVariant> = discriminant(&SingleVariant::V);

fn main() {
    assert_eq!(TEST_A, TEST_A_OTHER);
    assert_eq!(TEST_A, discriminant(identity(&Test::A(17))));
    assert_eq!(TEST_B, discriminant(identity(&Test::B)));
    assert_ne!(TEST_A, TEST_B);
    assert_ne!(TEST_B, discriminant(identity(&Test::C { a: 42, b: 7 })));

    assert_eq!(TEST_V, discriminant(identity(&SingleVariant::V)));
}
<|endoftext|>


<commit_before>#[derive(Debug, PartialEq)]
pub struct RawHashlineParseData {
    indent_depth: usize,
    name: String,
    opts: String,
    args: String,
    comment: String,
}

#[derive(Debug, PartialEq)]
pub struct RawItemlineParseData {
    indent_depth: usize,
    item: String,
}

#[derive(Debug, PartialEq)]
pub enum Hashline {
    OpenEnv(Environment),
    PlainLine(String),
}

#[derive(Debug, PartialEq)]
pub struct Environment {
    indent_depth: usize,
    name: String,
    opts: String,
    comment: String,
    is_list_like: bool,
}

impl RawHashlineParseData {
    pub fn new(
        indent_depth: usize,
        name: String,
        opts: String,
        args: String,
        comment: String,
    ) -> Self {
        Self {
            indent_depth,
            name,
            opts,
            args,
            comment,
        }
    }
}

#[inline]
fn is_a_list_environment(input: &str) -> bool {
    fn parser(input: &str) -> nom::IResult<&str, &str> {
        use nom::branch::alt;
        use nom::bytes::complete::tag;

        alt((tag("itemize"), tag("enumerate"), tag("description")))(input)
    }
    parser(input.trim_start()).is_ok()
}

impl From<RawHashlineParseData> for Hashline {
    fn from(raw_hashline: RawHashlineParseData) -> Self {
        if raw_hashline.args.trim().is_empty() {
            // If no args are given, it's an environment
            let is_list_like = is_a_list_environment(raw_hashline.name.as_ref());
            Hashline::OpenEnv(Environment {
                indent_depth: raw_hashline.indent_depth,
                name: raw_hashline.name,
                opts: raw_hashline.opts,
                comment: raw_hashline.comment,
                is_list_like: is_list_like,
            })
        } else {
            // If there are some args, it's a single-line command
            Hashline::PlainLine(format!(
                r"{dummy:ind$}\{name}{opts}{{{args}}}{comment_sep}{comment}",
                dummy = "",
                ind = raw_hashline.indent_depth,
                name = raw_hashline.name,
                opts = raw_hashline.opts,
                args = raw_hashline.args,
                comment_sep = if raw_hashline.comment.is_empty() {
                    ""
                } else {
                    " "
                },
                comment = raw_hashline.comment.trim(),
            ))
        }
    }
}

impl RawItemlineParseData {
    pub fn new(indent_depth: usize, item: String) -> Self {
        Self { indent_depth, item }
    }
}

impl From<RawItemlineParseData> for Hashline {
    fn from(raw_itemline: RawItemlineParseData) -> Self {
        Hashline::PlainLine(format!(
            r"{dummy:ind$}\item{item_sep}{content}",
            dummy = "",
            ind = raw_itemline.indent_depth,
            content = raw_itemline.item,
            item_sep = if raw_itemline.item.is_empty() {
                ""
            } else {
                " "
            },
        ))
    }
}

impl Environment {
    pub fn new(
        indent_depth: usize,
        name: String,
        opts: String,
        comment: String,
        is_list_like: bool,
    ) -> Self {
        Self {
            indent_depth,
            name,
            opts,
            comment,
            is_list_like,
        }
    }

    pub fn latex_begin(&self) -> String {
        format!(
            r"{dummy:ind$}\begin{{{name}}}{opts}{comment_sep}{comment}",
            name = self.name,
            opts = self.opts,
            comment = self.comment,
            dummy = "",
            ind = self.indent_depth,
            comment_sep = if self.comment.is_empty() { "" } else { " " },
        )
    }

    pub fn latex_end(&self) -> String {
        format!(
            r"{dummy:ind$}\end{{{name}}}",
            name = self.name,
            dummy = "",
            ind = self.indent_depth,
        )
    }

    pub fn indent_depth(&self) -> usize {
        self.indent_depth
    }

    pub fn is_list_like(&self) -> bool {
        self.is_list_like
    }
}

// LCOV_EXCL_START
#[cfg(test)]
mod tests {
    #[test]
    fn list_environment_recognition() {
        use super::is_a_list_environment;

        assert_eq!(is_a_list_environment("itemize"), true);
        assert_eq!(is_a_list_environment("enumerate*"), true);
        assert_eq!(is_a_list_environment("  description  *"), true);
        assert_eq!(is_a_list_environment("    descriptionitemize"), true);
        assert_eq!(is_a_list_environment("item"), false);
        assert_eq!(is_a_list_environment("   itemiz"), false);
        assert_eq!(is_a_list_environment("   foobar"), false);
    }

    #[cfg(test)]
    mod raw_hashline_parser_data_into_hashline {
        use super::super::{Hashline, RawHashlineParseData};

        #[test]
        fn plain_lines() {
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 0,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    args: "bar".to_string(),
                    comment: "".to_string()
                }),
                Hashline::PlainLine("\\foo{bar}".to_string())
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 2,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    args: "bar".to_string(),
                    comment: "qux".to_string()
                }),
                Hashline::PlainLine("  \\foo{bar} qux".to_string())
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 4,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    args: "qux".to_string(),
                    comment: "".to_string()
                }),
                Hashline::PlainLine("    \\foobar{qux}".to_string())
            );
        }

        #[test]
        fn environments() {
            use super::super::Environment;

            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 0,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    args: "".to_string(),
                    comment: "".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 0,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    comment: "".to_string(),
                    is_list_like: false,
                })
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 2,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    args: "".to_string(),
                    comment: "bar".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 2,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    comment: "bar".to_string(),
                    is_list_like: false,
                })
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 4,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    args: "".to_string(),
                    comment: "qux".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 4,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    comment: "qux".to_string(),
                    is_list_like: false,
                })
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 0,
                    name: "itemize".to_string(),
                    opts: "bar".to_string(),
                    args: "".to_string(),
                    comment: "qux".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 0,
                    name: "itemize".to_string(),
                    opts: "bar".to_string(),
                    comment: "qux".to_string(),
                    is_list_like: true,
                })
            );
        }
    }

    #[test]
    fn raw_itemline_parser_data_into_hashline() {
        use super::{Hashline, RawItemlineParseData};

        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "".to_string())),
            Hashline::PlainLine(r"\item".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "".to_string())),
            Hashline::PlainLine(r"\item".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(2, "".to_string())),
            Hashline::PlainLine(r"  \item".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "foo".to_string())),
            Hashline::PlainLine(r"\item foo".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(3, "bar".to_string())),
            Hashline::PlainLine(r"   \item bar".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "**".to_string())),
            Hashline::PlainLine(r"\item **".to_string())
        );
    }

    #[test]
    fn environment_methods() {
        use super::Environment;

        let env_1 = Environment {
            indent_depth: 0,
            name: "foo".to_string(),
            opts: "bar".to_string(),
            comment: "% baz".to_string(),
            is_list_like: true,
        };

        assert_eq!(env_1.latex_begin(), "\\begin{foo}bar % baz");
        assert_eq!(env_1.latex_end(), "\\end{foo}");
        assert_eq!(env_1.is_list_like(), true);
        assert_eq!(env_1.indent_depth(), 0);

        let env_2 = Environment {
            indent_depth: 2,
            name: "abc".to_string(),
            opts: "def".to_string(),
            comment: "".to_string(),
            is_list_like: false,
        };

        assert_eq!(env_2.latex_begin(), "  \\begin{abc}def");
        assert_eq!(env_2.latex_end(), "  \\end{abc}");
        assert_eq!(env_2.is_list_like(), false);
        assert_eq!(env_2.indent_depth(), 2);
    }
}
// LCOV_EXCL_STOP
<commit_msg>Fix clippy warnings<commit_after>#[derive(Debug, PartialEq)]
pub struct RawHashlineParseData {
    indent_depth: usize,
    name: String,
    opts: String,
    args: String,
    comment: String,
}

#[derive(Debug, PartialEq)]
pub struct RawItemlineParseData {
    indent_depth: usize,
    item: String,
}

#[derive(Debug, PartialEq)]
pub enum Hashline {
    OpenEnv(Environment),
    PlainLine(String),
}

#[derive(Debug, PartialEq)]
pub struct Environment {
    indent_depth: usize,
    name: String,
    opts: String,
    comment: String,
    is_list_like: bool,
}

impl RawHashlineParseData {
    pub fn new(
        indent_depth: usize,
        name: String,
        opts: String,
        args: String,
        comment: String,
    ) -> Self {
        Self {
            indent_depth,
            name,
            opts,
            args,
            comment,
        }
    }
}

#[inline]
fn is_a_list_environment(input: &str) -> bool {
    fn parser(input: &str) -> nom::IResult<&str, &str> {
        use nom::branch::alt;
        use nom::bytes::complete::tag;

        alt((tag("itemize"), tag("enumerate"), tag("description")))(input)
    }
    parser(input.trim_start()).is_ok()
}

impl From<RawHashlineParseData> for Hashline {
    fn from(raw_hashline: RawHashlineParseData) -> Self {
        if raw_hashline.args.trim().is_empty() {
            // If no args are given, it's an environment
            let is_list_like = is_a_list_environment(raw_hashline.name.as_ref());
            Hashline::OpenEnv(Environment {
                indent_depth: raw_hashline.indent_depth,
                name: raw_hashline.name,
                opts: raw_hashline.opts,
                comment: raw_hashline.comment,
                is_list_like,
            })
        } else {
            // If there are some args, it's a single-line command
            Hashline::PlainLine(format!(
                r"{dummy:ind$}\{name}{opts}{{{args}}}{comment_sep}{comment}",
                dummy = "",
                ind = raw_hashline.indent_depth,
                name = raw_hashline.name,
                opts = raw_hashline.opts,
                args = raw_hashline.args,
                comment_sep = if raw_hashline.comment.is_empty() {
                    ""
                } else {
                    " "
                },
                comment = raw_hashline.comment.trim(),
            ))
        }
    }
}

impl RawItemlineParseData {
    pub fn new(indent_depth: usize, item: String) -> Self {
        Self { indent_depth, item }
    }
}

impl From<RawItemlineParseData> for Hashline {
    fn from(raw_itemline: RawItemlineParseData) -> Self {
        Hashline::PlainLine(format!(
            r"{dummy:ind$}\item{item_sep}{content}",
            dummy = "",
            ind = raw_itemline.indent_depth,
            content = raw_itemline.item,
            item_sep = if raw_itemline.item.is_empty() {
                ""
            } else {
                " "
            },
        ))
    }
}

impl Environment {
    #[cfg(test)]
    pub fn new(
        indent_depth: usize,
        name: String,
        opts: String,
        comment: String,
        is_list_like: bool,
    ) -> Self {
        Self {
            indent_depth,
            name,
            opts,
            comment,
            is_list_like,
        }
    }

    pub fn latex_begin(&self) -> String {
        format!(
            r"{dummy:ind$}\begin{{{name}}}{opts}{comment_sep}{comment}",
            name = self.name,
            opts = self.opts,
            comment = self.comment,
            dummy = "",
            ind = self.indent_depth,
            comment_sep = if self.comment.is_empty() { "" } else { " " },
        )
    }

    pub fn latex_end(&self) -> String {
        format!(
            r"{dummy:ind$}\end{{{name}}}",
            name = self.name,
            dummy = "",
            ind = self.indent_depth,
        )
    }

    pub fn indent_depth(&self) -> usize {
        self.indent_depth
    }

    pub fn is_list_like(&self) -> bool {
        self.is_list_like
    }
}

// LCOV_EXCL_START
#[cfg(test)]
mod tests {
    #[test]
    fn list_environment_recognition() {
        use super::is_a_list_environment;

        assert_eq!(is_a_list_environment("itemize"), true);
        assert_eq!(is_a_list_environment("enumerate*"), true);
        assert_eq!(is_a_list_environment("  description  *"), true);
        assert_eq!(is_a_list_environment("    descriptionitemize"), true);
        assert_eq!(is_a_list_environment("item"), false);
        assert_eq!(is_a_list_environment("   itemiz"), false);
        assert_eq!(is_a_list_environment("   foobar"), false);
    }

    #[cfg(test)]
    mod raw_hashline_parser_data_into_hashline {
        use super::super::{Hashline, RawHashlineParseData};

        #[test]
        fn plain_lines() {
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 0,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    args: "bar".to_string(),
                    comment: "".to_string()
                }),
                Hashline::PlainLine("\\foo{bar}".to_string())
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 2,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    args: "bar".to_string(),
                    comment: "qux".to_string()
                }),
                Hashline::PlainLine("  \\foo{bar} qux".to_string())
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 4,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    args: "qux".to_string(),
                    comment: "".to_string()
                }),
                Hashline::PlainLine("    \\foobar{qux}".to_string())
            );
        }

        #[test]
        fn environments() {
            use super::super::Environment;

            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 0,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    args: "".to_string(),
                    comment: "".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 0,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    comment: "".to_string(),
                    is_list_like: false,
                })
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 2,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    args: "".to_string(),
                    comment: "bar".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 2,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    comment: "bar".to_string(),
                    is_list_like: false,
                })
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 4,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    args: "".to_string(),
                    comment: "qux".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 4,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    comment: "qux".to_string(),
                    is_list_like: false,
                })
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 0,
                    name: "itemize".to_string(),
                    opts: "bar".to_string(),
                    args: "".to_string(),
                    comment: "qux".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 0,
                    name: "itemize".to_string(),
                    opts: "bar".to_string(),
                    comment: "qux".to_string(),
                    is_list_like: true,
                })
            );
        }
    }

    #[test]
    fn raw_itemline_parser_data_into_hashline() {
        use super::{Hashline, RawItemlineParseData};

        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "".to_string())),
            Hashline::PlainLine(r"\item".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "".to_string())),
            Hashline::PlainLine(r"\item".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(2, "".to_string())),
            Hashline::PlainLine(r"  \item".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "foo".to_string())),
            Hashline::PlainLine(r"\item foo".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(3, "bar".to_string())),
            Hashline::PlainLine(r"   \item bar".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "**".to_string())),
            Hashline::PlainLine(r"\item **".to_string())
        );
    }

    #[test]
    fn environment_methods() {
        use super::Environment;

        let env_1 = Environment {
            indent_depth: 0,
            name: "foo".to_string(),
            opts: "bar".to_string(),
            comment: "% baz".to_string(),
            is_list_like: true,
        };

        assert_eq!(env_1.latex_begin(), "\\begin{foo}bar % baz");
        assert_eq!(env_1.latex_end(), "\\end{foo}");
        assert_eq!(env_1.is_list_like(), true);
        assert_eq!(env_1.indent_depth(), 0);

        let env_2 = Environment {
            indent_depth: 2,
            name: "abc".to_string(),
            opts: "def".to_string(),
            comment: "".to_string(),
            is_list_like: false,
        };

        assert_eq!(env_2.latex_begin(), "  \\begin{abc}def");
        assert_eq!(env_2.latex_end(), "  \\end{abc}");
        assert_eq!(env_2.is_list_like(), false);
        assert_eq!(env_2.indent_depth(), 2);
    }
}
// LCOV_EXCL_STOP
<|endoftext|>


<commit_before>#[derive(Debug, PartialEq)]
pub struct RawHashlineParseData {
    indent_depth: usize,
    name: String,
    opts: String,
    args: String,
    comment: String,
}

#[derive(Debug, PartialEq)]
pub struct RawItemlineParseData {
    indent_depth: usize,
    item: String,
}

#[derive(Debug, PartialEq)]
pub enum Hashline {
    OpenEnv(Environment),
    PlainLine(String),
}

#[derive(Debug, PartialEq)]
pub struct Environment {
    indent_depth: usize,
    name: String,
    opts: String,
    comment: String,
    is_list_like: bool,
}

impl RawHashlineParseData {
    pub fn new(
        indent_depth: usize,
        name: String,
        opts: String,
        args: String,
        comment: String,
    ) -> Self {
        Self {
            indent_depth,
            name,
            opts,
            args,
            comment,
        }
    }
}

#[inline]
fn is_a_list_environment(input: &str) -> bool {
    fn parser(input: &str) -> nom::IResult<&str, &str> {
        use nom::branch::alt;
        use nom::bytes::complete::tag;

        alt((tag("itemize"), tag("enumerate"), tag("description")))(input)
    }
    parser(input.trim_start()).is_ok()
}

impl From<RawHashlineParseData> for Hashline {
    fn from(raw_hashline: RawHashlineParseData) -> Self {
        if raw_hashline.args.trim().is_empty() {
            // If no args are given, it's an environment
            let is_list_like = is_a_list_environment(raw_hashline.name.as_ref());
            Hashline::OpenEnv(Environment {
                indent_depth: raw_hashline.indent_depth,
                name: raw_hashline.name, // FIXME: Avoid reallocation here
                opts: raw_hashline.opts, // FIXME: Avoid reallocation here
                comment: raw_hashline.comment, // FIXME: Avoid reallocation here
                is_list_like: is_list_like,
            })
        } else {
            // If there are some args, it's a single-line command
            Hashline::PlainLine(format!(
                r"{dummy:ind$}\{name}{opts}{{{args}}}{comment_sep}{comment}",
                dummy = "",
                ind = raw_hashline.indent_depth,
                name = raw_hashline.name,
                opts = raw_hashline.opts,
                args = raw_hashline.args,
                comment_sep = if raw_hashline.comment.is_empty() {
                    ""
                } else {
                    " "
                },
                comment = raw_hashline.comment.trim(),
            ))
        }
    }
}

impl RawItemlineParseData {
    pub fn new(indent_depth: usize, item: String) -> Self {
        Self { indent_depth, item }
    }
}

impl From<RawItemlineParseData> for Hashline {
    fn from(raw_itemline: RawItemlineParseData) -> Self {
        Hashline::PlainLine(format!(
            r"{dummy:ind$}\item{item_sep}{content}",
            dummy = "",
            ind = raw_itemline.indent_depth,
            content = raw_itemline.item,
            item_sep = if raw_itemline.item.is_empty() {
                ""
            } else {
                " "
            },
        ))
    }
}

impl Environment {
    pub fn latex_begin(&self) -> String {
        format!(
            r"{dummy:ind$}\begin{{{name}}}{opts}{comment_sep}{comment}",
            name = self.name,
            opts = self.opts,
            comment = self.comment,
            dummy = "",
            ind = self.indent_depth,
            comment_sep = if self.comment.is_empty() { "" } else { " " },
        )
    }

    pub fn latex_end(&self) -> String {
        format!(
            r"{dummy:ind$}\end{{{name}}}",
            name = self.name,
            dummy = "",
            ind = self.indent_depth,
        )
    }

    pub fn indent_depth(&self) -> usize {
        self.indent_depth
    }

    pub fn is_list_like(&self) -> bool {
        self.is_list_like
    }
}

// LCOV_EXCL_START
#[cfg(test)]
mod tests {
    #[test]
    fn list_environment_recognition() {
        use super::is_a_list_environment;

        assert_eq!(is_a_list_environment("itemize"), true);
        assert_eq!(is_a_list_environment("enumerate*"), true);
        assert_eq!(is_a_list_environment("  description  *"), true);
        assert_eq!(is_a_list_environment("    descriptionitemize"), true);
        assert_eq!(is_a_list_environment("item"), false);
        assert_eq!(is_a_list_environment("   itemiz"), false);
        assert_eq!(is_a_list_environment("   foobar"), false);
    }

    #[cfg(test)]
    mod raw_hashline_parser_data_into_hashline {
        use super::super::{Hashline, RawHashlineParseData};

        #[test]
        fn plain_lines() {
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 0,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    args: "bar".to_string(),
                    comment: "".to_string()
                }),
                Hashline::PlainLine("\\foo{bar}".to_string())
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 2,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    args: "bar".to_string(),
                    comment: "qux".to_string()
                }),
                Hashline::PlainLine("  \\foo{bar} qux".to_string())
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 4,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    args: "qux".to_string(),
                    comment: "".to_string()
                }),
                Hashline::PlainLine("    \\foobar{qux}".to_string())
            );
        }

        #[test]
        fn environments() {
            use super::super::Environment;

            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 0,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    args: "".to_string(),
                    comment: "".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 0,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    comment: "".to_string(),
                    is_list_like: false,
                })
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 2,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    args: "".to_string(),
                    comment: "bar".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 2,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    comment: "bar".to_string(),
                    is_list_like: false,
                })
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 4,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    args: "".to_string(),
                    comment: "qux".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 4,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    comment: "qux".to_string(),
                    is_list_like: false,
                })
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 0,
                    name: "itemize".to_string(),
                    opts: "bar".to_string(),
                    args: "".to_string(),
                    comment: "qux".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 0,
                    name: "itemize".to_string(),
                    opts: "bar".to_string(),
                    comment: "qux".to_string(),
                    is_list_like: true,
                })
            );
        }
    }

    #[test]
    fn raw_itemline_parser_data_into_hashline() {
        use super::{Hashline, RawItemlineParseData};

        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "".to_string())),
            Hashline::PlainLine(r"\item".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "".to_string())),
            Hashline::PlainLine(r"\item".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(2, "".to_string())),
            Hashline::PlainLine(r"  \item".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "foo".to_string())),
            Hashline::PlainLine(r"\item foo".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(3, "bar".to_string())),
            Hashline::PlainLine(r"   \item bar".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "**".to_string())),
            Hashline::PlainLine(r"\item **".to_string())
        );
    }

    #[test]
    fn environment_methods() {
        use super::Environment;

        let env_1 = Environment {
            indent_depth: 0,
            name: "foo".to_string(),
            opts: "bar".to_string(),
            comment: "% baz".to_string(),
            is_list_like: true,
        };

        assert_eq!(env_1.latex_begin(), "\\begin{foo}bar % baz");
        assert_eq!(env_1.latex_end(), "\\end{foo}");
        assert_eq!(env_1.is_list_like(), true);
        assert_eq!(env_1.indent_depth(), 0);

        let env_2 = Environment {
            indent_depth: 2,
            name: "abc".to_string(),
            opts: "def".to_string(),
            comment: "".to_string(),
            is_list_like: false,
        };

        assert_eq!(env_2.latex_begin(), "  \\begin{abc}def");
        assert_eq!(env_2.latex_end(), "  \\end{abc}");
        assert_eq!(env_2.is_list_like(), false);
        assert_eq!(env_2.indent_depth(), 2);
    }
}
// LCOV_EXCL_STOP
<commit_msg>Remove obsolete comments<commit_after>#[derive(Debug, PartialEq)]
pub struct RawHashlineParseData {
    indent_depth: usize,
    name: String,
    opts: String,
    args: String,
    comment: String,
}

#[derive(Debug, PartialEq)]
pub struct RawItemlineParseData {
    indent_depth: usize,
    item: String,
}

#[derive(Debug, PartialEq)]
pub enum Hashline {
    OpenEnv(Environment),
    PlainLine(String),
}

#[derive(Debug, PartialEq)]
pub struct Environment {
    indent_depth: usize,
    name: String,
    opts: String,
    comment: String,
    is_list_like: bool,
}

impl RawHashlineParseData {
    pub fn new(
        indent_depth: usize,
        name: String,
        opts: String,
        args: String,
        comment: String,
    ) -> Self {
        Self {
            indent_depth,
            name,
            opts,
            args,
            comment,
        }
    }
}

#[inline]
fn is_a_list_environment(input: &str) -> bool {
    fn parser(input: &str) -> nom::IResult<&str, &str> {
        use nom::branch::alt;
        use nom::bytes::complete::tag;

        alt((tag("itemize"), tag("enumerate"), tag("description")))(input)
    }
    parser(input.trim_start()).is_ok()
}

impl From<RawHashlineParseData> for Hashline {
    fn from(raw_hashline: RawHashlineParseData) -> Self {
        if raw_hashline.args.trim().is_empty() {
            // If no args are given, it's an environment
            let is_list_like = is_a_list_environment(raw_hashline.name.as_ref());
            Hashline::OpenEnv(Environment {
                indent_depth: raw_hashline.indent_depth,
                name: raw_hashline.name,
                opts: raw_hashline.opts,
                comment: raw_hashline.comment,
                is_list_like: is_list_like,
            })
        } else {
            // If there are some args, it's a single-line command
            Hashline::PlainLine(format!(
                r"{dummy:ind$}\{name}{opts}{{{args}}}{comment_sep}{comment}",
                dummy = "",
                ind = raw_hashline.indent_depth,
                name = raw_hashline.name,
                opts = raw_hashline.opts,
                args = raw_hashline.args,
                comment_sep = if raw_hashline.comment.is_empty() {
                    ""
                } else {
                    " "
                },
                comment = raw_hashline.comment.trim(),
            ))
        }
    }
}

impl RawItemlineParseData {
    pub fn new(indent_depth: usize, item: String) -> Self {
        Self { indent_depth, item }
    }
}

impl From<RawItemlineParseData> for Hashline {
    fn from(raw_itemline: RawItemlineParseData) -> Self {
        Hashline::PlainLine(format!(
            r"{dummy:ind$}\item{item_sep}{content}",
            dummy = "",
            ind = raw_itemline.indent_depth,
            content = raw_itemline.item,
            item_sep = if raw_itemline.item.is_empty() {
                ""
            } else {
                " "
            },
        ))
    }
}

impl Environment {
    pub fn latex_begin(&self) -> String {
        format!(
            r"{dummy:ind$}\begin{{{name}}}{opts}{comment_sep}{comment}",
            name = self.name,
            opts = self.opts,
            comment = self.comment,
            dummy = "",
            ind = self.indent_depth,
            comment_sep = if self.comment.is_empty() { "" } else { " " },
        )
    }

    pub fn latex_end(&self) -> String {
        format!(
            r"{dummy:ind$}\end{{{name}}}",
            name = self.name,
            dummy = "",
            ind = self.indent_depth,
        )
    }

    pub fn indent_depth(&self) -> usize {
        self.indent_depth
    }

    pub fn is_list_like(&self) -> bool {
        self.is_list_like
    }
}

// LCOV_EXCL_START
#[cfg(test)]
mod tests {
    #[test]
    fn list_environment_recognition() {
        use super::is_a_list_environment;

        assert_eq!(is_a_list_environment("itemize"), true);
        assert_eq!(is_a_list_environment("enumerate*"), true);
        assert_eq!(is_a_list_environment("  description  *"), true);
        assert_eq!(is_a_list_environment("    descriptionitemize"), true);
        assert_eq!(is_a_list_environment("item"), false);
        assert_eq!(is_a_list_environment("   itemiz"), false);
        assert_eq!(is_a_list_environment("   foobar"), false);
    }

    #[cfg(test)]
    mod raw_hashline_parser_data_into_hashline {
        use super::super::{Hashline, RawHashlineParseData};

        #[test]
        fn plain_lines() {
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 0,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    args: "bar".to_string(),
                    comment: "".to_string()
                }),
                Hashline::PlainLine("\\foo{bar}".to_string())
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 2,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    args: "bar".to_string(),
                    comment: "qux".to_string()
                }),
                Hashline::PlainLine("  \\foo{bar} qux".to_string())
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 4,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    args: "qux".to_string(),
                    comment: "".to_string()
                }),
                Hashline::PlainLine("    \\foobar{qux}".to_string())
            );
        }

        #[test]
        fn environments() {
            use super::super::Environment;

            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 0,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    args: "".to_string(),
                    comment: "".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 0,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    comment: "".to_string(),
                    is_list_like: false,
                })
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 2,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    args: "".to_string(),
                    comment: "bar".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 2,
                    name: "foo".to_string(),
                    opts: "".to_string(),
                    comment: "bar".to_string(),
                    is_list_like: false,
                })
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 4,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    args: "".to_string(),
                    comment: "qux".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 4,
                    name: "foo".to_string(),
                    opts: "bar".to_string(),
                    comment: "qux".to_string(),
                    is_list_like: false,
                })
            );
            assert_eq!(
                Hashline::from(RawHashlineParseData {
                    indent_depth: 0,
                    name: "itemize".to_string(),
                    opts: "bar".to_string(),
                    args: "".to_string(),
                    comment: "qux".to_string()
                }),
                Hashline::OpenEnv(Environment {
                    indent_depth: 0,
                    name: "itemize".to_string(),
                    opts: "bar".to_string(),
                    comment: "qux".to_string(),
                    is_list_like: true,
                })
            );
        }
    }

    #[test]
    fn raw_itemline_parser_data_into_hashline() {
        use super::{Hashline, RawItemlineParseData};

        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "".to_string())),
            Hashline::PlainLine(r"\item".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "".to_string())),
            Hashline::PlainLine(r"\item".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(2, "".to_string())),
            Hashline::PlainLine(r"  \item".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "foo".to_string())),
            Hashline::PlainLine(r"\item foo".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(3, "bar".to_string())),
            Hashline::PlainLine(r"   \item bar".to_string())
        );
        assert_eq!(
            Hashline::from(RawItemlineParseData::new(0, "**".to_string())),
            Hashline::PlainLine(r"\item **".to_string())
        );
    }

    #[test]
    fn environment_methods() {
        use super::Environment;

        let env_1 = Environment {
            indent_depth: 0,
            name: "foo".to_string(),
            opts: "bar".to_string(),
            comment: "% baz".to_string(),
            is_list_like: true,
        };

        assert_eq!(env_1.latex_begin(), "\\begin{foo}bar % baz");
        assert_eq!(env_1.latex_end(), "\\end{foo}");
        assert_eq!(env_1.is_list_like(), true);
        assert_eq!(env_1.indent_depth(), 0);

        let env_2 = Environment {
            indent_depth: 2,
            name: "abc".to_string(),
            opts: "def".to_string(),
            comment: "".to_string(),
            is_list_like: false,
        };

        assert_eq!(env_2.latex_begin(), "  \\begin{abc}def");
        assert_eq!(env_2.latex_end(), "  \\end{abc}");
        assert_eq!(env_2.is_list_like(), false);
        assert_eq!(env_2.indent_depth(), 2);
    }
}
// LCOV_EXCL_STOP
<|endoftext|>


<commit_before>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Printer for displaying crate as a graoh.

use std::fmt::{self, Write};

use petgraph::{
    graph::NodeIndex,
    visit::{IntoNodeReferences, NodeRef},
};
use ra_ap_hir as hir;
use ra_ap_ide::RootDatabase;

use crate::{
    graph::{
        edge::{Edge, EdgeKind},
        node::{visibility::NodeVisibility, Node, NodeKind},
        util, Graph,
    },
    theme::graph::{edge_styles, node_styles},
};

const INDENTATION: &str = "    ";

#[derive(Clone, Debug)]
pub struct Options {
    pub layout: String,
    pub full_paths: bool,
}

pub struct Printer {
    options: Options,
    member_krate: String,
}

impl Printer {
    pub fn new(options: Options, member_krate: hir::Crate, db: &RootDatabase) -> Self {
        let member_krate = util::krate_name(member_krate, db);
        Self {
            options,
            member_krate,
        }
    }

    pub fn fmt(
        &self,
        f: &mut dyn fmt::Write,
        graph: &Graph,
        start_node_idx: NodeIndex,
    ) -> Result<(), anyhow::Error> {
        let root_node = &graph[start_node_idx];
        let crate_name = root_node.display_name();
        let layout_name = &self.options.layout[..];

        writeln!(f, "digraph {{")?;

        writeln!(f)?;

        indoc::writedoc!(
            f,
            r#"
            {i}graph [
            {i}    label={label:?},
            {i}    labelloc=t,

            {i}    pad=0.4,

            {i}    // Consider rendering the graph using a different layout algorithm, such as:
            {i}    // [dot, neato, twopi, circo, fdp, sfdp]
            {i}    layout={layout},
            {i}    overlap=false,
            {i}    splines="line",
            {i}    rankdir=LR,

            {i}    fontname="Helvetica",
            {i}    fontsize="36",
            {i}];
            "#,
            i = INDENTATION,
            label = crate_name,
            layout = layout_name,
        )?;

        writeln!(f)?;

        indoc::writedoc!(
            f,
            r#"
            {i}node [
            {i}    fontname="monospace",
            {i}    fontsize="10",
            {i}    shape="record",
            {i}    style="filled",
            {i}];
            "#,
            i = INDENTATION,
        )?;

        writeln!(f)?;

        indoc::writedoc!(
            f,
            r#"
            {i}edge [
            {i}    fontname="monospace",
            {i}    fontsize="10",
            {i}];
            "#,
            i = INDENTATION,
        )?;

        writeln!(f)?;

        self.fmt_nodes(f, graph)?;

        writeln!(f)?;

        self.fmt_edges(f, graph)?;

        writeln!(f)?;

        writeln!(f, "}}")?;

        Ok(())
    }

    fn fmt_nodes(&self, f: &mut dyn fmt::Write, graph: &Graph) -> fmt::Result {
        for node_ref in graph.node_references() {
            let node: &Node = node_ref.weight();

            let id = node.path.join("::");
            let kind = node.kind.display_name().unwrap_or("orphan");

            let label = self.node_label(node)?;
            let attributes = self.node_attributes(node);

            writeln!(
                f,
                r#"{i}{id:?} [label={label:?}{attributes}]; // {kind:?} node"#,
                i = INDENTATION,
                id = id,
                label = label,
                attributes = attributes,
                kind = kind,
            )?;
        }

        Ok(())
    }

    fn fmt_edges(&self, f: &mut dyn fmt::Write, graph: &Graph) -> fmt::Result {
        for edge_idx in graph.edge_indices() {
            let edge = &graph[edge_idx];
            let (source_idx, target_idx) = graph.edge_endpoints(edge_idx).unwrap();

            let source = graph[source_idx].path.join("::");
            let target = graph[target_idx].path.join("::");

            let kind = edge.kind.display_name();

            let label = self.edge_label(edge);
            let attributes = self.edge_attributes(edge);

            let constraint = match edge.kind {
                EdgeKind::Uses => "[constraint=false]",
                EdgeKind::Owns => "[constraint=true]",
            };

            writeln!(
                f,
                r#"{i}{source:?} -> {target:?} [label={label:?}{attributes}] {constraint}; // {kind:?} edge"#,
                i = INDENTATION,
                source = source,
                target = target,
                label = label,
                attributes = attributes,
                kind = kind,
                constraint = constraint
            )?;
        }

        Ok(())
    }

    fn node_label(&self, node: &Node) -> Result<String, fmt::Error> {
        let mut label = String::new();

        self.fmt_node_header(&mut label, node)?;
        write!(&mut label, "|")?;
        self.fmt_node_body(&mut label, node)?;

        Ok(label)
    }

    fn fmt_node_header(&self, f: &mut dyn fmt::Write, node: &Node) -> fmt::Result {
        let is_external = node.krate != Some(self.member_krate.clone());

        let visibility = match &node.visibility {
            Some(visibility) => {
                if is_external {
                    Some("external".to_owned())
                } else if node.kind == NodeKind::Crate {
                    None
                } else {
                    Some(format!("{}", visibility))
                }
            }
            None => Some("orphan".to_owned()),
        };

        let kind = node.kind.display_name().unwrap_or("mod");

        if let Some(visibility) = visibility {
            write!(f, "{} ", visibility)?;
        }

        write!(f, "{}", kind)
    }

    fn fmt_node_body(&self, f: &mut dyn fmt::Write, node: &Node) -> fmt::Result {
        let path = if self.options.full_paths {
            // If we explicitly want full paths, return it unaltered:
            node.path.join("::")
        } else if node.path.len() > 1 {
            // Otherwise try to drop the crate-name from the path:
            node.path[1..].join("::")
        } else {
            node.path.join("::")
        };

        write!(f, "{}", path)
    }

    fn node_attributes(&self, node: &Node) -> String {
        let styles = node_styles();

        let style = match &node.kind {
            NodeKind::Crate => styles.krate,
            NodeKind::Orphan => styles.orphan,
            _ => match &node.visibility {
                Some(visibility) => match visibility {
                    NodeVisibility::Crate => styles.visibility.pub_crate,
                    NodeVisibility::Module(_) => styles.visibility.pub_module,
                    NodeVisibility::Private => styles.visibility.pub_private,
                    NodeVisibility::Public => styles.visibility.pub_global,
                    NodeVisibility::Super => styles.visibility.pub_super,
                },
                None => styles.visibility.pub_global,
            },
        };

        format!(r#", fillcolor="{}""#, style.fill_color)
    }

    fn edge_label(&self, edge: &Edge) -> String {
        edge.kind.display_name().to_owned()
    }

    fn edge_attributes(&self, edge: &Edge) -> String {
        let styles = edge_styles();

        let style = match edge.kind {
            EdgeKind::Uses { .. } => styles.uses,
            EdgeKind::Owns => styles.owns,
        };

        format!(r#", color="{}", style="{}""#, style.color, style.stroke)
    }
}
<commit_msg>Clean up graph printer<commit_after>// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.

//! Printer for displaying crate as a graoh.

use std::fmt::{self, Write};

use petgraph::{
    graph::NodeIndex,
    visit::{IntoNodeReferences, NodeRef},
};
use ra_ap_hir as hir;
use ra_ap_ide::RootDatabase;

use crate::{
    graph::{
        edge::{Edge, EdgeKind},
        node::{visibility::NodeVisibility, Node, NodeKind},
        util, Graph,
    },
    theme::graph::{edge_styles, node_styles},
};

const INDENTATION: &str = "    ";

#[derive(Clone, Debug)]
pub struct Options {
    pub layout: String,
    pub full_paths: bool,
}

pub struct Printer {
    options: Options,
    member_krate: String,
}

impl Printer {
    pub fn new(options: Options, member_krate: hir::Crate, db: &RootDatabase) -> Self {
        let member_krate = util::krate_name(member_krate, db);
        Self {
            options,
            member_krate,
        }
    }

    pub fn fmt(
        &self,
        f: &mut dyn fmt::Write,
        graph: &Graph,
        start_node_idx: NodeIndex,
    ) -> Result<(), anyhow::Error> {
        let root_node = &graph[start_node_idx];
        let label = root_node.display_path();
        let layout = &self.options.layout[..];
        let i = INDENTATION;

        writeln!(f, "digraph {{")?;

        writeln!(f)?;

        indoc::writedoc!(
            f,
            r#"
            {i}graph [
            {i}    label={label:?},
            {i}    labelloc=t,

            {i}    pad=0.4,

            {i}    // Consider rendering the graph using a different layout algorithm, such as:
            {i}    // [dot, neato, twopi, circo, fdp, sfdp]
            {i}    layout={layout},
            {i}    overlap=false,
            {i}    splines="line",
            {i}    rankdir=LR,

            {i}    fontname="Helvetica",
            {i}    fontsize="36",
            {i}];
            "#,
        )?;

        writeln!(f)?;

        indoc::writedoc!(
            f,
            r#"
            {i}node [
            {i}    fontname="monospace",
            {i}    fontsize="10",
            {i}    shape="record",
            {i}    style="filled",
            {i}];
            "#,
        )?;

        writeln!(f)?;

        indoc::writedoc!(
            f,
            r#"
            {i}edge [
            {i}    fontname="monospace",
            {i}    fontsize="10",
            {i}];
            "#,
        )?;

        writeln!(f)?;

        self.fmt_nodes(f, graph)?;

        writeln!(f)?;

        self.fmt_edges(f, graph)?;

        writeln!(f)?;

        writeln!(f, "}}")?;

        Ok(())
    }

    fn fmt_nodes(&self, f: &mut dyn fmt::Write, graph: &Graph) -> fmt::Result {
        for node_ref in graph.node_references() {
            let node: &Node = node_ref.weight();

            let id = node.path.join("::");
            let kind = node.kind.display_name().unwrap_or("orphan");

            let label = self.node_label(node)?;
            let attributes = self.node_attributes(node);

            let i = INDENTATION;

            writeln!(
                f,
                r#"{i}{id:?} [label={label:?}{attributes}]; // {kind:?} node"#,
            )?;
        }

        Ok(())
    }

    fn fmt_edges(&self, f: &mut dyn fmt::Write, graph: &Graph) -> fmt::Result {
        for edge_idx in graph.edge_indices() {
            let edge = &graph[edge_idx];
            let (source_idx, target_idx) = graph.edge_endpoints(edge_idx).unwrap();

            let source = graph[source_idx].path.join("::");
            let target = graph[target_idx].path.join("::");

            let kind = edge.kind.display_name();

            let label = self.edge_label(edge);
            let attributes = self.edge_attributes(edge);

            let constraint = match edge.kind {
                EdgeKind::Uses => "[constraint=false]",
                EdgeKind::Owns => "[constraint=true]",
            };

            let i = INDENTATION;

            writeln!(
                f,
                r#"{i}{source:?} -> {target:?} [label={label:?}{attributes}] {constraint}; // {kind:?} edge"#,
            )?;
        }

        Ok(())
    }

    fn node_label(&self, node: &Node) -> Result<String, fmt::Error> {
        let mut label = String::new();

        self.fmt_node_header(&mut label, node)?;
        write!(&mut label, "|")?;
        self.fmt_node_body(&mut label, node)?;

        Ok(label)
    }

    fn fmt_node_header(&self, f: &mut dyn fmt::Write, node: &Node) -> fmt::Result {
        let is_external = node.krate != Some(self.member_krate.clone());

        let visibility = match &node.visibility {
            Some(visibility) => {
                if is_external {
                    Some("external".to_owned())
                } else if node.kind == NodeKind::Crate {
                    None
                } else {
                    Some(format!("{}", visibility))
                }
            }
            None => Some("orphan".to_owned()),
        };

        let kind = node.kind.display_name().unwrap_or("mod");

        if let Some(visibility) = visibility {
            write!(f, "{} ", visibility)?;
        }

        write!(f, "{}", kind)
    }

    fn fmt_node_body(&self, f: &mut dyn fmt::Write, node: &Node) -> fmt::Result {
        let path = if self.options.full_paths {
            // If we explicitly want full paths, return it unaltered:
            node.path.join("::")
        } else if node.path.len() > 1 {
            // Otherwise try to drop the crate-name from the path:
            node.path[1..].join("::")
        } else {
            node.path.join("::")
        };

        write!(f, "{}", path)
    }

    fn node_attributes(&self, node: &Node) -> String {
        let styles = node_styles();

        let style = match &node.kind {
            NodeKind::Crate => styles.krate,
            NodeKind::Orphan => styles.orphan,
            _ => match &node.visibility {
                Some(visibility) => match visibility {
                    NodeVisibility::Crate => styles.visibility.pub_crate,
                    NodeVisibility::Module(_) => styles.visibility.pub_module,
                    NodeVisibility::Private => styles.visibility.pub_private,
                    NodeVisibility::Public => styles.visibility.pub_global,
                    NodeVisibility::Super => styles.visibility.pub_super,
                },
                None => styles.visibility.pub_global,
            },
        };

        format!(r#", fillcolor="{}""#, style.fill_color)
    }

    fn edge_label(&self, edge: &Edge) -> String {
        edge.kind.display_name().to_owned()
    }

    fn edge_attributes(&self, edge: &Edge) -> String {
        let styles = edge_styles();

        let style = match edge.kind {
            EdgeKind::Uses { .. } => styles.uses,
            EdgeKind::Owns => styles.owns,
        };

        format!(r#", color="{}", style="{}""#, style.color, style.stroke)
    }
}
<|endoftext|>