<commit_before>use crate::syntax::atom::Atom::{self, *};
use crate::syntax::report::Errors;
use crate::syntax::set::OrderedSet as Set;
use crate::syntax::{Api, Derive, Enum, ExternType, Struct, Type, TypeAlias};
use proc_macro2::Ident;
use quote::ToTokens;
use std::collections::{BTreeMap as Map, HashSet as UnorderedSet};

pub struct Types<'a> {
    pub all: Set<&'a Type>,
    pub structs: Map<&'a Ident, &'a Struct>,
    pub enums: Map<&'a Ident, &'a Enum>,
    pub cxx: Set<&'a Ident>,
    pub rust: Set<&'a Ident>,
    pub aliases: Map<&'a Ident, &'a TypeAlias>,
    pub untrusted: Map<&'a Ident, &'a ExternType>,
    pub required_trivial_aliases: Set<&'a Ident>,
}

impl<'a> Types<'a> {
    pub fn collect(cx: &mut Errors, apis: &'a [Api]) -> Self {
        let mut all = Set::new();
        let mut structs = Map::new();
        let mut enums = Map::new();
        let mut cxx = Set::new();
        let mut rust = Set::new();
        let mut aliases = Map::new();
        let mut untrusted = Map::new();

        fn visit<'a>(all: &mut Set<&'a Type>, ty: &'a Type) {
            all.insert(ty);
            match ty {
                Type::Ident(_) | Type::Str(_) | Type::Void(_) | Type::SliceRefU8(_) => {}
                Type::RustBox(ty)
                | Type::UniquePtr(ty)
                | Type::CxxVector(ty)
                | Type::RustVec(ty) => visit(all, &ty.inner),
                Type::Ref(r) => visit(all, &r.inner),
                Type::Slice(s) => visit(all, &s.inner),
                Type::Fn(f) => {
                    if let Some(ret) = &f.ret {
                        visit(all, ret);
                    }
                    for arg in &f.args {
                        visit(all, &arg.ty);
                    }
                }
            }
        }

        let mut type_names = UnorderedSet::new();
        let mut function_names = UnorderedSet::new();
        for api in apis {
            // The same identifier is permitted to be declared as both a shared
            // enum and extern C++ type, or shared struct and extern C++ type.
            // That indicates to not emit the C++ enum/struct definition because
            // it's defined by the included headers already.
            //
            // All other cases of duplicate identifiers are reported as an error.
            match api {
                Api::Include(_) => {}
                Api::Struct(strct) => {
                    let ident = &strct.ident;
                    if !type_names.insert(ident)
                        && (!cxx.contains(ident)
                            || structs.contains_key(ident)
                            || enums.contains_key(ident))
                    {
                        // If already declared as a struct or enum, or if
                        // colliding with something other than an extern C++
                        // type, then error.
                        duplicate_name(cx, strct, ident);
                    }
                    structs.insert(ident, strct);
                    for field in &strct.fields {
                        visit(&mut all, &field.ty);
                    }
                }
                Api::Enum(enm) => {
                    let ident = &enm.ident;
                    if !type_names.insert(ident)
                        && (!cxx.contains(ident)
                            || structs.contains_key(ident)
                            || enums.contains_key(ident))
                    {
                        // If already declared as a struct or enum, or if
                        // colliding with something other than an extern C++
                        // type, then error.
                        duplicate_name(cx, enm, ident);
                    }
                    enums.insert(ident, enm);
                }
                Api::CxxType(ety) => {
                    let ident = &ety.ident;
                    if !type_names.insert(ident)
                        && (cxx.contains(ident)
                            || !structs.contains_key(ident) && !enums.contains_key(ident))
                    {
                        // If already declared as an extern C++ type, or if
                        // colliding with something which is neither struct nor
                        // enum, then error.
                        duplicate_name(cx, ety, ident);
                    }
                    cxx.insert(ident);
                    if !ety.trusted {
                        untrusted.insert(ident, ety);
                    }
                }
                Api::RustType(ety) => {
                    let ident = &ety.ident;
                    if !type_names.insert(ident) {
                        duplicate_name(cx, ety, ident);
                    }
                    rust.insert(ident);
                }
                Api::CxxFunction(efn) | Api::RustFunction(efn) => {
                    let ident = &efn.ident;
                    if !function_names.insert((&efn.receiver, ident)) {
                        duplicate_name(cx, efn, ident);
                    }
                    for arg in &efn.args {
                        visit(&mut all, &arg.ty);
                    }
                    if let Some(ret) = &efn.ret {
                        visit(&mut all, ret);
                    }
                }
                Api::TypeAlias(alias) => {
                    let ident = &alias.ident;
                    if !type_names.insert(ident) {
                        duplicate_name(cx, alias, ident);
                    }
                    cxx.insert(ident);
                    aliases.insert(ident, alias);
                }
            }
        }

        // All these APIs may contain types passed by value. We need to ensure
        // we check that this is permissible. We do this _after_ scanning all
        // the APIs above, in case some function or struct references a type
        // which is declared subsequently.
        let mut required_trivial_aliases = Set::new();

        fn insist_alias_types_are_trivial<'c>(
            required_trivial_aliases: &mut Set<&'c Ident>,
            aliases: &Map<&'c Ident, &'c TypeAlias>,
            ty: &'c Type,
        ) {
            if let Type::Ident(ident) = ty {
                if aliases.contains_key(ident) {
                    required_trivial_aliases.insert(ident);
                }
            }
        }

        for api in apis {
            match api {
                Api::Struct(strct) => {
                    for field in &strct.fields {
                        insist_alias_types_are_trivial(
                            &mut required_trivial_aliases,
                            &aliases,
                            &field.ty,
                        );
                    }
                }
                Api::CxxFunction(efn) | Api::RustFunction(efn) => {
                    for arg in &efn.args {
                        insist_alias_types_are_trivial(
                            &mut required_trivial_aliases,
                            &aliases,
                            &arg.ty,
                        );
                    }
                    if let Some(ret) = &efn.ret {
                        insist_alias_types_are_trivial(
                            &mut required_trivial_aliases,
                            &aliases,
                            &ret,
                        );
                    }
                }
                _ => {}
            }
        }

        Types {
            all,
            structs,
            enums,
            cxx,
            rust,
            aliases,
            untrusted,
            required_trivial_aliases,
        }
    }

    pub fn needs_indirect_abi(&self, ty: &Type) -> bool {
        match ty {
            Type::Ident(ident) => {
                if let Some(strct) = self.structs.get(ident) {
                    !self.is_pod(strct)
                } else {
                    Atom::from(ident) == Some(RustString)
                }
            }
            Type::RustVec(_) => true,
            _ => false,
        }
    }

    pub fn is_pod(&self, strct: &Struct) -> bool {
        for derive in &strct.derives {
            if *derive == Derive::Copy {
                return true;
            }
        }
        false
    }
}

impl<'t, 'a> IntoIterator for &'t Types<'a> {
    type Item = &'a Type;
    type IntoIter = crate::syntax::set::Iter<'t, 'a, Type>;
    fn into_iter(self) -> Self::IntoIter {
        self.all.into_iter()
    }
}

fn duplicate_name(cx: &mut Errors, sp: impl ToTokens, ident: &Ident) {
    let msg = format!("the name `{}` is defined multiple times", ident);
    cx.error(sp, msg);
}
<commit_msg>Simplify scan for required trivial aliases using a closure<commit_after>use crate::syntax::atom::Atom::{self, *};
use crate::syntax::report::Errors;
use crate::syntax::set::OrderedSet as Set;
use crate::syntax::{Api, Derive, Enum, ExternType, Struct, Type, TypeAlias};
use proc_macro2::Ident;
use quote::ToTokens;
use std::collections::{BTreeMap as Map, HashSet as UnorderedSet};

pub struct Types<'a> {
    pub all: Set<&'a Type>,
    pub structs: Map<&'a Ident, &'a Struct>,
    pub enums: Map<&'a Ident, &'a Enum>,
    pub cxx: Set<&'a Ident>,
    pub rust: Set<&'a Ident>,
    pub aliases: Map<&'a Ident, &'a TypeAlias>,
    pub untrusted: Map<&'a Ident, &'a ExternType>,
    pub required_trivial_aliases: Set<&'a Ident>,
}

impl<'a> Types<'a> {
    pub fn collect(cx: &mut Errors, apis: &'a [Api]) -> Self {
        let mut all = Set::new();
        let mut structs = Map::new();
        let mut enums = Map::new();
        let mut cxx = Set::new();
        let mut rust = Set::new();
        let mut aliases = Map::new();
        let mut untrusted = Map::new();

        fn visit<'a>(all: &mut Set<&'a Type>, ty: &'a Type) {
            all.insert(ty);
            match ty {
                Type::Ident(_) | Type::Str(_) | Type::Void(_) | Type::SliceRefU8(_) => {}
                Type::RustBox(ty)
                | Type::UniquePtr(ty)
                | Type::CxxVector(ty)
                | Type::RustVec(ty) => visit(all, &ty.inner),
                Type::Ref(r) => visit(all, &r.inner),
                Type::Slice(s) => visit(all, &s.inner),
                Type::Fn(f) => {
                    if let Some(ret) = &f.ret {
                        visit(all, ret);
                    }
                    for arg in &f.args {
                        visit(all, &arg.ty);
                    }
                }
            }
        }

        let mut type_names = UnorderedSet::new();
        let mut function_names = UnorderedSet::new();
        for api in apis {
            // The same identifier is permitted to be declared as both a shared
            // enum and extern C++ type, or shared struct and extern C++ type.
            // That indicates to not emit the C++ enum/struct definition because
            // it's defined by the included headers already.
            //
            // All other cases of duplicate identifiers are reported as an error.
            match api {
                Api::Include(_) => {}
                Api::Struct(strct) => {
                    let ident = &strct.ident;
                    if !type_names.insert(ident)
                        && (!cxx.contains(ident)
                            || structs.contains_key(ident)
                            || enums.contains_key(ident))
                    {
                        // If already declared as a struct or enum, or if
                        // colliding with something other than an extern C++
                        // type, then error.
                        duplicate_name(cx, strct, ident);
                    }
                    structs.insert(ident, strct);
                    for field in &strct.fields {
                        visit(&mut all, &field.ty);
                    }
                }
                Api::Enum(enm) => {
                    let ident = &enm.ident;
                    if !type_names.insert(ident)
                        && (!cxx.contains(ident)
                            || structs.contains_key(ident)
                            || enums.contains_key(ident))
                    {
                        // If already declared as a struct or enum, or if
                        // colliding with something other than an extern C++
                        // type, then error.
                        duplicate_name(cx, enm, ident);
                    }
                    enums.insert(ident, enm);
                }
                Api::CxxType(ety) => {
                    let ident = &ety.ident;
                    if !type_names.insert(ident)
                        && (cxx.contains(ident)
                            || !structs.contains_key(ident) && !enums.contains_key(ident))
                    {
                        // If already declared as an extern C++ type, or if
                        // colliding with something which is neither struct nor
                        // enum, then error.
                        duplicate_name(cx, ety, ident);
                    }
                    cxx.insert(ident);
                    if !ety.trusted {
                        untrusted.insert(ident, ety);
                    }
                }
                Api::RustType(ety) => {
                    let ident = &ety.ident;
                    if !type_names.insert(ident) {
                        duplicate_name(cx, ety, ident);
                    }
                    rust.insert(ident);
                }
                Api::CxxFunction(efn) | Api::RustFunction(efn) => {
                    let ident = &efn.ident;
                    if !function_names.insert((&efn.receiver, ident)) {
                        duplicate_name(cx, efn, ident);
                    }
                    for arg in &efn.args {
                        visit(&mut all, &arg.ty);
                    }
                    if let Some(ret) = &efn.ret {
                        visit(&mut all, ret);
                    }
                }
                Api::TypeAlias(alias) => {
                    let ident = &alias.ident;
                    if !type_names.insert(ident) {
                        duplicate_name(cx, alias, ident);
                    }
                    cxx.insert(ident);
                    aliases.insert(ident, alias);
                }
            }
        }

