<commit_before>//! Allows for producing a unique string key for a mono item.
//! These keys are used by the handwritten auto-tests, so they need to be
//! predictable and human-readable.
//!
//! Note: A lot of this could looks very similar to what's already in `ty::print`.
//! FIXME(eddyb) implement a custom `PrettyPrinter` for this.

use rustc::bug;
use rustc::ty::subst::SubstsRef;
use rustc::ty::{self, Const, Instance, Ty, TyCtxt};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use std::fmt::Write;
use std::iter;

/// Same as `unique_type_name()` but with the result pushed onto the given
/// `output` parameter.
pub struct DefPathBasedNames<'tcx> {
    tcx: TyCtxt<'tcx>,
    omit_disambiguators: bool,
    omit_local_crate_name: bool,
}

impl DefPathBasedNames<'tcx> {
    pub fn new(tcx: TyCtxt<'tcx>, omit_disambiguators: bool, omit_local_crate_name: bool) -> Self {
        DefPathBasedNames { tcx, omit_disambiguators, omit_local_crate_name }
    }

    // Pushes the type name of the specified type to the provided string.
    // If `debug` is true, printing normally unprintable types is allowed
    // (e.g. `ty::GeneratorWitness`). This parameter should only be set when
    // this method is being used for logging purposes (e.g. with `debug!` or `info!`)
    // When being used for codegen purposes, `debug` should be set to `false`
    // in order to catch unexpected types that should never end up in a type name.
    pub fn push_type_name(&self, t: Ty<'tcx>, output: &mut String, debug: bool) {
        match t.kind {
            ty::Bool => output.push_str("bool"),
            ty::Char => output.push_str("char"),
            ty::Str => output.push_str("str"),
            ty::Never => output.push_str("!"),
            ty::Int(ty) => output.push_str(ty.name_str()),
            ty::Uint(ty) => output.push_str(ty.name_str()),
            ty::Float(ty) => output.push_str(ty.name_str()),
            ty::Adt(adt_def, substs) => {
                self.push_def_path(adt_def.did, output);
                self.push_generic_params(substs, iter::empty(), output, debug);
            }
            ty::Tuple(component_types) => {
                output.push('(');
                for &component_type in component_types {
                    self.push_type_name(component_type.expect_ty(), output, debug);
                    output.push_str(", ");
                }
                if !component_types.is_empty() {
                    output.pop();
                    output.pop();
                }
                output.push(')');
            }
            ty::RawPtr(ty::TypeAndMut { ty: inner_type, mutbl }) => {
                output.push('*');
                match mutbl {
                    hir::Mutability::Not => output.push_str("const "),
                    hir::Mutability::Mut => output.push_str("mut "),
                }

                self.push_type_name(inner_type, output, debug);
            }
            ty::Ref(_, inner_type, mutbl) => {
                output.push('&');
                output.push_str(mutbl.prefix_str());

                self.push_type_name(inner_type, output, debug);
            }
            ty::Array(inner_type, len) => {
                output.push('[');
                self.push_type_name(inner_type, output, debug);
                let len = len.eval_usize(self.tcx, ty::ParamEnv::reveal_all());
                write!(output, "; {}", len).unwrap();
                output.push(']');
            }
            ty::Slice(inner_type) => {
                output.push('[');
                self.push_type_name(inner_type, output, debug);
                output.push(']');
            }
            ty::Dynamic(ref trait_data, ..) => {
                if let Some(principal) = trait_data.principal() {
                    self.push_def_path(principal.def_id(), output);
                    self.push_generic_params(
                        principal.skip_binder().substs,
                        trait_data.projection_bounds(),
                        output,
                        debug,
                    );
                } else {
                    output.push_str("dyn '_");
                }
            }
            ty::Foreign(did) => self.push_def_path(did, output),
            ty::FnDef(..) | ty::FnPtr(_) => {
                let sig = t.fn_sig(self.tcx);
                output.push_str(sig.unsafety().prefix_str());

                let abi = sig.abi();
                if abi != ::rustc_target::spec::abi::Abi::Rust {
                    output.push_str("extern \"");
                    output.push_str(abi.name());
                    output.push_str("\" ");
                }

                output.push_str("fn(");

                let sig =
                    self.tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), &sig);

                if !sig.inputs().is_empty() {
                    for &parameter_type in sig.inputs() {
                        self.push_type_name(parameter_type, output, debug);
                        output.push_str(", ");
                    }
                    output.pop();
                    output.pop();
                }

                if sig.c_variadic {
                    if !sig.inputs().is_empty() {
                        output.push_str(", ...");
                    } else {
                        output.push_str("...");
                    }
                }

                output.push(')');

                if !sig.output().is_unit() {
                    output.push_str(" -> ");
                    self.push_type_name(sig.output(), output, debug);
                }
            }
            ty::Generator(def_id, substs, _) | ty::Closure(def_id, substs) => {
                self.push_def_path(def_id, output);
                let generics = self.tcx.generics_of(self.tcx.closure_base_def_id(def_id));
                let substs = substs.truncate_to(self.tcx, generics);
                self.push_generic_params(substs, iter::empty(), output, debug);
            }
            ty::Error
            | ty::Bound(..)
            | ty::Infer(_)
            | ty::Placeholder(..)
            | ty::UnnormalizedProjection(..)
            | ty::Projection(..)
            | ty::Param(_)
            | ty::GeneratorWitness(_)
            | ty::Opaque(..) => {
                if debug {
                    output.push_str(&format!("`{:?}`", t));
                } else {
                    bug!(
                        "DefPathBasedNames: trying to create type name for unexpected type: {:?}",
                        t,
                    );
                }
            }
        }
    }

    // Pushes the the name of the specified const to the provided string.
    // If `debug` is true, usually-unprintable consts (such as `Infer`) will be printed,
    // as well as the unprintable types of constants (see `push_type_name` for more details).
    pub fn push_const_name(&self, c: &Const<'tcx>, output: &mut String, debug: bool) {
        if let ty::ConstKind::Value(_) = c.val {
            // FIXME(const_generics): we could probably do a better job here.
            write!(output, "{:?}", c).unwrap()
        } else if debug {
            write!(output, "{:?}", c).unwrap()
        } else {
            bug!("DefPathBasedNames: trying to create const name for unexpected const: {:?}", c,);
        }
        output.push_str(": ");
        self.push_type_name(c.ty, output, debug);
    }

    pub fn push_def_path(&self, def_id: DefId, output: &mut String) {
        let def_path = self.tcx.def_path(def_id);

        // some_crate::
        if !(self.omit_local_crate_name && def_id.is_local()) {
            output.push_str(&self.tcx.crate_name(def_path.krate).as_str());
            output.push_str("::");
        }

        // foo::bar::ItemName::
        for part in self.tcx.def_path(def_id).data {
            if self.omit_disambiguators {
                write!(output, "{}::", part.data.as_symbol()).unwrap();
            } else {
                write!(output, "{}[{}]::", part.data.as_symbol(), part.disambiguator).unwrap();
            }
        }

        // remove final "::"
        output.pop();
        output.pop();
    }

    fn push_generic_params<I>(
        &self,
        substs: SubstsRef<'tcx>,
        projections: I,
        output: &mut String,
        debug: bool,
    ) where
        I: Iterator<Item = ty::PolyExistentialProjection<'tcx>>,
    {
        let mut projections = projections.peekable();
        if substs.non_erasable_generics().next().is_none() && projections.peek().is_none() {
            return;
        }

        output.push('<');

        for type_parameter in substs.types() {
            self.push_type_name(type_parameter, output, debug);
            output.push_str(", ");
        }

        for projection in projections {
            let projection = projection.skip_binder();
            let name = &self.tcx.associated_item(projection.item_def_id).ident.as_str();
            output.push_str(name);
            output.push_str("=");
            self.push_type_name(projection.ty, output, debug);
            output.push_str(", ");
        }

        for const_parameter in substs.consts() {
            self.push_const_name(const_parameter, output, debug);
            output.push_str(", ");
        }

        output.pop();
        output.pop();

        output.push('>');
    }

    pub fn push_instance_as_string(
        &self,
        instance: Instance<'tcx>,
        output: &mut String,
        debug: bool,
    ) {
        self.push_def_path(instance.def_id(), output);
        self.push_generic_params(instance.substs, iter::empty(), output, debug);
    }
}
<commit_msg>Rollup merge of #67909 - varkor:obsolete-const-print, r=davidtwco<commit_after>//! Allows for producing a unique string key for a mono item.
//! These keys are used by the handwritten auto-tests, so they need to be
//! predictable and human-readable.
//!
//! Note: A lot of this could looks very similar to what's already in `ty::print`.
//! FIXME(eddyb) implement a custom `PrettyPrinter` for this.

use rustc::bug;
use rustc::ty::subst::SubstsRef;
use rustc::ty::{self, Const, Instance, Ty, TyCtxt};
use rustc_hir as hir;
use rustc_hir::def_id::DefId;
use std::fmt::Write;
use std::iter;

/// Same as `unique_type_name()` but with the result pushed onto the given
/// `output` parameter.
pub struct DefPathBasedNames<'tcx> {
    tcx: TyCtxt<'tcx>,
    omit_disambiguators: bool,
    omit_local_crate_name: bool,
}

