Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Consider alternative argument parsing library #38

Open
ericcornelissen opened this issue May 24, 2023 · 2 comments
Open

Consider alternative argument parsing library #38

ericcornelissen opened this issue May 24, 2023 · 2 comments
Labels
dependencies Changes to the project's dependencies refactor Changes that don't change functionality

Comments

@ericcornelissen
Copy link
Owner

ericcornelissen commented May 24, 2023

Technical Change

Summary

This project currently uses the clap crate for CLI argument parsing. Clap is easy to work with, concise, and provides a lot out of the box. However, it is not small (see "Size Analysis" section for more details).

As such, it might be worth investigating alternative argument parsing libraries for this project. After all, this project doesn't require anything special, just:

Requirements

  • long flags (e.g. --recursive)
  • short flags (e.g. -r)
  • combined short flags (e.g. -rf)
  • (TBD) exact value flags (e.g. --interactive=WHEN)
  • flag escape argument (--)
  • normal arguments (e.g. foo.bar)
  • OsString (or similar) arguments

Alternatives

Size Analysis

Using cargo-bloat we can find the functions of the executable that take up the most space. For example, of the 25 biggest functions, 15 are clap-related:

$ cargo bloat --release -n 25                    
    Finished release [optimized] target(s) in 0.09s
    Analyzing target/release/rust-rm

 File  .text     Size        Crate Name
 3.3%   4.9%  31.1KiB          std std::backtrace_rs::symbolize::gimli::resolve::{{closure}}
 2.9%   4.3%  27.5KiB clap_builder clap_builder::parser::parser::Parser::get_matches_with
 2.4%   3.5%  22.4KiB          std addr2line::ResDwarf<R>::parse
 1.8%   2.7%  17.0KiB     rust_rm? <rust_rm::cli::Args as clap_builder::derive::CommandFactory>::command
 1.7%   2.5%  16.1KiB        trash <chrono::format::DelayedFormat<I> as core::fmt::Display>::fmt
 1.5%   2.3%  14.4KiB clap_builder clap_builder::parser::validator::Validator::validate
 1.4%   2.0%  13.0KiB        trash trash::platform::<impl trash::TrashContext>::delete_all_canonicalized
 1.2%   1.7%  11.0KiB          std addr2line::ResUnit<R>::parse_lines
 1.1%   1.7%  10.6KiB      rust_rm rust_rm::main
 1.1%   1.6%  10.4KiB clap_builder clap_builder::error::Error<F>::exit
 1.0%   1.5%   9.3KiB clap_builder clap_builder::builder::command::Command::_build_self
 0.9%   1.3%   8.5KiB          std miniz_oxide::inflate::core::decompress
 0.8%   1.1%   7.2KiB clap_builder clap_builder::parser::parser::Parser::react
 0.6%   1.0%   6.1KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_args
 0.6%   0.9%   6.0KiB clap_builder clap_builder::output::usage::Usage::write_args
 0.6%   0.9%   5.6KiB          std gimli::read::abbrev::Abbreviations::insert
 0.6%   0.9%   5.5KiB          std gimli::read::unit::parse_attribute
 0.6%   0.8%   5.3KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_subcommands
 0.5%   0.8%   5.2KiB clap_builder <alloc::vec::Vec<T,A> as core::clone::Clone>::clone
 0.5%   0.8%   5.1KiB        trash trash::platform::<impl trash::TrashContext>::delete_all_canonicalized::{{closure}}
 0.5%   0.8%   4.9KiB clap_builder clap_builder::error::Error<F>::invalid_value
 0.5%   0.7%   4.6KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_templated_help
 0.5%   0.7%   4.5KiB clap_builder clap_builder::output::help_template::HelpTemplate::spec_vals
 0.5%   0.7%   4.4KiB clap_builder <clap_builder::builder::value_parser::RangedI64ValueParser<T> as clap_builder::builder::value_parser::TypedValueParser>::parse_ref
 0.5%   0.7%   4.4KiB clap_builder clap_builder::parser::features::suggestions::did_you_mean_flag