        // All these APIs may contain types passed by value. We need to ensure
        // we check that this is permissible. We do this _after_ scanning all
        // the APIs above, in case some function or struct references a type
        // which is declared subsequently.
        let mut required_trivial_aliases = Set::new();
        let mut insist_alias_types_are_trivial = |ty: &'a Type| {
            if let Type::Ident(ident) = ty {
                if aliases.contains_key(ident) {
                    required_trivial_aliases.insert(ident);
                }
            }
        };
        for api in apis {
            match api {
                Api::Struct(strct) => {
                    for field in &strct.fields {
                        insist_alias_types_are_trivial(&field.ty);
                    }
                }
                Api::CxxFunction(efn) | Api::RustFunction(efn) => {
                    for arg in &efn.args {
                        insist_alias_types_are_trivial(&arg.ty);
                    }
                    if let Some(ret) = &efn.ret {
                        insist_alias_types_are_trivial(&ret);
                    }
                }
                _ => {}
            }
        }

        Types {
            all,
            structs,
            enums,
            cxx,
            rust,
            aliases,
            untrusted,
            required_trivial_aliases,
        }
    }

    pub fn needs_indirect_abi(&self, ty: &Type) -> bool {
        match ty {
            Type::Ident(ident) => {
                if let Some(strct) = self.structs.get(ident) {
                    !self.is_pod(strct)
                } else {
                    Atom::from(ident) == Some(RustString)
                }
            }
            Type::RustVec(_) => true,
            _ => false,
        }
    }

    pub fn is_pod(&self, strct: &Struct) -> bool {
        for derive in &strct.derives {
            if *derive == Derive::Copy {
                return true;
            }
        }
        false
    }
}

impl<'t, 'a> IntoIterator for &'t Types<'a> {
    type Item = &'a Type;
    type IntoIter = crate::syntax::set::Iter<'t, 'a, Type>;
    fn into_iter(self) -> Self::IntoIter {
        self.all.into_iter()
    }
}

fn duplicate_name(cx: &mut Errors, sp: impl ToTokens, ident: &Ident) {
    let msg = format!("the name `{}` is defined multiple times", ident);
    cx.error(sp, msg);
}
<|endoftext|>


<commit_before>
pub( crate ) mod private
{
  use crate::command::*;
  use crate::instruction::*;
  use wtools::meta::*;
  use wtools::error::*;
  use wtools::string::split;
  use wtools::former::Former;

  ///
  /// Commands aggregator.
  ///

  /*
     Dmytro : owned types are used because Former does not work with combination of links and
     containers
   */

  #[ derive( Debug, PartialEq ) ]
  #[ derive( Former ) ]
  #[ allow( missing_docs ) ]
  pub struct CommandsAggregator
  {
    pub base_path : Option<std::path::PathBuf>,
    #[ default( "".to_string() ) ]
    pub command_prefix : String,
    #[ default( vec![ ".".to_string(), " ".to_string() ] ) ]
    pub delimeter : Vec< String >,
    #[ default( ";".to_string() ) ]
    pub command_explicit_delimeter : String,
    #[ default( " ".to_string() ) ]
    pub command_implicit_delimeter : String,
    #[ default( true ) ]
    pub commands_explicit_delimiting : bool,
    #[ default( false ) ]
    pub commands_implicit_delimiting : bool,
    #[ default( false ) ]
    pub properties_map_parsing : bool,
    #[ default( true ) ]
    pub several_values : bool,
    #[ default( true ) ]
    pub with_help : bool,
    #[ default( true ) ]
    pub changing_exit_code : bool,
    // logger : Option<Logger>, /* qqq : implement */
    pub commands : std::collections::HashMap< String, Command >,
    // pub vocabulary : Option<vocabulary>, /* qqq : implement */
  }

  impl CommandsAggregator
  {
    /// Perform instructions queue as single program.
    pub fn program_perform( &self, program : impl AsRef< str > ) -> Result< (), BasicError >
    {
      let program = program.as_ref().trim();

      if !program.starts_with( '.' /* aggregator.vocabulary.default_delimeter */ )
        || program.starts_with( "./" /* `${aggregator.vocabulary.default_delimeter}/` */ )
        || program.starts_with( ".\\" /* `${aggregator.vocabulary.default_delimeter}\\` */ )
      {
        return self.on_syntax_error( program );
      }

      /* should use logger and condition */
      println!( "Command \"{}\"", program );

      let instructions = self.instructions_parse( program );

      for instruction in &instructions
      {
        match self._instruction_perform( instruction )
        {
          Ok( _ ) => {},
          Err( err ) => { return Err( err ) }
        }
      }

      Ok( () )
    }

    /// Perform instruction.
    pub fn instruction_perform( &self, instruction : impl AsRef< str > ) -> Result< (), BasicError >
    {
      let parsed : Instruction = instruction_parse()
      .instruction( instruction.as_ref() )
      .several_values( self.several_values )
      .properties_map_parsing( self.properties_map_parsing )
      .quoting( true )
      .unquoting( true )
      .perform();

      self._instruction_perform( &parsed )
    }

    //

    fn _instruction_perform( &self, instruction : &Instruction ) -> Result< (), BasicError >
    {
      let result = match self.command_resolve( instruction )
      {
        Some( command ) =>
        {
          command.perform( instruction )
        },
        None =>
        {
          if self.with_help
          {
            match self.on_ambiguity( instruction.command_name.as_str() )
            {
              _ => (),
            }
          }
          if self.changing_exit_code
          {
            std::process::exit( 1 );
          }
          Ok( () )
        },
      };

      result
    }

    /// Print help for command.
    fn command_help( &self, command : impl AsRef< str > )
    {
      if command.as_ref() == ""
      {
        for ( _name, command_descriptor ) in self.commands.iter()
        {
          println!( "{}", command_descriptor.help_short() );
        }
      }
      else
      {
        if let Some( command_descriptor ) = self.commands.get( command.as_ref() )
        {
          println!( "{}", command_descriptor.help_long() );
        }
        else
        {
          match self.on_unknown_command_error( command.as_ref() )
          {
            _ => ()
          };
        }
      }
    }

    /// Find command in dictionary.
    fn command_resolve( &self, instruction : &Instruction ) -> Option<&Command>
    {
      self.commands.get( &instruction.command_name )
    }

    /// Parse multiple instructions.
    pub fn instructions_parse( &self, program : impl AsRef< str > ) -> Vec< Instruction >
    {
      let commands = split()
      .src( program.as_ref().trim() )
      .delimeter( self.command_explicit_delimeter.as_str() )
      .preserving_empty( false )
      .preserving_delimeters( false )
      .preserving_quoting( false )
      .perform();
      let commands = commands.map( | e | String::from( e ) ).collect::< Vec< _ > >();

      let mut string_commands = vec![];
      for command in commands
      {
        let splitted = split()
        .src( command.trim() )
        .delimeter( self.command_implicit_delimeter.as_str() )
        .preserving_empty( false )
        .preserving_delimeters( false )
        .preserving_quoting( false )
        .perform();
        let splitted = splitted.map( | e | String::from( e ) ).collect::< Vec< _ > >();

        if self.command_implicit_delimeter == " "
        {
          let start_index = if splitted[ 0 ].is_empty() { 1 } else { 0 };
          let mut string_command = String::from( &splitted[ start_index ] );

          for i in start_index + 1 .. splitted.len()
          {
            let part = splitted[ i ].trim();
            if part.starts_with( '.' )
            {
              string_commands.push( string_command );
              string_command = String::from( part );
            }
            else
            {
              string_command.push( ' ' );
              string_command.push_str( part );
            }
          }

          string_commands.push( string_command );
        }
        else
        {
          for command in splitted
          {
            string_commands.push( String::from( command.trim() ) );
          }
        }
      }

      let instructions = string_commands.iter().map( | instruction |
      {
        instruction_parse()
        .instruction( instruction.as_str() )
        .properties_map_parsing( true )
        .several_values( true )
        .perform()
      }).collect::< Vec< Instruction > >();

      instructions
    }
  }

  //

  ///
  /// On error helper.
  ///

  pub trait OnError
  {
    /// Handle error.
    fn on_error( &self, err : BasicError ) -> Result< (), BasicError >;
  }

  ///
  /// On syntax error helper.
  ///

  pub trait OnSyntaxError
  {
    /// Handle syntax error.
    fn on_syntax_error( &self, command : impl AsRef< str > ) -> Result< (), BasicError >;
  }

  ///
  /// On ambiguity helper.
  ///

  pub trait OnAmbiguity
  {
    /// Handle ambiguity.
    fn on_ambiguity( &self, command : impl AsRef< str > ) -> Result< (), BasicError >;
  }

  ///
  /// On unknown command error helper.
  ///

  pub trait OnUnknownCommandError
  {
    /// Handle unknown command error.
    fn on_unknown_command_error( &self, command : impl AsRef< str > ) -> Result< (), BasicError >;
  }

  ///
  /// Help helper.
  ///

  pub trait OnGetHelp
  {
    /// Get help.
    fn on_get_help( &self ) -> Result< (), BasicError >;
  }

  ///
  /// Printing commands helper.
  ///

  pub trait OnPrintCommands
  {
    /// Print all commands.
    fn on_print_commands( &self ) -> Result< (), BasicError >;
  }

  ///
  /// Super trait that checks that all helpers are implemented.
  ///

  pub trait CommandsAggregatorHandlers : OnError + OnSyntaxError + OnAmbiguity + OnUnknownCommandError + OnGetHelp + OnPrintCommands
  {
  }

  impl CommandsAggregatorHandlers for CommandsAggregator {}

