<commit_before>

use visit::IntoNeighbors;
use visit::{VisitMap, Visitable};

/// Strictly monotonically increasing event time for a depth first search.
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Default)]
pub struct Time(pub usize);

/// A depth first search (DFS) visitor event.
#[derive(Copy, Clone, Debug)]
pub enum DfsEvent<N> {
    Discover(N, Time),
    /// An edge of the tree formed by the traversal.
    TreeEdge(N, N),
    /// An edge to an already visited node.
    BackEdge(N, N),
    /// A cross or forward edge.
    ///
    /// For an edge *(u, v)*, if the discover time of *v* is greater than *u*,
    /// then it is a forward edge, else a cross edge.
    CrossForwardEdge(N, N),
    Finish(N, Time),
}

macro_rules! control {
    ($e:expr) => {
        match $e {
            x => if x.should_break() {
                return x;
            } else {
            }
        }
    }
}

/// Control flow for visitor callbacks.
#[derive(Copy, Clone, Debug)]
pub enum Control<B> {
    Continue,
    Break(B),
}

impl<B> Control<B> {
    /// Get the value in `Control::Break(_)`, if present.
    pub fn break_value(self) -> Option<B> {
        match self {
            Control::Continue => None,
            Control::Break(b) => Some(b),
        }
    }
}

/// The default is `Continue`.
impl<B> Default for Control<B> {
    fn default() -> Self { Control::Continue }
}

/// Control flow for visitor callbacks.
///
/// The empty return value `()` will continue until the visit is done.
pub trait ControlFlow : Default {
    #[inline]
    fn should_break(&self) -> bool { false }
}

impl ControlFlow for () { }

impl<B> ControlFlow for Control<B> {
    fn should_break(&self) -> bool {
        if let Control::Break(_) = *self { true } else { false }
    }
}

/// A recursive depth first search.
///
/// Starting points are the nodes in the iterator `starts` (specify just one
/// start vertex *x* by using `Some(x)`).
///
/// The traversal emits discovery and finish events for each reachable vertex,
/// and edge classification of each reachable edge. `visitor` is called for each
/// event, see [`DfsEvent`][de] for possible values.
///
/// If the return value of the visitor is simply `()`, the visit runs until it
/// is finished. If the return value is a `Control<B>`, it can be used to
/// break the visit early, and the last control value is returned by the
/// function.
///
/// [de]: enum.DfsEvent.html
///
/// # Example
///
/// Find a path from vertex 0 to 5, and exit the visit as soon as we reach
/// the goal vertex.
///
/// ```
/// use petgraph::prelude::*;
/// use petgraph::graph::node_index as n;
/// use petgraph::visit::depth_first_search;
/// use petgraph::visit::{DfsEvent, Control};
///
/// let gr: Graph<(), ()> = Graph::from_edges(&[
///     (0, 1), (0, 2), (0, 3),
///     (1, 3),
///     (2, 3), (2, 4),
///     (4, 0), (4, 5),
/// ]);
///
/// // record each predecessor, mapping node → node
/// let mut predecessor = vec![NodeIndex::end(); gr.node_count()];
/// let start = n(0);
/// let goal = n(5);
/// depth_first_search(&gr, Some(start), |event| {
///     if let DfsEvent::TreeEdge(u, v) = event {
///         predecessor[v.index()] = u;
///         if v == goal {
///             return Control::Break(v);
///         }
///     }
///     Control::Continue
/// });
///
/// let mut next = goal;
/// let mut path = vec![next];
/// while next != start {
///     let pred = predecessor[next.index()];
///     path.push(pred);
///     next = pred;
/// }
/// path.reverse();
/// assert_eq!(&path, &[n(0), n(2), n(4), n(5)]);
/// ```
pub fn depth_first_search<G, F, C, I>(graph: G, starts: I, mut visitor: F) -> C
    where G: IntoNeighbors + Visitable,
          F: FnMut(DfsEvent<G::NodeId>) -> C,
          I: IntoIterator<Item=G::NodeId>,
          C: ControlFlow,
{
    let time = &mut Time(0);
    let discovered = &mut graph.visit_map();
    let finished = &mut graph.visit_map();

    for start in starts {
        control!(dfs_visitor(graph, start, &mut visitor, discovered, finished, time));
    }
    C::default()
}

fn dfs_visitor<G, F, C>(graph: G, u: G::NodeId, visitor: &mut F,
                     discovered: &mut G::Map, finished: &mut G::Map,
                     time: &mut Time) -> C
    where G: IntoNeighbors + Visitable,
          F: FnMut(DfsEvent<G::NodeId>) -> C,
          C: ControlFlow,
{
    if !discovered.visit(u) {
        return C::default();
    }
    control!(visitor(DfsEvent::Discover(u, time_post_inc(time))));
    for v in graph.neighbors(u) {
        if !discovered.is_visited(&v) {
            control!(visitor(DfsEvent::TreeEdge(u, v)));
            control!(dfs_visitor(graph, v, visitor, discovered, finished, time));
        } else if !finished.is_visited(&v) {
            control!(visitor(DfsEvent::BackEdge(u, v)));
        } else {
            control!(visitor(DfsEvent::CrossForwardEdge(u, v)));
        }
    }
    let first_finish = finished.visit(u);
    debug_assert!(first_finish);
    control!(visitor(DfsEvent::Finish(u, time_post_inc(time))));
    C::default()
}

fn time_post_inc(x: &mut Time) -> Time {
    let v = *x;
    x.0 += 1;
    v
}
<commit_msg>depth_first_search: Order typarms and where clauses<commit_after>

use visit::IntoNeighbors;
use visit::{VisitMap, Visitable};

/// Strictly monotonically increasing event time for a depth first search.
#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Default)]
pub struct Time(pub usize);

/// A depth first search (DFS) visitor event.
#[derive(Copy, Clone, Debug)]
pub enum DfsEvent<N> {
    Discover(N, Time),
    /// An edge of the tree formed by the traversal.
    TreeEdge(N, N),
    /// An edge to an already visited node.
    BackEdge(N, N),
    /// A cross or forward edge.
    ///
    /// For an edge *(u, v)*, if the discover time of *v* is greater than *u*,
    /// then it is a forward edge, else a cross edge.
    CrossForwardEdge(N, N),
    Finish(N, Time),
}

macro_rules! control {
    ($e:expr) => {
        match $e {
            x => if x.should_break() {
                return x;
            } else {
            }
        }
    }
}

/// Control flow for visitor callbacks.
#[derive(Copy, Clone, Debug)]
pub enum Control<B> {
    Continue,
    Break(B),
}

impl<B> Control<B> {
    /// Get the value in `Control::Break(_)`, if present.
    pub fn break_value(self) -> Option<B> {
        match self {
            Control::Continue => None,
            Control::Break(b) => Some(b),
        }
    }
}

/// The default is `Continue`.
impl<B> Default for Control<B> {
    fn default() -> Self { Control::Continue }
}

/// Control flow for visitor callbacks.
///
/// The empty return value `()` will continue until the visit is done.
pub trait ControlFlow : Default {
    #[inline]
    fn should_break(&self) -> bool { false }
}

impl ControlFlow for () { }

impl<B> ControlFlow for Control<B> {
    fn should_break(&self) -> bool {
        if let Control::Break(_) = *self { true } else { false }
    }
}

/// A recursive depth first search.
///
/// Starting points are the nodes in the iterator `starts` (specify just one
/// start vertex *x* by using `Some(x)`).
///
/// The traversal emits discovery and finish events for each reachable vertex,
/// and edge classification of each reachable edge. `visitor` is called for each
/// event, see [`DfsEvent`][de] for possible values.
///
/// If the return value of the visitor is simply `()`, the visit runs until it
/// is finished. If the return value is a `Control<B>`, it can be used to
/// break the visit early, and the last control value is returned by the
/// function.
///
/// [de]: enum.DfsEvent.html
///
/// # Example
///
/// Find a path from vertex 0 to 5, and exit the visit as soon as we reach
/// the goal vertex.
///
/// ```
/// use petgraph::prelude::*;
/// use petgraph::graph::node_index as n;
/// use petgraph::visit::depth_first_search;
/// use petgraph::visit::{DfsEvent, Control};
///
/// let gr: Graph<(), ()> = Graph::from_edges(&[
///     (0, 1), (0, 2), (0, 3),
///     (1, 3),
///     (2, 3), (2, 4),
///     (4, 0), (4, 5),
/// ]);
///
/// // record each predecessor, mapping node → node
/// let mut predecessor = vec![NodeIndex::end(); gr.node_count()];
/// let start = n(0);
/// let goal = n(5);
/// depth_first_search(&gr, Some(start), |event| {
///     if let DfsEvent::TreeEdge(u, v) = event {
///         predecessor[v.index()] = u;
///         if v == goal {
///             return Control::Break(v);
///         }
///     }
///     Control::Continue
/// });
///
/// let mut next = goal;
/// let mut path = vec![next];
/// while next != start {
///     let pred = predecessor[next.index()];
///     path.push(pred);
///     next = pred;
/// }
/// path.reverse();
/// assert_eq!(&path, &[n(0), n(2), n(4), n(5)]);
/// ```
pub fn depth_first_search<G, I, F, C>(graph: G, starts: I, mut visitor: F) -> C
    where G: IntoNeighbors + Visitable,
          I: IntoIterator<Item=G::NodeId>,
          F: FnMut(DfsEvent<G::NodeId>) -> C,
          C: ControlFlow,
{
    let time = &mut Time(0);
    let discovered = &mut graph.visit_map();
    let finished = &mut graph.visit_map();

    for start in starts {
        control!(dfs_visitor(graph, start, &mut visitor, discovered, finished, time));
    }
    C::default()
}

fn dfs_visitor<G, F, C>(graph: G, u: G::NodeId, visitor: &mut F,
                     discovered: &mut G::Map, finished: &mut G::Map,
                     time: &mut Time) -> C
    where G: IntoNeighbors + Visitable,
          F: FnMut(DfsEvent<G::NodeId>) -> C,
          C: ControlFlow,
{
    if !discovered.visit(u) {
        return C::default();
    }
    control!(visitor(DfsEvent::Discover(u, time_post_inc(time))));
    for v in graph.neighbors(u) {
        if !discovered.is_visited(&v) {
            control!(visitor(DfsEvent::TreeEdge(u, v)));
            control!(dfs_visitor(graph, v, visitor, discovered, finished, time));
        } else if !finished.is_visited(&v) {
            control!(visitor(DfsEvent::BackEdge(u, v)));
        } else {
            control!(visitor(DfsEvent::CrossForwardEdge(u, v)));
        }
    }
    let first_finish = finished.visit(u);
    debug_assert!(first_finish);
    control!(visitor(DfsEvent::Finish(u, time_post_inc(time))));
    C::default()
}

fn time_post_inc(x: &mut Time) -> Time {
    let v = *x;
    x.0 += 1;
    v
}
<|endoftext|>


<commit_before>use std::{
    collections::HashMap,
    path::{Path, PathBuf},
};

use xtask::{
    codegen::{self, Mode},
    not_bash::{fs2, run},
    project_root, run_rustfmt, rust_files,
};

#[test]
fn generated_grammar_is_fresh() {
    if let Err(error) = codegen::generate_syntax(Mode::Verify) {
        panic!("{}. Please update it by running `cargo xtask codegen`", error);
    }
}

#[test]
fn generated_tests_are_fresh() {
    if let Err(error) = codegen::generate_parser_tests(Mode::Verify) {
        panic!("{}. Please update tests by running `cargo xtask codegen`", error);
    }
}

#[test]
fn generated_assists_are_fresh() {
    if let Err(error) = codegen::generate_assists_tests(Mode::Verify) {
        panic!("{}. Please update assists by running `cargo xtask codegen`", error);
    }
}

#[test]
fn check_code_formatting() {
    if let Err(error) = run_rustfmt(Mode::Verify) {
        panic!("{}. Please format the code by running `cargo format`", error);
    }
}

#[test]
fn rust_files_are_tidy() {
    let mut tidy_docs = TidyDocs::default();
    for path in rust_files(&project_root().join("crates")) {
        let text = fs2::read_to_string(&path).unwrap();
        check_todo(&path, &text);
        check_trailing_ws(&path, &text);
        deny_clippy(&path, &text);
        tidy_docs.visit(&path, &text);
    }
    tidy_docs.finish();
}