impl DefPathBasedNames<'tcx> {
    pub fn new(tcx: TyCtxt<'tcx>, omit_disambiguators: bool, omit_local_crate_name: bool) -> Self {
        DefPathBasedNames { tcx, omit_disambiguators, omit_local_crate_name }
    }

    // Pushes the type name of the specified type to the provided string.
    // If `debug` is true, printing normally unprintable types is allowed
    // (e.g. `ty::GeneratorWitness`). This parameter should only be set when
    // this method is being used for logging purposes (e.g. with `debug!` or `info!`)
    // When being used for codegen purposes, `debug` should be set to `false`
    // in order to catch unexpected types that should never end up in a type name.
    pub fn push_type_name(&self, t: Ty<'tcx>, output: &mut String, debug: bool) {
        match t.kind {
            ty::Bool => output.push_str("bool"),
            ty::Char => output.push_str("char"),
            ty::Str => output.push_str("str"),
            ty::Never => output.push_str("!"),
            ty::Int(ty) => output.push_str(ty.name_str()),
            ty::Uint(ty) => output.push_str(ty.name_str()),
            ty::Float(ty) => output.push_str(ty.name_str()),
            ty::Adt(adt_def, substs) => {
                self.push_def_path(adt_def.did, output);
                self.push_generic_params(substs, iter::empty(), output, debug);
            }
            ty::Tuple(component_types) => {
                output.push('(');
                for &component_type in component_types {
                    self.push_type_name(component_type.expect_ty(), output, debug);
                    output.push_str(", ");
                }
                if !component_types.is_empty() {
                    output.pop();
                    output.pop();
                }
                output.push(')');
            }
            ty::RawPtr(ty::TypeAndMut { ty: inner_type, mutbl }) => {
                output.push('*');
                match mutbl {
                    hir::Mutability::Not => output.push_str("const "),
                    hir::Mutability::Mut => output.push_str("mut "),
                }

                self.push_type_name(inner_type, output, debug);
            }
            ty::Ref(_, inner_type, mutbl) => {
                output.push('&');
                output.push_str(mutbl.prefix_str());

                self.push_type_name(inner_type, output, debug);
            }
            ty::Array(inner_type, len) => {
                output.push('[');
                self.push_type_name(inner_type, output, debug);
                let len = len.eval_usize(self.tcx, ty::ParamEnv::reveal_all());
                write!(output, "; {}", len).unwrap();
                output.push(']');
            }
            ty::Slice(inner_type) => {
                output.push('[');
                self.push_type_name(inner_type, output, debug);
                output.push(']');
            }
            ty::Dynamic(ref trait_data, ..) => {
                if let Some(principal) = trait_data.principal() {
                    self.push_def_path(principal.def_id(), output);
                    self.push_generic_params(
                        principal.skip_binder().substs,
                        trait_data.projection_bounds(),
                        output,
                        debug,
                    );
                } else {
                    output.push_str("dyn '_");
                }
            }
            ty::Foreign(did) => self.push_def_path(did, output),
            ty::FnDef(..) | ty::FnPtr(_) => {
                let sig = t.fn_sig(self.tcx);
                output.push_str(sig.unsafety().prefix_str());

                let abi = sig.abi();
                if abi != ::rustc_target::spec::abi::Abi::Rust {
                    output.push_str("extern \"");
                    output.push_str(abi.name());
                    output.push_str("\" ");
                }

                output.push_str("fn(");

                let sig =
                    self.tcx.normalize_erasing_late_bound_regions(ty::ParamEnv::reveal_all(), &sig);

                if !sig.inputs().is_empty() {
                    for &parameter_type in sig.inputs() {
                        self.push_type_name(parameter_type, output, debug);
                        output.push_str(", ");
                    }
                    output.pop();
                    output.pop();
                }

                if sig.c_variadic {
                    if !sig.inputs().is_empty() {
                        output.push_str(", ...");
                    } else {
                        output.push_str("...");
                    }
                }

                output.push(')');

                if !sig.output().is_unit() {
                    output.push_str(" -> ");
                    self.push_type_name(sig.output(), output, debug);
                }
            }
            ty::Generator(def_id, substs, _) | ty::Closure(def_id, substs) => {
                self.push_def_path(def_id, output);
                let generics = self.tcx.generics_of(self.tcx.closure_base_def_id(def_id));
                let substs = substs.truncate_to(self.tcx, generics);
                self.push_generic_params(substs, iter::empty(), output, debug);
            }
            ty::Error
            | ty::Bound(..)
            | ty::Infer(_)
            | ty::Placeholder(..)
            | ty::UnnormalizedProjection(..)
            | ty::Projection(..)
            | ty::Param(_)
            | ty::GeneratorWitness(_)
            | ty::Opaque(..) => {
                if debug {
                    output.push_str(&format!("`{:?}`", t));
                } else {
                    bug!(
                        "DefPathBasedNames: trying to create type name for unexpected type: {:?}",
                        t,
                    );
                }
            }
        }
    }

    // Pushes the the name of the specified const to the provided string.
    // If `debug` is true, the unprintable types of constants will be printed with `fmt::Debug`
    // (see `push_type_name` for more details).
    pub fn push_const_name(&self, ct: &Const<'tcx>, output: &mut String, debug: bool) {
        write!(output, "{}", ct).unwrap();
        output.push_str(": ");
        self.push_type_name(ct.ty, output, debug);
    }

    pub fn push_def_path(&self, def_id: DefId, output: &mut String) {
        let def_path = self.tcx.def_path(def_id);

        // some_crate::
        if !(self.omit_local_crate_name && def_id.is_local()) {
            output.push_str(&self.tcx.crate_name(def_path.krate).as_str());
            output.push_str("::");
        }

        // foo::bar::ItemName::
        for part in self.tcx.def_path(def_id).data {
            if self.omit_disambiguators {
                write!(output, "{}::", part.data.as_symbol()).unwrap();
            } else {
                write!(output, "{}[{}]::", part.data.as_symbol(), part.disambiguator).unwrap();
            }
        }

        // remove final "::"
        output.pop();
        output.pop();
    }

    fn push_generic_params<I>(
        &self,
        substs: SubstsRef<'tcx>,
        projections: I,
        output: &mut String,
        debug: bool,
    ) where
        I: Iterator<Item = ty::PolyExistentialProjection<'tcx>>,
    {
        let mut projections = projections.peekable();
        if substs.non_erasable_generics().next().is_none() && projections.peek().is_none() {
            return;
        }

        output.push('<');

        for type_parameter in substs.types() {
            self.push_type_name(type_parameter, output, debug);
            output.push_str(", ");
        }

        for projection in projections {
            let projection = projection.skip_binder();
            let name = &self.tcx.associated_item(projection.item_def_id).ident.as_str();
            output.push_str(name);
            output.push_str("=");
            self.push_type_name(projection.ty, output, debug);
            output.push_str(", ");
        }

        for const_parameter in substs.consts() {
            self.push_const_name(const_parameter, output, debug);
            output.push_str(", ");
        }

        output.pop();
        output.pop();

        output.push('>');
    }

    pub fn push_instance_as_string(
        &self,
        instance: Instance<'tcx>,
        output: &mut String,
        debug: bool,
    ) {
        self.push_def_path(instance.def_id(), output);
        self.push_generic_params(instance.substs, iter::empty(), output, debug);
    }
}
<|endoftext|>


<commit_before>//* This file is part of the uutils coreutils package.
//*
//* (c) Roman Gafiyatullin <r.gafiyatullin@me.com>
//*
//* For the full copyright and license information, please view the LICENSE
//* file that was distributed with this source code.

#[macro_use]
extern crate uucore;

use clap::{crate_version, App, Arg};
use uucore::InvalidEncodingHandling;

mod syntax_tree;
mod tokens;

const VERSION: &str = "version";
const HELP: &str = "help";

pub fn uu_app() -> App<'static, 'static> {
    App::new(uucore::util_name())
        .arg(Arg::with_name(VERSION).long(VERSION))
        .arg(Arg::with_name(HELP).long(HELP))
}

pub fn uumain(args: impl uucore::Args) -> i32 {
    let args = args
        .collect_str(InvalidEncodingHandling::ConvertLossy)
        .accept_any();

    // For expr utility we do not want getopts.
    // The following usage should work without escaping hyphens: `expr -15 = 1 +  2 \* \( 3 - -4 \)`

    if maybe_handle_help_or_version(&args) {
        0
    } else {
        let token_strings = args[1..].to_vec();

        match process_expr(&token_strings) {
            Ok(expr_result) => print_expr_ok(&expr_result),
            Err(expr_error) => print_expr_error(&expr_error),
        }
    }
}

fn process_expr(token_strings: &[String]) -> Result<String, String> {
    let maybe_tokens = tokens::strings_to_tokens(token_strings);
    let maybe_ast = syntax_tree::tokens_to_ast(maybe_tokens);
    evaluate_ast(maybe_ast)
}

fn print_expr_ok(expr_result: &str) -> i32 {
    println!("{}", expr_result);
    if expr_result == "0" || expr_result.is_empty() {
        1
    } else {
        0
    }
}

fn print_expr_error(expr_error: &str) -> ! {
    crash!(2, "{}", expr_error)
}

fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> {
    maybe_ast.and_then(|ast| ast.evaluate())
}

fn maybe_handle_help_or_version(args: &[String]) -> bool {
    if args.len() == 2 {
        if args[1] == "--help" {
            print_help();
            true
        } else if args[1] == "--version" {
            print_version();
            true
        } else {
            false
        }
    } else {
        false
    }
}

fn print_help() {
    //! The following is taken from GNU coreutils' "expr --help" output.
    println!(
        r#"Usage: expr EXPRESSION
  or:  expr OPTION

      --help       display this help and exit
      --version    output version information and exit

Print the value of EXPRESSION to standard output.  A blank line below
separates increasing precedence groups.  EXPRESSION may be:

  ARG1 | ARG2       ARG1 if it is neither null nor 0, otherwise ARG2

  ARG1 & ARG2       ARG1 if neither argument is null or 0, otherwise 0

  ARG1 < ARG2       ARG1 is less than ARG2
  ARG1 <= ARG2      ARG1 is less than or equal to ARG2
  ARG1 = ARG2       ARG1 is equal to ARG2
  ARG1 != ARG2      ARG1 is unequal to ARG2
  ARG1 >= ARG2      ARG1 is greater than or equal to ARG2
  ARG1 > ARG2       ARG1 is greater than ARG2

  ARG1 + ARG2       arithmetic sum of ARG1 and ARG2
  ARG1 - ARG2       arithmetic difference of ARG1 and ARG2

  ARG1 * ARG2       arithmetic product of ARG1 and ARG2
  ARG1 / ARG2       arithmetic quotient of ARG1 divided by ARG2
  ARG1 % ARG2       arithmetic remainder of ARG1 divided by ARG2

  STRING : REGEXP   anchored pattern match of REGEXP in STRING

  match STRING REGEXP        same as STRING : REGEXP
  substr STRING POS LENGTH   substring of STRING, POS counted from 1
  index STRING CHARS         index in STRING where any CHARS is found, or 0
  length STRING              length of STRING
  + TOKEN                    interpret TOKEN as a string, even if it is a
                               keyword like 'match' or an operator like '/'

  ( EXPRESSION )             value of EXPRESSION

Beware that many operators need to be escaped or quoted for shells.
Comparisons are arithmetic if both ARGs are numbers, else lexicographical.
Pattern matches return the string matched between \( and \) or null; if
\( and \) are not used, they return the number of characters matched or 0.

Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null
or 0, 2 if EXPRESSION is syntactically invalid, and 3 if an error occurred.

Environment variables:
    * EXPR_DEBUG_TOKENS=1   dump expression's tokens
    * EXPR_DEBUG_RPN=1      dump expression represented in reverse polish notation
    * EXPR_DEBUG_SYA_STEP=1 dump each parser step
    * EXPR_DEBUG_AST=1      dump expression represented abstract syntax tree"#
    );
}

fn print_version() {
    println!("{} {}", uucore::util_name(), crate_version!());
}
<commit_msg>expr: use UResult<commit_after>//* This file is part of the uutils coreutils package.
//*
//* (c) Roman Gafiyatullin <r.gafiyatullin@me.com>
//*
//* For the full copyright and license information, please view the LICENSE
//* file that was distributed with this source code.

#[macro_use]
extern crate uucore;

use clap::{crate_version, App, Arg};
use uucore::error::{UResult, USimpleError};
use uucore::InvalidEncodingHandling;

mod syntax_tree;
mod tokens;

const VERSION: &str = "version";
const HELP: &str = "help";

pub fn uu_app() -> App<'static, 'static> {
    App::new(uucore::util_name())
        .arg(Arg::with_name(VERSION).long(VERSION))
        .arg(Arg::with_name(HELP).long(HELP))
}

#[uucore_procs::gen_uumain]
pub fn uumain(args: impl uucore::Args) -> UResult<()> {
    let args = args
        .collect_str(InvalidEncodingHandling::ConvertLossy)
        .accept_any();

    // For expr utility we do not want getopts.
    // The following usage should work without escaping hyphens: `expr -15 = 1 +  2 \* \( 3 - -4 \)`

    if maybe_handle_help_or_version(&args) {
        Ok(())
    } else {
        let token_strings = args[1..].to_vec();

        match process_expr(&token_strings) {
            Ok(expr_result) => print_expr_ok(&expr_result),
            Err(expr_error) => Err(USimpleError::new(2, &expr_error)),
        }
    }
}

fn process_expr(token_strings: &[String]) -> Result<String, String> {
    let maybe_tokens = tokens::strings_to_tokens(token_strings);
    let maybe_ast = syntax_tree::tokens_to_ast(maybe_tokens);
    evaluate_ast(maybe_ast)
}

fn print_expr_ok(expr_result: &str) -> UResult<()> {
    println!("{}", expr_result);
    if expr_result == "0" || expr_result.is_empty() {
        Err(1.into())
    } else {
        Ok(())
    }
}

fn evaluate_ast(maybe_ast: Result<Box<syntax_tree::AstNode>, String>) -> Result<String, String> {
    maybe_ast.and_then(|ast| ast.evaluate())
}

fn maybe_handle_help_or_version(args: &[String]) -> bool {
    if args.len() == 2 {
        if args[1] == "--help" {
            print_help();
            true
        } else if args[1] == "--version" {
            print_version();
            true
        } else {
            false
        }
    } else {
        false
    }
}