  #[ cfg( feature = "on_error_default" ) ]
  impl OnError for CommandsAggregator
  {
    /// Handle error.
    fn on_error( &self, err : BasicError ) -> Result< (), BasicError >
    {
      if self.changing_exit_code
      {
        unimplemented!();
      }
      Err( err )
    }
  }

  #[ cfg( feature = "on_syntax_error_default" ) ]
  impl OnSyntaxError for CommandsAggregator
  {
    /// Handle syntax error.
    fn on_syntax_error( &self, command : impl AsRef< str > ) -> Result< (), BasicError >
    {
      let err_formatted = format!( "Illformed command \"{}\"", command.as_ref() );
      eprintln!( "{}", err_formatted );
      self.on_get_help().unwrap();

      let err = BasicError::new( err_formatted );
      return self.on_error( err );
    }
  }

  #[ cfg( feature = "on_ambiguity_default" ) ]
  impl OnAmbiguity for CommandsAggregator
  {
    /// Handle ambiguity.
    fn on_ambiguity( &self, command : impl AsRef< str > ) -> Result< (), BasicError >
    {
      eprintln!( "Ambiguity. Did you mean?" );
      self.command_help( command.as_ref() );
      println!( "" );

      let err_formatted = format!( "Ambiguity \"{}\"", command.as_ref() );
      let err = BasicError::new( err_formatted );
      return self.on_error( err );
    }
  }

  #[ cfg( feature = "on_unknown_command_error_default" ) ]
  impl OnUnknownCommandError for CommandsAggregator
  {
    /// Handle unknown command error.
    fn on_unknown_command_error( &self, command : impl AsRef< str > ) -> Result< (), BasicError >
    {
      let mut err_formatted = format!( "Unknown command \"{}\"", command.as_ref() );

      let instruction = instruction_parse()
      .instruction( ".help" )
      .perform();
      if self.command_resolve( &instruction ).is_some()
      {
        err_formatted.push_str( "\nTry \".help\"" );
      }
      let err = BasicError::new( err_formatted );
      return self.on_error( err );
    }
  }

  #[ cfg( feature = "on_get_help_default" ) ]
  impl OnGetHelp for CommandsAggregator
  {
    /// Get help.
    fn on_get_help( &self ) -> Result< (), BasicError >
    {
      let instruction = instruction_parse()
      .instruction( ".help" )
      .perform();
      if let Some( command ) = self.command_resolve( &instruction )
      {
        let instruction = instruction_parse()
        .instruction( "" )
        .perform();
        return command.perform( &instruction );
      }
      else
      {
        self.command_help( "" );
        return Ok( () );
      }
    }
  }

  #[ cfg( feature = "on_print_commands_default" ) ]
  impl OnPrintCommands for CommandsAggregator
  {
    /// Print all commands.
    fn on_print_commands( &self ) -> Result< (), BasicError >
    {
      println!( "" );
      self.command_help( "" );
      println!( "" );
      Ok( () )
    }
  }

  //

  ///
  /// Get instruction parser builder.
  ///

  pub fn commands_aggregator() -> CommandsAggregatorFormer
  {
    CommandsAggregator::former()
  }

}

/// Protected namespace of the module.
pub mod protected
{
  pub use super::private::CommandsAggregator;
  pub use super::private::OnError;
  pub use super::private::OnSyntaxError;
  pub use super::private::OnAmbiguity;
  pub use super::private::OnUnknownCommandError;
  pub use super::private::OnGetHelp;
  pub use super::private::OnPrintCommands;
  pub use super::private::commands_aggregator;
}

pub use protected::*;

/// Exposed namespace of the module.
pub mod exposed
{
  pub use super::prelude::*;
}

/// Namespace of the module to include with `use module::*`.
pub mod prelude
{
  pub use super::private::CommandsAggregator;
  pub use super::private::OnError;
  pub use super::private::OnSyntaxError;
  pub use super::private::OnAmbiguity;
  pub use super::private::OnUnknownCommandError;
  pub use super::private::OnGetHelp;
  pub use super::private::OnPrintCommands;
  pub use super::private::commands_aggregator;
}

<commit_msg>Fix routine `on_error` of module `wca`, do not panic, add comment<commit_after>
pub( crate ) mod private
{
  use crate::command::*;
  use crate::instruction::*;
  use wtools::meta::*;
  use wtools::error::*;
  use wtools::string::split;
  use wtools::former::Former;

  ///
  /// Commands aggregator.
  ///

  /*
     Dmytro : owned types are used because Former does not work with combination of links and
     containers
   */

  #[ derive( Debug, PartialEq ) ]
  #[ derive( Former ) ]
  #[ allow( missing_docs ) ]
  pub struct CommandsAggregator
  {
    pub base_path : Option<std::path::PathBuf>,
    #[ default( "".to_string() ) ]
    pub command_prefix : String,
    #[ default( vec![ ".".to_string(), " ".to_string() ] ) ]
    pub delimeter : Vec< String >,
    #[ default( ";".to_string() ) ]
    pub command_explicit_delimeter : String,
    #[ default( " ".to_string() ) ]
    pub command_implicit_delimeter : String,
    #[ default( true ) ]
    pub commands_explicit_delimiting : bool,
    #[ default( false ) ]
    pub commands_implicit_delimiting : bool,
    #[ default( false ) ]
    pub properties_map_parsing : bool,
    #[ default( true ) ]
    pub several_values : bool,
    #[ default( true ) ]
    pub with_help : bool,
    #[ default( true ) ]
    pub changing_exit_code : bool,
    // logger : Option<Logger>, /* qqq : implement */
    pub commands : std::collections::HashMap< String, Command >,
    // pub vocabulary : Option<vocabulary>, /* qqq : implement */
  }

  impl CommandsAggregator
  {
    /// Perform instructions queue as single program.
    pub fn program_perform( &self, program : impl AsRef< str > ) -> Result< (), BasicError >
    {
      let program = program.as_ref().trim();

      if !program.starts_with( '.' /* aggregator.vocabulary.default_delimeter */ )
        || program.starts_with( "./" /* `${aggregator.vocabulary.default_delimeter}/` */ )
        || program.starts_with( ".\\" /* `${aggregator.vocabulary.default_delimeter}\\` */ )
      {
        return self.on_syntax_error( program );
      }

      /* should use logger and condition */
      println!( "Command \"{}\"", program );

      let instructions = self.instructions_parse( program );

      for instruction in &instructions
      {
        match self._instruction_perform( instruction )
        {
          Ok( _ ) => {},
          Err( err ) => { return Err( err ) }
        }
      }

      Ok( () )
    }

    /// Perform instruction.
    pub fn instruction_perform( &self, instruction : impl AsRef< str > ) -> Result< (), BasicError >
    {
      let parsed : Instruction = instruction_parse()
      .instruction( instruction.as_ref() )
      .several_values( self.several_values )
      .properties_map_parsing( self.properties_map_parsing )
      .quoting( true )
      .unquoting( true )
      .perform();

      self._instruction_perform( &parsed )
    }

    //

    fn _instruction_perform( &self, instruction : &Instruction ) -> Result< (), BasicError >
    {
      let result = match self.command_resolve( instruction )
      {
        Some( command ) =>
        {
          command.perform( instruction )
        },
        None =>
        {
          if self.with_help
          {
            match self.on_ambiguity( instruction.command_name.as_str() )
            {
              _ => (),
            }
          }
          if self.changing_exit_code
          {
            std::process::exit( 1 );
          }
          Ok( () )
        },
      };

      result
    }

    /// Print help for command.
    fn command_help( &self, command : impl AsRef< str > )
    {
      if command.as_ref() == ""
      {
        for ( _name, command_descriptor ) in self.commands.iter()
        {
          println!( "{}", command_descriptor.help_short() );
        }
      }
      else
      {
        if let Some( command_descriptor ) = self.commands.get( command.as_ref() )
        {
          println!( "{}", command_descriptor.help_long() );
        }
        else
        {
          match self.on_unknown_command_error( command.as_ref() )
          {
            _ => ()
          };
        }
      }
    }

    /// Find command in dictionary.
    fn command_resolve( &self, instruction : &Instruction ) -> Option<&Command>
    {
      self.commands.get( &instruction.command_name )
    }

    /// Parse multiple instructions.
    pub fn instructions_parse( &self, program : impl AsRef< str > ) -> Vec< Instruction >
    {
      let commands = split()
      .src( program.as_ref().trim() )
      .delimeter( self.command_explicit_delimeter.as_str() )
      .preserving_empty( false )
      .preserving_delimeters( false )
      .preserving_quoting( false )
      .perform();
      let commands = commands.map( | e | String::from( e ) ).collect::< Vec< _ > >();

      let mut string_commands = vec![];
      for command in commands
      {
        let splitted = split()
        .src( command.trim() )
        .delimeter( self.command_implicit_delimeter.as_str() )
        .preserving_empty( false )
        .preserving_delimeters( false )
        .preserving_quoting( false )
        .perform();
        let splitted = splitted.map( | e | String::from( e ) ).collect::< Vec< _ > >();

        if self.command_implicit_delimeter == " "
        {
          let start_index = if splitted[ 0 ].is_empty() { 1 } else { 0 };
          let mut string_command = String::from( &splitted[ start_index ] );

          for i in start_index + 1 .. splitted.len()
          {
            let part = splitted[ i ].trim();
            if part.starts_with( '.' )
            {
              string_commands.push( string_command );
              string_command = String::from( part );
            }
            else
            {
              string_command.push( ' ' );
              string_command.push_str( part );
            }
          }

          string_commands.push( string_command );
        }
        else
        {
          for command in splitted
          {
            string_commands.push( String::from( command.trim() ) );
          }
        }
      }

      let instructions = string_commands.iter().map( | instruction |
      {
        instruction_parse()
        .instruction( instruction.as_str() )
        .properties_map_parsing( true )
        .several_values( true )
        .perform()
      }).collect::< Vec< Instruction > >();

      instructions
    }
  }

  //

  ///
  /// On error helper.
  ///

  pub trait OnError
  {
    /// Handle error.
    fn on_error( &self, err : BasicError ) -> Result< (), BasicError >;
  }

  ///
  /// On syntax error helper.
  ///

  pub trait OnSyntaxError
  {
    /// Handle syntax error.
    fn on_syntax_error( &self, command : impl AsRef< str > ) -> Result< (), BasicError >;
  }

  ///
  /// On ambiguity helper.
  ///

  pub trait OnAmbiguity
  {
    /// Handle ambiguity.
    fn on_ambiguity( &self, command : impl AsRef< str > ) -> Result< (), BasicError >;
  }

  ///
  /// On unknown command error helper.
  ///

  pub trait OnUnknownCommandError
  {
    /// Handle unknown command error.
    fn on_unknown_command_error( &self, command : impl AsRef< str > ) -> Result< (), BasicError >;
  }

  ///
  /// Help helper.
  ///

  pub trait OnGetHelp
  {
    /// Get help.
    fn on_get_help( &self ) -> Result< (), BasicError >;
  }