#[test]
fn check_merge_commits() {
    let stdout = run!("git rev-list --merges --invert-grep --author 'bors\\[bot\\]' HEAD~19.."; echo = false)
        .unwrap();
    if !stdout.is_empty() {
        panic!(
            "
Merge commits are not allowed in the history.

When updating a pull-request, please rebase your feature branch
on top of master by running `git rebase master`. If rebase fails,
you can re-apply your changes like this:

  # Abort in-progress rebase, if any.
  $ git rebase --abort

  # Make the branch point to the latest commit from master,
  # while maintaining your local changes uncommited.
  $ git reset --soft origin/master

  # Commit all changes in a single batch.
  $ git commit -am'My changes'

  # Push the changes. We did a rebase, so we need `--force` option.
  # `--force-with-lease` is a more safe (Rusty) version of `--force`.
  $ git push --force-with-lease

And don't fear to mess something up during a rebase -- you can
always restore the previous state using `git ref-log`:

https://github.blog/2015-06-08-how-to-undo-almost-anything-with-git/#redo-after-undo-local
"
        );
    }
}

fn deny_clippy(path: &PathBuf, text: &String) {
    if text.contains("[\u{61}llow(clippy") {
        panic!(
            "\n\nallowing lints is forbidden: {}.
rust-analyzer intentionally doesn't check clippy on CI.
You can allow lint globally via `xtask clippy`.
See https://github.com/rust-lang/rust-clippy/issues/5537 for discussion.

",
            path.display()
        )
    }
}

#[test]
fn check_licenses() {
    let expected = "
0BSD OR MIT OR Apache-2.0
Apache-2.0
Apache-2.0 OR BSL-1.0
Apache-2.0 OR MIT
Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT
Apache-2.0/MIT
BSD-2-Clause
BSD-3-Clause
CC0-1.0
ISC
MIT
MIT / Apache-2.0
MIT OR Apache-2.0
MIT/Apache-2.0
Unlicense OR MIT
Unlicense/MIT
Zlib OR Apache-2.0 OR MIT
"
    .lines()
    .filter(|it| !it.is_empty())
    .collect::<Vec<_>>();

    let meta = run!("cargo metadata --format-version 1"; echo = false).unwrap();
    let mut licenses = meta
        .split(|c| c == ',' || c == '{' || c == '}')
        .filter(|it| it.contains(r#""license""#))
        .map(|it| it.trim())
        .map(|it| it[r#""license":"#.len()..].trim_matches('"'))
        .collect::<Vec<_>>();
    licenses.sort();
    licenses.dedup();
    assert_eq!(licenses, expected);
}

fn check_todo(path: &Path, text: &str) {
    let need_todo = &[
        // This file itself obviously needs to use todo (<- like this!).
        "tests/cli.rs",
        // Some of our assists generate `todo!()`.
        "tests/generated.rs",
        "handlers/add_missing_impl_members.rs",
        "handlers/add_turbo_fish.rs",
        "handlers/generate_function.rs",
        // To support generating `todo!()` in assists, we have `expr_todo()` in
        // `ast::make`.
        "ast/make.rs",
        // The documentation in string literals may contain anything for its own purposes
        "completion/generated_features.rs",
    ];
    if need_todo.iter().any(|p| path.ends_with(p)) {
        return;
    }
    if text.contains("TODO") || text.contains("TOOD") || text.contains("todo!") {
        panic!(
            "\nTODO markers or todo! macros should not be committed to the master branch,\n\
             use FIXME instead\n\
             {}\n",
            path.display(),
        )
    }
}

fn check_trailing_ws(path: &Path, text: &str) {
    if is_exclude_dir(path, &["test_data"]) {
        return;
    }
    for (line_number, line) in text.lines().enumerate() {
        if line.chars().last().map(char::is_whitespace) == Some(true) {
            panic!("Trailing whitespace in {} at line {}", path.display(), line_number)
        }
    }
}

#[derive(Default)]
struct TidyDocs {
    missing_docs: Vec<String>,
    contains_fixme: Vec<PathBuf>,
}

impl TidyDocs {
    fn visit(&mut self, path: &Path, text: &str) {
        // Test hopefully don't really need comments, and for assists we already
        // have special comments which are source of doc tests and user docs.
        if is_exclude_dir(path, &["tests", "test_data"]) {
            return;
        }

        if is_exclude_file(path) {
            return;
        }

        let first_line = match text.lines().next() {
            Some(it) => it,
            None => return,
        };

        if first_line.starts_with("//!") {
            if first_line.contains("FIXME") {
                self.contains_fixme.push(path.to_path_buf());
            }
        } else {
            if text.contains("// Feature:") || text.contains("// Assist:") {
                return;
            }
            self.missing_docs.push(path.display().to_string());
        }

        fn is_exclude_file(d: &Path) -> bool {
            let file_names = ["tests.rs"];

            d.file_name()
                .unwrap_or_default()
                .to_str()
                .map(|f_n| file_names.iter().any(|name| *name == f_n))
                .unwrap_or(false)
        }
    }

    fn finish(self) {
        if !self.missing_docs.is_empty() {
            panic!(
                "\nMissing docs strings\n\n\
                 modules:\n{}\n\n",
                self.missing_docs.join("\n")
            )
        }

        let poorly_documented = [
            "hir",
            "hir_expand",
            "ide",
            "mbe",
            "parser",
            "profile",
            "project_model",
            "syntax",
            "tt",
            "hir_ty",
        ];

        let mut has_fixmes =
            poorly_documented.iter().map(|it| (*it, false)).collect::<HashMap<&str, bool>>();
        'outer: for path in self.contains_fixme {
            for krate in poorly_documented.iter() {
                if path.components().any(|it| it.as_os_str() == *krate) {
                    has_fixmes.insert(krate, true);
                    continue 'outer;
                }
            }
            panic!("FIXME doc in a fully-documented crate: {}", path.display())
        }

        for (krate, has_fixme) in has_fixmes.iter() {
            if !has_fixme {
                panic!("crate {} is fully documented :tada:, remove it from the list of poorly documented crates", krate)
            }
        }
    }
}

fn is_exclude_dir(p: &Path, dirs_to_exclude: &[&str]) -> bool {
    p.strip_prefix(project_root())
        .unwrap()
        .components()
        .rev()
        .skip(1)
        .filter_map(|it| it.as_os_str().to_str())
        .any(|it| dirs_to_exclude.contains(&it))
}
<commit_msg>Smoke test docs generation<commit_after>use std::{
    collections::HashMap,
    path::{Path, PathBuf},
};

use xtask::{
    codegen::{self, Mode},
    not_bash::{fs2, run},
    project_root, run_rustfmt, rust_files,
};

#[test]
fn generated_grammar_is_fresh() {
    if let Err(error) = codegen::generate_syntax(Mode::Verify) {
        panic!("{}. Please update it by running `cargo xtask codegen`", error);
    }
}

#[test]
fn generated_tests_are_fresh() {
    if let Err(error) = codegen::generate_parser_tests(Mode::Verify) {
        panic!("{}. Please update tests by running `cargo xtask codegen`", error);
    }
}

#[test]
fn generated_assists_are_fresh() {
    if let Err(error) = codegen::generate_assists_tests(Mode::Verify) {
        panic!("{}. Please update assists by running `cargo xtask codegen`", error);
    }
}

#[test]
fn check_code_formatting() {
    if let Err(error) = run_rustfmt(Mode::Verify) {
        panic!("{}. Please format the code by running `cargo format`", error);
    }
}

#[test]
fn smoke_test_docs_generation() {
    // We don't commit docs to the repo, so we can just overwrite in tests.
    codegen::generate_assists_docs(Mode::Overwrite).unwrap();
    codegen::generate_feature_docs(Mode::Overwrite).unwrap();
}

#[test]
fn rust_files_are_tidy() {
    let mut tidy_docs = TidyDocs::default();
    for path in rust_files(&project_root().join("crates")) {
        let text = fs2::read_to_string(&path).unwrap();
        check_todo(&path, &text);
        check_trailing_ws(&path, &text);
        deny_clippy(&path, &text);
        tidy_docs.visit(&path, &text);
    }
    tidy_docs.finish();
}

#[test]
fn check_merge_commits() {
    let stdout = run!("git rev-list --merges --invert-grep --author 'bors\\[bot\\]' HEAD~19.."; echo = false)
        .unwrap();
    if !stdout.is_empty() {
        panic!(
            "
Merge commits are not allowed in the history.

When updating a pull-request, please rebase your feature branch
on top of master by running `git rebase master`. If rebase fails,
you can re-apply your changes like this:

  # Abort in-progress rebase, if any.
  $ git rebase --abort

  # Make the branch point to the latest commit from master,
  # while maintaining your local changes uncommited.
  $ git reset --soft origin/master

  # Commit all changes in a single batch.
  $ git commit -am'My changes'

  # Push the changes. We did a rebase, so we need `--force` option.
  # `--force-with-lease` is a more safe (Rusty) version of `--force`.
  $ git push --force-with-lease

And don't fear to mess something up during a rebase -- you can
always restore the previous state using `git ref-log`:

https://github.blog/2015-06-08-how-to-undo-almost-anything-with-git/#redo-after-undo-local
"
        );
    }
}

fn deny_clippy(path: &PathBuf, text: &String) {
    if text.contains("[\u{61}llow(clippy") {
        panic!(
            "\n\nallowing lints is forbidden: {}.
rust-analyzer intentionally doesn't check clippy on CI.
You can allow lint globally via `xtask clippy`.
See https://github.com/rust-lang/rust-clippy/issues/5537 for discussion.

",
            path.display()
        )
    }
}

#[test]
fn check_licenses() {
    let expected = "
0BSD OR MIT OR Apache-2.0
Apache-2.0
Apache-2.0 OR BSL-1.0
Apache-2.0 OR MIT
Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT
Apache-2.0/MIT
BSD-2-Clause
BSD-3-Clause
CC0-1.0
ISC
MIT
MIT / Apache-2.0
MIT OR Apache-2.0
MIT/Apache-2.0
Unlicense OR MIT
Unlicense/MIT
Zlib OR Apache-2.0 OR MIT
"
    .lines()
    .filter(|it| !it.is_empty())
    .collect::<Vec<_>>();

    let meta = run!("cargo metadata --format-version 1"; echo = false).unwrap();
    let mut licenses = meta
        .split(|c| c == ',' || c == '{' || c == '}')
        .filter(|it| it.contains(r#""license""#))
        .map(|it| it.trim())
        .map(|it| it[r#""license":"#.len()..].trim_matches('"'))
        .collect::<Vec<_>>();
    licenses.sort();
    licenses.dedup();
    assert_eq!(licenses, expected);
}

fn check_todo(path: &Path, text: &str) {
    let need_todo = &[
        // This file itself obviously needs to use todo (<- like this!).
        "tests/cli.rs",
        // Some of our assists generate `todo!()`.
        "tests/generated.rs",
        "handlers/add_missing_impl_members.rs",
        "handlers/add_turbo_fish.rs",
        "handlers/generate_function.rs",
        // To support generating `todo!()` in assists, we have `expr_todo()` in
        // `ast::make`.
        "ast/make.rs",
        // The documentation in string literals may contain anything for its own purposes
        "completion/generated_features.rs",
    ];
    if need_todo.iter().any(|p| path.ends_with(p)) {
        return;
    }
    if text.contains("TODO") || text.contains("TOOD") || text.contains("todo!") {
        panic!(
            "\nTODO markers or todo! macros should not be committed to the master branch,\n\
             use FIXME instead\n\
             {}\n",
            path.display(),
        )
    }
}

fn check_trailing_ws(path: &Path, text: &str) {
    if is_exclude_dir(path, &["test_data"]) {
        return;
    }
    for (line_number, line) in text.lines().enumerate() {
        if line.chars().last().map(char::is_whitespace) == Some(true) {
            panic!("Trailing whitespace in {} at line {}", path.display(), line_number)
        }
    }
}

#[derive(Default)]
struct TidyDocs {
    missing_docs: Vec<String>,
    contains_fixme: Vec<PathBuf>,
}

impl TidyDocs {
    fn visit(&mut self, path: &Path, text: &str) {
        // Test hopefully don't really need comments, and for assists we already
        // have special comments which are source of doc tests and user docs.
        if is_exclude_dir(path, &["tests", "test_data"]) {
            return;
        }

        if is_exclude_file(path) {
            return;
        }

        let first_line = match text.lines().next() {
            Some(it) => it,
            None => return,
        };

        if first_line.starts_with("//!") {
            if first_line.contains("FIXME") {
                self.contains_fixme.push(path.to_path_buf());
            }
        } else {
            if text.contains("// Feature:") || text.contains("// Assist:") {
                return;
            }
            self.missing_docs.push(path.display().to_string());
        }

        fn is_exclude_file(d: &Path) -> bool {
            let file_names = ["tests.rs"];

            d.file_name()
                .unwrap_or_default()
                .to_str()
                .map(|f_n| file_names.iter().any(|name| *name == f_n))
                .unwrap_or(false)
        }
    }

    fn finish(self) {
        if !self.missing_docs.is_empty() {
            panic!(
                "\nMissing docs strings\n\n\
                 modules:\n{}\n\n",
                self.missing_docs.join("\n")
            )
        }

        let poorly_documented = [
            "hir",
            "hir_expand",
            "ide",
            "mbe",
            "parser",
            "profile",
            "project_model",
            "syntax",
            "tt",
            "hir_ty",
        ];

        let mut has_fixmes =
            poorly_documented.iter().map(|it| (*it, false)).collect::<HashMap<&str, bool>>();
        'outer: for path in self.contains_fixme {
            for krate in poorly_documented.iter() {
                if path.components().any(|it| it.as_os_str() == *krate) {
                    has_fixmes.insert(krate, true);
                    continue 'outer;
                }
            }
            panic!("FIXME doc in a fully-documented crate: {}", path.display())
        }

        for (krate, has_fixme) in has_fixmes.iter() {
            if !has_fixme {
                panic!("crate {} is fully documented :tada:, remove it from the list of poorly documented crates", krate)
            }
        }
    }
}

fn is_exclude_dir(p: &Path, dirs_to_exclude: &[&str]) -> bool {
    p.strip_prefix(project_root())
        .unwrap()
        .components()
        .rev()
        .skip(1)
        .filter_map(|it| it.as_os_str().to_str())
        .any(|it| dirs_to_exclude.contains(&it))
}
<|endoftext|>


<commit_before>use clap::{Arg, App};
use std::path::Path;

use inkwell::memory_buffer::MemoryBuffer;
use inkwell::context::Context;
// use inkwell::module::Module;

fn main() {
    let matches = App::new("Test inkwell")
        // .version("0.1.0")
        // .author("")
        // .about("")
        .arg(Arg::with_name("output")
                 .short("o")
                 .long("output")
                 .takes_value(true)
                 .help("Output file name"))
        .arg(Arg::with_name("INPUT")
                 .required(true)
                 .index(1)
                 .help("The input file"))
        .get_matches();

    let path_in  = matches.value_of("INPUT").expect("ERROR: missing input file name argument.");
    let path_in  = Path::new(path_in);
    let path_out = matches.value_of("output").unwrap_or("out");
    let path_out = Path::new(path_out);

    let memory_buffer = MemoryBuffer::create_from_file(path_in)
        .expect("ERROR: failed to open file.");
    let context = Context::create();
    let module = context.create_module_from_ir(memory_buffer)
        .expect("ERROR: failed to create module.");
    module.print_to_file(path_out)
        .expect("ERROR: failed to write to file.");
}
<commit_msg>Minor tweak: iterate over globals<commit_after>use clap::{Arg, App};
use std::path::Path;

use inkwell::memory_buffer::MemoryBuffer;
use inkwell::context::Context;
// use inkwell::module::Module;

fn main() {
    let matches = App::new("Test inkwell")
        // .version("0.1.0")
        // .author("")
        // .about("")
        .arg(Arg::with_name("output")
                 .short("o")
                 .long("output")
                 .takes_value(true)
                 .help("Output file name"))
        .arg(Arg::with_name("INPUT")
                 .required(true)
                 .index(1)
                 .help("The input file"))
        .get_matches();

    let path_in  = matches.value_of("INPUT").expect("ERROR: missing input file name argument.");
    let path_in  = Path::new(path_in);
    let path_out = matches.value_of("output").unwrap_or("out");
    let path_out = Path::new(path_out);

    let memory_buffer = MemoryBuffer::create_from_file(path_in)
        .expect("ERROR: failed to open file.");
    let context = Context::create();
    let module = context.create_module_from_ir(memory_buffer)
        .expect("ERROR: failed to create module.");
    let mut og = module.get_first_global();
    while let Some(g) = og {
        println!("Global {}", g.get_name().to_str().unwrap());
        og = g.get_next_global()
    }
    module.print_to_file(path_out)
        .expect("ERROR: failed to write to file.");
}
<|endoftext|>


<commit_before>use crate::common::util::*;

#[test]
fn test_rm_one_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file = "test_rm_one_file";

    at.touch(file);

    ucmd.arg(file).succeeds().no_stderr();

    assert!(!at.file_exists(file));
}

#[test]
fn test_rm_failed() {
    let (_at, mut ucmd) = at_and_ucmd!();
    let file = "test_rm_one_file"; // Doesn't exist

    ucmd.arg(file).fails().stderr_contains(&format!(
        "cannot remove '{}': No such file or directory",
        file
    ));
}

#[test]
fn test_rm_multiple_files() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file_a = "test_rm_multiple_file_a";
    let file_b = "test_rm_multiple_file_b";

    at.touch(file_a);
    at.touch(file_b);

    ucmd.arg(file_a).arg(file_b).succeeds().no_stderr();

    assert!(!at.file_exists(file_a));
    assert!(!at.file_exists(file_b));
}

#[test]
fn test_rm_interactive() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;

    let file_a = "test_rm_interactive_file_a";
    let file_b = "test_rm_interactive_file_b";

    at.touch(file_a);
    at.touch(file_b);

    scene
        .ucmd()
        .arg("-i")
        .arg(file_a)
        .arg(file_b)
        .pipe_in("n")
        .succeeds();

    assert!(at.file_exists(file_a));
    assert!(at.file_exists(file_b));

    scene
        .ucmd()
        .arg("-i")
        .arg(file_a)
        .arg(file_b)
        .pipe_in("Yesh") // spell-checker:disable-line
        .succeeds();

    assert!(!at.file_exists(file_a));
    assert!(at.file_exists(file_b));
}

#[test]
fn test_rm_force() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file_a = "test_rm_force_a";
    let file_b = "test_rm_force_b";

    ucmd.arg("-f")
        .arg(file_a)
        .arg(file_b)
        .succeeds()
        .no_stderr();

    assert!(!at.file_exists(file_a));
    assert!(!at.file_exists(file_b));
}

#[test]
fn test_rm_force_multiple() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file_a = "test_rm_force_a";
    let file_b = "test_rm_force_b";

    ucmd.arg("-f")
        .arg("-f")
        .arg("-f")
        .arg(file_a)
        .arg(file_b)
        .succeeds()
        .no_stderr();

    assert!(!at.file_exists(file_a));
    assert!(!at.file_exists(file_b));
}

#[test]
fn test_rm_empty_directory() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_empty_directory";

    at.mkdir(dir);

    ucmd.arg("-d").arg(dir).succeeds().no_stderr();

    assert!(!at.dir_exists(dir));
}