38.7%  57.9% 367.3KiB              And 870 smaller methods. Use -n N to show more.
66.8% 100.0% 634.3KiB              .text section size, the file size is 949.7KiB
More stats

N=10

4 of the 10 biggest function are related to clap:

$ cargo bloat --release -n 10
    Finished release [optimized] target(s) in 0.07s
    Analyzing target/release/rust-rm

 File  .text     Size        Crate Name
 3.3%   4.9%  31.1KiB          std std::backtrace_rs::symbolize::gimli::resolve::{{closure}}
 2.9%   4.3%  27.5KiB clap_builder clap_builder::parser::parser::Parser::get_matches_with
 2.4%   3.5%  22.4KiB          std addr2line::ResDwarf<R>::parse
 1.8%   2.7%  17.0KiB     rust_rm? <rust_rm::cli::Args as clap_builder::derive::CommandFactory>::command
 1.7%   2.5%  16.1KiB        trash <chrono::format::DelayedFormat<I> as core::fmt::Display>::fmt
 1.5%   2.3%  14.4KiB clap_builder clap_builder::parser::validator::Validator::validate
 1.4%   2.0%  13.0KiB        trash trash::platform::<impl trash::TrashContext>::delete_all_canonicalized
 1.2%   1.7%  11.0KiB          std addr2line::ResUnit<R>::parse_lines
 1.1%   1.7%  10.6KiB      rust_rm rust_rm::main
 1.1%   1.6%  10.4KiB clap_builder clap_builder::error::Error<F>::exit
47.8%  71.6% 454.0KiB              And 885 smaller methods. Use -n N to show more.
66.8% 100.0% 634.3KiB              .text section size, the file size is 949.7KiB

N=50

26 of the 50 biggest function are related to clap:

$ cargo bloat --release -n 50                    
    Finished release [optimized] target(s) in 0.07s
    Analyzing target/release/rust-rm

 File  .text     Size        Crate Name
 3.3%   4.9%  31.1KiB          std std::backtrace_rs::symbolize::gimli::resolve::{{closure}}
 2.9%   4.3%  27.5KiB clap_builder clap_builder::parser::parser::Parser::get_matches_with
 2.4%   3.5%  22.4KiB          std addr2line::ResDwarf<R>::parse
 1.8%   2.7%  17.0KiB     rust_rm? <rust_rm::cli::Args as clap_builder::derive::CommandFactory>::command
 1.7%   2.5%  16.1KiB        trash <chrono::format::DelayedFormat<I> as core::fmt::Display>::fmt
 1.5%   2.3%  14.4KiB clap_builder clap_builder::parser::validator::Validator::validate
 1.4%   2.0%  13.0KiB        trash trash::platform::<impl trash::TrashContext>::delete_all_canonicalized
 1.2%   1.7%  11.0KiB          std addr2line::ResUnit<R>::parse_lines
 1.1%   1.7%  10.6KiB      rust_rm rust_rm::main
 1.1%   1.6%  10.4KiB clap_builder clap_builder::error::Error<F>::exit
 1.0%   1.5%   9.3KiB clap_builder clap_builder::builder::command::Command::_build_self
 0.9%   1.3%   8.5KiB          std miniz_oxide::inflate::core::decompress
 0.8%   1.1%   7.2KiB clap_builder clap_builder::parser::parser::Parser::react
 0.6%   1.0%   6.1KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_args
 0.6%   0.9%   6.0KiB clap_builder clap_builder::output::usage::Usage::write_args
 0.6%   0.9%   5.6KiB          std gimli::read::abbrev::Abbreviations::insert
 0.6%   0.9%   5.5KiB          std gimli::read::unit::parse_attribute
 0.6%   0.8%   5.3KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_subcommands
 0.5%   0.8%   5.2KiB clap_builder <alloc::vec::Vec<T,A> as core::clone::Clone>::clone
 0.5%   0.8%   5.1KiB        trash trash::platform::<impl trash::TrashContext>::delete_all_canonicalized::{{closure}}
 0.5%   0.8%   4.9KiB clap_builder clap_builder::error::Error<F>::invalid_value
 0.5%   0.7%   4.6KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_templated_help
 0.5%   0.7%   4.5KiB clap_builder clap_builder::output::help_template::HelpTemplate::spec_vals
 0.5%   0.7%   4.4KiB clap_builder <clap_builder::builder::value_parser::RangedI64ValueParser<T> as clap_builder::builder::value_parser::TypedValueParser>::parse_ref
 0.5%   0.7%   4.4KiB clap_builder clap_builder::parser::features::suggestions::did_you_mean_flag
 0.5%   0.7%   4.3KiB clap_builder clap_builder::output::usage::Usage::get_required_usage_from
 0.4%   0.6%   4.1KiB          std addr2line::function::Function<R>::parse_children
 0.4%   0.6%   3.7KiB      rust_rm rust_rm::walk::recurse_path
 0.4%   0.6%   3.7KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_all_args
 0.4%   0.6%   3.6KiB       chrono chrono::offset::local::tz_info::parser::parse
 0.4%   0.6%   3.5KiB          std core::slice::sort::recurse
 0.4%   0.5%   3.4KiB clap_builder clap_builder::output::help_template::HelpTemplate::help
 0.4%   0.5%   3.4KiB          std std::backtrace_rs::symbolize::gimli::Context::new
 0.4%   0.5%   3.3KiB clap_builder clap_builder::parser::features::suggestions::did_you_mean
 0.3%   0.5%   3.3KiB          std <&T as core::fmt::Display>::fmt
 0.3%   0.5%   3.3KiB       chrono chrono::offset::local::inner::current_zone
 0.3%   0.5%   3.2KiB     clap_lex core::num::dec2flt::dec2flt
 0.3%   0.5%   3.0KiB          std gimli::read::line::parse_attribute
 0.3%   0.5%   3.0KiB          std rustc_demangle::try_demangle
 0.3%   0.5%   2.9KiB clap_builder clap_builder::builder::arg::Arg::stylize_arg_suffix
 0.3%   0.5%   2.9KiB clap_builder clap_builder::error::Error<F>::unknown_argument
 0.3%   0.5%   2.9KiB          std std::backtrace_rs::symbolize::gimli::elf::<impl std::backtrace_rs::symbolize::gimli::Mapping>::new_debug
 0.3%   0.4%   2.7KiB      rust_rm rust_rm::transform::interactive
 0.3%   0.4%   2.7KiB clap_builder clap_builder::parser::arg_matcher::ArgMatcher::fill_in_global_values
 0.3%   0.4%   2.7KiB          std gimli::read::rnglists::RngListIter<R>::next
 0.3%   0.4%   2.7KiB    [Unknown] main
 0.3%   0.4%   2.6KiB clap_builder clap_builder::output::usage::Usage::create_help_usage
 0.3%   0.4%   2.5KiB clap_builder clap_builder::builder::command::Command::_build_subcommand
 0.3%   0.4%   2.5KiB       strsim strsim::jaro
 0.3%   0.4%   2.4KiB clap_builder clap_builder::parser::matches::arg_matches::ArgMatches::remove_one
30.4%  45.6% 289.1KiB              And 845 smaller methods. Use -n N to show more.
66.8% 100.0% 634.3KiB              .text section size, the file size is 949.7KiB

N=100

37 of the 100 biggest function are related to clap:

$ cargo bloat --release -n 100                    
    Finished release [optimized] target(s) in 0.08s
    Analyzing target/release/rust-rm

 File  .text     Size        Crate Name
 3.3%   4.9%  31.1KiB          std std::backtrace_rs::symbolize::gimli::resolve::{{closure}}
 2.9%   4.3%  27.5KiB clap_builder clap_builder::parser::parser::Parser::get_matches_with
 2.4%   3.5%  22.4KiB          std addr2line::ResDwarf<R>::parse
 1.8%   2.7%  17.0KiB     rust_rm? <rust_rm::cli::Args as clap_builder::derive::CommandFactory>::command
 1.7%   2.5%  16.1KiB        trash <chrono::format::DelayedFormat<I> as core::fmt::Display>::fmt
 1.5%   2.3%  14.4KiB clap_builder clap_builder::parser::validator::Validator::validate
 1.4%   2.0%  13.0KiB        trash trash::platform::<impl trash::TrashContext>::delete_all_canonicalized
 1.2%   1.7%  11.0KiB          std addr2line::ResUnit<R>::parse_lines
 1.1%   1.7%  10.6KiB      rust_rm rust_rm::main
 1.1%   1.6%  10.4KiB clap_builder clap_builder::error::Error<F>::exit
 1.0%   1.5%   9.3KiB clap_builder clap_builder::builder::command::Command::_build_self
 0.9%   1.3%   8.5KiB          std miniz_oxide::inflate::core::decompress
 0.8%   1.1%   7.2KiB clap_builder clap_builder::parser::parser::Parser::react
 0.6%   1.0%   6.1KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_args
 0.6%   0.9%   6.0KiB clap_builder clap_builder::output::usage::Usage::write_args
 0.6%   0.9%   5.6KiB          std gimli::read::abbrev::Abbreviations::insert
 0.6%   0.9%   5.5KiB          std gimli::read::unit::parse_attribute
 0.6%   0.8%   5.3KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_subcommands
 0.5%   0.8%   5.2KiB clap_builder <alloc::vec::Vec<T,A> as core::clone::Clone>::clone
 0.5%   0.8%   5.1KiB        trash trash::platform::<impl trash::TrashContext>::delete_all_canonicalized::{{closure}}
 0.5%   0.8%   4.9KiB clap_builder clap_builder::error::Error<F>::invalid_value
 0.5%   0.7%   4.6KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_templated_help
 0.5%   0.7%   4.5KiB clap_builder clap_builder::output::help_template::HelpTemplate::spec_vals
 0.5%   0.7%   4.4KiB clap_builder <clap_builder::builder::value_parser::RangedI64ValueParser<T> as clap_builder::builder::value_parser::TypedValueParser>::parse_ref
 0.5%   0.7%   4.4KiB clap_builder clap_builder::parser::features::suggestions::did_you_mean_flag
 0.5%   0.7%   4.3KiB clap_builder clap_builder::output::usage::Usage::get_required_usage_from
 0.4%   0.6%   4.1KiB          std addr2line::function::Function<R>::parse_children
 0.4%   0.6%   3.7KiB      rust_rm rust_rm::walk::recurse_path
 0.4%   0.6%   3.7KiB clap_builder clap_builder::output::help_template::HelpTemplate::write_all_args
 0.4%   0.6%   3.6KiB       chrono chrono::offset::local::tz_info::parser::parse
 0.4%   0.6%   3.5KiB          std core::slice::sort::recurse
 0.4%   0.5%   3.4KiB clap_builder clap_builder::output::help_template::HelpTemplate::help
 0.4%   0.5%   3.4KiB          std std::backtrace_rs::symbolize::gimli::Context::new
 0.4%   0.5%   3.3KiB clap_builder clap_builder::parser::features::suggestions::did_you_mean
 0.3%   0.5%   3.3KiB          std <&T as core::fmt::Display>::fmt
 0.3%   0.5%   3.3KiB       chrono chrono::offset::local::inner::current_zone
 0.3%   0.5%   3.2KiB     clap_lex core::num::dec2flt::dec2flt
 0.3%   0.5%   3.0KiB          std gimli::read::line::parse_attribute
 0.3%   0.5%   3.0KiB          std rustc_demangle::try_demangle
 0.3%   0.5%   2.9KiB clap_builder clap_builder::builder::arg::Arg::stylize_arg_suffix
 0.3%   0.5%   2.9KiB clap_builder clap_builder::error::Error<F>::unknown_argument
 0.3%   0.5%   2.9KiB          std std::backtrace_rs::symbolize::gimli::elf::<impl std::backtrace_rs::symbolize::gimli::Mapping>::new_debug
 0.3%   0.4%   2.7KiB      rust_rm rust_rm::transform::interactive
 0.3%   0.4%   2.7KiB clap_builder clap_builder::parser::arg_matcher::ArgMatcher::fill_in_global_values
 0.3%   0.4%   2.7KiB          std gimli::read::rnglists::RngListIter<R>::next
 0.3%   0.4%   2.7KiB    [Unknown] main
 0.3%   0.4%   2.6KiB clap_builder clap_builder::output::usage::Usage::create_help_usage
 0.3%   0.4%   2.5KiB clap_builder clap_builder::builder::command::Command::_build_subcommand
 0.3%   0.4%   2.5KiB       strsim strsim::jaro
 0.3%   0.4%   2.4KiB clap_builder clap_builder::parser::matches::arg_matches::ArgMatches::remove_one
 0.2%   0.3%   2.2KiB          std <&str as core::str::pattern::Pattern>::is_contained_in
 0.2%   0.3%   2.1KiB      rust_rm rust_rm::rm::dispose
 0.2%   0.3%   2.1KiB clap_builder clap_builder::error::Error<F>::invalid_subcommand
 0.2%   0.3%   2.1KiB clap_builder <alloc::vec::Vec<T,A> as core::clone::Clone>::clone
 0.2%   0.3%   2.1KiB clap_builder core::slice::sort::merge_sort
 0.2%   0.3%   2.0KiB       chrono chrono::offset::local::Local::now
 0.2%   0.3%   2.0KiB      rust_rm rust_rm::fs::open
 0.2%   0.3%   1.9KiB          std std::backtrace_rs::print::BacktraceFrameFmt::print_raw_with_column
 0.2%   0.3%   1.9KiB          std alloc::str::<impl str>::to_lowercase
 0.2%   0.3%   1.8KiB clap_builder clap_builder::builder::styled_str::StyledStr::replace_newline_var
 0.2%   0.3%   1.8KiB          std rustc_demangle::v0::Printer::print_const
 0.2%   0.3%   1.8KiB          std rustc_demangle::v0::Printer::print_path
 0.2%   0.3%   1.8KiB clap_builder clap_builder::parser::matches::arg_matches::ArgMatches::remove_many
 0.2%   0.3%   1.7KiB          std <core::time::Duration as core::fmt::Debug>::fmt::fmt_decimal
 0.2%   0.3%   1.7KiB          std <alloc::vec::Vec<T> as alloc::vec::spec_from_iter::SpecFromIter<T,I>>::from_iter
 0.2%   0.3%   1.7KiB     rust_rm? <rust_rm::logging::Logger as log::Log>::log
 0.2%   0.3%   1.6KiB      rust_rm rust_rm::walk::recurse::{{closure}}
 0.2%   0.3%   1.6KiB          std <core::str::lossy::Debug as core::fmt::Debug>::fmt
 0.2%   0.3%   1.6KiB clap_builder clap_builder::parser::parser::Parser::add_defaults
 0.2%   0.3%   1.6KiB       chrono chrono::offset::local::tz_info::rule::RuleDay::transition_date
 0.2%   0.2%   1.6KiB          std core::str::count::do_count_chars
 0.2%   0.2%   1.5KiB    once_cell once_cell::imp::initialize_or_wait
 0.2%   0.2%   1.5KiB clap_builder clap_builder::parser::parser::Parser::start_custom_arg
 0.2%   0.2%   1.5KiB          std core::ops::function::impls::<impl core::ops::function::FnMut<A> for &mut F>::call_mut
 0.2%   0.2%   1.5KiB clap_builder clap_builder::parser::parser::Parser::possible_subcommand
 0.2%   0.2%   1.5KiB          std std::backtrace_rs::symbolize::gimli::elf::Object::parse
 0.2%   0.2%   1.5KiB          std <std::path::Components as core::iter::traits::double_ended::DoubleEndedIterator>::next_back
 0.2%   0.2%   1.4KiB          std <core::iter::adapters::GenericShunt<I,R> as core::iter::traits::iterator::Iterator>::next
 0.2%   0.2%   1.4KiB          std <str as core::fmt::Debug>::fmt
 0.1%   0.2%   1.4KiB          std <std::io::error::Error as core::fmt::Display>::fmt
 0.1%   0.2%   1.4KiB       chrono <chrono::naive::datetime::NaiveDateTime as core::fmt::Debug>::fmt
 0.1%   0.2%   1.4KiB          std miniz_oxide::inflate::core::init_tree
 0.1%   0.2%   1.4KiB       chrono chrono::offset::local::tz_info::timezone::TimeZone::from_posix_tz
 0.1%   0.2%   1.3KiB          std std::fs::DirBuilder::create_dir_all
 0.1%   0.2%   1.3KiB          std std::panicking::rust_panic_with_hook
 0.1%   0.2%   1.3KiB clap_builder clap_builder::output::help_template::HelpTemplate::sc_spec_vals
 0.1%   0.2%   1.3KiB          std rustc_demangle::v0::Printer::print_const_str_literal
 0.1%   0.2%   1.3KiB       chrono chrono::offset::local::tz_info::rule::TransitionRule::from_tz_string
 0.1%   0.2%   1.3KiB          std rustc_demangle::v0::Printer::print_quoted_escaped_chars
 0.1%   0.2%   1.3KiB          std alloc::str::<impl str>::replace
 0.1%   0.2%   1.3KiB          std rustc_demangle::v0::Printer::print_type::{{closure}}
 0.1%   0.2%   1.3KiB clap_builder <core::iter::adapters::map::Map<I,F> as core::iter::traits::iterator::Iterator>::fold
 0.1%   0.2%   1.3KiB          std <rustc_demangle::v0::Ident as core::fmt::Display>::fmt
 0.1%   0.2%   1.3KiB clap_builder clap_builder::error::format::did_you_mean
 0.1%   0.2%   1.3KiB          std core::str::pattern::StrSearcher::new
 0.1%   0.2%   1.3KiB          std rustc_demangle::v0::Printer::print_type
 0.1%   0.2%   1.2KiB          std gimli::read::unit::skip_attributes
 0.1%   0.2%   1.2KiB      anstyle <anstyle::style::StyleDisplay as core::fmt::Display>::fmt
 0.1%   0.2%   1.2KiB          std <std::io::error::Error as core::fmt::Debug>::fmt
 0.1%   0.2%   1.2KiB       chrono chrono::offset::local::tz_info::timezone::TimeZone::new