  ///
  /// Printing commands helper.
  ///

  pub trait OnPrintCommands
  {
    /// Print all commands.
    fn on_print_commands( &self ) -> Result< (), BasicError >;
  }

  ///
  /// Super trait that checks that all helpers are implemented.
  ///

  pub trait CommandsAggregatorHandlers : OnError + OnSyntaxError + OnAmbiguity + OnUnknownCommandError + OnGetHelp + OnPrintCommands
  {
  }

  impl CommandsAggregatorHandlers for CommandsAggregator {}

  #[ cfg( feature = "on_error_default" ) ]
  impl OnError for CommandsAggregator
  {
    /// Handle error.
    fn on_error( &self, err : BasicError ) -> Result< (), BasicError >
    {
      if self.changing_exit_code
      {
        /* qqq : implement */
        // unimplemented!();
      }
      Err( err )
    }
  }

  #[ cfg( feature = "on_syntax_error_default" ) ]
  impl OnSyntaxError for CommandsAggregator
  {
    /// Handle syntax error.
    fn on_syntax_error( &self, command : impl AsRef< str > ) -> Result< (), BasicError >
    {
      let err_formatted = format!( "Illformed command \"{}\"", command.as_ref() );
      eprintln!( "{}", err_formatted );
      self.on_get_help().unwrap();

      let err = BasicError::new( err_formatted );
      return self.on_error( err );
    }
  }

  #[ cfg( feature = "on_ambiguity_default" ) ]
  impl OnAmbiguity for CommandsAggregator
  {
    /// Handle ambiguity.
    fn on_ambiguity( &self, command : impl AsRef< str > ) -> Result< (), BasicError >
    {
      eprintln!( "Ambiguity. Did you mean?" );
      self.command_help( command.as_ref() );
      println!( "" );

      let err_formatted = format!( "Ambiguity \"{}\"", command.as_ref() );
      let err = BasicError::new( err_formatted );
      return self.on_error( err );
    }
  }

  #[ cfg( feature = "on_unknown_command_error_default" ) ]
  impl OnUnknownCommandError for CommandsAggregator
  {
    /// Handle unknown command error.
    fn on_unknown_command_error( &self, command : impl AsRef< str > ) -> Result< (), BasicError >
    {
      let mut err_formatted = format!( "Unknown command \"{}\"", command.as_ref() );

      let instruction = instruction_parse()
      .instruction( ".help" )
      .perform();
      if self.command_resolve( &instruction ).is_some()
      {
        err_formatted.push_str( "\nTry \".help\"" );
      }
      let err = BasicError::new( err_formatted );
      return self.on_error( err );
    }
  }

  #[ cfg( feature = "on_get_help_default" ) ]
  impl OnGetHelp for CommandsAggregator
  {
    /// Get help.
    fn on_get_help( &self ) -> Result< (), BasicError >
    {
      let instruction = instruction_parse()
      .instruction( ".help" )
      .perform();
      if let Some( command ) = self.command_resolve( &instruction )
      {
        let instruction = instruction_parse()
        .instruction( "" )
        .perform();
        return command.perform( &instruction );
      }
      else
      {
        self.command_help( "" );
        return Ok( () );
      }
    }
  }

  #[ cfg( feature = "on_print_commands_default" ) ]
  impl OnPrintCommands for CommandsAggregator
  {
    /// Print all commands.
    fn on_print_commands( &self ) -> Result< (), BasicError >
    {
      println!( "" );
      self.command_help( "" );
      println!( "" );
      Ok( () )
    }
  }

  //

  ///
  /// Get instruction parser builder.
  ///

  pub fn commands_aggregator() -> CommandsAggregatorFormer
  {
    CommandsAggregator::former()
  }

}

/// Protected namespace of the module.
pub mod protected
{
  pub use super::private::CommandsAggregator;
  pub use super::private::OnError;
  pub use super::private::OnSyntaxError;
  pub use super::private::OnAmbiguity;
  pub use super::private::OnUnknownCommandError;
  pub use super::private::OnGetHelp;
  pub use super::private::OnPrintCommands;
  pub use super::private::commands_aggregator;
}

pub use protected::*;

/// Exposed namespace of the module.
pub mod exposed
{
  pub use super::prelude::*;
}

/// Namespace of the module to include with `use module::*`.
pub mod prelude
{
  pub use super::private::CommandsAggregator;
  pub use super::private::OnError;
  pub use super::private::OnSyntaxError;
  pub use super::private::OnAmbiguity;
  pub use super::private::OnUnknownCommandError;
  pub use super::private::OnGetHelp;
  pub use super::private::OnPrintCommands;
  pub use super::private::commands_aggregator;
}

<|endoftext|>


<commit_before>//! Provides a panic hook with a user-friendly message.
//!
//! When Rust panics, the default message is pretty terse:
//!
//! ```text
//! thread '<unnamed>' panicked at 'Can't obtain a global logger', rust/libnewsboat/src/logger.rs:283:5
//! note: Run with `RUST_BACKTRACE=1` for a backtrace.
//! ```
//!
//! An end user probably won't know what to do with this, and even if they *did* re-run with
//! a backtrace enabled, the crash might not occur again. To fix that, this module provides a hook
//! that will print a more user-friendly message:
//!
//! ```text
//! Newsboat crashed; sorry about that. Please submit a report (below)
//! so we can investigate:
//!
//! - online: https://github.com/newsboat/newsboat/issues/new
//!
//!   You will need a GitHub account.
//!
//! - via email: newsboat@googlegroups.com
//!
//! We might have some follow-up questions, so please check your email
//! periodically. Thank you!
//!
//! The crash report:
//!
//! --->8----->8----->8----->8----->8----->8----->8----->8----->8----->8----->8---
//!
//! Newsboat version: 2.14.0
//! Couldn't determine the crash cause.
//! Message: Can't obtain the global logger
//! Crash location: rust/libnewsboat/src/logger.rs:283
//! stack backtrace:
//!    0:     0x559d490f8245 - backtrace::backtrace::libunwind::trace::ha74bb38523d900bf
//!                         at /home/minoru/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.9/src/backtrace/libunwind.rs:53
//!                          - backtrace::backtrace::trace::h22649ed4442cef5c
//!                         at /home/minoru/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.9/src/backtrace/mod.rs:42
//!    1:     0x559d490f2e3e - backtrace::capture::Backtrace::new_unresolved::h4fedf8cbb0cc5848
//!                         at /home/minoru/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.9/src/capture.rs:88
//!    2:     0x559d490f2d9c - backtrace::capture::Backtrace::new::hd207511a8a11ca29
//!                         at /home/minoru/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.9/src/capture.rs:63
//!    3:     0x559d48dcb00a - libnewsboat::human_panic::print_panic_msg::h212b04a7a3a5da8a
//!                         at rust/libnewsboat/src/human_panic.rs:59
//!    4:     0x559d48dca56c - libnewsboat::human_panic::setup::{{closure}}::hf29969d76d6a13df
//!                         at rust/libnewsboat/src/human_panic.rs:23
//!    5:     0x559d4911c593 - std::panicking::rust_panic_with_hook::hafa9461392bef12a
//!    6:     0x559d490c72bf - std::panicking::begin_panic::h76310ac575368f0c
//!                         at libstd/panicking.rs:411
//!    7:     0x559d48dcde7a - libnewsboat::logger::get_instance::h602edda973838b01
//!                         at rust/libnewsboat/src/logger.rs:283
//!    8:     0x559d48dc24f8 - rs_get_loglevel
//!                         at rust/libnewsboat-ffi/src/lib.rs:240
//!    9:     0x559d48a900a1 - _ZN8newsboat6Logger3logIJPcEEEvNS_5LevelERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEDpT_
//!                         at include/logger.h:29
//!   10:     0x559d48b0d2e4 - _ZN8newsboat10Controller3runERKNS_13CliArgsParserE
//!                         at src/controller.cpp:143
//!   11:     0x559d48a71b06 - main
//!                         at /home/minoru/src/newsboat/newsboat.cpp:188
//!   12:     0x7f251efc2b16 - __libc_start_main
//!   13:     0x559d48a6d399 - _start
//!   14:                0x0 - <unknown>
//!
//! --->8----->8----->8----->8----->8----->8----->8----->8----->8----->8----->8---
//! ```
//!
//! All you (the programmer) need to do is run this module's `setup()` somewhere towards the
//! beginning of the program.
use std::panic::{self, PanicInfo};
use backtrace::Backtrace;
use std::io::{self, Write, BufWriter, stderr};

/// Sets up a panic hook with a user-friendly message.
///
/// See module description for details.
pub fn setup() {
    match ::std::env::var("RUST_BACKTRACE") {
        Err(_) => {
            panic::set_hook(Box::new(move |panic_info: &PanicInfo| {
                print_panic_msg(panic_info)
                    .expect("An error occurred while preparing a crash report");
            }));
        },
        Ok(_) => {}
    }
}

fn print_panic_msg(panic_info: &PanicInfo) -> io::Result<()> {
    // Locking the handle to make sure all the messages are printed out in one chunk.
    let stderr = stderr();
    let handle = stderr.lock();
    let mut stderr = BufWriter::new(handle);

    writeln!(&mut stderr,
             "Newsboat crashed; sorry about that. Please submit a report (below)\n\
             so we can investigate:\n\
             \n\
             - online: https://github.com/newsboat/newsboat/issues/new\n\
             \n  \
             You will need a GitHub account.\n\
             \n\
             - via email: newsboat@googlegroups.com\n\
             \n\
             We might have some follow-up questions, so please check your email\n\
             periodically. Thank you!\n\
             \n\
             The crash report:\n\
             \n\
             --->8----->8----->8----->8----->8----->8----->8----->8----->8----->8----->8---\n")?;

    writeln!(&mut stderr, "Newsboat version: {}", env!("CARGO_PKG_VERSION"))?;
    writeln!(&mut stderr, "{}", get_crash_cause(&panic_info))?;
    writeln!(&mut stderr, "{}", get_error_message(&panic_info))?;
    writeln!(&mut stderr, "{}", get_location(&panic_info))?;

    writeln!(&mut stderr, "{:#?}", Backtrace::new())?;

    writeln!(&mut stderr,
             "\n--->8----->8----->8----->8----->8----->8----->8----->8----->8----->8----->8---");

    Ok(())
}

#[cfg(feature = "nightly")]
fn get_crash_cause(panic_info: &PanicInfo) -> String {
    match panic_info.message() {
        Some(m) => format!("Crash cause: {}", m),
        None => "Crash cause unknown".into(),
    };
}

#[cfg(not(feature = "nightly"))]
fn get_crash_cause(_panic_info: &PanicInfo) -> String {
    String::from("Couldn't determine the crash cause.")
}

fn get_error_message(panic_info: &PanicInfo) -> String {
    let payload = panic_info.payload().downcast_ref::<&str>();
    if let Some(payload) = payload {
        format!("Message: {}", &payload)
    } else {
        String::from("No error message")
    }
}