fn print_help() {
    //! The following is taken from GNU coreutils' "expr --help" output.
    println!(
        r#"Usage: expr EXPRESSION
  or:  expr OPTION

      --help       display this help and exit
      --version    output version information and exit

Print the value of EXPRESSION to standard output.  A blank line below
separates increasing precedence groups.  EXPRESSION may be:

  ARG1 | ARG2       ARG1 if it is neither null nor 0, otherwise ARG2

  ARG1 & ARG2       ARG1 if neither argument is null or 0, otherwise 0

  ARG1 < ARG2       ARG1 is less than ARG2
  ARG1 <= ARG2      ARG1 is less than or equal to ARG2
  ARG1 = ARG2       ARG1 is equal to ARG2
  ARG1 != ARG2      ARG1 is unequal to ARG2
  ARG1 >= ARG2      ARG1 is greater than or equal to ARG2
  ARG1 > ARG2       ARG1 is greater than ARG2

  ARG1 + ARG2       arithmetic sum of ARG1 and ARG2
  ARG1 - ARG2       arithmetic difference of ARG1 and ARG2

  ARG1 * ARG2       arithmetic product of ARG1 and ARG2
  ARG1 / ARG2       arithmetic quotient of ARG1 divided by ARG2
  ARG1 % ARG2       arithmetic remainder of ARG1 divided by ARG2

  STRING : REGEXP   anchored pattern match of REGEXP in STRING

  match STRING REGEXP        same as STRING : REGEXP
  substr STRING POS LENGTH   substring of STRING, POS counted from 1
  index STRING CHARS         index in STRING where any CHARS is found, or 0
  length STRING              length of STRING
  + TOKEN                    interpret TOKEN as a string, even if it is a
                               keyword like 'match' or an operator like '/'

  ( EXPRESSION )             value of EXPRESSION

Beware that many operators need to be escaped or quoted for shells.
Comparisons are arithmetic if both ARGs are numbers, else lexicographical.
Pattern matches return the string matched between \( and \) or null; if
\( and \) are not used, they return the number of characters matched or 0.

Exit status is 0 if EXPRESSION is neither null nor 0, 1 if EXPRESSION is null
or 0, 2 if EXPRESSION is syntactically invalid, and 3 if an error occurred.

Environment variables:
    * EXPR_DEBUG_TOKENS=1   dump expression's tokens
    * EXPR_DEBUG_RPN=1      dump expression represented in reverse polish notation
    * EXPR_DEBUG_SYA_STEP=1 dump each parser step
    * EXPR_DEBUG_AST=1      dump expression represented abstract syntax tree"#
    );
}

fn print_version() {
    println!("{} {}", uucore::util_name(), crate_version!());
}
<|endoftext|>


<commit_before>//  * This file is part of the uutils coreutils package.
//  *
//  * (c) Martin Kysel <code@martinkysel.com>
//  *
//  * For the full copyright and license information, please view the LICENSE file
//  * that was distributed with this source code.

// spell-checker:ignore (methods) isnt

#[macro_use]
extern crate uucore;

use std::{
    fs::File,
    io::{stdin, stdout, BufReader, Read, Stdout, Write},
    path::Path,
    time::Duration,
};

#[cfg(all(unix, not(target_os = "fuchsia")))]
extern crate nix;

use clap::{crate_version, App, Arg};
use crossterm::{
    event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
    execute, queue,
    style::Attribute,
    terminal,
};

use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;

pub mod options {
    pub const SILENT: &str = "silent";
    pub const LOGICAL: &str = "logical";
    pub const NO_PAUSE: &str = "no-pause";
    pub const PRINT_OVER: &str = "print-over";
    pub const CLEAN_PRINT: &str = "clean-print";
    pub const SQUEEZE: &str = "squeeze";
    pub const PLAIN: &str = "plain";
    pub const LINES: &str = "lines";
    pub const NUMBER: &str = "number";
    pub const PATTERN: &str = "pattern";
    pub const FROM_LINE: &str = "from-line";
    pub const FILES: &str = "files";
}

const MULTI_FILE_TOP_PROMPT: &str = "::::::::::::::\n{}\n::::::::::::::\n";

pub fn uumain(args: impl uucore::Args) -> i32 {
    let matches = App::new(executable!())
        .about("A file perusal filter for CRT viewing.")
        .version(crate_version!())
        // The commented arguments below are unimplemented:
        /*
        .arg(
            Arg::with_name(options::SILENT)
                .short("d")
                .long(options::SILENT)
                .help("Display help instead of ringing bell"),
        )
        .arg(
            Arg::with_name(options::LOGICAL)
                .short("f")
                .long(options::LOGICAL)
                .help("Count logical rather than screen lines"),
        )
        .arg(
            Arg::with_name(options::NO_PAUSE)
                .short("l")
                .long(options::NO_PAUSE)
                .help("Suppress pause after form feed"),
        )
        .arg(
            Arg::with_name(options::PRINT_OVER)
                .short("c")
                .long(options::PRINT_OVER)
                .help("Do not scroll, display text and clean line ends"),
        )
        .arg(
            Arg::with_name(options::CLEAN_PRINT)
                .short("p")
                .long(options::CLEAN_PRINT)
                .help("Do not scroll, clean screen and display text"),
        )
        .arg(
            Arg::with_name(options::SQUEEZE)
                .short("s")
                .long(options::SQUEEZE)
                .help("Squeeze multiple blank lines into one"),
        )
        .arg(
            Arg::with_name(options::PLAIN)
                .short("u")
                .long(options::PLAIN)
                .help("Suppress underlining and bold"),
        )
        .arg(
            Arg::with_name(options::LINES)
                .short("n")
                .long(options::LINES)
                .value_name("number")
                .takes_value(true)
                .help("The number of lines per screen full"),
        )
        .arg(
            Arg::with_name(options::NUMBER)
                .allow_hyphen_values(true)
                .long(options::NUMBER)
                .required(false)
                .takes_value(true)
                .help("Same as --lines"),
        )
        .arg(
            Arg::with_name(options::FROM_LINE)
                .short("F")
                .allow_hyphen_values(true)
                .required(false)
                .takes_value(true)
                .value_name("number")
                .help("Display file beginning from line number"),
        )
        .arg(
            Arg::with_name(options::PATTERN)
                .short("P")
                .allow_hyphen_values(true)
                .required(false)
                .takes_value(true)
                .help("Display file beginning from pattern match"),
        )
        */
        .arg(
            Arg::with_name(options::FILES)
                .required(false)
                .multiple(true)
                .help("Path to the files to be read"),
        )
        .get_matches_from(args);

    let mut buff = String::new();
    if let Some(files) = matches.values_of(options::FILES) {
        let mut stdout = setup_term();
        let length = files.len();

        let mut files_iter = files.peekable();
        while let (Some(file), next_file) = (files_iter.next(), files_iter.peek()) {
            let file = Path::new(file);
            if file.is_dir() {
                terminal::disable_raw_mode().unwrap();
                show_usage_error!("'{}' is a directory.", file.display());
                return 1;
            }
            if !file.exists() {
                terminal::disable_raw_mode().unwrap();
                show_error!("cannot open {}: No such file or directory", file.display());
                return 1;
            }
            if length > 1 {
                buff.push_str(&MULTI_FILE_TOP_PROMPT.replace("{}", file.to_str().unwrap()));
            }
            let mut reader = BufReader::new(File::open(file).unwrap());
            reader.read_to_string(&mut buff).unwrap();
            more(&buff, &mut stdout, next_file.copied());
            buff.clear();
        }
        reset_term(&mut stdout);
    } else if atty::isnt(atty::Stream::Stdin) {
        stdin().read_to_string(&mut buff).unwrap();
        let mut stdout = setup_term();
        more(&buff, &mut stdout, None);
        reset_term(&mut stdout);
    } else {
        show_usage_error!("bad usage");
    }
    0
}

#[cfg(not(target_os = "fuchsia"))]
fn setup_term() -> std::io::Stdout {
    let stdout = stdout();
    terminal::enable_raw_mode().unwrap();
    stdout
}

#[cfg(target_os = "fuchsia")]
#[inline(always)]
fn setup_term() -> usize {
    0
}

#[cfg(not(target_os = "fuchsia"))]
fn reset_term(stdout: &mut std::io::Stdout) {
    terminal::disable_raw_mode().unwrap();
    // Clear the prompt
    queue!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
    // Move cursor to the beginning without printing new line
    print!("\r");
    stdout.flush().unwrap();
}

#[cfg(target_os = "fuchsia")]
#[inline(always)]
fn reset_term(_: &mut usize) {}

fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) {
    let (cols, rows) = terminal::size().unwrap();
    let lines = break_buff(buff, usize::from(cols));

    let mut pager = Pager::new(rows as usize, lines, next_file);
    pager.draw(stdout);
    if pager.should_close() {
        return;
    }

    loop {
        if event::poll(Duration::from_millis(10)).unwrap() {
            match event::read().unwrap() {
                Event::Key(KeyEvent {
                    code: KeyCode::Char('q'),
                    modifiers: KeyModifiers::NONE,
                })
                | Event::Key(KeyEvent {
                    code: KeyCode::Char('c'),
                    modifiers: KeyModifiers::CONTROL,
                }) => {
                    reset_term(&mut stdout);
                    std::process::exit(0);
                }
                Event::Key(KeyEvent {
                    code: KeyCode::Down,
                    modifiers: KeyModifiers::NONE,
                })
                | Event::Key(KeyEvent {
                    code: KeyCode::Char(' '),
                    modifiers: KeyModifiers::NONE,
                }) => {
                    pager.page_down();
                }
                Event::Key(KeyEvent {
                    code: KeyCode::Up,
                    modifiers: KeyModifiers::NONE,
                }) => {
                    pager.page_up();
                }
                _ => continue,
            }

            pager.draw(stdout);
            if pager.should_close() {
                return;
            }
        }
    }
}

struct Pager<'a> {
    // The current line at the top of the screen
    upper_mark: usize,
    // The number of rows that fit on the screen
    content_rows: usize,
    lines: Vec<String>,
    next_file: Option<&'a str>,
    line_count: usize,
    close_on_down: bool,
}

impl<'a> Pager<'a> {
    fn new(rows: usize, lines: Vec<String>, next_file: Option<&'a str>) -> Self {
        let line_count = lines.len();
        Self {
            upper_mark: 0,
            content_rows: rows - 1,
            lines,
            next_file,
            line_count,
            close_on_down: false,
        }
    }

    fn should_close(&mut self) -> bool {
        if self.upper_mark + self.content_rows >= self.line_count {
            if self.close_on_down {
                return true;
            }
            if self.next_file.is_none() {
                return true;
            } else {
                self.close_on_down = true;
            }
        } else {
            self.close_on_down = false;
        }
        false
    }

    fn page_down(&mut self) {
        self.upper_mark = self
            .line_count
            .saturating_sub(self.content_rows)
            .min(self.upper_mark + self.content_rows);
    }

    fn page_up(&mut self) {
        self.upper_mark = self.upper_mark.saturating_sub(self.content_rows);
    }

    fn draw(&self, stdout: &mut std::io::Stdout) {
        let lower_mark = self.line_count.min(self.upper_mark + self.content_rows);
        self.draw_lines(stdout);
        self.draw_prompt(stdout, lower_mark);
        stdout.flush().unwrap();
    }

    fn draw_lines(&self, stdout: &mut std::io::Stdout) {
        execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
        let displayed_lines = self
            .lines
            .iter()
            .skip(self.upper_mark)
            .take(self.content_rows);

        for line in displayed_lines {
            stdout
                .write_all(format!("\r{}\n", line).as_bytes())
                .unwrap();
        }
    }