#[test]
fn test_rm_empty_directory_verbose() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_empty_directory_verbose";

    at.mkdir(dir);

    ucmd.arg("-d")
        .arg("-v")
        .arg(dir)
        .succeeds()
        .stdout_only(format!("removed directory '{}'\n", dir));

    assert!(!at.dir_exists(dir));
}

#[test]
fn test_rm_non_empty_directory() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_non_empty_dir";
    let file_a = &format!("{}/test_rm_non_empty_file_a", dir);

    at.mkdir(dir);
    at.touch(file_a);

    ucmd.arg("-d")
        .arg(dir)
        .fails()
        .stderr_contains(&format!("cannot remove '{}': Directory not empty", dir));
    assert!(at.file_exists(file_a));
    assert!(at.dir_exists(dir));
}

#[test]
fn test_rm_recursive() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_recursive_directory";
    let file_a = "test_rm_recursive_directory/test_rm_recursive_file_a";
    let file_b = "test_rm_recursive_directory/test_rm_recursive_file_b";

    at.mkdir(dir);
    at.touch(file_a);
    at.touch(file_b);

    ucmd.arg("-r").arg(dir).succeeds().no_stderr();

    assert!(!at.dir_exists(dir));
    assert!(!at.file_exists(file_a));
    assert!(!at.file_exists(file_b));
}

#[test]
fn test_rm_recursive_multiple() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_recursive_directory";
    let file_a = "test_rm_recursive_directory/test_rm_recursive_file_a";
    let file_b = "test_rm_recursive_directory/test_rm_recursive_file_b";

    at.mkdir(dir);
    at.touch(file_a);
    at.touch(file_b);

    ucmd.arg("-r")
        .arg("-r")
        .arg("-r")
        .arg(dir)
        .succeeds()
        .no_stderr();

    assert!(!at.dir_exists(dir));
    assert!(!at.file_exists(file_a));
    assert!(!at.file_exists(file_b));
}

#[test]
fn test_rm_directory_without_flag() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_directory_without_flag_dir";

    at.mkdir(dir);

    ucmd.arg(dir)
        .fails()
        .stderr_contains(&format!("cannot remove '{}': Is a directory", dir));
}

#[test]
fn test_rm_verbose() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file_a = "test_rm_verbose_file_a";
    let file_b = "test_rm_verbose_file_b";

    at.touch(file_a);
    at.touch(file_b);

    ucmd.arg("-v")
        .arg(file_a)
        .arg(file_b)
        .succeeds()
        .stdout_only(format!("removed '{}'\nremoved '{}'\n", file_a, file_b));
}

#[test]
#[cfg(not(windows))]
// on unix symlink_dir is a file
fn test_rm_symlink_dir() {
    let (at, mut ucmd) = at_and_ucmd!();

    let dir = "test_rm_symlink_dir_directory";
    let link = "test_rm_symlink_dir_link";

    at.mkdir(dir);
    at.symlink_dir(dir, link);

    ucmd.arg(link).succeeds();
}

#[test]
#[cfg(windows)]
// on windows removing symlink_dir requires "-r" or "-d"
fn test_rm_symlink_dir() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;

    let dir = "test_rm_symlink_dir_directory";
    let link = "test_rm_symlink_dir_link";

    at.mkdir(dir);
    at.symlink_dir(dir, link);

    scene
        .ucmd()
        .arg(link)
        .fails()
        .stderr_contains(&format!("cannot remove '{}': Is a directory", link));

    assert!(at.dir_exists(link));

    scene.ucmd().arg("-r").arg(link).succeeds();
}

#[test]
fn test_rm_invalid_symlink() {
    let (at, mut ucmd) = at_and_ucmd!();
    let link = "test_rm_invalid_symlink";

    at.symlink_file(link, link);

    ucmd.arg(link).succeeds();
}

#[test]
fn test_rm_force_no_operand() {
    let mut ucmd = new_ucmd!();

    ucmd.arg("-f").succeeds().no_stderr();
}

#[test]
fn test_rm_no_operand() {
    let ts = TestScenario::new(util_name!());
    ts.ucmd().fails().stderr_is(&format!(
        "{0}: missing operand\nTry '{1} {0} --help' for more information.\n",
        ts.util_name,
        ts.bin_path.to_string_lossy()
    ));
}

#[test]
fn test_rm_verbose_slash() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_verbose_slash_directory";
    let file_a = &format!("{}/test_rm_verbose_slash_file_a", dir);

    at.mkdir(dir);
    at.touch(file_a);

    let file_a_normalized = &format!(
        "{}{}test_rm_verbose_slash_file_a",
        dir,
        std::path::MAIN_SEPARATOR
    );

    ucmd.arg("-r")
        .arg("-f")
        .arg("-v")
        .arg(&format!("{}///", dir))
        .succeeds()
        .stdout_only(format!(
            "removed '{}'\nremoved directory '{}'\n",
            file_a_normalized, dir
        ));

    assert!(!at.dir_exists(dir));
    assert!(!at.file_exists(file_a));
}

#[test]
fn test_rm_silently_accepts_presume_input_tty2() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file_2 = "test_rm_silently_accepts_presume_input_tty2";

    at.touch(file_2);

    ucmd.arg("---presume-input-tty").arg(file_2).succeeds();

    assert!(!at.file_exists(file_2));
}

#[test]
fn test_rm_interactive_never() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;

    let file_2 = "test_rm_interactive";

    at.touch(file_2);
    #[cfg(feature = "chmod")]
    scene.ccmd("chmod").arg("0").arg(file_2).succeeds();

    scene
        .ucmd()
        .arg("--interactive=never")
        .arg(file_2)
        .succeeds()
        .stdout_is("");

    assert!(!at.file_exists(file_2));
}