fn get_location(panic_info: &PanicInfo) -> String {
    match panic_info.location() {
        Some(location) => format!("Crash location: {}:{}", location.file(), location.line()),
        None => String::from("Crash location unknown"),
    }
}
<commit_msg>Check return of writeln! in human_panic<commit_after>//! Provides a panic hook with a user-friendly message.
//!
//! When Rust panics, the default message is pretty terse:
//!
//! ```text
//! thread '<unnamed>' panicked at 'Can't obtain a global logger', rust/libnewsboat/src/logger.rs:283:5
//! note: Run with `RUST_BACKTRACE=1` for a backtrace.
//! ```
//!
//! An end user probably won't know what to do with this, and even if they *did* re-run with
//! a backtrace enabled, the crash might not occur again. To fix that, this module provides a hook
//! that will print a more user-friendly message:
//!
//! ```text
//! Newsboat crashed; sorry about that. Please submit a report (below)
//! so we can investigate:
//!
//! - online: https://github.com/newsboat/newsboat/issues/new
//!
//!   You will need a GitHub account.
//!
//! - via email: newsboat@googlegroups.com
//!
//! We might have some follow-up questions, so please check your email
//! periodically. Thank you!
//!
//! The crash report:
//!
//! --->8----->8----->8----->8----->8----->8----->8----->8----->8----->8----->8---
//!
//! Newsboat version: 2.14.0
//! Couldn't determine the crash cause.
//! Message: Can't obtain the global logger
//! Crash location: rust/libnewsboat/src/logger.rs:283
//! stack backtrace:
//!    0:     0x559d490f8245 - backtrace::backtrace::libunwind::trace::ha74bb38523d900bf
//!                         at /home/minoru/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.9/src/backtrace/libunwind.rs:53
//!                          - backtrace::backtrace::trace::h22649ed4442cef5c
//!                         at /home/minoru/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.9/src/backtrace/mod.rs:42
//!    1:     0x559d490f2e3e - backtrace::capture::Backtrace::new_unresolved::h4fedf8cbb0cc5848
//!                         at /home/minoru/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.9/src/capture.rs:88
//!    2:     0x559d490f2d9c - backtrace::capture::Backtrace::new::hd207511a8a11ca29
//!                         at /home/minoru/.cargo/registry/src/github.com-1ecc6299db9ec823/backtrace-0.3.9/src/capture.rs:63
//!    3:     0x559d48dcb00a - libnewsboat::human_panic::print_panic_msg::h212b04a7a3a5da8a
//!                         at rust/libnewsboat/src/human_panic.rs:59
//!    4:     0x559d48dca56c - libnewsboat::human_panic::setup::{{closure}}::hf29969d76d6a13df
//!                         at rust/libnewsboat/src/human_panic.rs:23
//!    5:     0x559d4911c593 - std::panicking::rust_panic_with_hook::hafa9461392bef12a
//!    6:     0x559d490c72bf - std::panicking::begin_panic::h76310ac575368f0c
//!                         at libstd/panicking.rs:411
//!    7:     0x559d48dcde7a - libnewsboat::logger::get_instance::h602edda973838b01
//!                         at rust/libnewsboat/src/logger.rs:283
//!    8:     0x559d48dc24f8 - rs_get_loglevel
//!                         at rust/libnewsboat-ffi/src/lib.rs:240
//!    9:     0x559d48a900a1 - _ZN8newsboat6Logger3logIJPcEEEvNS_5LevelERKNSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEEDpT_
//!                         at include/logger.h:29
//!   10:     0x559d48b0d2e4 - _ZN8newsboat10Controller3runERKNS_13CliArgsParserE
//!                         at src/controller.cpp:143
//!   11:     0x559d48a71b06 - main
//!                         at /home/minoru/src/newsboat/newsboat.cpp:188
//!   12:     0x7f251efc2b16 - __libc_start_main
//!   13:     0x559d48a6d399 - _start
//!   14:                0x0 - <unknown>
//!
//! --->8----->8----->8----->8----->8----->8----->8----->8----->8----->8----->8---
//! ```
//!
//! All you (the programmer) need to do is run this module's `setup()` somewhere towards the
//! beginning of the program.
use std::panic::{self, PanicInfo};
use backtrace::Backtrace;
use std::io::{self, Write, BufWriter, stderr};

/// Sets up a panic hook with a user-friendly message.
///
/// See module description for details.
pub fn setup() {
    match ::std::env::var("RUST_BACKTRACE") {
        Err(_) => {
            panic::set_hook(Box::new(move |panic_info: &PanicInfo| {
                print_panic_msg(panic_info)
                    .expect("An error occurred while preparing a crash report");
            }));
        },
        Ok(_) => {}
    }
}

fn print_panic_msg(panic_info: &PanicInfo) -> io::Result<()> {
    // Locking the handle to make sure all the messages are printed out in one chunk.
    let stderr = stderr();
    let handle = stderr.lock();
    let mut stderr = BufWriter::new(handle);

    writeln!(&mut stderr,
             "Newsboat crashed; sorry about that. Please submit a report (below)\n\
             so we can investigate:\n\
             \n\
             - online: https://github.com/newsboat/newsboat/issues/new\n\
             \n  \
             You will need a GitHub account.\n\
             \n\
             - via email: newsboat@googlegroups.com\n\
             \n\
             We might have some follow-up questions, so please check your email\n\
             periodically. Thank you!\n\
             \n\
             The crash report:\n\
             \n\
             --->8----->8----->8----->8----->8----->8----->8----->8----->8----->8----->8---\n")?;

    writeln!(&mut stderr, "Newsboat version: {}", env!("CARGO_PKG_VERSION"))?;
    writeln!(&mut stderr, "{}", get_crash_cause(&panic_info))?;
    writeln!(&mut stderr, "{}", get_error_message(&panic_info))?;
    writeln!(&mut stderr, "{}", get_location(&panic_info))?;

    writeln!(&mut stderr, "{:#?}", Backtrace::new())?;

    writeln!(&mut stderr,
             "\n--->8----->8----->8----->8----->8----->8----->8----->8----->8----->8----->8---")?;

    Ok(())
}

#[cfg(feature = "nightly")]
fn get_crash_cause(panic_info: &PanicInfo) -> String {
    match panic_info.message() {
        Some(m) => format!("Crash cause: {}", m),
        None => "Crash cause unknown".into(),
    };
}

#[cfg(not(feature = "nightly"))]
fn get_crash_cause(_panic_info: &PanicInfo) -> String {
    String::from("Couldn't determine the crash cause.")
}

fn get_error_message(panic_info: &PanicInfo) -> String {
    let payload = panic_info.payload().downcast_ref::<&str>();
    if let Some(payload) = payload {
        format!("Message: {}", &payload)
    } else {
        String::from("No error message")
    }
}

fn get_location(panic_info: &PanicInfo) -> String {
    match panic_info.location() {
        Some(location) => format!("Crash location: {}:{}", location.file(), location.line()),
        None => String::from("Crash location unknown"),
    }
}
<|endoftext|>


<commit_before>//! An information element is a portion of a SBD message.
//!
//! Information elements come after the SBD header. They come in many types,
//! including more header-type information and the actual data payload.

use chrono::Utc;
use mo::{Header, SessionStatus};
use std::io::{Read, Write};

/// A mobile-originated information element, or IE.
///
/// These are the building blocks of a SBD message.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InformationElement {
    /// Information element holding the mobile-originated header.
    Header(Header),
    /// The mobile originated payload.
    Payload(Vec<u8>),
    /// The mobile originated location information.
    ///
    /// FIXME I don't have a test case for this, yet, so I haven't actually broken out the location
    /// information into fields.
    ///
    /// See #9.
    LocationInformation([u8; 7]),
}

/// Mobile-originated information element errors.
#[derive(Clone, Copy, Debug, Fail)]
pub enum Error {
    /// The identifier is invalid.
    #[fail(display = "invalid information element identifier: {}", _0)]
    InvalidInformationElementIdentifier(u8),

    /// The timestamp is negative.
    #[fail(display = "negative timestamp: {}", _0)]
    NegativeTimestamp(i64),

    /// The payload is too long.
    #[fail(display = "the payload is too long at {} bytes", _0)]
    PayloadTooLong(usize),
}

impl InformationElement {
    /// Reads this information element from a `Read`.
    pub fn read_from<R: Read>(mut read: R) -> Result<InformationElement, ::failure::Error> {
        use byteorder::{BigEndian, ReadBytesExt};
        use chrono::TimeZone;

        let iei = read.read_u8()?;
        let length = read.read_u16::<BigEndian>()?;
        match iei {
            1 => {
                let auto_id = read.read_u32::<BigEndian>()?;
                let mut imei = [0; 15];
                read.read_exact(&mut imei)?;
                let session_status = SessionStatus::new(read.read_u8()?)?;
                let momsn = read.read_u16::<BigEndian>()?;
                let mtmsn = read.read_u16::<BigEndian>()?;
                let time_of_session = read
                    .read_u32::<BigEndian>()
                    .map_err(::failure::Error::from)
                    .map(|n| Utc.timestamp(i64::from(n), 0))?;
                Ok(InformationElement::Header(Header {
                    auto_id: auto_id,
                    imei: imei,
                    session_status: session_status,
                    momsn: momsn,
                    mtmsn: mtmsn,
                    time_of_session: time_of_session,
                }))
            }
            2 => {
                let mut payload = vec![0; length as usize];
                read.read_exact(&mut payload)?;
                Ok(InformationElement::Payload(payload))
            }
            3 => {
                let mut bytes = [0; 7];
                read.read_exact(&mut bytes)?;
                Ok(InformationElement::LocationInformation(bytes))
            }
            5 => unimplemented!(),
            _ => Err(Error::InvalidInformationElementIdentifier(iei).into()),
        }
    }

    /// Returns the length of this information element, including the information element header.
    pub fn len(&self) -> usize {
        match *self {
            InformationElement::Header(_) => 31,
            InformationElement::Payload(ref payload) => 3 + payload.len(),
            InformationElement::LocationInformation(_) => 10,
        }
    }

    /// Returns true if this information element is empty.
    ///
    /// At this point, only can be true if the payload is empty.
    pub fn is_empty(&self) -> bool {
        match *self {
            InformationElement::Payload(ref payload) => payload.is_empty(),
            _ => false,
        }
    }