    fn draw_prompt(&self, stdout: &mut Stdout, lower_mark: usize) {
        let status = if lower_mark == self.line_count {
            format!("Next file: {}", self.next_file.unwrap_or_default())
        } else {
            format!(
                "{}%",
                (lower_mark as f64 / self.line_count as f64 * 100.0).round() as usize
            )
        };
        write!(
            stdout,
            "\r{}--More--({}){}",
            Attribute::Reverse,
            status,
            Attribute::Reset
        )
        .unwrap();
    }
}

// Break the lines on the cols of the terminal
fn break_buff(buff: &str, cols: usize) -> Vec<String> {
    let mut lines = Vec::new();

    for l in buff.lines() {
        lines.append(&mut break_line(l, cols));
    }
    lines
}

fn break_line(line: &str, cols: usize) -> Vec<String> {
    let width = UnicodeWidthStr::width(line);
    let mut lines = Vec::new();
    if width < cols {
        lines.push(line.to_string());
        return lines;
    }

    let gr_idx = UnicodeSegmentation::grapheme_indices(line, true);
    let mut last_index = 0;
    let mut total_width = 0;
    for (index, grapheme) in gr_idx {
        let width = UnicodeWidthStr::width(grapheme);
        total_width += width;

        if total_width > cols {
            lines.push(line[last_index..index].to_string());
            last_index = index;
            total_width = width;
        }
    }

    if last_index != line.len() {
        lines.push(line[last_index..].to_string());
    }
    lines
}

#[cfg(test)]
mod tests {
    use super::break_line;
    use unicode_width::UnicodeWidthStr;

    #[test]
    fn test_break_lines_long() {
        let mut test_string = String::with_capacity(100);
        for _ in 0..200 {
            test_string.push('#');
        }

        let lines = break_line(&test_string, 80);
        let widths: Vec<usize> = lines
            .iter()
            .map(|s| UnicodeWidthStr::width(&s[..]))
            .collect();

        assert_eq!((80, 80, 40), (widths[0], widths[1], widths[2]));
    }

    #[test]
    fn test_break_lines_short() {
        let mut test_string = String::with_capacity(100);
        for _ in 0..20 {
            test_string.push('#');
        }

        let lines = break_line(&test_string, 80);

        assert_eq!(20, lines[0].len());
    }

    #[test]
    fn test_break_line_zwj() {
        let mut test_string = String::with_capacity(1100);
        for _ in 0..20 {
            test_string.push_str("👩🏻🔬");
        }

        let lines = break_line(&test_string, 80);

        let widths: Vec<usize> = lines
            .iter()
            .map(|s| UnicodeWidthStr::width(&s[..]))
            .collect();

        // Each 👩🏻🔬 is 6 character width it break line to the closest number to 80 => 6 * 13 = 78
        assert_eq!((78, 42), (widths[0], widths[1]));
    }
}
<commit_msg>more: simpler page_down<commit_after>//  * This file is part of the uutils coreutils package.
//  *
//  * (c) Martin Kysel <code@martinkysel.com>
//  *
//  * For the full copyright and license information, please view the LICENSE file
//  * that was distributed with this source code.

// spell-checker:ignore (methods) isnt

#[macro_use]
extern crate uucore;

use std::{
    fs::File,
    io::{stdin, stdout, BufReader, Read, Stdout, Write},
    path::Path,
    time::Duration,
};

#[cfg(all(unix, not(target_os = "fuchsia")))]
extern crate nix;

use clap::{crate_version, App, Arg};
use crossterm::{
    event::{self, Event, KeyCode, KeyEvent, KeyModifiers},
    execute, queue,
    style::Attribute,
    terminal,
};

use unicode_segmentation::UnicodeSegmentation;
use unicode_width::UnicodeWidthStr;

pub mod options {
    pub const SILENT: &str = "silent";
    pub const LOGICAL: &str = "logical";
    pub const NO_PAUSE: &str = "no-pause";
    pub const PRINT_OVER: &str = "print-over";
    pub const CLEAN_PRINT: &str = "clean-print";
    pub const SQUEEZE: &str = "squeeze";
    pub const PLAIN: &str = "plain";
    pub const LINES: &str = "lines";
    pub const NUMBER: &str = "number";
    pub const PATTERN: &str = "pattern";
    pub const FROM_LINE: &str = "from-line";
    pub const FILES: &str = "files";
}

const MULTI_FILE_TOP_PROMPT: &str = "::::::::::::::\n{}\n::::::::::::::\n";

pub fn uumain(args: impl uucore::Args) -> i32 {
    let matches = App::new(executable!())
        .about("A file perusal filter for CRT viewing.")
        .version(crate_version!())
        // The commented arguments below are unimplemented:
        /*
        .arg(
            Arg::with_name(options::SILENT)
                .short("d")
                .long(options::SILENT)
                .help("Display help instead of ringing bell"),
        )
        .arg(
            Arg::with_name(options::LOGICAL)
                .short("f")
                .long(options::LOGICAL)
                .help("Count logical rather than screen lines"),
        )
        .arg(
            Arg::with_name(options::NO_PAUSE)
                .short("l")
                .long(options::NO_PAUSE)
                .help("Suppress pause after form feed"),
        )
        .arg(
            Arg::with_name(options::PRINT_OVER)
                .short("c")
                .long(options::PRINT_OVER)
                .help("Do not scroll, display text and clean line ends"),
        )
        .arg(
            Arg::with_name(options::CLEAN_PRINT)
                .short("p")
                .long(options::CLEAN_PRINT)
                .help("Do not scroll, clean screen and display text"),
        )
        .arg(
            Arg::with_name(options::SQUEEZE)
                .short("s")
                .long(options::SQUEEZE)
                .help("Squeeze multiple blank lines into one"),
        )
        .arg(
            Arg::with_name(options::PLAIN)
                .short("u")
                .long(options::PLAIN)
                .help("Suppress underlining and bold"),
        )
        .arg(
            Arg::with_name(options::LINES)
                .short("n")
                .long(options::LINES)
                .value_name("number")
                .takes_value(true)
                .help("The number of lines per screen full"),
        )
        .arg(
            Arg::with_name(options::NUMBER)
                .allow_hyphen_values(true)
                .long(options::NUMBER)
                .required(false)
                .takes_value(true)
                .help("Same as --lines"),
        )
        .arg(
            Arg::with_name(options::FROM_LINE)
                .short("F")
                .allow_hyphen_values(true)
                .required(false)
                .takes_value(true)
                .value_name("number")
                .help("Display file beginning from line number"),
        )
        .arg(
            Arg::with_name(options::PATTERN)
                .short("P")
                .allow_hyphen_values(true)
                .required(false)
                .takes_value(true)
                .help("Display file beginning from pattern match"),
        )
        */
        .arg(
            Arg::with_name(options::FILES)
                .required(false)
                .multiple(true)
                .help("Path to the files to be read"),
        )
        .get_matches_from(args);

    let mut buff = String::new();
    if let Some(files) = matches.values_of(options::FILES) {
        let mut stdout = setup_term();
        let length = files.len();

        let mut files_iter = files.peekable();
        while let (Some(file), next_file) = (files_iter.next(), files_iter.peek()) {
            let file = Path::new(file);
            if file.is_dir() {
                terminal::disable_raw_mode().unwrap();
                show_usage_error!("'{}' is a directory.", file.display());
                return 1;
            }
            if !file.exists() {
                terminal::disable_raw_mode().unwrap();
                show_error!("cannot open {}: No such file or directory", file.display());
                return 1;
            }
            if length > 1 {
                buff.push_str(&MULTI_FILE_TOP_PROMPT.replace("{}", file.to_str().unwrap()));
            }
            let mut reader = BufReader::new(File::open(file).unwrap());
            reader.read_to_string(&mut buff).unwrap();
            more(&buff, &mut stdout, next_file.copied());
            buff.clear();
        }
        reset_term(&mut stdout);
    } else if atty::isnt(atty::Stream::Stdin) {
        stdin().read_to_string(&mut buff).unwrap();
        let mut stdout = setup_term();
        more(&buff, &mut stdout, None);
        reset_term(&mut stdout);
    } else {
        show_usage_error!("bad usage");
    }
    0
}

#[cfg(not(target_os = "fuchsia"))]
fn setup_term() -> std::io::Stdout {
    let stdout = stdout();
    terminal::enable_raw_mode().unwrap();
    stdout
}

#[cfg(target_os = "fuchsia")]
#[inline(always)]
fn setup_term() -> usize {
    0
}

#[cfg(not(target_os = "fuchsia"))]
fn reset_term(stdout: &mut std::io::Stdout) {
    terminal::disable_raw_mode().unwrap();
    // Clear the prompt
    queue!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
    // Move cursor to the beginning without printing new line
    print!("\r");
    stdout.flush().unwrap();
}

#[cfg(target_os = "fuchsia")]
#[inline(always)]
fn reset_term(_: &mut usize) {}

fn more(buff: &str, mut stdout: &mut Stdout, next_file: Option<&str>) {
    let (cols, rows) = terminal::size().unwrap();
    let lines = break_buff(buff, usize::from(cols));

    let mut pager = Pager::new(rows as usize, lines, next_file);
    pager.draw(stdout);
    if pager.should_close() {
        return;
    }

    loop {
        if event::poll(Duration::from_millis(10)).unwrap() {
            match event::read().unwrap() {
                Event::Key(KeyEvent {
                    code: KeyCode::Char('q'),
                    modifiers: KeyModifiers::NONE,
                })
                | Event::Key(KeyEvent {
                    code: KeyCode::Char('c'),
                    modifiers: KeyModifiers::CONTROL,
                }) => {
                    reset_term(&mut stdout);
                    std::process::exit(0);
                }
                Event::Key(KeyEvent {
                    code: KeyCode::Down,
                    modifiers: KeyModifiers::NONE,
                })
                | Event::Key(KeyEvent {
                    code: KeyCode::Char(' '),
                    modifiers: KeyModifiers::NONE,
                }) => {
                    pager.page_down();
                }
                Event::Key(KeyEvent {
                    code: KeyCode::Up,
                    modifiers: KeyModifiers::NONE,
                }) => {
                    pager.page_up();
                }
                _ => continue,
            }

            pager.draw(stdout);
            if pager.should_close() {
                return;
            }
        }
    }
}

struct Pager<'a> {
    // The current line at the top of the screen
    upper_mark: usize,
    // The number of rows that fit on the screen
    content_rows: usize,
    lines: Vec<String>,
    next_file: Option<&'a str>,
    line_count: usize,
    close_on_down: bool,
}

impl<'a> Pager<'a> {
    fn new(rows: usize, lines: Vec<String>, next_file: Option<&'a str>) -> Self {
        let line_count = lines.len();
        Self {
            upper_mark: 0,
            content_rows: rows - 1,
            lines,
            next_file,
            line_count,
            close_on_down: false,
        }
    }

    fn should_close(&mut self) -> bool {
        if self.upper_mark + self.content_rows >= self.line_count {
            if self.close_on_down {
                return true;
            }
            if self.next_file.is_none() {
                return true;
            } else {
                self.close_on_down = true;
            }
        } else {
            self.close_on_down = false;
        }
        false
    }

    fn page_down(&mut self) {
        self.upper_mark += self.content_rows;
    }

    fn page_up(&mut self) {
        self.upper_mark = self.upper_mark.saturating_sub(self.content_rows);
    }

    fn draw(&self, stdout: &mut std::io::Stdout) {
        let lower_mark = self.line_count.min(self.upper_mark + self.content_rows);
        self.draw_lines(stdout);
        self.draw_prompt(stdout, lower_mark);
        stdout.flush().unwrap();
    }

    fn draw_lines(&self, stdout: &mut std::io::Stdout) {
        execute!(stdout, terminal::Clear(terminal::ClearType::CurrentLine)).unwrap();
        let displayed_lines = self
            .lines
            .iter()
            .skip(self.upper_mark)
            .take(self.content_rows);

        for line in displayed_lines {
            stdout
                .write_all(format!("\r{}\n", line).as_bytes())
                .unwrap();
        }
    }