22.2%  33.3% 211.2KiB              And 795 smaller methods. Use -n N to show more.
66.8% 100.0% 634.3KiB              .text section size, the file size is 949.7KiB

Footnotes

  1. Based on https://github.com/rosetta-rs/argparse-rosetta-rs/tree/ec59407e70cb8993f9a81f8b2c3df1397fe84422#results derived code is less optimized for size.

@ericcornelissen ericcornelissen added dependencies Changes to the project's dependencies refactor Changes that don't change functionality labels May 24, 2023
@blyxxyz
Copy link

blyxxyz commented Jun 12, 2023

You might be interested in these benchmarks.

There are also features you don't use now but might want to have in the future:

  • Support for invalid Unicode: Out of that list, clap and lexopt and xflags can handle it. You don't get it for free, you'd have to switch from String to OsString/PathBuf, but it's useful for an rm to be able to delete files with problematic names.
  • argh doesn't support the --interactive=once and --preserve-root=all syntax of GNU rm, and some of the other parsers might not either. (In clap this is require_equals, in getopts this is optflagopt, in lexopt this is optional_value.)

@ericcornelissen
Copy link
Owner Author

Thanks for chiming in @blyxxyz, the benchmarks are very useful and so are the other suggestions. In fact, I created an issue to support invalid Unicode paths. I also added the considerations to the issue description as (for now) potential requirements.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
dependencies Changes to the project's dependencies refactor Changes that don't change functionality
Projects
None yet
Development

No branches or pull requests

2 participants