    /// Writes this information element to a `Write`.
    pub fn write_to<W: Write>(&self, mut write: W) -> Result<(), ::failure::Error> {
        use byteorder::{BigEndian, WriteBytesExt};
        use std::u16;

        match *self {
            InformationElement::Header(ref header) => {
                write.write_u8(1)?;
                write.write_u16::<BigEndian>(31)?;
                write.write_u32::<BigEndian>(header.auto_id)?;
                write.write_all(&header.imei)?;
                write.write_u8(header.session_status as u8)?;
                write.write_u16::<BigEndian>(header.momsn)?;
                write.write_u16::<BigEndian>(header.mtmsn)?;
                let timestamp = header.time_of_session.timestamp();
                if timestamp < 0 {
                    return Err(Error::NegativeTimestamp(timestamp).into());
                } else {
                    write.write_u32::<BigEndian>(timestamp as u32)?;
                };
            }
            InformationElement::Payload(ref payload) => {
                write.write_u8(2)?;
                let len = payload.len();
                if len > u16::MAX as usize {
                    return Err(Error::PayloadTooLong(len).into());
                } else {
                    write.write_u16::<BigEndian>(len as u16)?;
                }
                write.write_all(payload)?;
            }
            InformationElement::LocationInformation(ref bytes) => {
                write.write_u8(3)?;
                write.write_u16::<BigEndian>(7)?;
                write.write_all(bytes)?;
            }
        }
        Ok(())
    }
}

impl From<Header> for InformationElement {
    fn from(header: Header) -> InformationElement {
        InformationElement::Header(header)
    }
}

impl From<Vec<u8>> for InformationElement {
    fn from(payload: Vec<u8>) -> InformationElement {
        InformationElement::Payload(payload)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::TimeZone;
    use std::fs::File;
    use std::io::{Cursor, Read, Seek, SeekFrom};

    #[test]
    fn read_from() {
        let mut file = File::open("data/0-mo.sbd").unwrap();
        file.seek(SeekFrom::Start(3)).unwrap();
        {
            let read = Read::by_ref(&mut file).take(31);
            match InformationElement::read_from(read).unwrap() {
                InformationElement::Header(header) => {
                    assert_eq!(1894516585, header.auto_id);
                    assert_eq!(b"300234063904190", &header.imei);
                    assert_eq!(SessionStatus::Ok, header.session_status);
                    assert_eq!(75, header.momsn);
                    assert_eq!(0, header.mtmsn);
                    assert_eq!(
                        Utc.ymd(2015, 7, 9).and_hms(18, 15, 08),
                        header.time_of_session
                    );
                }
                _ => panic!("Unexpected information element"),
            }
        }
        match InformationElement::read_from(file).unwrap() {
            InformationElement::Payload(data) => {
                assert_eq!(b"test message from pete", data.as_slice())
            }
            _ => panic!("Unexpected information element"),
        }
    }

    #[test]
    fn undersized() {
        let mut file = File::open("data/0-mo.sbd").unwrap();
        file.seek(SeekFrom::Start(3)).unwrap();
        let read = file.take(30);
        assert!(InformationElement::read_from(read).is_err());
    }

    #[test]
    fn header_len() {
        let header = Header {
            auto_id: 1,
            imei: [0; 15],
            session_status: SessionStatus::Ok,
            momsn: 1,
            mtmsn: 1,
            time_of_session: Utc.ymd(2017, 10, 17).and_hms(12, 0, 0),
        };
        let ie = InformationElement::from(header);
        assert_eq!(31, ie.len());
    }

    #[test]
    fn payload_len() {
        assert_eq!(4, InformationElement::from(vec![1]).len());
    }

    #[test]
    fn location_information_len() {
        assert_eq!(10, InformationElement::LocationInformation([0; 7]).len());
    }

    #[test]
    fn roundtrip_header() {
        let header = Header {
            auto_id: 1,
            imei: [0; 15],
            session_status: SessionStatus::Ok,
            momsn: 1,
            mtmsn: 1,
            time_of_session: Utc.ymd(2017, 10, 17).and_hms(12, 0, 0),
        };
        let ie = InformationElement::from(header);
        let mut cursor = Cursor::new(Vec::new());
        ie.write_to(&mut cursor).unwrap();
        cursor.set_position(0);
        assert_eq!(ie, InformationElement::read_from(cursor).unwrap());
    }

    #[test]
    fn header_time_of_session_too_old() {
        let header = Header {
            auto_id: 1,
            imei: [0; 15],
            session_status: SessionStatus::Ok,
            momsn: 1,
            mtmsn: 1,
            time_of_session: Utc.ymd(1969, 12, 31).and_hms(23, 59, 59),
        };
        assert!(
            InformationElement::from(header)
                .write_to(Cursor::new(Vec::new()))
                .is_err()
        );
    }

    #[test]
    fn roundtrip_payload() {
        let payload = vec![1];
        let ie = InformationElement::from(payload);
        let mut cursor = Cursor::new(Vec::new());
        ie.write_to(&mut cursor).unwrap();
        cursor.set_position(0);
        assert_eq!(ie, InformationElement::read_from(cursor).unwrap());
    }

    #[test]
    fn payload_too_long() {
        use std::u16;
        let payload = vec![0; u16::MAX as usize + 1];
        assert!(
            InformationElement::from(payload)
                .write_to(Cursor::new(Vec::new()))
                .is_err()
        );
    }

    #[test]
    fn roundtrip_location_information() {
        let ie = InformationElement::LocationInformation([1; 7]);
        let mut cursor = Cursor::new(Vec::new());
        ie.write_to(&mut cursor).unwrap();
        cursor.set_position(0);
        assert_eq!(ie, InformationElement::read_from(cursor).unwrap());
    }
}
<commit_msg>Fix incorrect MO Location Info Length<commit_after>//! An information element is a portion of a SBD message.
//!
//! Information elements come after the SBD header. They come in many types,
//! including more header-type information and the actual data payload.

use chrono::Utc;
use mo::{Header, SessionStatus};
use std::io::{Read, Write};

/// A mobile-originated information element, or IE.
///
/// These are the building blocks of a SBD message.
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum InformationElement {
    /// Information element holding the mobile-originated header.
    Header(Header),
    /// The mobile originated payload.
    Payload(Vec<u8>),
    /// The mobile originated location information.
    ///
    /// FIXME I don't have a test case for this, yet, so I haven't actually broken out the location
    /// information into fields.
    ///
    /// See #9.
    LocationInformation([u8; 7]),
}

/// Mobile-originated information element errors.
#[derive(Clone, Copy, Debug, Fail)]
pub enum Error {
    /// The identifier is invalid.
    #[fail(display = "invalid information element identifier: {}", _0)]
    InvalidInformationElementIdentifier(u8),

    /// The timestamp is negative.
    #[fail(display = "negative timestamp: {}", _0)]
    NegativeTimestamp(i64),

    /// The payload is too long.
    #[fail(display = "the payload is too long at {} bytes", _0)]
    PayloadTooLong(usize),
}

impl InformationElement {
    /// Reads this information element from a `Read`.
    pub fn read_from<R: Read>(mut read: R) -> Result<InformationElement, ::failure::Error> {
        use byteorder::{BigEndian, ReadBytesExt};
        use chrono::TimeZone;

        let iei = read.read_u8()?;
        let length = read.read_u16::<BigEndian>()?;
        match iei {
            1 => {
                let auto_id = read.read_u32::<BigEndian>()?;
                let mut imei = [0; 15];
                read.read_exact(&mut imei)?;
                let session_status = SessionStatus::new(read.read_u8()?)?;
                let momsn = read.read_u16::<BigEndian>()?;
                let mtmsn = read.read_u16::<BigEndian>()?;
                let time_of_session = read
                    .read_u32::<BigEndian>()
                    .map_err(::failure::Error::from)
                    .map(|n| Utc.timestamp(i64::from(n), 0))?;
                Ok(InformationElement::Header(Header {
                    auto_id: auto_id,
                    imei: imei,
                    session_status: session_status,
                    momsn: momsn,
                    mtmsn: mtmsn,
                    time_of_session: time_of_session,
                }))
            }
            2 => {
                let mut payload = vec![0; length as usize];
                read.read_exact(&mut payload)?;
                Ok(InformationElement::Payload(payload))
            }
            3 => {
                let mut bytes = [0; 7];
                read.read_exact(&mut bytes)?;
                Ok(InformationElement::LocationInformation(bytes))
            }
            5 => unimplemented!(),
            _ => Err(Error::InvalidInformationElementIdentifier(iei).into()),
        }
    }

    /// Returns the length of this information element, including the information element header.
    pub fn len(&self) -> usize {
        match *self {
            InformationElement::Header(_) => 31,
            InformationElement::Payload(ref payload) => 3 + payload.len(),
            InformationElement::LocationInformation(_) => 10,
        }
    }

    /// Returns true if this information element is empty.
    ///
    /// At this point, only can be true if the payload is empty.
    pub fn is_empty(&self) -> bool {
        match *self {
            InformationElement::Payload(ref payload) => payload.is_empty(),
            _ => false,
        }
    }