    fn draw_prompt(&self, stdout: &mut Stdout, lower_mark: usize) {
        let status = if lower_mark == self.line_count {
            format!("Next file: {}", self.next_file.unwrap_or_default())
        } else {
            format!(
                "{}%",
                (lower_mark as f64 / self.line_count as f64 * 100.0).round() as usize
            )
        };
        write!(
            stdout,
            "\r{}--More--({}){}",
            Attribute::Reverse,
            status,
            Attribute::Reset
        )
        .unwrap();
    }
}

// Break the lines on the cols of the terminal
fn break_buff(buff: &str, cols: usize) -> Vec<String> {
    let mut lines = Vec::new();

    for l in buff.lines() {
        lines.append(&mut break_line(l, cols));
    }
    lines
}

fn break_line(line: &str, cols: usize) -> Vec<String> {
    let width = UnicodeWidthStr::width(line);
    let mut lines = Vec::new();
    if width < cols {
        lines.push(line.to_string());
        return lines;
    }

    let gr_idx = UnicodeSegmentation::grapheme_indices(line, true);
    let mut last_index = 0;
    let mut total_width = 0;
    for (index, grapheme) in gr_idx {
        let width = UnicodeWidthStr::width(grapheme);
        total_width += width;

        if total_width > cols {
            lines.push(line[last_index..index].to_string());
            last_index = index;
            total_width = width;
        }
    }

    if last_index != line.len() {
        lines.push(line[last_index..].to_string());
    }
    lines
}

#[cfg(test)]
mod tests {
    use super::break_line;
    use unicode_width::UnicodeWidthStr;

    #[test]
    fn test_break_lines_long() {
        let mut test_string = String::with_capacity(100);
        for _ in 0..200 {
            test_string.push('#');
        }

        let lines = break_line(&test_string, 80);
        let widths: Vec<usize> = lines
            .iter()
            .map(|s| UnicodeWidthStr::width(&s[..]))
            .collect();

        assert_eq!((80, 80, 40), (widths[0], widths[1], widths[2]));
    }

    #[test]
    fn test_break_lines_short() {
        let mut test_string = String::with_capacity(100);
        for _ in 0..20 {
            test_string.push('#');
        }

        let lines = break_line(&test_string, 80);

        assert_eq!(20, lines[0].len());
    }

    #[test]
    fn test_break_line_zwj() {
        let mut test_string = String::with_capacity(1100);
        for _ in 0..20 {
            test_string.push_str("👩🏻🔬");
        }

        let lines = break_line(&test_string, 80);

        let widths: Vec<usize> = lines
            .iter()
            .map(|s| UnicodeWidthStr::width(&s[..]))
            .collect();

        // Each 👩🏻🔬 is 6 character width it break line to the closest number to 80 => 6 * 13 = 78
        assert_eq!((78, 42), (widths[0], widths[1]));
    }
}
<|endoftext|>


<commit_before>use wasm_context::{
    Export,
    ImportExport,
    FunctionKind,
    Context
};

pub fn process( ctx: &mut Context ) -> Option< String > {
    let main_index = ctx.functions.iter().find( |&(_, function)| {
        match *function {
            FunctionKind::Definition { ref export, .. } => {
                export.names.iter().any( |name| name == "main" )
            },
            _ => false
        }
    }).map( |(&index, _)| index );

    let start_index = ctx.start.take();
    if main_index.is_some() {
        Some( "main".to_owned() )
    } else if let Some( start_index ) = start_index {
        let start_fn = ctx.functions.get_mut( &start_index ).unwrap();
        *start_fn.as_export_mut() = Export::some( "__web_main".to_owned() );
        Some( "__web_main".to_owned() )
    } else {
        None
    }
}
<commit_msg>Always call the main symbol `main`<commit_after>use wasm_context::{
    Export,
    ImportExport,
    FunctionKind,
    Context
};

pub fn process( ctx: &mut Context ) -> Option< String > {
    let main_index = ctx.functions.iter().find( |&(_, function)| {
        match *function {
            FunctionKind::Definition { ref export, .. } => {
                export.names.iter().any( |name| name == "main" )
            },
            _ => false
        }
    }).map( |(&index, _)| index );

    let start_index = ctx.start.take();
    if main_index.is_some() {
        Some( "main".to_owned() )
    } else if let Some( start_index ) = start_index {
        let start_fn = ctx.functions.get_mut( &start_index ).unwrap();
        *start_fn.as_export_mut() = Export::some( "main".to_owned() );
        Some( "main".to_owned() )
    } else {
        None
    }
}
<|endoftext|>


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

//! Implementation of cookie storage as specified in
//! http://tools.ietf.org/html/rfc6265

use cookie::Cookie;
use cookie_rs;
use net_traits::CookieSource;
use servo_url::ServoUrl;
use std::cmp::Ordering;

#[derive(Clone, RustcDecodable, RustcEncodable)]
pub struct CookieStorage {
    version: u32,
    cookies: Vec<Cookie>
}

impl CookieStorage {
    pub fn new() -> CookieStorage {
        CookieStorage {
            version: 1,
            cookies: Vec::new()
        }
    }

    // http://tools.ietf.org/html/rfc6265#section-5.3
    pub fn remove(&mut self, cookie: &Cookie, source: CookieSource) -> Result<Option<Cookie>, ()> {
        // Step 1
        let position = self.cookies.iter().position(|c| {
            c.cookie.domain == cookie.cookie.domain &&
            c.cookie.path == cookie.cookie.path &&
            c.cookie.name == cookie.cookie.name
        });

        if let Some(ind) = position {
            let c = self.cookies.remove(ind);

            // http://tools.ietf.org/html/rfc6265#section-5.3 step 11.2
            if !c.cookie.httponly || source == CookieSource::HTTP {
                Ok(Some(c))
            } else {
                // Undo the removal.
                self.cookies.push(c);
                Err(())
            }
        } else {
            Ok(None)
        }
    }

    // http://tools.ietf.org/html/rfc6265#section-5.3
    pub fn push(&mut self, mut cookie: Cookie, source: CookieSource) {
        let old_cookie = self.remove(&cookie, source);
        if old_cookie.is_err() {
            // This new cookie is not allowed to overwrite an existing one.
            return;
        }

        // Step 11
        if let Some(old_cookie) = old_cookie.unwrap() {
            // Step 11.3
            cookie.creation_time = old_cookie.creation_time;
        }

        // Step 12
        self.cookies.push(cookie);
    }

    pub fn cookie_comparator(a: &Cookie, b: &Cookie) -> Ordering {
        let a_path_len = a.cookie.path.as_ref().map_or(0, |p| p.len());
        let b_path_len = b.cookie.path.as_ref().map_or(0, |p| p.len());
        match a_path_len.cmp(&b_path_len) {
            Ordering::Equal => {
                let a_creation_time = a.creation_time.to_timespec();
                let b_creation_time = b.creation_time.to_timespec();
                a_creation_time.cmp(&b_creation_time)
            }
            // Ensure that longer paths are sorted earlier than shorter paths
            Ordering::Greater => Ordering::Less,
            Ordering::Less => Ordering::Greater,
        }
    }

    // http://tools.ietf.org/html/rfc6265#section-5.4
    pub fn cookies_for_url(&mut self, url: &ServoUrl, source: CookieSource) -> Option<String> {
        let filterer = |c: &&mut Cookie| -> bool {
            info!(" === SENT COOKIE : {} {} {:?} {:?}",
                  c.cookie.name, c.cookie.value, c.cookie.domain, c.cookie.path);
            info!(" === SENT COOKIE RESULT {}", c.appropriate_for_url(url, source));
            // Step 1
            c.appropriate_for_url(url, source)
        };

        // Step 2
        let mut url_cookies: Vec<&mut Cookie> = self.cookies.iter_mut().filter(filterer).collect();
        url_cookies.sort_by(|a, b| CookieStorage::cookie_comparator(*a, *b));

        let reducer = |acc: String, c: &mut &mut Cookie| -> String {
            // Step 3
            c.touch();

            // Step 4
            (match acc.len() {
                0 => acc,
                _ => acc + "; "
            }) + &c.cookie.name + "=" + &c.cookie.value
        };
        let result = url_cookies.iter_mut().fold("".to_owned(), reducer);

        info!(" === COOKIES SENT: {}", result);
        match result.len() {
            0 => None,
            _ => Some(result)
        }
    }

    pub fn cookies_data_for_url<'a>(&'a mut self, url: &'a ServoUrl,
                                    source: CookieSource) -> Box<Iterator<Item=cookie_rs::Cookie> + 'a> {
        Box::new(self.cookies.iter_mut().filter(move |c| { c.appropriate_for_url(url, source) }).map(|c| {
            c.touch();
            c.cookie.clone()
        }))
    }
}
<commit_msg>Auto merge of #14208 - servo:CookieStorage-debug, r=jdm<commit_after>/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

//! Implementation of cookie storage as specified in
//! http://tools.ietf.org/html/rfc6265

use cookie::Cookie;
use cookie_rs;
use net_traits::CookieSource;
use servo_url::ServoUrl;
use std::cmp::Ordering;

#[derive(Clone, Debug, RustcDecodable, RustcEncodable)]
pub struct CookieStorage {
    version: u32,
    cookies: Vec<Cookie>
}

impl CookieStorage {
    pub fn new() -> CookieStorage {
        CookieStorage {
            version: 1,
            cookies: Vec::new()
        }
    }

    // http://tools.ietf.org/html/rfc6265#section-5.3
    pub fn remove(&mut self, cookie: &Cookie, source: CookieSource) -> Result<Option<Cookie>, ()> {
        // Step 1
        let position = self.cookies.iter().position(|c| {
            c.cookie.domain == cookie.cookie.domain &&
            c.cookie.path == cookie.cookie.path &&
            c.cookie.name == cookie.cookie.name
        });

        if let Some(ind) = position {
            let c = self.cookies.remove(ind);

            // http://tools.ietf.org/html/rfc6265#section-5.3 step 11.2
            if !c.cookie.httponly || source == CookieSource::HTTP {
                Ok(Some(c))
            } else {
                // Undo the removal.
                self.cookies.push(c);
                Err(())
            }
        } else {
            Ok(None)
        }
    }

    // http://tools.ietf.org/html/rfc6265#section-5.3
    pub fn push(&mut self, mut cookie: Cookie, source: CookieSource) {
        let old_cookie = self.remove(&cookie, source);
        if old_cookie.is_err() {
            // This new cookie is not allowed to overwrite an existing one.
            return;
        }

        // Step 11
        if let Some(old_cookie) = old_cookie.unwrap() {
            // Step 11.3
            cookie.creation_time = old_cookie.creation_time;
        }

        // Step 12
        self.cookies.push(cookie);
    }

    pub fn cookie_comparator(a: &Cookie, b: &Cookie) -> Ordering {
        let a_path_len = a.cookie.path.as_ref().map_or(0, |p| p.len());
        let b_path_len = b.cookie.path.as_ref().map_or(0, |p| p.len());
        match a_path_len.cmp(&b_path_len) {
            Ordering::Equal => {
                let a_creation_time = a.creation_time.to_timespec();
                let b_creation_time = b.creation_time.to_timespec();
                a_creation_time.cmp(&b_creation_time)
            }
            // Ensure that longer paths are sorted earlier than shorter paths
            Ordering::Greater => Ordering::Less,
            Ordering::Less => Ordering::Greater,
        }
    }