#[test]
#[ignore = "issue #3722"]
fn test_rm_directory_rights_rm1() {
    let (at, mut ucmd) = at_and_ucmd!();
    at.mkdir_all("b/a/p");
    at.mkdir_all("b/c");
    at.mkdir_all("b/d");
    at.set_readonly("b/a");
    ucmd.args(&["-rf", "b"])
        .fails()
        .stderr_contains("Permission denied");
    assert!(at.dir_exists("b/a/p"));
    assert!(!at.dir_exists("b/c"));
    assert!(!at.dir_exists("b/d"));
}
<commit_msg>rm: added test for rm_prompt_write_protected<commit_after>use crate::common::util::*;

#[test]
fn test_rm_one_file() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file = "test_rm_one_file";

    at.touch(file);

    ucmd.arg(file).succeeds().no_stderr();

    assert!(!at.file_exists(file));
}

#[test]
fn test_rm_failed() {
    let (_at, mut ucmd) = at_and_ucmd!();
    let file = "test_rm_one_file"; // Doesn't exist

    ucmd.arg(file).fails().stderr_contains(&format!(
        "cannot remove '{}': No such file or directory",
        file
    ));
}

#[test]
fn test_rm_multiple_files() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file_a = "test_rm_multiple_file_a";
    let file_b = "test_rm_multiple_file_b";

    at.touch(file_a);
    at.touch(file_b);

    ucmd.arg(file_a).arg(file_b).succeeds().no_stderr();

    assert!(!at.file_exists(file_a));
    assert!(!at.file_exists(file_b));
}

#[test]
fn test_rm_interactive() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;

    let file_a = "test_rm_interactive_file_a";
    let file_b = "test_rm_interactive_file_b";

    at.touch(file_a);
    at.touch(file_b);

    scene
        .ucmd()
        .arg("-i")
        .arg(file_a)
        .arg(file_b)
        .pipe_in("n")
        .succeeds();

    assert!(at.file_exists(file_a));
    assert!(at.file_exists(file_b));

    scene
        .ucmd()
        .arg("-i")
        .arg(file_a)
        .arg(file_b)
        .pipe_in("Yesh") // spell-checker:disable-line
        .succeeds();

    assert!(!at.file_exists(file_a));
    assert!(at.file_exists(file_b));
}

#[test]
fn test_rm_force() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file_a = "test_rm_force_a";
    let file_b = "test_rm_force_b";

    ucmd.arg("-f")
        .arg(file_a)
        .arg(file_b)
        .succeeds()
        .no_stderr();

    assert!(!at.file_exists(file_a));
    assert!(!at.file_exists(file_b));
}

#[test]
fn test_rm_force_multiple() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file_a = "test_rm_force_a";
    let file_b = "test_rm_force_b";

    ucmd.arg("-f")
        .arg("-f")
        .arg("-f")
        .arg(file_a)
        .arg(file_b)
        .succeeds()
        .no_stderr();

    assert!(!at.file_exists(file_a));
    assert!(!at.file_exists(file_b));
}

#[test]
fn test_rm_empty_directory() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_empty_directory";

    at.mkdir(dir);

    ucmd.arg("-d").arg(dir).succeeds().no_stderr();

    assert!(!at.dir_exists(dir));
}

#[test]
fn test_rm_empty_directory_verbose() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_empty_directory_verbose";

    at.mkdir(dir);

    ucmd.arg("-d")
        .arg("-v")
        .arg(dir)
        .succeeds()
        .stdout_only(format!("removed directory '{}'\n", dir));

    assert!(!at.dir_exists(dir));
}

#[test]
fn test_rm_non_empty_directory() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_non_empty_dir";
    let file_a = &format!("{}/test_rm_non_empty_file_a", dir);

    at.mkdir(dir);
    at.touch(file_a);

    ucmd.arg("-d")
        .arg(dir)
        .fails()
        .stderr_contains(&format!("cannot remove '{}': Directory not empty", dir));
    assert!(at.file_exists(file_a));
    assert!(at.dir_exists(dir));
}

#[test]
fn test_rm_recursive() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_recursive_directory";
    let file_a = "test_rm_recursive_directory/test_rm_recursive_file_a";
    let file_b = "test_rm_recursive_directory/test_rm_recursive_file_b";

    at.mkdir(dir);
    at.touch(file_a);
    at.touch(file_b);

    ucmd.arg("-r").arg(dir).succeeds().no_stderr();

    assert!(!at.dir_exists(dir));
    assert!(!at.file_exists(file_a));
    assert!(!at.file_exists(file_b));
}

#[test]
fn test_rm_recursive_multiple() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_recursive_directory";
    let file_a = "test_rm_recursive_directory/test_rm_recursive_file_a";
    let file_b = "test_rm_recursive_directory/test_rm_recursive_file_b";

    at.mkdir(dir);
    at.touch(file_a);
    at.touch(file_b);

    ucmd.arg("-r")
        .arg("-r")
        .arg("-r")
        .arg(dir)
        .succeeds()
        .no_stderr();

    assert!(!at.dir_exists(dir));
    assert!(!at.file_exists(file_a));
    assert!(!at.file_exists(file_b));
}

#[test]
fn test_rm_directory_without_flag() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_directory_without_flag_dir";

    at.mkdir(dir);

    ucmd.arg(dir)
        .fails()
        .stderr_contains(&format!("cannot remove '{}': Is a directory", dir));
}

#[test]
fn test_rm_verbose() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file_a = "test_rm_verbose_file_a";
    let file_b = "test_rm_verbose_file_b";

    at.touch(file_a);
    at.touch(file_b);

    ucmd.arg("-v")
        .arg(file_a)
        .arg(file_b)
        .succeeds()
        .stdout_only(format!("removed '{}'\nremoved '{}'\n", file_a, file_b));
}

#[test]
#[cfg(not(windows))]
// on unix symlink_dir is a file
fn test_rm_symlink_dir() {
    let (at, mut ucmd) = at_and_ucmd!();

    let dir = "test_rm_symlink_dir_directory";
    let link = "test_rm_symlink_dir_link";

    at.mkdir(dir);
    at.symlink_dir(dir, link);

    ucmd.arg(link).succeeds();
}

#[test]
#[cfg(windows)]
// on windows removing symlink_dir requires "-r" or "-d"
fn test_rm_symlink_dir() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;

    let dir = "test_rm_symlink_dir_directory";
    let link = "test_rm_symlink_dir_link";

    at.mkdir(dir);
    at.symlink_dir(dir, link);

    scene
        .ucmd()
        .arg(link)
        .fails()
        .stderr_contains(&format!("cannot remove '{}': Is a directory", link));

    assert!(at.dir_exists(link));

    scene.ucmd().arg("-r").arg(link).succeeds();
}

#[test]
fn test_rm_invalid_symlink() {
    let (at, mut ucmd) = at_and_ucmd!();
    let link = "test_rm_invalid_symlink";

    at.symlink_file(link, link);

    ucmd.arg(link).succeeds();
}

#[test]
fn test_rm_force_no_operand() {
    let mut ucmd = new_ucmd!();

    ucmd.arg("-f").succeeds().no_stderr();
}

#[test]
fn test_rm_no_operand() {
    let ts = TestScenario::new(util_name!());
    ts.ucmd().fails().stderr_is(&format!(
        "{0}: missing operand\nTry '{1} {0} --help' for more information.\n",
        ts.util_name,
        ts.bin_path.to_string_lossy()
    ));
}

#[test]
fn test_rm_verbose_slash() {
    let (at, mut ucmd) = at_and_ucmd!();
    let dir = "test_rm_verbose_slash_directory";
    let file_a = &format!("{}/test_rm_verbose_slash_file_a", dir);

    at.mkdir(dir);
    at.touch(file_a);

    let file_a_normalized = &format!(
        "{}{}test_rm_verbose_slash_file_a",
        dir,
        std::path::MAIN_SEPARATOR
    );

    ucmd.arg("-r")
        .arg("-f")
        .arg("-v")
        .arg(&format!("{}///", dir))
        .succeeds()
        .stdout_only(format!(
            "removed '{}'\nremoved directory '{}'\n",
            file_a_normalized, dir
        ));

    assert!(!at.dir_exists(dir));
    assert!(!at.file_exists(file_a));
}

#[test]
fn test_rm_silently_accepts_presume_input_tty2() {
    let (at, mut ucmd) = at_and_ucmd!();
    let file_2 = "test_rm_silently_accepts_presume_input_tty2";

    at.touch(file_2);

    ucmd.arg("---presume-input-tty").arg(file_2).succeeds();

    assert!(!at.file_exists(file_2));
}

#[test]
fn test_rm_interactive_never() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;

    let file_2 = "test_rm_interactive";

    at.touch(file_2);
    #[cfg(feature = "chmod")]
    scene.ccmd("chmod").arg("0").arg(file_2).succeeds();

    scene
        .ucmd()
        .arg("--interactive=never")
        .arg(file_2)
        .succeeds()
        .stdout_is("");

    assert!(!at.file_exists(file_2));
}

#[test]
#[ignore = "issue #3722"]
fn test_rm_directory_rights_rm1() {
    let (at, mut ucmd) = at_and_ucmd!();
    at.mkdir_all("b/a/p");
    at.mkdir_all("b/c");
    at.mkdir_all("b/d");
    at.set_readonly("b/a");
    ucmd.args(&["-rf", "b"])
        .fails()
        .stderr_contains("Permission denied");
    assert!(at.dir_exists("b/a/p"));
    assert!(!at.dir_exists("b/c"));
    assert!(!at.dir_exists("b/d"));
}

#[test]
fn test_prompt_write_protected_yes() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;
    let file_1 = "test_rm_prompt_write_protected_1";

    at.touch(file_1);

    #[cfg(feature = "chmod")]
    scene.ccmd("chmod").arg("0").arg(file_1).succeeds();

    scene.ucmd().arg(file_1).pipe_in("y").succeeds();
    assert!(!at.file_exists(file_1));
}

#[test]
fn test_prompt_write_protected_no() {
    let scene = TestScenario::new(util_name!());
    let at = &scene.fixtures;
    let file_2 = "test_rm_prompt_write_protected_2";

    at.touch(file_2);

    #[cfg(feature = "chmod")]
    scene.ccmd("chmod").arg("0").arg(file_2).succeeds();

    scene.ucmd().arg(file_2).pipe_in("n").succeeds();
    assert!(at.file_exists(file_2));
}
<|endoftext|>


<commit_before>use crate::common::util::*;

#[test]
fn test_to_upper() {
    new_ucmd!()
        .args(&["a-z", "A-Z"])
        .pipe_in("!abcd!")
        .run()
        .stdout_is("!ABCD!");
}

#[test]
fn test_small_set2() {
    new_ucmd!()
        .args(&["0-9", "X"])
        .pipe_in("@0123456789")
        .run()
        .stdout_is("@XXXXXXXXXX");
}

#[test]
fn test_unicode() {
    new_ucmd!()
        .args(&[", ┬─┬", "╯︵┻━┻"])
        .pipe_in("(,°□°）, ┬─┬")
        .run()
        .stdout_is("(╯°□°）╯︵┻━┻");
}

#[test]
fn test_delete() {
    new_ucmd!()
        .args(&["-d", "a-z"])
        .pipe_in("aBcD")
        .run()
        .stdout_is("BD");
}

#[test]
fn test_delete_complement() {
    new_ucmd!()
        .args(&["-d", "-c", "a-z"])
        .pipe_in("aBcD")
        .run()
        .stdout_is("ac");
}

#[test]
fn test_delete_complement_2() {
    new_ucmd!()
        .args(&["-d", "-C", "0-9"])
        .pipe_in("Phone: 01234 567890")
        .succeeds()
        .stdout_is("01234567890");
    new_ucmd!()
        .args(&["-d", "--complement", "0-9"])
        .pipe_in("Phone: 01234 567890")
        .succeeds()
        .stdout_is("01234567890");
}

#[test]
fn test_complement1() {
    new_ucmd!()
        .args(&["-c", "a", "X"])
        .pipe_in("ab")
        .run()
        .stdout_is("aX");
}

#[test]
fn test_complement2() {
    new_ucmd!()
        .args(&["-c", "0-9", "x"])
        .pipe_in("Phone: 01234 567890")
        .run()
        .stdout_is("xxxxxxx01234x567890");
}