    /// Writes this information element to a `Write`.
    pub fn write_to<W: Write>(&self, mut write: W) -> Result<(), ::failure::Error> {
        use byteorder::{BigEndian, WriteBytesExt};
        use std::u16;

        match *self {
            InformationElement::Header(ref header) => {
                write.write_u8(1)?;
                write.write_u16::<BigEndian>(31)?;
                write.write_u32::<BigEndian>(header.auto_id)?;
                write.write_all(&header.imei)?;
                write.write_u8(header.session_status as u8)?;
                write.write_u16::<BigEndian>(header.momsn)?;
                write.write_u16::<BigEndian>(header.mtmsn)?;
                let timestamp = header.time_of_session.timestamp();
                if timestamp < 0 {
                    return Err(Error::NegativeTimestamp(timestamp).into());
                } else {
                    write.write_u32::<BigEndian>(timestamp as u32)?;
                };
            }
            InformationElement::Payload(ref payload) => {
                write.write_u8(2)?;
                let len = payload.len();
                if len > u16::MAX as usize {
                    return Err(Error::PayloadTooLong(len).into());
                } else {
                    write.write_u16::<BigEndian>(len as u16)?;
                }
                write.write_all(payload)?;
            }
            InformationElement::LocationInformation(ref bytes) => {
                write.write_u8(3)?;
                write.write_u16::<BigEndian>(20)?;
                write.write_all(bytes)?;
            }
        }
        Ok(())
    }
}

impl From<Header> for InformationElement {
    fn from(header: Header) -> InformationElement {
        InformationElement::Header(header)
    }
}

impl From<Vec<u8>> for InformationElement {
    fn from(payload: Vec<u8>) -> InformationElement {
        InformationElement::Payload(payload)
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use chrono::TimeZone;
    use std::fs::File;
    use std::io::{Cursor, Read, Seek, SeekFrom};

    #[test]
    fn read_from() {
        let mut file = File::open("data/0-mo.sbd").unwrap();
        file.seek(SeekFrom::Start(3)).unwrap();
        {
            let read = Read::by_ref(&mut file).take(31);
            match InformationElement::read_from(read).unwrap() {
                InformationElement::Header(header) => {
                    assert_eq!(1894516585, header.auto_id);
                    assert_eq!(b"300234063904190", &header.imei);
                    assert_eq!(SessionStatus::Ok, header.session_status);
                    assert_eq!(75, header.momsn);
                    assert_eq!(0, header.mtmsn);
                    assert_eq!(
                        Utc.ymd(2015, 7, 9).and_hms(18, 15, 08),
                        header.time_of_session
                    );
                }
                _ => panic!("Unexpected information element"),
            }
        }
        match InformationElement::read_from(file).unwrap() {
            InformationElement::Payload(data) => {
                assert_eq!(b"test message from pete", data.as_slice())
            }
            _ => panic!("Unexpected information element"),
        }
    }

    #[test]
    fn undersized() {
        let mut file = File::open("data/0-mo.sbd").unwrap();
        file.seek(SeekFrom::Start(3)).unwrap();
        let read = file.take(30);
        assert!(InformationElement::read_from(read).is_err());
    }

    #[test]
    fn header_len() {
        let header = Header {
            auto_id: 1,
            imei: [0; 15],
            session_status: SessionStatus::Ok,
            momsn: 1,
            mtmsn: 1,
            time_of_session: Utc.ymd(2017, 10, 17).and_hms(12, 0, 0),
        };
        let ie = InformationElement::from(header);
        assert_eq!(31, ie.len());
    }

    #[test]
    fn payload_len() {
        assert_eq!(4, InformationElement::from(vec![1]).len());
    }

    #[test]
    fn location_information_len() {
        assert_eq!(10, InformationElement::LocationInformation([0; 7]).len());
    }

    #[test]
    fn roundtrip_header() {
        let header = Header {
            auto_id: 1,
            imei: [0; 15],
            session_status: SessionStatus::Ok,
            momsn: 1,
            mtmsn: 1,
            time_of_session: Utc.ymd(2017, 10, 17).and_hms(12, 0, 0),
        };
        let ie = InformationElement::from(header);
        let mut cursor = Cursor::new(Vec::new());
        ie.write_to(&mut cursor).unwrap();
        cursor.set_position(0);
        assert_eq!(ie, InformationElement::read_from(cursor).unwrap());
    }

    #[test]
    fn header_time_of_session_too_old() {
        let header = Header {
            auto_id: 1,
            imei: [0; 15],
            session_status: SessionStatus::Ok,
            momsn: 1,
            mtmsn: 1,
            time_of_session: Utc.ymd(1969, 12, 31).and_hms(23, 59, 59),
        };
        assert!(
            InformationElement::from(header)
                .write_to(Cursor::new(Vec::new()))
                .is_err()
        );
    }

    #[test]
    fn roundtrip_payload() {
        let payload = vec![1];
        let ie = InformationElement::from(payload);
        let mut cursor = Cursor::new(Vec::new());
        ie.write_to(&mut cursor).unwrap();
        cursor.set_position(0);
        assert_eq!(ie, InformationElement::read_from(cursor).unwrap());
    }

    #[test]
    fn payload_too_long() {
        use std::u16;
        let payload = vec![0; u16::MAX as usize + 1];
        assert!(
            InformationElement::from(payload)
                .write_to(Cursor::new(Vec::new()))
                .is_err()
        );
    }

    #[test]
    fn roundtrip_location_information() {
        let ie = InformationElement::LocationInformation([1; 7]);
        let mut cursor = Cursor::new(Vec::new());
        ie.write_to(&mut cursor).unwrap();
        cursor.set_position(0);
        assert_eq!(ie, InformationElement::read_from(cursor).unwrap());
    }
}
<|endoftext|>


<commit_before>use ::quote::Tokens;
use ::yaml_rust::Yaml;

use infer::path;
use infer::TokensResult;

pub(super) fn infer_v3(schema: &Yaml) -> TokensResult {
    let paths = schema["paths"].clone();
    let path_impls: Vec<Tokens> = paths.as_hash()
        .expect("Paths must be a map.")
        .iter()
        .map(|(path, path_schema)| path::infer_v3(
            path.as_str().expect("Path must be a string."), &path_schema
        ).unwrap())
        .collect();

    let api_url = schema["servers"][0]["url"].as_str()
        .expect("Must have at least one server URL.");

    let schema_ref_struct_defs: Vec<Tokens> = Vec::new();

    Ok(quote! {
        #[macro_use]
        extern crate serde_derive;

        use tapioca::serde as serde;
        use tapioca::serde::json as serde_json;

        mod schema_ref {
            #(#schema_ref_struct_defs)*
        }

        const API_URL: &'static str = #api_url;

        #(#path_impls)*
    })
}
<commit_msg>Implement synthesis of types from $ref schema<commit_after>use ::quote::Tokens;
use ::syn::Ident;
use ::yaml_rust::Yaml;

use infer::parameter;
use infer::path;
use infer::TokensResult;

pub(super) fn infer_v3(schema: &Yaml) -> TokensResult {
    let paths = schema["paths"].clone();
    let path_impls: Vec<Tokens> = paths.as_hash()
        .expect("Paths must be a map.")
        .iter()
        .map(|(path, path_schema)| path::infer_v3(
            path.as_str().expect("Path must be a string."), &path_schema
        ).unwrap())
        .collect();

    let api_url = schema["servers"][0]["url"].as_str()
        .expect("Must have at least one server URL.");

    let mut schema_ref_struct_defs: Vec<Tokens> = Vec::new();
    for (schema_ref, schema) in schema["components"]["schemas"].as_hash()
        .expect("#/components/schemas must be a map.")
    {
        let schema_ref_name = schema_ref.as_str()
            .expect("$ref name must be a string.");
        let ident = Ident::new(schema_ref_name);
        let mut fields: Vec<Tokens> = Vec::new();

        for (field_name, schema) in schema["properties"].as_hash()
            .expect("Properties must be a map.")
        {
            let field_name = field_name.as_str()
                .expect("Property must be a string.");
            let field_ident = Ident::new(field_name);
            let field_type = parameter::infer_type(&schema)?;

            fields.push(quote!{ #field_ident: #field_type });
        }

        schema_ref_struct_defs.push(quote! {
            struct #ident {
                #(#fields),*
            }
        });
    }

    Ok(quote! {
        #[macro_use]
        extern crate serde_derive;

        use tapioca::serde as serde;
        use tapioca::serde::json as serde_json;

        mod schema_ref {
            #(#schema_ref_struct_defs)*
        }

        const API_URL: &'static str = #api_url;

        #(#path_impls)*
    })
}
<|endoftext|>


<commit_before>use std::cmp;
use std::fmt::{mod, Show, Formatter};
use std::io::fs;
use glob::Pattern;

use core::{Package, PackageId, Summary, SourceId, Source, Dependency, Registry};
use ops;
use util::{CargoResult, internal, internal_error, process};

pub struct PathSource {
    id: SourceId,
    path: Path,
    updated: bool,
    packages: Vec<Package>
}

// TODO: Figure out if packages should be discovered in new or self should be
// mut and packages are discovered in update
impl PathSource {

    pub fn for_path(path: &Path) -> CargoResult<PathSource> {
        log!(5, "PathSource::for_path; path={}", path.display());
        Ok(PathSource::new(path, &try!(SourceId::for_path(path))))
    }

    /// Invoked with an absolute path to a directory that contains a Cargo.toml.
    /// The source will read the manifest and find any other packages contained
    /// in the directory structure reachable by the root manifest.
    pub fn new(path: &Path, id: &SourceId) -> PathSource {
        log!(5, "new; id={}", id);

        PathSource {
            id: id.clone(),
            path: path.clone(),
            updated: false,
            packages: Vec::new()
        }
    }

    pub fn get_root_package(&self) -> CargoResult<Package> {
        log!(5, "get_root_package; source={}", self);

        if !self.updated {
            return Err(internal("source has not been updated"))
        }

        match self.packages.as_slice().head() {
            Some(pkg) => Ok(pkg.clone()),
            None => Err(internal("no package found in source"))
        }
    }

    fn read_packages(&self) -> CargoResult<Vec<Package>> {
        if self.updated {
            Ok(self.packages.clone())
        } else {
            ops::read_packages(&self.path, &self.id)
        }
    }

    /// List all files relevant to building this package inside this source.
    ///
    /// This function will use the appropriate methods to determine what is the
    /// set of files underneath this source's directory which are relevant for
    /// building `pkg`.
    ///
    /// The basic assumption of this method is that all files in the directory
    /// are relevant for building this package, but it also contains logic to
    /// use other methods like .gitignore to filter the list of files.
    pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<Path>> {
        let candidates = try!(if self.path.join(".git").exists() {
            self.list_files_git(pkg)
        } else {
            self.list_files_walk(pkg)
        });

        let pats = pkg.get_manifest().get_exclude().iter().map(|p| {
            Pattern::new(p.as_slice())
        }).collect::<Vec<Pattern>>();

        let root = pkg.get_manifest_path().dir_path();
        Ok(candidates.move_iter().filter(|candidate| {
            let candidate = candidate.path_relative_from(&root).unwrap();
            !pats.iter().any(|p| p.matches_path(&candidate)) &&
                candidate.is_file()
        }).collect())
    }

    fn list_files_git(&self, pkg: &Package) -> CargoResult<Vec<Path>> {
        let cwd = pkg.get_manifest_path().dir_path();
        let mut cmd = process("git").cwd(cwd.clone());
        cmd = cmd.arg("ls-files").arg("-z")
                 .arg("-x").arg("/target")
                 .arg("-x").arg("/Cargo.lock");

        // Filter out all other packages with a filter directive
        for pkg in self.packages.iter().filter(|p| *p != pkg) {
            if cwd.is_ancestor_of(pkg.get_manifest_path()) {
                let filter = pkg.get_manifest_path().dir_path()
                                .path_relative_from(&self.path).unwrap();
                cmd = cmd.arg("-x").arg(filter);
            }
        }

        log!(5, "listing git files with: {}", cmd);
        let output = try!(cmd.arg(".").exec_with_output());
        let output = output.output.as_slice();
        Ok(output.split(|x| *x == 0).map(|p| cwd.join(p)).collect())
    }

    fn list_files_walk(&self, pkg: &Package) -> CargoResult<Vec<Path>> {
        let mut ret = Vec::new();
        for pkg in self.packages.iter().filter(|p| *p == pkg) {
            let loc = pkg.get_manifest_path().dir_path();
            try!(walk(&loc, &mut ret, true));
        }
        return Ok(ret);

        fn walk(path: &Path, ret: &mut Vec<Path>,
                is_root: bool) -> CargoResult<()> {
            if !path.is_dir() {
                ret.push(path.clone());
                return Ok(())
            }
            // Don't recurse into any sub-packages that we have
            if !is_root && path.join("Cargo.toml").exists() { return Ok(()) }
            for dir in try!(fs::readdir(path)).iter() {
                match (is_root, dir.filename_str()) {
                    (_,    Some(".git")) |
                    (true, Some("target")) |
                    (true, Some("Cargo.lock")) => continue,
                    _ => {}
                }
                try!(walk(dir, ret, false));
            }
            return Ok(())
        }
    }
}

impl Show for PathSource {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "the paths source")
    }
}

impl Registry for PathSource {
    fn query(&mut self, dep: &Dependency) -> CargoResult<Vec<Summary>> {
        let mut summaries: Vec<Summary> = self.packages.iter()
                                              .map(|p| p.get_summary().clone())
                                              .collect();
        summaries.query(dep)
    }
}

impl Source for PathSource {
    fn update(&mut self) -> CargoResult<()> {
        if !self.updated {
            let packages = try!(self.read_packages());
            self.packages.push_all_move(packages);
            self.updated = true;
        }

        Ok(())
    }