    // http://tools.ietf.org/html/rfc6265#section-5.4
    pub fn cookies_for_url(&mut self, url: &ServoUrl, source: CookieSource) -> Option<String> {
        let filterer = |c: &&mut Cookie| -> bool {
            info!(" === SENT COOKIE : {} {} {:?} {:?}",
                  c.cookie.name, c.cookie.value, c.cookie.domain, c.cookie.path);
            info!(" === SENT COOKIE RESULT {}", c.appropriate_for_url(url, source));
            // Step 1
            c.appropriate_for_url(url, source)
        };

        // Step 2
        let mut url_cookies: Vec<&mut Cookie> = self.cookies.iter_mut().filter(filterer).collect();
        url_cookies.sort_by(|a, b| CookieStorage::cookie_comparator(*a, *b));

        let reducer = |acc: String, c: &mut &mut Cookie| -> String {
            // Step 3
            c.touch();

            // Step 4
            (match acc.len() {
                0 => acc,
                _ => acc + "; "
            }) + &c.cookie.name + "=" + &c.cookie.value
        };
        let result = url_cookies.iter_mut().fold("".to_owned(), reducer);

        info!(" === COOKIES SENT: {}", result);
        match result.len() {
            0 => None,
            _ => Some(result)
        }
    }

    pub fn cookies_data_for_url<'a>(&'a mut self, url: &'a ServoUrl,
                                    source: CookieSource) -> Box<Iterator<Item=cookie_rs::Cookie> + 'a> {
        Box::new(self.cookies.iter_mut().filter(move |c| { c.appropriate_for_url(url, source) }).map(|c| {
            c.touch();
            c.cookie.clone()
        }))
    }
}
<|endoftext|>


<commit_before>use codec::{utils::vec_string, CodecTrait};
use codec_latex::LatexCodec;
use test_props::{article, proptest::prelude::*, Freedom};
use test_utils::{
    assert_json_eq,
    common::{once_cell::sync::Lazy, tokio},
};

static RUNTIME: Lazy<tokio::runtime::Runtime> =
    Lazy::new(|| tokio::runtime::Runtime::new().unwrap());

proptest! {
    #![proptest_config(ProptestConfig::with_cases(30))]

    #[test]
    fn test(input in article(
        Freedom::Min,
        [
            LatexCodec::spec().unsupported_types,
            // Pandoc seems to add a caption "image" to image objects, which breaks this test
            // so exclude for the current time.
            vec_string!["ImageObject"]
        ].concat(),
        LatexCodec::spec().unsupported_properties
    )) {
        RUNTIME.block_on(async {
            let latex = LatexCodec::to_string_async(&input, None).await.unwrap();
            let output = LatexCodec::from_str_async(&latex, None).await.unwrap();
            assert_json_eq!(input, output)
        })
    }
}
<commit_msg>test(LaTeX codec): Exclude `Table` from prop test<commit_after>use codec::{utils::vec_string, CodecTrait};
use codec_latex::LatexCodec;
use test_props::{article, proptest::prelude::*, Freedom};
use test_utils::{
    assert_json_eq,
    common::{once_cell::sync::Lazy, tokio},
};

static RUNTIME: Lazy<tokio::runtime::Runtime> =
    Lazy::new(|| tokio::runtime::Runtime::new().unwrap());

proptest! {
    #![proptest_config(ProptestConfig::with_cases(30))]

    #[test]
    fn test(input in article(
        Freedom::Min,
        [
            LatexCodec::spec().unsupported_types,
            // Pandoc seems to add a caption "image" to image objects, which breaks this test
            // so exclude for the current time.
            // Tables are also currently failing this test
            vec_string!["ImageObject", "Table"]
        ].concat(),
        LatexCodec::spec().unsupported_properties
    )) {
        RUNTIME.block_on(async {
            let latex = LatexCodec::to_string_async(&input, None).await.unwrap();
            let output = LatexCodec::from_str_async(&latex, None).await.unwrap();
            assert_json_eq!(input, output)
        })
    }
}
<|endoftext|>


<commit_before>/// A library for performing deterministic, pseudo-random shuffling on a vector.
///
/// This library is designed to confirm to the Ethereum 2.0 specification.
extern crate hashing;

mod rng;

use self::rng::ShuffleRng;

#[derive(Debug)]
pub enum ShuffleErr {
    ExceedsListLength,
}

/// Performs a deterministic, in-place shuffle of a vector.
///
/// The final order of the shuffle is determined by successive hashes
/// of the supplied `seed`.
///
/// This is a Fisher-Yates-Durtstenfeld shuffle.
pub fn shuffle<T>(seed: &[u8], mut list: Vec<T>) -> Result<Vec<T>, ShuffleErr> {
    let mut rng = ShuffleRng::new(seed);

    if list.len() > rng.rand_max as usize {
        return Err(ShuffleErr::ExceedsListLength);
    }

    if list.is_empty() {
        return Ok(list);
    }

    for i in 0..(list.len() - 1) {
        let n = list.len() - i;
        let j = rng.rand_range(n as u32) as usize + i;
        list.swap(i, j);
    }
    Ok(list)
}

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

    use self::yaml_rust::yaml;
    use super::hashing::canonical_hash;
    use super::*;
    use std::fs::File;
    use std::io::prelude::*;

    #[test]
    #[should_panic]
    fn test_shuffling() {
        let mut file = File::open("./src/specs/shuffle_test_vectors.yaml").unwrap();
        let mut yaml_str = String::new();

        file.read_to_string(&mut yaml_str).unwrap();

        let docs = yaml::YamlLoader::load_from_str(&yaml_str).unwrap();
        let doc = &docs[0];
        let test_cases = doc["test_cases"].as_vec();

        for test_case in test_cases.unwrap() {
            let input = test_case["input"].clone().into_vec().unwrap();
            let output = test_case["output"].clone().into_vec().unwrap();
            let seed_bytes = test_case["seed"].as_str().unwrap().as_bytes();
            let mut seed;

            if seed_bytes.len() > 0 {
                seed = canonical_hash(seed_bytes);
            } else {
                seed = vec![];
            }

            let mut s = shuffle(&seed, input).unwrap();

            assert_eq!(s, output);
        }
    }
}
<commit_msg>Add issue link to vec_shuffle/src/lib.rs<commit_after>/// A library for performing deterministic, pseudo-random shuffling on a vector.
///
/// This library is designed to confirm to the Ethereum 2.0 specification.
extern crate hashing;

mod rng;

use self::rng::ShuffleRng;

#[derive(Debug)]
pub enum ShuffleErr {
    ExceedsListLength,
}

/// Performs a deterministic, in-place shuffle of a vector.
///
/// The final order of the shuffle is determined by successive hashes
/// of the supplied `seed`.
///
/// This is a Fisher-Yates-Durtstenfeld shuffle.
pub fn shuffle<T>(seed: &[u8], mut list: Vec<T>) -> Result<Vec<T>, ShuffleErr> {
    let mut rng = ShuffleRng::new(seed);

    if list.len() > rng.rand_max as usize {
        return Err(ShuffleErr::ExceedsListLength);
    }

    if list.is_empty() {
        return Ok(list);
    }

    for i in 0..(list.len() - 1) {
        let n = list.len() - i;
        let j = rng.rand_range(n as u32) as usize + i;
        list.swap(i, j);
    }
    Ok(list)
}

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

    use self::yaml_rust::yaml;
    use super::hashing::canonical_hash;
    use super::*;
    use std::fs::File;
    use std::io::prelude::*;

    // TODO: update test vectors to use keccak instead of blake.
    // https://github.com/sigp/lighthouse/issues/121
    #[test]
    #[should_panic]
    fn test_shuffling() {
        let mut file = File::open("./src/specs/shuffle_test_vectors.yaml").unwrap();
        let mut yaml_str = String::new();

        file.read_to_string(&mut yaml_str).unwrap();

        let docs = yaml::YamlLoader::load_from_str(&yaml_str).unwrap();
        let doc = &docs[0];
        let test_cases = doc["test_cases"].as_vec();

        for test_case in test_cases.unwrap() {
            let input = test_case["input"].clone().into_vec().unwrap();
            let output = test_case["output"].clone().into_vec().unwrap();
            let seed_bytes = test_case["seed"].as_str().unwrap().as_bytes();
            let mut seed;

            if seed_bytes.len() > 0 {
                seed = canonical_hash(seed_bytes);
            } else {
                seed = vec![];
            }

            let mut s = shuffle(&seed, input).unwrap();

            assert_eq!(s, output);
        }
    }
}
<|endoftext|>


<commit_before>// Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

use std::collections::hash_map::DefaultHasher;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};

use itertools::Itertools;
use pyo3::basic::CompareOp;
use pyo3::exceptions::{PyException, PyTypeError, PyValueError};
use pyo3::prelude::*;
use pyo3::types::{PyIterator, PyString, PyTuple, PyType};

use fs::{
  DirectoryDigest, GlobExpansionConjunction, PathGlobs, PreparedPathGlobs, StrictGlobMatching,
  EMPTY_DIRECTORY_DIGEST,
};
use hashing::{Digest, Fingerprint, EMPTY_DIGEST};
use store::Snapshot;

pub(crate) fn register(m: &PyModule) -> PyResult<()> {
  m.add_class::<PyDigest>()?;
  m.add_class::<PyFileDigest>()?;
  m.add_class::<PySnapshot>()?;
  m.add_class::<PyMergeDigests>()?;
  m.add_class::<PyAddPrefix>()?;
  m.add_class::<PyRemovePrefix>()?;

  m.add("EMPTY_DIGEST", PyDigest(EMPTY_DIRECTORY_DIGEST.clone()))?;
  m.add("EMPTY_FILE_DIGEST", PyFileDigest(EMPTY_DIGEST))?;
  m.add("EMPTY_SNAPSHOT", PySnapshot(Snapshot::empty()))?;

  m.add_function(wrap_pyfunction!(match_path_globs, m)?)?;
  m.add_function(wrap_pyfunction!(default_cache_path, m)?)?;
  Ok(())
}

#[pyclass(name = "Digest")]
#[derive(Clone, Debug, PartialEq)]
pub struct PyDigest(pub DirectoryDigest);

impl fmt::Display for PyDigest {
  fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
    let digest = self.0.as_digest();
    write!(
      f,
      "Digest('{}', {})",
      digest.hash.to_hex(),
      digest.size_bytes,
    )
  }
}

#[pymethods]
impl PyDigest {
  /// NB: This constructor is only safe for use in testing, or when there is some other guarantee
  /// that the Digest has been persisted.
  #[new]
  fn __new__(fingerprint: &str, serialized_bytes_length: usize) -> PyResult<Self> {
    let fingerprint = Fingerprint::from_hex_string(fingerprint)
      .map_err(|e| PyValueError::new_err(format!("Invalid digest hex: {}", e)))?;
    Ok(Self(DirectoryDigest::from_persisted_digest(Digest::new(
      fingerprint,
      serialized_bytes_length,
    ))))
  }

  fn __hash__(&self) -> u64 {
    self.0.as_digest().hash.prefix_hash()
  }

  fn __repr__(&self) -> String {
    format!("{}", self)
  }

  fn __richcmp__(&self, other: &PyDigest, op: CompareOp, py: Python) -> PyObject {
    match op {
      CompareOp::Eq => (self == other).into_py(py),
      CompareOp::Ne => (self != other).into_py(py),
      _ => py.NotImplemented(),
    }
  }

  #[getter]
  fn fingerprint(&self) -> String {
    self.0.as_digest().hash.to_hex()
  }

  #[getter]
  fn serialized_bytes_length(&self) -> usize {
    self.0.as_digest().size_bytes
  }
}

#[pyclass(name = "FileDigest")]
#[derive(Debug, Clone, PartialEq)]
pub struct PyFileDigest(pub Digest);

#[pymethods]
impl PyFileDigest {
  #[new]
  fn __new__(fingerprint: &str, serialized_bytes_length: usize) -> PyResult<Self> {
    let fingerprint = Fingerprint::from_hex_string(fingerprint)
      .map_err(|e| PyValueError::new_err(format!("Invalid file digest hex: {}", e)))?;
    Ok(Self(Digest::new(fingerprint, serialized_bytes_length)))
  }