#[test]
fn test_complement3() {
    new_ucmd!()
        .args(&["-c", "abcdefgh", "123"]) // spell-checker:disable-line
        .pipe_in("the cat and the bat")
        .run()
        .stdout_is("3he3ca33a3d33he3ba3"); // spell-checker:disable-line
}

#[test]
fn test_complement4() {
    // $ echo -n '0x1y2z3' | tr -c '0-@' '*-~'
    // 0~1~2~3
    new_ucmd!()
        .args(&["-c", "0-@", "*-~"])
        .pipe_in("0x1y2z3")
        .run()
        .stdout_is("0~1~2~3");
}

#[test]
#[ignore = "fixme: GNU tr returns '0a1b2c3' instead of '0~1~2~3', see #2158"]
fn test_complement5() {
    // $ echo '0x1y2z3' | tr -c '\0-@' '*-~'
    // 0a1b2c3
    new_ucmd!()
        .args(&["-c", "\\0-@", "*-~"])
        .pipe_in("0x1y2z3")
        .run()
        .stdout_is("0a1b2c3");
}

#[test]
fn test_squeeze() {
    new_ucmd!()
        .args(&["-s", "a-z"])
        .pipe_in("aaBBcDcc")
        .run()
        .stdout_is("aBBcDc");
}

#[test]
fn test_squeeze_complement() {
    new_ucmd!()
        .args(&["-sc", "a-z"])
        .pipe_in("aaBBcDcc")
        .run()
        .stdout_is("aaBcDcc");
}

#[test]
fn test_squeeze_complement_two_sets() {
    new_ucmd!()
        .args(&["-sc", "a", "_"])
        .pipe_in("test a aa with 3 ___ spaaaces +++") // spell-checker:disable-line
        .run()
        .stdout_is("_a_aa_aaa_");
}

#[test]
fn test_translate_and_squeeze() {
    new_ucmd!()
        .args(&["-s", "x", "y"])
        .pipe_in("xx")
        .run()
        .stdout_is("y");
}

#[test]
fn test_translate_and_squeeze_multiple_lines() {
    new_ucmd!()
        .args(&["-s", "x", "y"])
        .pipe_in("xxaax\nxaaxx") // spell-checker:disable-line
        .run()
        .stdout_is("yaay\nyaay"); // spell-checker:disable-line
}

#[test]
fn test_delete_and_squeeze() {
    new_ucmd!()
        .args(&["-ds", "a-z", "A-Z"])
        .pipe_in("abBcB")
        .run()
        .stdout_is("B");
}

#[test]
fn test_delete_and_squeeze_complement() {
    new_ucmd!()
        .args(&["-dsc", "a-z", "A-Z"])
        .pipe_in("abBcB")
        .run()
        .stdout_is("abc");
}

#[test]
fn test_set1_longer_than_set2() {
    new_ucmd!()
        .args(&["abc", "xy"])
        .pipe_in("abcde")
        .run()
        .stdout_is("xyyde"); // spell-checker:disable-line
}

#[test]
fn test_set1_shorter_than_set2() {
    new_ucmd!()
        .args(&["ab", "xyz"])
        .pipe_in("abcde")
        .run()
        .stdout_is("xycde"); // spell-checker:disable-line
}

#[test]
fn test_truncate() {
    new_ucmd!()
        .args(&["-t", "abc", "xy"])
        .pipe_in("abcde")
        .run()
        .stdout_is("xycde"); // spell-checker:disable-line
}

#[test]
fn test_truncate_with_set1_shorter_than_set2() {
    new_ucmd!()
        .args(&["-t", "ab", "xyz"])
        .pipe_in("abcde")
        .run()
        .stdout_is("xycde"); // spell-checker:disable-line
}

#[test]
fn missing_args_fails() {
    let (_, mut ucmd) = at_and_ucmd!();
    ucmd.fails().stderr_contains("missing operand");
}

#[test]
fn missing_required_second_arg_fails() {
    let (_, mut ucmd) = at_and_ucmd!();
    ucmd.args(&["foo"])
        .fails()
        .stderr_contains("missing operand after");
}

#[test]
fn test_interpret_backslash_escapes() {
    new_ucmd!()
        .args(&["abfnrtv", r"\a\b\f\n\r\t\v"]) // spell-checker:disable-line
        .pipe_in("abfnrtv") // spell-checker:disable-line
        .succeeds()
        .stdout_is("\u{7}\u{8}\u{c}\n\r\t\u{b}");
}

#[test]
fn test_interpret_unrecognized_backslash_escape_as_character() {
    new_ucmd!()
        .args(&["qcz+=~-", r"\q\c\z\+\=\~\-"])
        .pipe_in("qcz+=~-")
        .succeeds()
        .stdout_is("qcz+=~-");
}

#[test]
fn test_interpret_single_octal_escape() {
    new_ucmd!()
        .args(&["X", r"\015"])
        .pipe_in("X")
        .succeeds()
        .stdout_is("\r");
}

#[test]
fn test_interpret_one_and_two_digit_octal_escape() {
    new_ucmd!()
        .args(&["XYZ", r"\0\11\77"])
        .pipe_in("XYZ")
        .succeeds()
        .stdout_is("\0\t?");
}

#[test]
fn test_octal_escape_is_at_most_three_digits() {
    new_ucmd!()
        .args(&["XY", r"\0156"])
        .pipe_in("XY")
        .succeeds()
        .stdout_is("\r6");
}

#[test]
fn test_non_octal_digit_ends_escape() {
    new_ucmd!()
        .args(&["rust", r"\08\11956"])
        .pipe_in("rust")
        .succeeds()
        .stdout_is("\08\t9");
}