    fn download(&self, _: &[PackageId])  -> CargoResult<()>{
        // TODO: assert! that the PackageId is contained by the source
        Ok(())
    }

    fn get(&self, ids: &[PackageId]) -> CargoResult<Vec<Package>> {
        log!(5, "getting packages; ids={}", ids);

        Ok(self.packages.iter()
           .filter(|pkg| ids.iter().any(|id| pkg.get_package_id() == id))
           .map(|pkg| pkg.clone())
           .collect())
    }

    fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
        if !self.updated {
            return Err(internal_error("BUG: source was not updated", ""));
        }

        let mut max = 0;
        for file in try!(self.list_files(pkg)).iter() {
            // An fs::stat error here is either because path is a
            // broken symlink, a permissions error, or a race
            // condition where this path was rm'ed - either way,
            // we can ignore the error and treat the path's mtime
            // as 0.
            max = cmp::max(max, file.stat().map(|s| s.modified).unwrap_or(0));
        }
        log!(5, "fingerprint {}: {}", self.path.display(), max);
        Ok(max.to_string())
    }
}
<commit_msg>auto merge of #547 : alexcrichton/cargo/git2-for-package, r=brson<commit_after>use std::cmp;
use std::fmt::{mod, Show, Formatter};
use std::io::fs;
use glob::Pattern;
use git2;

use core::{Package, PackageId, Summary, SourceId, Source, Dependency, Registry};
use ops;
use util::{CargoResult, internal, internal_error};

pub struct PathSource {
    id: SourceId,
    path: Path,
    updated: bool,
    packages: Vec<Package>
}

// TODO: Figure out if packages should be discovered in new or self should be
// mut and packages are discovered in update
impl PathSource {

    pub fn for_path(path: &Path) -> CargoResult<PathSource> {
        log!(5, "PathSource::for_path; path={}", path.display());
        Ok(PathSource::new(path, &try!(SourceId::for_path(path))))
    }

    /// Invoked with an absolute path to a directory that contains a Cargo.toml.
    /// The source will read the manifest and find any other packages contained
    /// in the directory structure reachable by the root manifest.
    pub fn new(path: &Path, id: &SourceId) -> PathSource {
        log!(5, "new; id={}", id);

        PathSource {
            id: id.clone(),
            path: path.clone(),
            updated: false,
            packages: Vec::new()
        }
    }

    pub fn get_root_package(&self) -> CargoResult<Package> {
        log!(5, "get_root_package; source={}", self);

        if !self.updated {
            return Err(internal("source has not been updated"))
        }

        match self.packages.as_slice().head() {
            Some(pkg) => Ok(pkg.clone()),
            None => Err(internal("no package found in source"))
        }
    }

    fn read_packages(&self) -> CargoResult<Vec<Package>> {
        if self.updated {
            Ok(self.packages.clone())
        } else {
            ops::read_packages(&self.path, &self.id)
        }
    }

    /// List all files relevant to building this package inside this source.
    ///
    /// This function will use the appropriate methods to determine what is the
    /// set of files underneath this source's directory which are relevant for
    /// building `pkg`.
    ///
    /// The basic assumption of this method is that all files in the directory
    /// are relevant for building this package, but it also contains logic to
    /// use other methods like .gitignore to filter the list of files.
    pub fn list_files(&self, pkg: &Package) -> CargoResult<Vec<Path>> {
        let candidates = match git2::Repository::open(&self.path) {
            Ok(repo) => try!(self.list_files_git(pkg, repo)),
            Err(..) => try!(self.list_files_walk(pkg)),
        };

        let pats = pkg.get_manifest().get_exclude().iter().map(|p| {
            Pattern::new(p.as_slice())
        }).collect::<Vec<Pattern>>();

        let root = pkg.get_manifest_path().dir_path();
        Ok(candidates.move_iter().filter(|candidate| {
            let candidate = candidate.path_relative_from(&root).unwrap();
            !pats.iter().any(|p| p.matches_path(&candidate)) &&
                candidate.is_file()
        }).collect())
    }

    fn list_files_git(&self, pkg: &Package, repo: git2::Repository)
                      -> CargoResult<Vec<Path>> {
        let index = try!(repo.index());
        let mut ret = Vec::new();
        'outer: for i in range(0, index.len()) {
            let entry = match index.get(i) { Some(e) => e, None => continue };
            let fname = entry.path.as_bytes_no_nul();
            let path = pkg.get_manifest_path().dir_path().join(fname);

            // Filter out Cargo.lock and target always
            if fname == b"Cargo.lock" { continue }
            if fname == b"target" { continue }

            // Filter out all other packages
            for pkg in self.packages.iter().filter(|p| *p != pkg) {
                let pkg_path = pkg.get_manifest_path().dir_path();
                if pkg_path.is_ancestor_of(&path) { continue 'outer; }
            }

            // We found a file!
            ret.push(path);
        }
        Ok(ret)
    }

    fn list_files_walk(&self, pkg: &Package) -> CargoResult<Vec<Path>> {
        let mut ret = Vec::new();
        for pkg in self.packages.iter().filter(|p| *p == pkg) {
            let loc = pkg.get_manifest_path().dir_path();
            try!(walk(&loc, &mut ret, true));
        }
        return Ok(ret);

        fn walk(path: &Path, ret: &mut Vec<Path>,
                is_root: bool) -> CargoResult<()> {
            if !path.is_dir() {
                ret.push(path.clone());
                return Ok(())
            }
            // Don't recurse into any sub-packages that we have
            if !is_root && path.join("Cargo.toml").exists() { return Ok(()) }
            for dir in try!(fs::readdir(path)).iter() {
                match (is_root, dir.filename_str()) {
                    (_,    Some(".git")) |
                    (true, Some("target")) |
                    (true, Some("Cargo.lock")) => continue,
                    _ => {}
                }
                try!(walk(dir, ret, false));
            }
            return Ok(())
        }
    }
}

impl Show for PathSource {
    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
        write!(f, "the paths source")
    }
}

impl Registry for PathSource {
    fn query(&mut self, dep: &Dependency) -> CargoResult<Vec<Summary>> {
        let mut summaries: Vec<Summary> = self.packages.iter()
                                              .map(|p| p.get_summary().clone())
                                              .collect();
        summaries.query(dep)
    }
}

impl Source for PathSource {
    fn update(&mut self) -> CargoResult<()> {
        if !self.updated {
            let packages = try!(self.read_packages());
            self.packages.push_all_move(packages);
            self.updated = true;
        }

        Ok(())
    }

    fn download(&self, _: &[PackageId])  -> CargoResult<()>{
        // TODO: assert! that the PackageId is contained by the source
        Ok(())
    }

    fn get(&self, ids: &[PackageId]) -> CargoResult<Vec<Package>> {
        log!(5, "getting packages; ids={}", ids);

        Ok(self.packages.iter()
           .filter(|pkg| ids.iter().any(|id| pkg.get_package_id() == id))
           .map(|pkg| pkg.clone())
           .collect())
    }

    fn fingerprint(&self, pkg: &Package) -> CargoResult<String> {
        if !self.updated {
            return Err(internal_error("BUG: source was not updated", ""));
        }

        let mut max = 0;
        for file in try!(self.list_files(pkg)).iter() {
            // An fs::stat error here is either because path is a
            // broken symlink, a permissions error, or a race
            // condition where this path was rm'ed - either way,
            // we can ignore the error and treat the path's mtime
            // as 0.
            max = cmp::max(max, file.stat().map(|s| s.modified).unwrap_or(0));
        }
        log!(5, "fingerprint {}: {}", self.path.display(), max);
        Ok(max.to_string())
    }
}
<|endoftext|>


<commit_before>#![feature(collections)]
#![feature(str_words)]

fn main() {
    // (all the type annotations are superfluous)
    // A reference to a string allocated in read only memory
    let pangram: &'static str = "the quick brown fox jumps over the lazy dog";
    println!("Pangram: {}", pangram);

    // Iterate over words in reverse, no new string is allocated
    println!("Words in reverse");
    for word in pangram.words().rev() {
        println!("> {}", word);
    }

    // Copy chars into a vector, sort and remove duplicates
    let mut chars: Vec<char> = pangram.chars().collect();
    chars.sort();
    chars.dedup();

    // Create an empty and growable `String`
    let mut string = String::new();
    for c in chars {
        // Insert a char at the end of string
        string.push(c);
        // Insert a string at the end of string
        string.push_str(", ");
    }

    // The trimmed string is a slice to the original string, hence no new
    // allocation is performed
    let chars_to_trim: &[char] = &[' ', ','];
    let trimmed_str: &str = string.trim_matches(chars_to_trim);
    println!("Used characters: {}", trimmed_str);

    // Heap allocate a string
    let alice = String::from_str("I like dogs");
    // Allocate new memory and store the modified string there
    let bob: String = alice.replace("dog", "cat");

    println!("Alice says: {}", alice);
    println!("Bob says: {}", bob);
}
<commit_msg>Fix warning<commit_after>#![feature(collections)]

fn main() {
    // (all the type annotations are superfluous)
    // A reference to a string allocated in read only memory
    let pangram: &'static str = "the quick brown fox jumps over the lazy dog";
    println!("Pangram: {}", pangram);

    // Iterate over words in reverse, no new string is allocated
    println!("Words in reverse");
    for word in pangram.split_whitespace().rev() {
        println!("> {}", word);
    }

    // Copy chars into a vector, sort and remove duplicates
    let mut chars: Vec<char> = pangram.chars().collect();
    chars.sort();
    chars.dedup();

    // Create an empty and growable `String`
    let mut string = String::new();
    for c in chars {
        // Insert a char at the end of string
        string.push(c);
        // Insert a string at the end of string
        string.push_str(", ");
    }

    // The trimmed string is a slice to the original string, hence no new
    // allocation is performed
    let chars_to_trim: &[char] = &[' ', ','];
    let trimmed_str: &str = string.trim_matches(chars_to_trim);
    println!("Used characters: {}", trimmed_str);

    // Heap allocate a string
    let alice = String::from_str("I like dogs");
    // Allocate new memory and store the modified string there
    let bob: String = alice.replace("dog", "cat");

    println!("Alice says: {}", alice);
    println!("Bob says: {}", bob);
}
<|endoftext|>