  fn __hash__(&self) -> u64 {
    self.0.hash.prefix_hash()
  }

  fn __repr__(&self) -> String {
    format!(
      "FileDigest('{}', {})",
      self.0.hash.to_hex(),
      self.0.size_bytes
    )
  }

  fn __richcmp__(&self, other: &PyFileDigest, op: CompareOp, py: Python) -> PyObject {
    match op {
      CompareOp::Eq => (self == other).into_py(py),
      CompareOp::Ne => (self != other).into_py(py),
      _ => py.NotImplemented(),
    }
  }

  #[getter]
  fn fingerprint(&self) -> String {
    self.0.hash.to_hex()
  }

  #[getter]
  fn serialized_bytes_length(&self) -> usize {
    self.0.size_bytes
  }
}

#[pyclass(name = "Snapshot")]
pub struct PySnapshot(pub Snapshot);

#[pymethods]
impl PySnapshot {
  #[classmethod]
  fn _unsafe_create(
    _cls: &PyType,
    py_digest: PyDigest,
    files: Vec<String>,
    dirs: Vec<String>,
  ) -> PyResult<Self> {
    let snapshot =
      unsafe { Snapshot::create_for_testing_ffi(py_digest.0.as_digest(), files, dirs) };
    Ok(Self(snapshot.map_err(PyException::new_err)?))
  }

  fn __hash__(&self) -> u64 {
    self.0.digest.hash.prefix_hash()
  }

  fn __repr__(&self) -> PyResult<String> {
    let (dirs, files): (Vec<_>, Vec<_>) = self.0.tree.files_and_directories();

    Ok(format!(
      "Snapshot(digest=({}, {}), dirs=({}), files=({}))",
      self.0.digest.hash.to_hex(),
      self.0.digest.size_bytes,
      dirs
        .into_iter()
        .map(|d| d.display().to_string())
        .collect::<Vec<_>>()
        .join(","),
      files
        .into_iter()
        .map(|d| d.display().to_string())
        .collect::<Vec<_>>()
        .join(","),
    ))
  }

  fn __richcmp__(&self, other: &PySnapshot, op: CompareOp, py: Python) -> PyObject {
    match op {
      CompareOp::Eq => (self.0.digest == other.0.digest).into_py(py),
      CompareOp::Ne => (self.0.digest != other.0.digest).into_py(py),
      _ => py.NotImplemented(),
    }
  }

  #[getter]
  fn digest(&self) -> PyDigest {
    PyDigest(self.0.clone().into())
  }

  #[getter]
  fn files<'py>(&self, py: Python<'py>) -> &'py PyTuple {
    let (files, _) = self.0.tree.files_and_directories();
    PyTuple::new(
      py,
      files
        .into_iter()
        .map(|path| PyString::new(py, &path.to_string_lossy()))
        .collect::<Vec<_>>(),
    )
  }

  #[getter]
  fn dirs<'py>(&self, py: Python<'py>) -> &'py PyTuple {
    let (_, dirs) = self.0.tree.files_and_directories();
    PyTuple::new(
      py,
      dirs
        .into_iter()
        .map(|path| PyString::new(py, &path.to_string_lossy()))
        .collect::<Vec<_>>(),
    )
  }

  // NB: Prefix with underscore. The Python call will be hidden behind a helper which returns a much
  // richer type.
  fn _diff<'py>(&self, other: &PySnapshot, py: Python<'py>) -> &'py PyTuple {
    let result = self.0.tree.diff(&other.0.tree);

    let into_tuple = |x: &Vec<PathBuf>| -> &'py PyTuple {
      PyTuple::new(
        py,
        x.iter()
          .map(|path| PyString::new(py, &path.to_string_lossy()))
          .collect::<Vec<_>>(),
      )
    };

    PyTuple::new(
      py,
      vec![
        into_tuple(&result.our_unique_files),
        into_tuple(&result.our_unique_dirs),
        into_tuple(&result.their_unique_files),
        into_tuple(&result.their_unique_dirs),
        into_tuple(&result.changed_files),
      ],
    )
  }
}

#[pyclass(name = "MergeDigests")]
#[derive(Debug, PartialEq)]
pub struct PyMergeDigests(pub Vec<DirectoryDigest>);

#[pymethods]
impl PyMergeDigests {
  #[new]
  fn __new__(digests: &PyAny, py: Python) -> PyResult<Self> {
    let digests: PyResult<Vec<DirectoryDigest>> = PyIterator::from_object(py, digests)?
      .map(|v| {
        let py_digest = v?.extract::<PyDigest>()?;
        Ok(py_digest.0)
      })
      .collect();
    Ok(Self(digests?))
  }

  fn __hash__(&self) -> u64 {
    let mut s = DefaultHasher::new();
    self.0.hash(&mut s);
    s.finish()
  }

  fn __repr__(&self) -> String {
    let digests = self
      .0
      .iter()
      .map(|d| format!("{}", PyDigest(d.clone())))
      .join(", ");
    format!("MergeDigests([{}])", digests)
  }

  fn __richcmp__(&self, other: &PyMergeDigests, op: CompareOp, py: Python) -> PyObject {
    match op {
      CompareOp::Eq => (self == other).into_py(py),
      CompareOp::Ne => (self != other).into_py(py),
      _ => py.NotImplemented(),
    }
  }
}

#[pyclass(name = "AddPrefix")]
#[derive(Debug, PartialEq)]
pub struct PyAddPrefix {
  pub digest: DirectoryDigest,
  pub prefix: PathBuf,
}

#[pymethods]
impl PyAddPrefix {
  #[new]
  fn __new__(digest: PyDigest, prefix: PathBuf) -> Self {
    Self {
      digest: digest.0,
      prefix,
    }
  }

  fn __hash__(&self) -> u64 {
    let mut s = DefaultHasher::new();
    self.digest.as_digest().hash.prefix_hash().hash(&mut s);
    self.prefix.hash(&mut s);
    s.finish()
  }

  fn __repr__(&self) -> String {
    format!(
      "AddPrefix('{}', {})",
      PyDigest(self.digest.clone()),
      self.prefix.display()
    )
  }

  fn __richcmp__(&self, other: &PyAddPrefix, op: CompareOp, py: Python) -> PyObject {
    match op {
      CompareOp::Eq => (self == other).into_py(py),
      CompareOp::Ne => (self != other).into_py(py),
      _ => py.NotImplemented(),
    }
  }
}

#[pyclass(name = "RemovePrefix")]
#[derive(Debug, PartialEq)]
pub struct PyRemovePrefix {
  pub digest: DirectoryDigest,
  pub prefix: PathBuf,
}

#[pymethods]
impl PyRemovePrefix {
  #[new]
  fn __new__(digest: PyDigest, prefix: PathBuf) -> Self {
    Self {
      digest: digest.0,
      prefix,
    }
  }

  fn __hash__(&self) -> u64 {
    let mut s = DefaultHasher::new();
    self.digest.as_digest().hash.prefix_hash().hash(&mut s);
    self.prefix.hash(&mut s);
    s.finish()
  }

  fn __repr__(&self) -> String {
    format!(
      "RemovePrefix('{}', {})",
      PyDigest(self.digest.clone()),
      self.prefix.display()
    )
  }

  fn __richcmp__(&self, other: &PyRemovePrefix, op: CompareOp, py: Python) -> PyObject {
    match op {
      CompareOp::Eq => (self == other).into_py(py),
      CompareOp::Ne => (self != other).into_py(py),
      _ => py.NotImplemented(),
    }
  }
}

// -----------------------------------------------------------------------------
// PathGlobs
// -----------------------------------------------------------------------------

struct PyPathGlobs(PathGlobs);

impl PyPathGlobs {
  fn parse(self) -> PyResult<PreparedPathGlobs> {
    self.0.clone().parse().map_err(|e| {
      PyValueError::new_err(format!(
        "Failed to parse PathGlobs: {:?}\n\nError: {}",
        self.0, e
      ))
    })
  }
}

impl<'source> FromPyObject<'source> for PyPathGlobs {
  fn extract(obj: &'source PyAny) -> PyResult<Self> {
    let globs: Vec<String> = obj.getattr("globs")?.extract()?;

    let description_of_origin_field = obj.getattr("description_of_origin")?;
    let description_of_origin = if description_of_origin_field.is_none() {
      None
    } else {
      Some(description_of_origin_field.extract()?)
    };

    let match_behavior_str: &str = obj
      .getattr("glob_match_error_behavior")?
      .getattr("value")?
      .extract()?;
    let match_behavior = StrictGlobMatching::create(match_behavior_str, description_of_origin)
      .map_err(PyValueError::new_err)?;

    let conjunction_str: &str = obj.getattr("conjunction")?.getattr("value")?.extract()?;
    let conjunction =
      GlobExpansionConjunction::create(conjunction_str).map_err(PyValueError::new_err)?;

    Ok(PyPathGlobs(PathGlobs::new(
      globs,
      match_behavior,
      conjunction,
    )))
  }
}

#[pyfunction]
fn match_path_globs(
  py_path_globs: PyPathGlobs,
  paths: Vec<String>,
  py: Python,
) -> PyResult<Vec<String>> {
  py.allow_threads(|| {
    let path_globs = py_path_globs.parse()?;
    Ok(
      paths
        .into_iter()
        .filter(|p| path_globs.matches(Path::new(p)))
        .collect(),
    )
  })
}

// -----------------------------------------------------------------------------
// Utils
// -----------------------------------------------------------------------------

#[pyfunction]
fn default_cache_path() -> PyResult<String> {
  fs::default_cache_path()
    .into_os_string()
    .into_string()
    .map_err(|s| {
      PyTypeError::new_err(format!(
        "Default cache path {:?} could not be converted to a string.",
        s
      ))
    })
}
<commit_msg>files/dirs swapped in Snapshot's repr (#15101)<commit_after>// Copyright 2020 Pants project contributors (see CONTRIBUTORS.md).
// Licensed under the Apache License, Version 2.0 (see LICENSE).

use std::collections::hash_map::DefaultHasher;
use std::fmt;
use std::hash::{Hash, Hasher};
use std::path::{Path, PathBuf};

use itertools::Itertools;
use pyo3::basic::CompareOp;
use pyo3::exceptions::{PyException, PyTypeError, PyValueError};
use pyo3::prelude::*;
use pyo3::types::{PyIterator, PyString, PyTuple, PyType};

use fs::{
  DirectoryDigest, GlobExpansionConjunction, PathGlobs, PreparedPathGlobs, StrictGlobMatching,
  EMPTY_DIRECTORY_DIGEST,
};
use hashing::{Digest, Fingerprint, EMPTY_DIGEST};
use store::Snapshot;

pub(crate) fn register(m: &PyModule) -> PyResult<()> {
  m.add_class::<PyDigest>()?;
  m.add_class::<PyFileDigest>()?;
  m.add_class::<PySnapshot>()?;
  m.add_class::<PyMergeDigests>()?;
  m.add_class::<PyAddPrefix>()?;
  m.add_class::<PyRemovePrefix>()?;

  m.add("EMPTY_DIGEST", PyDigest(EMPTY_DIRECTORY_DIGEST.clone()))?;
  m.add("EMPTY_FILE_DIGEST", PyFileDigest(EMPTY_DIGEST))?;
  m.add("EMPTY_SNAPSHOT", PySnapshot(Snapshot::empty()))?;

  m.add_function(wrap_pyfunction!(match_path_globs, m)?)?;
  m.add_function(wrap_pyfunction!(default_cache_path, m)?)?;
  Ok(())
}

#[pyclass(name = "Digest")]
#[derive(Clone, Debug, PartialEq)]
pub struct PyDigest(pub DirectoryDigest);