#[test]
fn test_interpret_backslash_at_eol_literally() {
    new_ucmd!()
        .args(&["X", r"\"])
        .pipe_in("X")
        .succeeds()
        .stdout_is("\\");
}

#[test]
fn test_more_than_2_sets() {
    new_ucmd!()
        .args(&["'abcdefgh'", "'a", "'b'"])
        .pipe_in("hello world")
        .fails();
}

#[test]
fn basic_translation_works() {
    // echo -n "abcdefabcdef" | tr "dabcdef"  "xyz"
    new_ucmd!()
        .args(&["abcdef", "xyz"])
        .pipe_in("abcdefabcdef")
        .succeeds()
        .stdout_is("xyzzzzxyzzzz");
}

#[test]
fn alnum_overrides_translation_to_fallback_1() {
    // echo -n "abcdefghijklmnopqrstuvwxyz" | tr "abc[:alpha:]" "xyz"
    new_ucmd!()
        .args(&["abc[:alpha:]", "xyz"])
        .pipe_in("abcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is("zzzzzzzzzzzzzzzzzzzzzzzzzz");
}

#[test]
fn alnum_overrides_translation_to_fallback_2() {
    // echo -n "abcdefghijklmnopqrstuvwxyz" | tr "[:alpha:]abc" "xyz"
    new_ucmd!()
        .args(&["[:alpha:]abc", "xyz"])
        .pipe_in("abcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is("zzzzzzzzzzzzzzzzzzzzzzzzzz");
}

#[test]
fn overrides_translation_pair_if_repeats() {
    // echo -n 'aaa' | tr "aaa" "xyz"
    new_ucmd!()
        .args(&["aaa", "xyz"])
        .pipe_in("aaa")
        .succeeds()
        .stdout_is("zzz");
}

#[test]
fn uppercase_conversion_works_1() {
    // echo -n 'abcdefghijklmnopqrstuvwxyz' | tr "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    new_ucmd!()
        .args(&["abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"])
        .pipe_in("abcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}

#[test]
fn uppercase_conversion_works_2() {
    // echo -n 'abcdefghijklmnopqrstuvwxyz' | tr "a-z" "A-Z"
    new_ucmd!()
        .args(&["a-z", "A-Z"])
        .pipe_in("abcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}

#[test]
fn uppercase_conversion_works_3() {
    // echo -n 'abcdefghijklmnopqrstuvwxyz' | tr "[:lower:]" "[:upper:]"
    new_ucmd!()
        .args(&["[:lower:]", "[:upper:]"])
        .pipe_in("abcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}

#[test]
fn translate_complement_set_in_order() {
    // echo -n '01234' | tr -c '@-~' ' -^'
    new_ucmd!()
        .args(&["-c", "@-~", " -^"])
        .pipe_in("01234")
        .succeeds()
        .stdout_is("PQRST");
}

#[test]
fn alpha_expands_uppercase_lowercase() {
    // echo -n "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | tr "[:alpha:]" " -_"
    new_ucmd!()
        .args(&["[:alpha:]", " -_"])
        .pipe_in("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is(r##" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRS"##);
}

#[test]
fn alnum_expands_number_uppercase_lowercase() {
    // echo -n "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | tr "[:alnum:]" " -_"
    new_ucmd!()
        .args(&["[:alnum:]", " -_"])
        .pipe_in("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is(r##" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]"##);
}
<commit_msg>Enabled the test for that weird backslash octal :)<commit_after>use crate::common::util::*;

#[test]
fn test_to_upper() {
    new_ucmd!()
        .args(&["a-z", "A-Z"])
        .pipe_in("!abcd!")
        .run()
        .stdout_is("!ABCD!");
}

#[test]
fn test_small_set2() {
    new_ucmd!()
        .args(&["0-9", "X"])
        .pipe_in("@0123456789")
        .run()
        .stdout_is("@XXXXXXXXXX");
}

#[test]
fn test_unicode() {
    new_ucmd!()
        .args(&[", ┬─┬", "╯︵┻━┻"])
        .pipe_in("(,°□°）, ┬─┬")
        .run()
        .stdout_is("(╯°□°）╯︵┻━┻");
}

#[test]
fn test_delete() {
    new_ucmd!()
        .args(&["-d", "a-z"])
        .pipe_in("aBcD")
        .run()
        .stdout_is("BD");
}

#[test]
fn test_delete_complement() {
    new_ucmd!()
        .args(&["-d", "-c", "a-z"])
        .pipe_in("aBcD")
        .run()
        .stdout_is("ac");
}

#[test]
fn test_delete_complement_2() {
    new_ucmd!()
        .args(&["-d", "-C", "0-9"])
        .pipe_in("Phone: 01234 567890")
        .succeeds()
        .stdout_is("01234567890");
    new_ucmd!()
        .args(&["-d", "--complement", "0-9"])
        .pipe_in("Phone: 01234 567890")
        .succeeds()
        .stdout_is("01234567890");
}

#[test]
fn test_complement1() {
    new_ucmd!()
        .args(&["-c", "a", "X"])
        .pipe_in("ab")
        .run()
        .stdout_is("aX");
}

#[test]
fn test_complement2() {
    new_ucmd!()
        .args(&["-c", "0-9", "x"])
        .pipe_in("Phone: 01234 567890")
        .run()
        .stdout_is("xxxxxxx01234x567890");
}

#[test]
fn test_complement3() {
    new_ucmd!()
        .args(&["-c", "abcdefgh", "123"]) // spell-checker:disable-line
        .pipe_in("the cat and the bat")
        .run()
        .stdout_is("3he3ca33a3d33he3ba3"); // spell-checker:disable-line
}

#[test]
fn test_complement4() {
    // $ echo -n '0x1y2z3' | tr -c '0-@' '*-~'
    // 0~1~2~3
    new_ucmd!()
        .args(&["-c", "0-@", "*-~"])
        .pipe_in("0x1y2z3")
        .run()
        .stdout_is("0~1~2~3");
}

#[test]
fn test_complement5() {
    // $ echo -n '0x1y2z3' | tr -c '\0-@' '*-~'
    // 0a1b2c3
    new_ucmd!()
        .args(&["-c", "\\0-@", "*-~"])
        .pipe_in("0x1y2z3")
        .run()
        .stdout_is("0a1b2c3");
}

#[test]
fn test_squeeze() {
    new_ucmd!()
        .args(&["-s", "a-z"])
        .pipe_in("aaBBcDcc")
        .run()
        .stdout_is("aBBcDc");
}

#[test]
fn test_squeeze_complement() {
    new_ucmd!()
        .args(&["-sc", "a-z"])
        .pipe_in("aaBBcDcc")
        .run()
        .stdout_is("aaBcDcc");
}

#[test]
fn test_squeeze_complement_two_sets() {
    new_ucmd!()
        .args(&["-sc", "a", "_"])
        .pipe_in("test a aa with 3 ___ spaaaces +++") // spell-checker:disable-line
        .run()
        .stdout_is("_a_aa_aaa_");
}

#[test]
fn test_translate_and_squeeze() {
    new_ucmd!()
        .args(&["-s", "x", "y"])
        .pipe_in("xx")
        .run()
        .stdout_is("y");
}

#[test]
fn test_translate_and_squeeze_multiple_lines() {
    new_ucmd!()
        .args(&["-s", "x", "y"])
        .pipe_in("xxaax\nxaaxx") // spell-checker:disable-line
        .run()
        .stdout_is("yaay\nyaay"); // spell-checker:disable-line
}

#[test]
fn test_delete_and_squeeze() {
    new_ucmd!()
        .args(&["-ds", "a-z", "A-Z"])
        .pipe_in("abBcB")
        .run()
        .stdout_is("B");
}

#[test]
fn test_delete_and_squeeze_complement() {
    new_ucmd!()
        .args(&["-dsc", "a-z", "A-Z"])
        .pipe_in("abBcB")
        .run()
        .stdout_is("abc");
}

#[test]
fn test_set1_longer_than_set2() {
    new_ucmd!()
        .args(&["abc", "xy"])
        .pipe_in("abcde")
        .run()
        .stdout_is("xyyde"); // spell-checker:disable-line
}

#[test]
fn test_set1_shorter_than_set2() {
    new_ucmd!()
        .args(&["ab", "xyz"])
        .pipe_in("abcde")
        .run()
        .stdout_is("xycde"); // spell-checker:disable-line
}

#[test]
fn test_truncate() {
    new_ucmd!()
        .args(&["-t", "abc", "xy"])
        .pipe_in("abcde")
        .run()
        .stdout_is("xycde"); // spell-checker:disable-line
}

#[test]
fn test_truncate_with_set1_shorter_than_set2() {
    new_ucmd!()
        .args(&["-t", "ab", "xyz"])
        .pipe_in("abcde")
        .run()
        .stdout_is("xycde"); // spell-checker:disable-line
}

#[test]
fn missing_args_fails() {
    let (_, mut ucmd) = at_and_ucmd!();
    ucmd.fails().stderr_contains("missing operand");
}

#[test]
fn missing_required_second_arg_fails() {
    let (_, mut ucmd) = at_and_ucmd!();
    ucmd.args(&["foo"])
        .fails()
        .stderr_contains("missing operand after");
}

#[test]
fn test_interpret_backslash_escapes() {
    new_ucmd!()
        .args(&["abfnrtv", r"\a\b\f\n\r\t\v"]) // spell-checker:disable-line
        .pipe_in("abfnrtv") // spell-checker:disable-line
        .succeeds()
        .stdout_is("\u{7}\u{8}\u{c}\n\r\t\u{b}");
}

#[test]
fn test_interpret_unrecognized_backslash_escape_as_character() {
    new_ucmd!()
        .args(&["qcz+=~-", r"\q\c\z\+\=\~\-"])
        .pipe_in("qcz+=~-")
        .succeeds()
        .stdout_is("qcz+=~-");
}

#[test]
fn test_interpret_single_octal_escape() {
    new_ucmd!()
        .args(&["X", r"\015"])
        .pipe_in("X")
        .succeeds()
        .stdout_is("\r");
}

#[test]
fn test_interpret_one_and_two_digit_octal_escape() {
    new_ucmd!()
        .args(&["XYZ", r"\0\11\77"])
        .pipe_in("XYZ")
        .succeeds()
        .stdout_is("\0\t?");
}

#[test]
fn test_octal_escape_is_at_most_three_digits() {
    new_ucmd!()
        .args(&["XY", r"\0156"])
        .pipe_in("XY")
        .succeeds()
        .stdout_is("\r6");
}

#[test]
fn test_non_octal_digit_ends_escape() {
    new_ucmd!()
        .args(&["rust", r"\08\11956"])
        .pipe_in("rust")
        .succeeds()
        .stdout_is("\08\t9");
}

#[test]
fn test_interpret_backslash_at_eol_literally() {
    new_ucmd!()
        .args(&["X", r"\"])
        .pipe_in("X")
        .succeeds()
        .stdout_is("\\");
}

#[test]
fn test_more_than_2_sets() {
    new_ucmd!()
        .args(&["'abcdefgh'", "'a", "'b'"])
        .pipe_in("hello world")
        .fails();
}

#[test]
fn basic_translation_works() {
    // echo -n "abcdefabcdef" | tr "dabcdef"  "xyz"
    new_ucmd!()
        .args(&["abcdef", "xyz"])
        .pipe_in("abcdefabcdef")
        .succeeds()
        .stdout_is("xyzzzzxyzzzz");
}

#[test]
fn alnum_overrides_translation_to_fallback_1() {
    // echo -n "abcdefghijklmnopqrstuvwxyz" | tr "abc[:alpha:]" "xyz"
    new_ucmd!()
        .args(&["abc[:alpha:]", "xyz"])
        .pipe_in("abcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is("zzzzzzzzzzzzzzzzzzzzzzzzzz");
}

#[test]
fn alnum_overrides_translation_to_fallback_2() {
    // echo -n "abcdefghijklmnopqrstuvwxyz" | tr "[:alpha:]abc" "xyz"
    new_ucmd!()
        .args(&["[:alpha:]abc", "xyz"])
        .pipe_in("abcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is("zzzzzzzzzzzzzzzzzzzzzzzzzz");
}

#[test]
fn overrides_translation_pair_if_repeats() {
    // echo -n 'aaa' | tr "aaa" "xyz"
    new_ucmd!()
        .args(&["aaa", "xyz"])
        .pipe_in("aaa")
        .succeeds()
        .stdout_is("zzz");
}

#[test]
fn uppercase_conversion_works_1() {
    // echo -n 'abcdefghijklmnopqrstuvwxyz' | tr "abcdefghijklmnopqrstuvwxyz" "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
    new_ucmd!()
        .args(&["abcdefghijklmnopqrstuvwxyz", "ABCDEFGHIJKLMNOPQRSTUVWXYZ"])
        .pipe_in("abcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}

#[test]
fn uppercase_conversion_works_2() {
    // echo -n 'abcdefghijklmnopqrstuvwxyz' | tr "a-z" "A-Z"
    new_ucmd!()
        .args(&["a-z", "A-Z"])
        .pipe_in("abcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}

#[test]
fn uppercase_conversion_works_3() {
    // echo -n 'abcdefghijklmnopqrstuvwxyz' | tr "[:lower:]" "[:upper:]"
    new_ucmd!()
        .args(&["[:lower:]", "[:upper:]"])
        .pipe_in("abcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is("ABCDEFGHIJKLMNOPQRSTUVWXYZ");
}

#[test]
fn translate_complement_set_in_order() {
    // echo -n '01234' | tr -c '@-~' ' -^'
    new_ucmd!()
        .args(&["-c", "@-~", " -^"])
        .pipe_in("01234")
        .succeeds()
        .stdout_is("PQRST");
}

#[test]
fn alpha_expands_uppercase_lowercase() {
    // echo -n "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | tr "[:alpha:]" " -_"
    new_ucmd!()
        .args(&["[:alpha:]", " -_"])
        .pipe_in("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is(r##" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRS"##);
}

#[test]
fn alnum_expands_number_uppercase_lowercase() {
    // echo -n "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | tr "[:alnum:]" " -_"
    new_ucmd!()
        .args(&["[:alnum:]", " -_"])
        .pipe_in("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz")
        .succeeds()
        .stdout_is(r##" !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]"##);
}

#[test]
#[ignore = "not expected to fully pass -- any help appreciated!"]
fn check_against_gnu_tr_tests() {
    // echo -n "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" | tr "[:alnum:]" " -_"
    new_ucmd!()
        .args(&["abcd", "[]*]"])
        .pipe_in("abcd")
        .succeeds()
        .stdout_is("]]]]");
    new_ucmd!()
        .args(&["abc", "[%*]xyz"])
        .pipe_in("abc")
        .succeeds()
        .stdout_is("xyz");
    new_ucmd!()
        .args(&["", "[.*]"])
        .pipe_in("abc")
        .succeeds()
        .stdout_is("abc");
    new_ucmd!()
        .args(&["-t", "abcd", "xy"])
        .pipe_in("abcde")
        .succeeds()
        .stdout_is("xycde");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
    new_ucmd!().args(&[""]).pipe_in("").succeeds().stdout_is("");
}
<|endoftext|>


<commit_before>extern crate pkg_config;

use std::env;

fn main() {
    let target_os = env::var("CARGO_CFG_TARGET_OS")
        .expect(r#"The CARGO_CFG_TARGET_OS environment is not set in the build script."#);

    // Prefer the built-in service/library if available, otherwise try
    // libpcsclite.
    match &*target_os {
        "windows" => {
            // Note we also have to specify this above the extern {} for
            // some reason (see comment there).
            println!("cargo:rustc-link-lib=dylib=winscard");
        }

        "macos" => {
            println!("cargo:rustc-link-lib=framework=PCSC");
        }

        _ => {
            pkg_config::Config::new()
                .atleast_version("1")
                .probe("libpcsclite")
                .expect(&format!(
                    r#"Could not find a PCSC library.
For the target OS `{}`, I tried to use pkg-config to find libpcsclite.
Do you have pkg-config and libpcsclite configured for this target?"#,
                    target_os
                ));
        }
    };
}
<commit_msg>allow to bypass pkgconfig<commit_after>extern crate pkg_config;

use std::env;

fn main() {
    let target_os = env::var("CARGO_CFG_TARGET_OS")
        .expect(r#"The CARGO_CFG_TARGET_OS environment is not set in the build script."#);

    // Prefer the built-in service/library if available, otherwise try
    // libpcsclite.
    match &*target_os {
        "windows" => {
            // Note we also have to specify this above the extern {} for
            // some reason (see comment there).
            println!("cargo:rustc-link-lib=dylib=winscard");
        }

        "macos" => {
            println!("cargo:rustc-link-lib=framework=PCSC");
        }

        _ => {
            if let Ok(lib_dir) = env::var("PCSC_LIB_DIR") {
                println!("cargo:rustc-link-search=native={}", lib_dir);
                println!("cargo:rustc-link-lib={}", env::var("PCSC_LIB_NAME").unwrap_or("pcsclite".to_string()));
            } else {
                pkg_config::Config::new()
                    .atleast_version("1")
                    .probe("libpcsclite")
                    .expect(&format!(
                        r#"Could not find a PCSC library.
For the target OS `{}`, I tried to use pkg-config to find libpcsclite.
Do you have pkg-config and libpcsclite configured for this target?"#,
                        target_os
                    ));
            }
        }
    };
}
<|endoftext|>


<commit_before>use std::io;

use futures::{Poll, Future, Async};

use AsyncWrite;

/// A future used to fully shutdown an I/O object.
///
/// Resolves to the underlying I/O object once the shutdown operation is
/// complete.
///
/// Created by the [`shutdown`] function.
///
/// [`shutdown`]: fn.shutdown.html
#[derive(Debug)]
pub struct Shutdown {
    a: Option,
}

/// Creates a future which will entirely shutdown an I/O object and then yield
/// the object itself.
///
/// This function will consume the object provided if an error happens, and
/// otherwise it will repeatedly call `shutdown` until it sees `Ok(())`,
/// scheduling a retry if `WouldBlock` is seen along the way.
pub fn shutdown(a: A) -> Shutdown
    where A: AsyncWrite,
{
    Shutdown {
        a: Some(a),
    }
}

impl Future for Shutdown
    where A: AsyncWrite,
{
    type Item = A;
    type Error = io::Error;

    fn poll(&mut self) -> Poll {
        try_nb!(self.a.as_mut().unwrap().shutdown());
        Ok(Async::Ready(self.a.take().unwrap()))
    }
}
<commit_msg>Fix: struct shoutdown ignore Async::NotReady (#114)<commit_after>use std::io;

use futures::{Poll, Future, Async};

use AsyncWrite;

/// A future used to fully shutdown an I/O object.
///
/// Resolves to the underlying I/O object once the shutdown operation is
/// complete.
///
/// Created by the [`shutdown`] function.
///
/// [`shutdown`]: fn.shutdown.html
#[derive(Debug)]
pub struct Shutdown {
    a: Option,
}

/// Creates a future which will entirely shutdown an I/O object and then yield
/// the object itself.
///
/// This function will consume the object provided if an error happens, and
/// otherwise it will repeatedly call `shutdown` until it sees `Ok(())`,
/// scheduling a retry if `WouldBlock` is seen along the way.
pub fn shutdown(a: A) -> Shutdown
    where A: AsyncWrite,
{
    Shutdown {
        a: Some(a),
    }
}

impl Future for Shutdown
    where A: AsyncWrite,
{
    type Item = A;
    type Error = io::Error;

    fn poll(&mut self) -> Poll {
        try_ready!(self.a.as_mut().unwrap().shutdown());
        Ok(Async::Ready(self.a.take().unwrap()))
    }
}
<|endoftext|>


<commit_before>use std::sync::mpsc::channel;
use std::io::Result as IoResult;
use std::sync::mpsc::{Sender, Receiver};
use std::io::Read;

/// A `Reader` that reads exactly the number of bytes from a sub-reader.
///
/// If the limit is reached, it returns EOF. If the limit is not reached
/// when the destructor is called, the remaining bytes will be read and
/// thrown away.
pub struct EqualReader<R> where R: Read {
    reader: R,
    size: usize,
    last_read_signal: Sender<IoResult<()>>,
}

impl<R> EqualReader<R> where R: Read {
    pub fn new(reader: R, size: usize) -> (EqualReader<R>, Receiver<IoResult<()>>) {
        let (tx, rx) = channel();

        let r = EqualReader {
            reader: reader,
            size: size,
            last_read_signal: tx,
        };

        (r, rx)
    }
}

impl<R> Read for EqualReader<R> where R: Read {
    fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
        if self.size == 0 {
            return Ok(0);
        }

        let buf = if buf.len() < self.size {
            buf
        } else {
            &mut buf[.. self.size]
        };

        self.reader.read(buf)
    }
}

impl<R> Drop for EqualReader<R> where R: Read {
    fn drop(&mut self) {
        let mut remaining_to_read = self.size;

        while remaining_to_read > 0 {
            let mut buf = vec![0 ; remaining_to_read];

            match self.reader.read(&mut buf) {
                Err(e) => { self.last_read_signal.send(Err(e)).ok(); break; }
                Ok(0) => { self.last_read_signal.send(Ok(())).ok(); break; },
                Ok(other) => { remaining_to_read -= other; }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::EqualReader;
    use std::io::Read;

    #[test]
    fn test_limit() {
        use std::io::Cursor;

        let mut org_reader = Cursor::new("hello world".to_string().into_bytes());

        {
            let (mut equal_reader, _) = EqualReader::new(org_reader.by_ref(), 5);

            let mut string = String::new();
            equal_reader.read_to_string(&mut string).unwrap();
            assert_eq!(string, "hello");
        }

        let mut string = String::new();
        org_reader.read_to_string(&mut string).unwrap();
        assert_eq!(string, " world");
    }

    #[test]
    fn test_not_enough() {
        use std::io::Cursor;

        let mut org_reader = Cursor::new("hello world".to_string().into_bytes());

        {
            let (mut equal_reader, _) = EqualReader::new(org_reader.by_ref(), 5);

            let mut vec = vec![];
            equal_reader.read(&mut vec).unwrap();
            assert_eq!(vec[0], b'h');
        }

        let mut string = String::new();
        org_reader.read_to_string(&mut string).unwrap();
        assert_eq!(string, " world");
    }
}
<commit_msg>Fix the equal_reader<commit_after>use std::sync::mpsc::channel;
use std::io::Result as IoResult;
use std::sync::mpsc::{Sender, Receiver};
use std::io::Read;

/// A `Reader` that reads exactly the number of bytes from a sub-reader.
///
/// If the limit is reached, it returns EOF. If the limit is not reached
/// when the destructor is called, the remaining bytes will be read and
/// thrown away.
pub struct EqualReader<R> where R: Read {
    reader: R,
    size: usize,
    last_read_signal: Sender<IoResult<()>>,
}

impl<R> EqualReader<R> where R: Read {
    pub fn new(reader: R, size: usize) -> (EqualReader<R>, Receiver<IoResult<()>>) {
        let (tx, rx) = channel();

        let r = EqualReader {
            reader: reader,
            size: size,
            last_read_signal: tx,
        };

        (r, rx)
    }
}

impl<R> Read for EqualReader<R> where R: Read {
    fn read(&mut self, buf: &mut [u8]) -> IoResult<usize> {
        if self.size == 0 {
            return Ok(0);
        }

        let buf = if buf.len() < self.size {
            buf
        } else {
            &mut buf[.. self.size]
        };

        match self.reader.read(buf) {
            Ok(len) => { self.size -= len; Ok(len) },
            err @ Err(_) => err
        }
    }
}

impl<R> Drop for EqualReader<R> where R: Read {
    fn drop(&mut self) {
        let mut remaining_to_read = self.size;

        while remaining_to_read > 0 {
            let mut buf = vec![0 ; remaining_to_read];

            match self.reader.read(&mut buf) {
                Err(e) => { self.last_read_signal.send(Err(e)).ok(); break; }
                Ok(0) => { self.last_read_signal.send(Ok(())).ok(); break; },
                Ok(other) => { remaining_to_read -= other; }
            }
        }
    }
}

#[cfg(test)]
mod tests {
    use super::EqualReader;
    use std::io::Read;

    #[test]
    fn test_limit() {
        use std::io::Cursor;

        let mut org_reader = Cursor::new("hello world".to_string().into_bytes());

        {
            let (mut equal_reader, _) = EqualReader::new(org_reader.by_ref(), 5);

            let mut string = String::new();
            equal_reader.read_to_string(&mut string).unwrap();
            assert_eq!(string, "hello");
        }

        let mut string = String::new();
        org_reader.read_to_string(&mut string).unwrap();
        assert_eq!(string, " world");
    }

    #[test]
    fn test_not_enough() {
        use std::io::Cursor;

        let mut org_reader = Cursor::new("hello world".to_string().into_bytes());

        {
            let (mut equal_reader, _) = EqualReader::new(org_reader.by_ref(), 5);

            let mut vec = [0];
            equal_reader.read(&mut vec).unwrap();
            assert_eq!(vec[0], b'h');
        }

        let mut string = String::new();
        org_reader.read_to_string(&mut string).unwrap();
        assert_eq!(string, " world");
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

use std::env;

fn main() {
    let target = env::var("TARGET").unwrap();
    if target.contains("apple-darwin") {
        println!("cargo:rustc-link-lib=c++");
        println!("cargo:rustc-link-lib=framework=MoltenVK");
        println!("cargo:rustc-link-lib=framework=IOSurface");
        println!("cargo:rustc-link-lib=framework=QuartzCore");
        println!("cargo:rustc-link-lib=framework=Metal");
        println!("cargo:rustc-link-lib=framework=Foundation");
    }
}
<commit_msg>Fix linking MoltenVK on Mac OS (#925)<commit_after>// Copyright (c) 2016 The vulkano developers
// Licensed under the Apache License, Version 2.0
// <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT
// license <LICENSE-MIT or http://opensource.org/licenses/MIT>,
// at your option. All files in the project carrying such
// notice may not be copied, modified, or distributed except
// according to those terms.

use std::env;

fn main() {
    let target = env::var("TARGET").unwrap();
    if target.contains("apple-darwin") {
        println!("cargo:rustc-link-search=framework=/Library/Frameworks/");
        println!("cargo:rustc-link-lib=c++");
        println!("cargo:rustc-link-lib=framework=MoltenVK");
        println!("cargo:rustc-link-lib=framework=IOSurface");
        println!("cargo:rustc-link-lib=framework=QuartzCore");
        println!("cargo:rustc-link-lib=framework=Metal");
        println!("cargo:rustc-link-lib=framework=Foundation");
    }
}
<|endoftext|>


<commit_before>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

extern crate clap;
extern crate env_logger;
extern crate habitat_core as hcore;
extern crate habitat_common as common;
extern crate habitat_pkg_export_docker as export_docker;
extern crate handlebars;
extern crate rand;
#[macro_use]
extern crate serde_json;
#[macro_use]
extern crate log;

extern crate failure;
#[macro_use]
extern crate failure_derive;

use clap::{App, Arg};
use handlebars::Handlebars;
use std::env;
use std::result;
use std::str::FromStr;
use std::io::prelude::*;
use std::io;
use std::fs::File;
use std::path::Path;

use hcore::channel;
use hcore::PROGRAM_NAME;
use hcore::url as hurl;
use hcore::package::{PackageArchive, PackageIdent};
use common::ui::UI;
use rand::Rng;

use export_docker::{Cli, Credentials, BuildSpec, Naming, Result};

// Synced with the version of the Habitat operator.
pub const VERSION: &'static str = "0.1.0";

// Kubernetes manifest template
const MANIFESTFILE: &'static str = include_str!("../defaults/KubernetesManifest.hbs");
const BINDFILE: &'static str = include_str!("../defaults/KubernetesBind.hbs");

#[derive(Debug, Fail)]
enum Error {
    #[fail(display = "Invalid bind specification '{}'", _0)]
    InvalidBindSpec(String),
}

fn main() {
    env_logger::init().unwrap();
    let mut ui = UI::default_with_env();
    if let Err(e) = start(&mut ui) {
        let _ = ui.fatal(e);
        std::process::exit(1)
    }
}

fn start(ui: &mut UI) -> Result<()> {
    let m = cli().get_matches();
    debug!("clap cli args: {:?}", m);

    if !m.is_present("NO_DOCKER_IMAGE") {
        gen_docker_img(ui, &m)?;
    }
    gen_k8s_manifest(ui, &m)
}

fn gen_docker_img(ui: &mut UI, matches: &clap::ArgMatches) -> Result<()> {
    let default_channel = channel::default();
    let default_url = hurl::default_bldr_url();
    let spec = BuildSpec::new_from_cli_matches(&matches, &default_channel, &default_url);
    let naming = Naming::new_from_cli_matches(&matches);

    let docker_image = export_docker::export(ui, spec, &naming)?;
    docker_image.create_report(
        ui,
        env::current_dir()?.join("results"),
    )?;

    if matches.is_present("PUSH_IMAGE") {
        let credentials = Credentials::new(
            naming.registry_type,
            matches.value_of("REGISTRY_USERNAME").unwrap(),
            matches.value_of("REGISTRY_PASSWORD").unwrap(),
        )?;
        docker_image.push(ui, &credentials, naming.registry_url)?;
    }
    if matches.is_present("RM_IMAGE") {
        docker_image.rm(ui)?;
    }

    Ok(())
}

fn gen_k8s_manifest(_ui: &mut UI, matches: &clap::ArgMatches) -> Result<()> {
    let count = matches.value_of("COUNT").unwrap_or("1");
    let topology = matches.value_of("TOPOLOGY").unwrap_or("standalone");
    let group = matches.value_of("GROUP");
    let config_secret_name = matches.value_of("CONFIG_SECRET_NAME");
    let ring_secret_name = matches.value_of("RING_SECRET_NAME");
    // clap ensures that we do have the mandatory args so unwrap() is fine here
    let pkg_ident_str = matches.value_of("PKG_IDENT_OR_ARTIFACT").unwrap();
    let pkg_ident = if Path::new(pkg_ident_str).is_file() {
        // We're going to use the `$pkg_origin/$pkg_name`, fuzzy form of a package
        // identifier to ensure that update strategies will work if desired
        PackageArchive::new(pkg_ident_str).ident()?
    } else {
        PackageIdent::from_str(pkg_ident_str)?
    };

    // To allow multiple instances of Habitat application in Kubernetes,
    // random suffix in metadata_name is needed.
    let metadata_name = format!(
        "{}-{}{}",
        pkg_ident.name,
        rand::thread_rng()
            .gen_ascii_chars()
            .filter(|c| c.is_lowercase() || c.is_numeric())
            .take(4)
            .collect::<String>(),
        rand::thread_rng()
            .gen_ascii_chars()
            .filter(|c| c.is_lowercase() && !c.is_numeric())
            .take(1)
            .collect::<String>()
    );

    let image = match matches.value_of("IMAGE_NAME") {
        Some(i) => i.to_string(),
        None => pkg_ident.origin + "/" + &pkg_ident.name,
    };
    let bind = matches.value_of("BIND");

    let json = json!({
        "metadata_name": metadata_name,
        "habitat_name": pkg_ident.name,
        "image": image,
        "count": count,
        "service_topology": topology,
        "service_group": group,
        "config_secret_name": config_secret_name,
        "ring_secret_name": ring_secret_name,
        "bind": bind,
    });

    let mut write: Box<Write> = match matches.value_of("OUTPUT") {
        Some(o) if o != "-" => Box::new(File::create(o)?),
        _ => Box::new(io::stdout()),
    };

    let r = Handlebars::new().template_render(MANIFESTFILE, &json)?;
    let mut out = r.lines().filter(|l| *l != "").collect::<Vec<_>>().join(
        "\n",
    ) + "\n";

    if let Some(binds) = matches.values_of("BIND") {
        for bind in binds {
            let split: Vec<&str> = bind.split(":").collect();
            if split.len() < 3 {
                return Err(Error::InvalidBindSpec(bind.to_string()).into());
            }

            let json = json!({
                "name": split[0],
                "service": split[1],
                "group": split[2],
            });

            out += &Handlebars::new().template_render(BINDFILE, &json)?;
        }
    }

    write.write(out.as_bytes())?;

    Ok(())
}

fn cli<'a, 'b>() -> App<'a, 'b> {
    let name: &str = &*PROGRAM_NAME;
    let about = "Creates a Docker image and Kubernetes manifest for a Habitat package. Habitat \
                 operator must be deployed within the Kubernetes cluster before the generated \
                 manifest can be applied to this cluster.";

    let app = Cli::new(name, about)
        .add_base_packages_args()
        .add_builder_args()
        .add_tagging_args()
        .add_publishing_args()
        .app;

    app.arg(
        Arg::with_name("OUTPUT")
            .value_name("OUTPUT")
            .long("output")
            .short("o")
            .help(
                "Name of manifest file to create. Pass '-' for stdout (default: -)",
            ),
    ).arg(
            Arg::with_name("COUNT")
                .value_name("COUNT")
                .long("count")
                .validator(valid_natural_number)
                .help("Count is the number of desired instances"),
        )
        .arg(
            Arg::with_name("TOPOLOGY")
                .value_name("TOPOLOGY")
                .long("topology")
                .short("t")
                .possible_values(&["standalone", "leader"])
                .help(
                    "A topology describes the intended relationship between peers \
                    within a Habitat service group. Specify either standalone or leader \
                    topology (default: standalone)",
                ),
        )
        .arg(
            Arg::with_name("GROUP")
                .value_name("GROUP")
                .long("service-group")
                .short("g")
                .help(
                    "group is a logical grouping of services with the same package and \
                    topology type connected together in a ring (default: default)",
                ),
        )
        .arg(
            Arg::with_name("CONFIG_SECRET_NAME")
                .value_name("CONFIG_SECRET_NAME")
                .long("config-secret-name")
                .short("n")
                .help(
                    "name of the Kubernetes Secret containing the config file - \
                    user.toml - that the user has previously created. Habitat will \
                    use it for initial configuration of the service",
                ),
        )
        .arg(
            Arg::with_name("RING_SECRET_NAME")
                .value_name("RING_SECRET_NAME")
                .long("ring-secret-name")
                .short("r")
                .help(
                    "name of the Kubernetes Secret that contains the ring key, which \
                    encrypts the communication between Habitat supervisors",
                ),
        )
        .arg(
            Arg::with_name("BIND")
                .value_name("BIND")
                .long("bind")
                .short("b")
                .multiple(true)
                .number_of_values(1)
                .help(
                    "Bind to another service to form a producer/consumer relationship, \
                    specified as name:service:group",
                ),
        )
        .arg(
            Arg::with_name("NO_DOCKER_IMAGE")
                .long("no-docker-image")
                .short("d")
                .help(
                    "Disable creation of the Docker image and only create a Kubernetes manifest",
                ),
        )
        .arg(
            Arg::with_name("PKG_IDENT_OR_ARTIFACT")
                .value_name("PKG_IDENT_OR_ARTIFACT")
                .required(true)
                .help("Habitat package identifier (ex: acme/redis)"),
        )
}

fn valid_natural_number(val: String) -> result::Result<(), String> {
    match val.parse::<u32>() {
        Ok(_) => Ok(()),
        Err(_) => Err(format!("{} is not a natural number", val)),
    }
}
<commit_msg>pkg-export-k8s: Make use of new export-docker API<commit_after>// Copyright (c) 2017 Chef Software Inc. and/or applicable contributors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

extern crate clap;
extern crate env_logger;
extern crate habitat_core as hcore;
extern crate habitat_common as common;
extern crate habitat_pkg_export_docker as export_docker;
extern crate handlebars;
extern crate rand;
#[macro_use]
extern crate serde_json;
#[macro_use]
extern crate log;

extern crate failure;
#[macro_use]
extern crate failure_derive;

use clap::{App, Arg};
use handlebars::Handlebars;
use std::env;
use std::result;
use std::str::FromStr;
use std::io::prelude::*;
use std::io;
use std::fs::File;
use std::path::Path;

use hcore::channel;
use hcore::PROGRAM_NAME;
use hcore::url as hurl;
use hcore::package::{PackageArchive, PackageIdent};
use common::ui::UI;
use rand::Rng;

use export_docker::{Cli, Credentials, BuildSpec, Naming, PkgIdentArgOptions, Result};

// Synced with the version of the Habitat operator.
pub const VERSION: &'static str = "0.1.0";

// Kubernetes manifest template
const MANIFESTFILE: &'static str = include_str!("../defaults/KubernetesManifest.hbs");
const BINDFILE: &'static str = include_str!("../defaults/KubernetesBind.hbs");

#[derive(Debug, Fail)]
enum Error {
    #[fail(display = "Invalid bind specification '{}'", _0)]
    InvalidBindSpec(String),
}

fn main() {
    env_logger::init().unwrap();
    let mut ui = UI::default_with_env();
    if let Err(e) = start(&mut ui) {
        let _ = ui.fatal(e);
        std::process::exit(1)
    }
}

fn start(ui: &mut UI) -> Result<()> {
    let m = cli().get_matches();
    debug!("clap cli args: {:?}", m);

    if !m.is_present("NO_DOCKER_IMAGE") {
        gen_docker_img(ui, &m)?;
    }
    gen_k8s_manifest(ui, &m)
}

fn gen_docker_img(ui: &mut UI, matches: &clap::ArgMatches) -> Result<()> {
    let default_channel = channel::default();
    let default_url = hurl::default_bldr_url();
    let spec = BuildSpec::new_from_cli_matches(&matches, &default_channel, &default_url);
    let naming = Naming::new_from_cli_matches(&matches);

    let docker_image = export_docker::export(ui, spec, &naming)?;
    docker_image.create_report(
        ui,
        env::current_dir()?.join("results"),
    )?;

    if matches.is_present("PUSH_IMAGE") {
        let credentials = Credentials::new(
            naming.registry_type,
            matches.value_of("REGISTRY_USERNAME").unwrap(),
            matches.value_of("REGISTRY_PASSWORD").unwrap(),
        )?;
        docker_image.push(ui, &credentials, naming.registry_url)?;
    }
    if matches.is_present("RM_IMAGE") {
        docker_image.rm(ui)?;
    }

    Ok(())
}

fn gen_k8s_manifest(_ui: &mut UI, matches: &clap::ArgMatches) -> Result<()> {
    let count = matches.value_of("COUNT").unwrap_or("1");
    let topology = matches.value_of("TOPOLOGY").unwrap_or("standalone");
    let group = matches.value_of("GROUP");
    let config_secret_name = matches.value_of("CONFIG_SECRET_NAME");
    let ring_secret_name = matches.value_of("RING_SECRET_NAME");
    // clap ensures that we do have the mandatory args so unwrap() is fine here
    let pkg_ident_str = matches.value_of("PKG_IDENT_OR_ARTIFACT").unwrap();
    let pkg_ident = if Path::new(pkg_ident_str).is_file() {
        // We're going to use the `$pkg_origin/$pkg_name`, fuzzy form of a package
        // identifier to ensure that update strategies will work if desired
        PackageArchive::new(pkg_ident_str).ident()?
    } else {
        PackageIdent::from_str(pkg_ident_str)?
    };

    // To allow multiple instances of Habitat application in Kubernetes,
    // random suffix in metadata_name is needed.
    let metadata_name = format!(
        "{}-{}{}",
        pkg_ident.name,
        rand::thread_rng()
            .gen_ascii_chars()
            .filter(|c| c.is_lowercase() || c.is_numeric())
            .take(4)
            .collect::<String>(),
        rand::thread_rng()
            .gen_ascii_chars()
            .filter(|c| c.is_lowercase() && !c.is_numeric())
            .take(1)
            .collect::<String>()
    );

    let image = match matches.value_of("IMAGE_NAME") {
        Some(i) => i.to_string(),
        None => pkg_ident.origin + "/" + &pkg_ident.name,
    };
    let bind = matches.value_of("BIND");

    let json = json!({
        "metadata_name": metadata_name,
        "habitat_name": pkg_ident.name,
        "image": image,
        "count": count,
        "service_topology": topology,
        "service_group": group,
        "config_secret_name": config_secret_name,
        "ring_secret_name": ring_secret_name,
        "bind": bind,
    });

    let mut write: Box<Write> = match matches.value_of("OUTPUT") {
        Some(o) if o != "-" => Box::new(File::create(o)?),
        _ => Box::new(io::stdout()),
    };

    let r = Handlebars::new().template_render(MANIFESTFILE, &json)?;
    let mut out = r.lines().filter(|l| *l != "").collect::<Vec<_>>().join(
        "\n",
    ) + "\n";

    if let Some(binds) = matches.values_of("BIND") {
        for bind in binds {
            let split: Vec<&str> = bind.split(":").collect();
            if split.len() < 3 {
                return Err(Error::InvalidBindSpec(bind.to_string()).into());
            }

            let json = json!({
                "name": split[0],
                "service": split[1],
                "group": split[2],
            });

            out += &Handlebars::new().template_render(BINDFILE, &json)?;
        }
    }

    write.write(out.as_bytes())?;

    Ok(())
}

fn cli<'a, 'b>() -> App<'a, 'b> {
    let name: &str = &*PROGRAM_NAME;
    let about = "Creates a Docker image and Kubernetes manifest for a Habitat package. Habitat \
                 operator must be deployed within the Kubernetes cluster before the generated \
                 manifest can be applied to this cluster.";

    let app = Cli::new(name, about)
        .add_base_packages_args()
        .add_builder_args()
        .add_tagging_args()
        .add_publishing_args()
        .add_pkg_ident_arg(PkgIdentArgOptions { multiple: false })
        .app;

    app.arg(
        Arg::with_name("OUTPUT")
            .value_name("OUTPUT")
            .long("output")
            .short("o")
            .help(
                "Name of manifest file to create. Pass '-' for stdout (default: -)",
            ),
    ).arg(
            Arg::with_name("COUNT")
                .value_name("COUNT")
                .long("count")
                .validator(valid_natural_number)
                .help("Count is the number of desired instances"),
        )
        .arg(
            Arg::with_name("TOPOLOGY")
                .value_name("TOPOLOGY")
                .long("topology")
                .short("t")
                .possible_values(&["standalone", "leader"])
                .help(
                    "A topology describes the intended relationship between peers \
                    within a Habitat service group. Specify either standalone or leader \
                    topology (default: standalone)",
                ),
        )
        .arg(
            Arg::with_name("GROUP")
                .value_name("GROUP")
                .long("service-group")
                .short("g")
                .help(
                    "group is a logical grouping of services with the same package and \
                    topology type connected together in a ring (default: default)",
                ),
        )
        .arg(
            Arg::with_name("CONFIG_SECRET_NAME")
                .value_name("CONFIG_SECRET_NAME")
                .long("config-secret-name")
                .short("n")
                .help(
                    "name of the Kubernetes Secret containing the config file - \
                    user.toml - that the user has previously created. Habitat will \
                    use it for initial configuration of the service",
                ),
        )
        .arg(
            Arg::with_name("RING_SECRET_NAME")
                .value_name("RING_SECRET_NAME")
                .long("ring-secret-name")
                .short("r")
                .help(
                    "name of the Kubernetes Secret that contains the ring key, which \
                    encrypts the communication between Habitat supervisors",
                ),
        )
        .arg(
            Arg::with_name("BIND")
                .value_name("BIND")
                .long("bind")
                .short("b")
                .multiple(true)
                .number_of_values(1)
                .help(
                    "Bind to another service to form a producer/consumer relationship, \
                    specified as name:service:group",
                ),
        )
        .arg(
            Arg::with_name("NO_DOCKER_IMAGE")
                .long("no-docker-image")
                .short("d")
                .help(
                    "Disable creation of the Docker image and only create a Kubernetes manifest",
                ),
        )
}

fn valid_natural_number(val: String) -> result::Result<(), String> {
    match val.parse::<u32>() {
        Ok(_) => Ok(()),
        Err(_) => Err(format!("{} is not a natural number", val)),
    }
}
<|endoftext|>