impl fmt::Display for PyDigest {
  fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
    let digest = self.0.as_digest();
    write!(
      f,
      "Digest('{}', {})",
      digest.hash.to_hex(),
      digest.size_bytes,
    )
  }
}

#[pymethods]
impl PyDigest {
  /// NB: This constructor is only safe for use in testing, or when there is some other guarantee
  /// that the Digest has been persisted.
  #[new]
  fn __new__(fingerprint: &str, serialized_bytes_length: usize) -> PyResult<Self> {
    let fingerprint = Fingerprint::from_hex_string(fingerprint)
      .map_err(|e| PyValueError::new_err(format!("Invalid digest hex: {}", e)))?;
    Ok(Self(DirectoryDigest::from_persisted_digest(Digest::new(
      fingerprint,
      serialized_bytes_length,
    ))))
  }

  fn __hash__(&self) -> u64 {
    self.0.as_digest().hash.prefix_hash()
  }

  fn __repr__(&self) -> String {
    format!("{}", self)
  }

  fn __richcmp__(&self, other: &PyDigest, op: CompareOp, py: Python) -> PyObject {
    match op {
      CompareOp::Eq => (self == other).into_py(py),
      CompareOp::Ne => (self != other).into_py(py),
      _ => py.NotImplemented(),
    }
  }

  #[getter]
  fn fingerprint(&self) -> String {
    self.0.as_digest().hash.to_hex()
  }

  #[getter]
  fn serialized_bytes_length(&self) -> usize {
    self.0.as_digest().size_bytes
  }
}

#[pyclass(name = "FileDigest")]
#[derive(Debug, Clone, PartialEq)]
pub struct PyFileDigest(pub Digest);

#[pymethods]
impl PyFileDigest {
  #[new]
  fn __new__(fingerprint: &str, serialized_bytes_length: usize) -> PyResult<Self> {
    let fingerprint = Fingerprint::from_hex_string(fingerprint)
      .map_err(|e| PyValueError::new_err(format!("Invalid file digest hex: {}", e)))?;
    Ok(Self(Digest::new(fingerprint, serialized_bytes_length)))
  }

  fn __hash__(&self) -> u64 {
    self.0.hash.prefix_hash()
  }

  fn __repr__(&self) -> String {
    format!(
      "FileDigest('{}', {})",
      self.0.hash.to_hex(),
      self.0.size_bytes
    )
  }

  fn __richcmp__(&self, other: &PyFileDigest, op: CompareOp, py: Python) -> PyObject {
    match op {
      CompareOp::Eq => (self == other).into_py(py),
      CompareOp::Ne => (self != other).into_py(py),
      _ => py.NotImplemented(),
    }
  }

  #[getter]
  fn fingerprint(&self) -> String {
    self.0.hash.to_hex()
  }

  #[getter]
  fn serialized_bytes_length(&self) -> usize {
    self.0.size_bytes
  }
}

#[pyclass(name = "Snapshot")]
pub struct PySnapshot(pub Snapshot);

#[pymethods]
impl PySnapshot {
  #[classmethod]
  fn _unsafe_create(
    _cls: &PyType,
    py_digest: PyDigest,
    files: Vec<String>,
    dirs: Vec<String>,
  ) -> PyResult<Self> {
    let snapshot =
      unsafe { Snapshot::create_for_testing_ffi(py_digest.0.as_digest(), files, dirs) };
    Ok(Self(snapshot.map_err(PyException::new_err)?))
  }

  fn __hash__(&self) -> u64 {
    self.0.digest.hash.prefix_hash()
  }

  fn __repr__(&self) -> PyResult<String> {
    let (files, dirs): (Vec<_>, Vec<_>) = self.0.tree.files_and_directories();

    Ok(format!(
      "Snapshot(digest=({}, {}), dirs=({}), files=({}))",
      self.0.digest.hash.to_hex(),
      self.0.digest.size_bytes,
      dirs
        .into_iter()
        .map(|d| d.display().to_string())
        .collect::<Vec<_>>()
        .join(","),
      files
        .into_iter()
        .map(|d| d.display().to_string())
        .collect::<Vec<_>>()
        .join(","),
    ))
  }

  fn __richcmp__(&self, other: &PySnapshot, op: CompareOp, py: Python) -> PyObject {
    match op {
      CompareOp::Eq => (self.0.digest == other.0.digest).into_py(py),
      CompareOp::Ne => (self.0.digest != other.0.digest).into_py(py),
      _ => py.NotImplemented(),
    }
  }

  #[getter]
  fn digest(&self) -> PyDigest {
    PyDigest(self.0.clone().into())
  }

  #[getter]
  fn files<'py>(&self, py: Python<'py>) -> &'py PyTuple {
    let (files, _) = self.0.tree.files_and_directories();
    PyTuple::new(
      py,
      files
        .into_iter()
        .map(|path| PyString::new(py, &path.to_string_lossy()))
        .collect::<Vec<_>>(),
    )
  }

  #[getter]
  fn dirs<'py>(&self, py: Python<'py>) -> &'py PyTuple {
    let (_, dirs) = self.0.tree.files_and_directories();
    PyTuple::new(
      py,
      dirs
        .into_iter()
        .map(|path| PyString::new(py, &path.to_string_lossy()))
        .collect::<Vec<_>>(),
    )
  }

  // NB: Prefix with underscore. The Python call will be hidden behind a helper which returns a much
  // richer type.
  fn _diff<'py>(&self, other: &PySnapshot, py: Python<'py>) -> &'py PyTuple {
    let result = self.0.tree.diff(&other.0.tree);

    let into_tuple = |x: &Vec<PathBuf>| -> &'py PyTuple {
      PyTuple::new(
        py,
        x.iter()
          .map(|path| PyString::new(py, &path.to_string_lossy()))
          .collect::<Vec<_>>(),
      )
    };

    PyTuple::new(
      py,
      vec![
        into_tuple(&result.our_unique_files),
        into_tuple(&result.our_unique_dirs),
        into_tuple(&result.their_unique_files),
        into_tuple(&result.their_unique_dirs),
        into_tuple(&result.changed_files),
      ],
    )
  }
}

#[pyclass(name = "MergeDigests")]
#[derive(Debug, PartialEq)]
pub struct PyMergeDigests(pub Vec<DirectoryDigest>);

#[pymethods]
impl PyMergeDigests {
  #[new]
  fn __new__(digests: &PyAny, py: Python) -> PyResult<Self> {
    let digests: PyResult<Vec<DirectoryDigest>> = PyIterator::from_object(py, digests)?
      .map(|v| {
        let py_digest = v?.extract::<PyDigest>()?;
        Ok(py_digest.0)
      })
      .collect();
    Ok(Self(digests?))
  }

  fn __hash__(&self) -> u64 {
    let mut s = DefaultHasher::new();
    self.0.hash(&mut s);
    s.finish()
  }

  fn __repr__(&self) -> String {
    let digests = self
      .0
      .iter()
      .map(|d| format!("{}", PyDigest(d.clone())))
      .join(", ");
    format!("MergeDigests([{}])", digests)
  }

  fn __richcmp__(&self, other: &PyMergeDigests, op: CompareOp, py: Python) -> PyObject {
    match op {
      CompareOp::Eq => (self == other).into_py(py),
      CompareOp::Ne => (self != other).into_py(py),
      _ => py.NotImplemented(),
    }
  }
}

#[pyclass(name = "AddPrefix")]
#[derive(Debug, PartialEq)]
pub struct PyAddPrefix {
  pub digest: DirectoryDigest,
  pub prefix: PathBuf,
}

#[pymethods]
impl PyAddPrefix {
  #[new]
  fn __new__(digest: PyDigest, prefix: PathBuf) -> Self {
    Self {
      digest: digest.0,
      prefix,
    }
  }

  fn __hash__(&self) -> u64 {
    let mut s = DefaultHasher::new();
    self.digest.as_digest().hash.prefix_hash().hash(&mut s);
    self.prefix.hash(&mut s);
    s.finish()
  }

  fn __repr__(&self) -> String {
    format!(
      "AddPrefix('{}', {})",
      PyDigest(self.digest.clone()),
      self.prefix.display()
    )
  }

  fn __richcmp__(&self, other: &PyAddPrefix, op: CompareOp, py: Python) -> PyObject {
    match op {
      CompareOp::Eq => (self == other).into_py(py),
      CompareOp::Ne => (self != other).into_py(py),
      _ => py.NotImplemented(),
    }
  }
}

#[pyclass(name = "RemovePrefix")]
#[derive(Debug, PartialEq)]
pub struct PyRemovePrefix {
  pub digest: DirectoryDigest,
  pub prefix: PathBuf,
}

#[pymethods]
impl PyRemovePrefix {
  #[new]
  fn __new__(digest: PyDigest, prefix: PathBuf) -> Self {
    Self {
      digest: digest.0,
      prefix,
    }
  }

  fn __hash__(&self) -> u64 {
    let mut s = DefaultHasher::new();
    self.digest.as_digest().hash.prefix_hash().hash(&mut s);
    self.prefix.hash(&mut s);
    s.finish()
  }

  fn __repr__(&self) -> String {
    format!(
      "RemovePrefix('{}', {})",
      PyDigest(self.digest.clone()),
      self.prefix.display()
    )
  }

  fn __richcmp__(&self, other: &PyRemovePrefix, op: CompareOp, py: Python) -> PyObject {
    match op {
      CompareOp::Eq => (self == other).into_py(py),
      CompareOp::Ne => (self != other).into_py(py),
      _ => py.NotImplemented(),
    }
  }
}

// -----------------------------------------------------------------------------
// PathGlobs
// -----------------------------------------------------------------------------

struct PyPathGlobs(PathGlobs);

impl PyPathGlobs {
  fn parse(self) -> PyResult<PreparedPathGlobs> {
    self.0.clone().parse().map_err(|e| {
      PyValueError::new_err(format!(
        "Failed to parse PathGlobs: {:?}\n\nError: {}",
        self.0, e
      ))
    })
  }
}

impl<'source> FromPyObject<'source> for PyPathGlobs {
  fn extract(obj: &'source PyAny) -> PyResult<Self> {
    let globs: Vec<String> = obj.getattr("globs")?.extract()?;

    let description_of_origin_field = obj.getattr("description_of_origin")?;
    let description_of_origin = if description_of_origin_field.is_none() {
      None
    } else {
      Some(description_of_origin_field.extract()?)
    };

    let match_behavior_str: &str = obj
      .getattr("glob_match_error_behavior")?
      .getattr("value")?
      .extract()?;
    let match_behavior = StrictGlobMatching::create(match_behavior_str, description_of_origin)
      .map_err(PyValueError::new_err)?;

    let conjunction_str: &str = obj.getattr("conjunction")?.getattr("value")?.extract()?;
    let conjunction =
      GlobExpansionConjunction::create(conjunction_str).map_err(PyValueError::new_err)?;

    Ok(PyPathGlobs(PathGlobs::new(
      globs,
      match_behavior,
      conjunction,
    )))
  }
}

#[pyfunction]
fn match_path_globs(
  py_path_globs: PyPathGlobs,
  paths: Vec<String>,
  py: Python,
) -> PyResult<Vec<String>> {
  py.allow_threads(|| {
    let path_globs = py_path_globs.parse()?;
    Ok(
      paths
        .into_iter()
        .filter(|p| path_globs.matches(Path::new(p)))
        .collect(),
    )
  })
}

// -----------------------------------------------------------------------------
// Utils
// -----------------------------------------------------------------------------

#[pyfunction]
fn default_cache_path() -> PyResult<String> {
  fs::default_cache_path()
    .into_os_string()
    .into_string()
    .map_err(|s| {
      PyTypeError::new_err(format!(
        "Default cache path {:?} could not be converted to a string.",
        s
      ))
    })
}
<|endoftext|>