cri-2.15.11/0000755000004100000410000000000014004133336012463 5ustar www-datawww-datacri-2.15.11/Gemfile.lock0000644000004100000410000000272214004133336014710 0ustar www-datawww-dataPATH remote: . specs: cri (2.15.11) GEM remote: https://rubygems.org/ specs: ast (2.4.1) coveralls (0.8.23) json (>= 1.8, < 3) simplecov (~> 0.16.1) term-ansicolor (~> 1.3) thor (>= 0.19.4, < 2.0) tins (~> 1.6) docile (1.3.4) json (2.5.1) m (1.5.1) method_source (>= 0.6.7) rake (>= 0.9.2.2) method_source (1.0.0) minitest (5.14.2) parallel (1.20.1) parser (3.0.0.0) ast (~> 2.4.1) rainbow (3.0.0) rake (13.0.3) regexp_parser (2.0.3) rexml (3.2.4) rubocop (1.7.0) parallel (~> 1.10) parser (>= 2.7.1.5) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 1.8, < 3.0) rexml rubocop-ast (>= 1.2.0, < 2.0) ruby-progressbar (~> 1.7) unicode-display_width (>= 1.4.0, < 2.0) rubocop-ast (1.3.0) parser (>= 2.7.1.5) rubocop-minitest (0.10.2) rubocop (>= 0.87, < 2.0) rubocop-rake (0.5.1) rubocop ruby-progressbar (1.11.0) simplecov (0.16.1) docile (~> 1.1) json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) sync (0.5.0) term-ansicolor (1.7.1) tins (~> 1.0) thor (1.0.1) tins (1.26.0) sync unicode-display_width (1.7.0) yard (0.9.26) PLATFORMS x86_64-darwin-20 DEPENDENCIES coveralls cri! m (~> 1.5) minitest rake rubocop rubocop-minitest (~> 0.10.2) rubocop-rake (~> 0.5.1) yard BUNDLED WITH 2.2.3 cri-2.15.11/test/0000755000004100000410000000000014004133336013442 5ustar www-datawww-datacri-2.15.11/test/helper.rb0000644000004100000410000000166214004133336015253 0ustar www-datawww-data# frozen_string_literal: true require 'coveralls' Coveralls.wear! require 'minitest' require 'minitest/autorun' require 'cri' require 'stringio' module Cri class TestCase < Minitest::Test def setup @orig_io = capture_io end def teardown uncapture_io(*@orig_io) end def capture_io_while orig_io = capture_io yield [$stdout.string, $stderr.string] ensure uncapture_io(*orig_io) end def lines(string) string.scan(/^.*\n/).map(&:chomp) end private def capture_io orig_stdout = $stdout orig_stderr = $stderr $stdout = StringIO.new $stderr = StringIO.new [orig_stdout, orig_stderr] end def uncapture_io(orig_stdout, orig_stderr) $stdout = orig_stdout $stderr = orig_stderr end end end # Unexpected system exit is unexpected ::MiniTest::Unit::TestCase::PASSTHROUGH_EXCEPTIONS.delete(SystemExit) cri-2.15.11/test/test_string_formatter.rb0000644000004100000410000000772714004133336020434 0ustar www-datawww-data# frozen_string_literal: true require 'helper' module Cri class CoreExtTestCase < Cri::TestCase def formatter Cri::StringFormatter.new end def test_string_to_paragraphs original = "Lorem ipsum dolor sit amet,\nconsectetur adipisicing.\n\n" \ "Sed do eiusmod\ntempor incididunt ut labore." expected = ['Lorem ipsum dolor sit amet, consectetur adipisicing.', 'Sed do eiusmod tempor incididunt ut labore.'] actual = formatter.to_paragraphs(original) assert_equal expected, actual end def test_string_wrap_and_indent_without_indent original = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, ' \ 'sed do eiusmod tempor incididunt ut labore et dolore ' \ 'magna aliqua.' expected = "Lorem ipsum dolor sit amet, consectetur\n" \ "adipisicing elit, sed do eiusmod tempor\n" \ "incididunt ut labore et dolore magna\n" \ 'aliqua.' actual = formatter.wrap_and_indent(original, 40, 0) assert_equal expected, actual end def test_string_wrap_and_indent_with_indent original = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, ' \ 'sed do eiusmod tempor incididunt ut labore et dolore ' \ 'magna aliqua.' expected = " Lorem ipsum dolor sit amet,\n" \ " consectetur adipisicing elit,\n" \ " sed do eiusmod tempor\n" \ " incididunt ut labore et dolore\n" \ ' magna aliqua.' actual = formatter.wrap_and_indent(original, 36, 4) assert_equal expected, actual end def test_string_wrap_and_indent_excluding_first_line original = 'Lorem ipsum dolor sit amet, consectetur adipisicing ' \ 'elit, sed do eiusmod tempor incididunt ut labore et dolore ' \ 'magna aliqua.' expected = "Lorem ipsum dolor sit amet,\n" \ " consectetur adipisicing elit,\n" \ " sed do eiusmod tempor\n" \ " incididunt ut labore et dolore\n" \ ' magna aliqua.' actual = formatter.wrap_and_indent(original, 36, 4, true) assert_equal expected, actual end def test_string_wrap_and_indent_with_large_indent original = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit, ' \ 'sed do eiusmod tempor incididunt ut labore et dolore ' \ 'magna aliqua.' expected = " Lorem ipsum\n" \ " dolor sit\n" \ " amet,\n" \ " consectetur\n" \ " adipisicing\n" \ " elit, sed do\n" \ " eiusmod\n" \ " tempor\n" \ " incididunt ut\n" \ " labore et\n" \ " dolore magna\n" \ ' aliqua.' actual = formatter.wrap_and_indent(original, 44, 30) assert_equal expected, actual end def test_string_wrap_and_indent_with_multiple_lines original = "Lorem ipsum dolor sit\namet, consectetur adipisicing elit, " \ "sed do\neiusmod tempor incididunt ut\nlabore et dolore " \ "magna\naliqua." expected = " Lorem ipsum dolor sit amet,\n" \ " consectetur adipisicing elit,\n" \ " sed do eiusmod tempor\n" \ " incididunt ut labore et dolore\n" \ ' magna aliqua.' actual = formatter.wrap_and_indent(original, 36, 4) assert_equal expected, actual end end end cri-2.15.11/test/test_base.rb0000644000004100000410000000020214004133336015732 0ustar www-datawww-data# frozen_string_literal: true require 'helper' module Cri class BaseTestCase < Cri::TestCase def test_stub; end end end cri-2.15.11/test/test_command.rb0000644000004100000410000006405614004133336016457 0ustar www-datawww-data# frozen_string_literal: true require 'helper' module Cri class CommandTestCase < Cri::TestCase def simple_cmd Cri::Command.define do name 'moo' usage 'moo [options] arg1 arg2 ...' summary 'does stuff' description 'This command does a lot of stuff.' option :a, :aaa, 'opt a', argument: :optional do |value, cmd| $stdout.puts "#{cmd.name}:#{value}" end required :b, :bbb, 'opt b' optional :c, :ccc, 'opt c' flag :d, :ddd, 'opt d' forbidden :e, :eee, 'opt e' required :t, :transform, 'opt t', transform: method(:Integer) run do |opts, args, c| $stdout.puts "Awesome #{c.name}!" $stdout.puts args.join(',') opts_strings = [] opts.each_pair { |k, v| opts_strings << "#{k}=#{v}" } $stdout.puts opts_strings.sort.join(',') end end end def bare_cmd Cri::Command.define do name 'moo' run do |_opts, _args| end end end def nested_cmd super_cmd = Cri::Command.define do name 'super' usage 'super [command] [options] [arguments]' summary 'does super stuff' description 'This command does super stuff.' option :a, :aaa, 'opt a', argument: :optional do |value, cmd| $stdout.puts "#{cmd.name}:#{value}" end required :b, :bbb, 'opt b' optional :c, :ccc, 'opt c' flag :d, :ddd, 'opt d' forbidden :e, :eee, 'opt e' end super_cmd.define_command do name 'sub' aliases 'sup' usage 'sub [options]' summary 'does subby stuff' description 'This command does subby stuff.' option :m, :mmm, 'opt m', argument: :optional required :n, :nnn, 'opt n' optional :o, :ooo, 'opt o' flag :p, :ppp, 'opt p' forbidden :q, :qqq, 'opt q' run do |opts, args| $stdout.puts 'Sub-awesome!' $stdout.puts args.join(',') opts_strings = [] opts.each_pair { |k, v| opts_strings << "#{k}=#{v}" } $stdout.puts opts_strings.join(',') end end super_cmd.define_command do name 'sink' usage 'sink thing_to_sink' summary 'sinks stuff' description 'Sinks stuff (like ships and the like).' run do |_opts, _args| end end super_cmd end def nested_cmd_with_run_block super_cmd = Cri::Command.define do name 'super' usage 'super [command] [options] [arguments]' summary 'does super stuff' description 'This command does super stuff.' run do |_opts, _args| $stdout.puts 'super' end end super_cmd.define_command do name 'sub' aliases 'sup' usage 'sub [options]' summary 'does subby stuff' description 'This command does subby stuff.' run do |_opts, _args| $stdout.puts 'sub' end end super_cmd end def test_invoke_simple_without_opts_or_args out, err = capture_io_while do simple_cmd.run(%w[]) end assert_equal ['Awesome moo!', '', ''], lines(out) assert_equal [], lines(err) end def test_invoke_simple_with_args out, err = capture_io_while do simple_cmd.run(%w[abc xyz]) end assert_equal ['Awesome moo!', 'abc,xyz', ''], lines(out) assert_equal [], lines(err) end def test_invoke_simple_with_opts out, err = capture_io_while do simple_cmd.run(%w[-c -b x]) end assert_equal ['Awesome moo!', '', 'bbb=x,ccc=true'], lines(out) assert_equal [], lines(err) end def test_invoke_simple_with_missing_opt_arg out, err = capture_io_while do err = assert_raises SystemExit do simple_cmd.run(%w[-b]) end assert_equal 1, err.status end assert_equal [], lines(out) assert_equal ['moo: option requires an argument -- b'], lines(err) end def test_invoke_simple_with_missing_opt_arg_no_exit out, err = capture_io_while do simple_cmd.run(%w[-b], {}, hard_exit: false) end assert_equal [], lines(out) assert_equal ['moo: option requires an argument -- b'], lines(err) end def test_invoke_simple_with_illegal_opt out, err = capture_io_while do err = assert_raises SystemExit do simple_cmd.run(%w[-z]) end assert_equal 1, err.status end assert_equal [], lines(out) assert_equal ['moo: unrecognised option -- z'], lines(err) end def test_invoke_simple_with_illegal_opt_no_exit out, err = capture_io_while do simple_cmd.run(%w[-z], {}, hard_exit: false) end assert_equal [], lines(out) assert_equal ['moo: unrecognised option -- z'], lines(err) end def test_invoke_simple_with_invalid_value_for_opt out, err = capture_io_while do err = assert_raises SystemExit do simple_cmd.run(%w[-t nope]) end assert_equal 1, err.status end assert_equal [], lines(out) assert_equal ['moo: invalid value "nope" for --transform option'], lines(err) end def test_invoke_simple_with_invalid_value_for_opt_no_exit out, err = capture_io_while do simple_cmd.run(%w[-t nope], {}, hard_exit: false) end assert_equal [], lines(out) assert_equal ['moo: invalid value "nope" for --transform option'], lines(err) end def test_invoke_simple_with_opt_with_block out, err = capture_io_while do simple_cmd.run(%w[-a 123]) end assert_equal ['moo:123', 'Awesome moo!', '', 'aaa=123'], lines(out) assert_equal [], lines(err) end def test_invoke_nested_without_opts_or_args out, err = capture_io_while do err = assert_raises SystemExit do nested_cmd.run(%w[]) end assert_equal 1, err.status end assert_equal [], lines(out) assert_equal ['super: no command given'], lines(err) end def test_invoke_nested_without_opts_or_args_no_exit out, err = capture_io_while do nested_cmd.run(%w[], {}, hard_exit: false) end assert_equal [], lines(out) assert_equal ['super: no command given'], lines(err) end def test_invoke_nested_with_correct_command_name out, err = capture_io_while do nested_cmd.run(%w[sub]) end assert_equal ['Sub-awesome!', '', ''], lines(out) assert_equal [], lines(err) end def test_invoke_nested_with_incorrect_command_name out, err = capture_io_while do err = assert_raises SystemExit do nested_cmd.run(%w[oogabooga]) end assert_equal 1, err.status end assert_equal [], lines(out) assert_equal ["super: unknown command 'oogabooga'"], lines(err) end def test_invoke_nested_with_incorrect_command_name_no_exit out, err = capture_io_while do nested_cmd.run(%w[oogabooga], {}, hard_exit: false) end assert_equal [], lines(out) assert_equal ["super: unknown command 'oogabooga'"], lines(err) end def test_invoke_nested_with_ambiguous_command_name out, err = capture_io_while do err = assert_raises SystemExit do nested_cmd.run(%w[s]) end assert_equal 1, err.status end assert_equal [], lines(out) assert_equal ["super: 's' is ambiguous:", ' sink sub'], lines(err) end def test_invoke_nested_with_ambiguous_command_name_no_exit out, err = capture_io_while do nested_cmd.run(%w[s], {}, hard_exit: false) end assert_equal [], lines(out) assert_equal ["super: 's' is ambiguous:", ' sink sub'], lines(err) end def test_invoke_nested_with_alias out, err = capture_io_while do nested_cmd.run(%w[sup]) end assert_equal ['Sub-awesome!', '', ''], lines(out) assert_equal [], lines(err) end def test_invoke_nested_with_options_before_command out, err = capture_io_while do nested_cmd.run(%w[-a 666 sub]) end assert_equal ['super:666', 'Sub-awesome!', '', 'aaa=666'], lines(out) assert_equal [], lines(err) end def test_invoke_nested_with_run_block out, err = capture_io_while do nested_cmd_with_run_block.run(%w[]) end assert_equal ['super'], lines(out) assert_equal [], lines(err) out, err = capture_io_while do nested_cmd_with_run_block.run(%w[sub]) end assert_equal ['sub'], lines(out) assert_equal [], lines(err) end def test_help_nested def $stdout.tty? true end help = nested_cmd.subcommands.find { |cmd| cmd.name == 'sub' }.help assert help.include?("USAGE\e[0m\e[0m\n \e[32msuper\e[0m \e[32msub\e[0m [options]\n") end def test_help_with_and_without_colors def $stdout.tty? true end help_on_tty = simple_cmd.help def $stdout.tty? false end help_not_on_tty = simple_cmd.help assert_includes help_on_tty, "\e[31mUSAGE\e[0m\e[0m\n \e[32mmoo" assert_includes help_not_on_tty, "USAGE\n moo" end def test_help_for_bare_cmd bare_cmd.help end def test_help_with_optional_options def $stdout.tty? true end cmd = Cri::Command.define do name 'build' flag :s, nil, 'short' flag nil, :long, 'long' end help = cmd.help assert_match(/--long.*-s/m, help) assert_match(/^ \e\[33m--long\e\[0m long$/, help) assert_match(/^ \e\[33m-s\e\[0m short$/, help) end def test_help_with_different_option_types_short_and_long def $stdout.tty? true end cmd = Cri::Command.define do name 'build' required :r, :required, 'required value' flag :f, :flag, 'forbidden value' optional :o, :optional, 'optional value' end help = cmd.help assert_match(/^ \e\[33m-r\e\[0m \e\[33m--required\e\[0m= required value$/, help) assert_match(/^ \e\[33m-f\e\[0m \e\[33m--flag\e\[0m forbidden value$/, help) assert_match(/^ \e\[33m-o\e\[0m \e\[33m--optional\e\[0m\[=\] optional value$/, help) end def test_help_with_different_option_types_short def $stdout.tty? true end cmd = Cri::Command.define do name 'build' required :r, nil, 'required value' flag :f, nil, 'forbidden value' optional :o, nil, 'optional value' end help = cmd.help assert_match(/^ \e\[33m-r\e\[0m required value$/, help) assert_match(/^ \e\[33m-f\e\[0m forbidden value$/, help) assert_match(/^ \e\[33m-o\e\[0m \[\] optional value$/, help) end def test_help_with_different_option_types_long def $stdout.tty? true end cmd = Cri::Command.define do name 'build' required nil, :required, 'required value' flag nil, :flag, 'forbidden value' optional nil, :optional, 'optional value' end help = cmd.help assert_match(/^ \e\[33m--required\e\[0m= required value$/, help) assert_match(/^ \e\[33m--flag\e\[0m forbidden value$/, help) assert_match(/^ \e\[33m--optional\e\[0m\[=\] optional value$/, help) end def test_help_with_multiple_groups help = nested_cmd.subcommands.find { |cmd| cmd.name == 'sub' }.help assert_match(/OPTIONS.*OPTIONS FOR SUPER/m, help) end def test_modify_with_block_argument cmd = Cri::Command.define do |c| c.name 'build' end assert_equal 'build', cmd.name cmd.modify do |c| c.name 'compile' end assert_equal 'compile', cmd.name end def test_help_with_wrapped_options def $stdout.tty? true end cmd = Cri::Command.define do name 'build' flag nil, :longflag, 'This is an option with a very long description that should be wrapped' end help = cmd.help assert_match(/^ \e\[33m--longflag\e\[0m This is an option with a very long description that$/, help) assert_match(/^ should be wrapped$/, help) end def test_modify_without_block_argument cmd = Cri::Command.define do name 'build' end assert_equal 'build', cmd.name cmd.modify do name 'compile' end assert_equal 'compile', cmd.name end def test_new_basic_root cmd = Cri::Command.new_basic_root.modify do name 'mytool' end # Check option definitions assert_equal 1, cmd.option_definitions.size opt_defn = cmd.option_definitions.to_a[0] assert_equal 'help', opt_defn.long # Check subcommand assert_equal 1, cmd.subcommands.size assert_equal 'help', cmd.subcommands.to_a[0].name end def test_define_with_block_argument cmd = Cri::Command.define do |c| c.name 'moo' end assert_equal 'moo', cmd.name end def test_define_without_block_argument cmd = Cri::Command.define do name 'moo' end assert_equal 'moo', cmd.name end def test_define_subcommand_with_block_argument cmd = bare_cmd cmd.define_command do |c| c.name 'baresub' end assert_equal 'baresub', cmd.subcommands.to_a[0].name end def test_define_subcommand_without_block_argument cmd = bare_cmd cmd.define_command do name 'baresub' end assert_equal 'baresub', cmd.subcommands.to_a[0].name end def test_backtrace_includes_filename error = assert_raises RuntimeError do Cri::Command.define('raise "boom"', 'mycommand.rb') end assert_match(/mycommand.rb/, error.backtrace.join("\n")) end def test_hidden_commands_single cmd = nested_cmd subcmd = simple_cmd cmd.add_command subcmd subcmd.modify do |c| c.name 'old-and-deprecated' c.summary 'does stuff the ancient, totally deprecated way' c.be_hidden end refute cmd.help.include?('hidden commands omitted') assert cmd.help.include?('hidden command omitted') refute cmd.help.include?('old-and-deprecated') refute cmd.help(verbose: true).include?('hidden commands omitted') refute cmd.help(verbose: true).include?('hidden command omitted') assert cmd.help(verbose: true).include?('old-and-deprecated') end def test_hidden_commands_multiple cmd = nested_cmd subcmd = simple_cmd cmd.add_command subcmd subcmd.modify do |c| c.name 'first' c.summary 'does stuff first' end subcmd = simple_cmd cmd.add_command subcmd subcmd.modify do |c| c.name 'old-and-deprecated' c.summary 'does stuff the old, deprecated way' c.be_hidden end subcmd = simple_cmd cmd.add_command subcmd subcmd.modify do |c| c.name 'ancient-and-deprecated' c.summary 'does stuff the ancient, reallydeprecated way' c.be_hidden end assert cmd.help.include?('hidden commands omitted') refute cmd.help.include?('hidden command omitted') refute cmd.help.include?('old-and-deprecated') refute cmd.help.include?('ancient-and-deprecated') refute cmd.help(verbose: true).include?('hidden commands omitted') refute cmd.help(verbose: true).include?('hidden command omitted') assert cmd.help(verbose: true).include?('old-and-deprecated') assert cmd.help(verbose: true).include?('ancient-and-deprecated') pattern = /ancient-and-deprecated.*first.*old-and-deprecated/m assert_match(pattern, cmd.help(verbose: true)) end def test_run_with_raw_args cmd = Cri::Command.define do name 'moo' run do |_opts, args| puts "args=#{args.join(',')}" end end out, _err = capture_io_while do cmd.run(%w[foo -- bar]) end assert_equal "args=foo,bar\n", out end def test_run_without_block cmd = Cri::Command.define do name 'moo' end assert_raises(Cri::NotImplementedError) do cmd.run([]) end end def test_runner_with_raw_args cmd = Cri::Command.define do name 'moo' runner(Class.new(Cri::CommandRunner) do def run puts "args=#{arguments.join(',')}" end end) end out, _err = capture_io_while do cmd.run(%w[foo -- bar]) end assert_equal "args=foo,bar\n", out end def test_compare foo = Cri::Command.define { name 'foo' } bar = Cri::Command.define { name 'bar' } qux = Cri::Command.define { name 'qux' } assert_equal [bar, foo, qux], [foo, bar, qux].sort end def test_default_subcommand subcommand = Cri::Command.define do name 'sub' run do |_opts, _args, _c| $stdout.puts 'I am the subcommand!' end end cmd = Cri::Command.define do name 'super' default_subcommand 'sub' subcommand subcommand end out, _err = capture_io_while do cmd.run([]) end assert_equal "I am the subcommand!\n", out end def test_skip_option_parsing command = Cri::Command.define do name 'super' skip_option_parsing run do |_opts, args, _c| puts "args=#{args.join(',')}" end end out, _err = capture_io_while do command.run(['--test', '-a', 'arg']) end assert_equal "args=--test,-a,arg\n", out end def test_subcommand_skip_option_parsing super_cmd = Cri::Command.define do name 'super' option :a, :aaa, 'opt a', argument: :optional end super_cmd.define_command do name 'sub' skip_option_parsing run do |opts, args, _c| puts "opts=#{opts.inspect} args=#{args.join(',')}" end end out, _err = capture_io_while do super_cmd.run(['--aaa', 'test', 'sub', '--test', 'value']) end assert_equal "opts={:aaa=>\"test\"} args=--test,value\n", out end def test_wrong_number_of_args cmd = Cri::Command.define do name 'publish' param :filename end out, err = capture_io_while do err = assert_raises SystemExit do cmd.run([]) end assert_equal 1, err.status end assert_equal [], lines(out) assert_equal ['publish: incorrect number of arguments given: expected 1, but got 0'], lines(err) end def test_no_params_zero_args dsl = Cri::CommandDSL.new dsl.instance_eval do name 'moo' usage 'dunno whatever' summary 'does stuff' description 'This command does a lot of stuff.' no_params run do |_opts, args| end end command = dsl.command command.run([]) end def test_no_params_one_arg dsl = Cri::CommandDSL.new dsl.instance_eval do name 'moo' usage 'dunno whatever' summary 'does stuff' description 'This command does a lot of stuff.' no_params run do |_opts, args| end end command = dsl.command out, err = capture_io_while do err = assert_raises SystemExit do command.run(['a']) end assert_equal 1, err.status end assert_equal [], lines(out) assert_equal ['moo: incorrect number of arguments given: expected 0, but got 1'], lines(err) end def test_load_file Dir.mktmpdir('foo') do |dir| filename = "#{dir}/moo.rb" File.write(filename, <<~CMD) name 'moo' usage 'dunno whatever' summary 'does stuff' description 'This command does a lot of stuff.' no_params run do |_opts, args| end CMD cmd = Cri::Command.load_file(filename) assert_equal('moo', cmd.name) end end def test_load_file_infer_name_false Dir.mktmpdir('foo') do |dir| filename = "#{dir}/moo.rb" File.write(filename, <<~CMD) usage 'dunno whatever' summary 'does stuff' description 'This command does a lot of stuff.' no_params run do |_opts, args| end CMD cmd = Cri::Command.load_file(filename) assert_equal(nil, cmd.name) end end def test_load_file_infer_name Dir.mktmpdir('foo') do |dir| filename = "#{dir}/moo.rb" File.write(filename, <<~CMD) usage 'dunno whatever' summary 'does stuff' description 'This command does a lot of stuff.' no_params run do |_opts, args| end CMD cmd = Cri::Command.load_file(filename, infer_name: true) assert_equal('moo', cmd.name) end end def test_load_file_infer_name_double Dir.mktmpdir('foo') do |dir| filename = "#{dir}/moo.rb" File.write(filename, <<~CMD) name 'oink' usage 'dunno whatever' summary 'does stuff' description 'This command does a lot of stuff.' no_params run do |_opts, args| end CMD cmd = Cri::Command.load_file(filename, infer_name: true) assert_equal('moo', cmd.name) end end def test_required_args_with_dash_h dsl = Cri::CommandDSL.new dsl.instance_eval do name 'moo' usage 'dunno whatever' summary 'does stuff' description 'This command does a lot of stuff.' param :foo option :h, :help, 'show help' do $helped = true exit 0 end end command = dsl.command $helped = false out, err = capture_io_while do assert_raises SystemExit do command.run(['-h']) end end assert $helped assert_equal [], lines(out) assert_equal [], lines(err) end def test_propagate_options_two_levels_down cmd_a = Cri::Command.define do name 'a' flag :t, :test, 'test' end cmd_b = cmd_a.define_command('b') do end cmd_b.define_command('c') do run do |opts, _args| puts "test? #{opts[:test].inspect}!" end end # test -t last out, err = capture_io_while do cmd_a.run(%w[b c -t]) end assert_equal ['test? true!'], lines(out) assert_equal [], lines(err) # test -t mid out, err = capture_io_while do cmd_a.run(%w[b -t c]) end assert_equal ['test? true!'], lines(out) assert_equal [], lines(err) # test -t first out, err = capture_io_while do cmd_a.run(%w[-t b c]) end assert_equal ['test? true!'], lines(out) assert_equal [], lines(err) end def test_flag_defaults_to_false cmd = Cri::Command.define do name 'a' option :f, :force2, 'push with force', argument: :forbidden run do |opts, _args, _cmd| puts "Force? #{opts[:force2].inspect}! Key present? #{opts.key?(:force2)}!" end end out, err = capture_io_while do cmd.run(%w[]) end assert_equal ['Force? false! Key present? false!'], lines(out) assert_equal [], lines(err) end def test_required_option_defaults_to_given_value cmd = Cri::Command.define do name 'a' option :a, :animal, 'specify animal', argument: :required, default: 'cow' run do |opts, _args, _cmd| puts "Animal = #{opts[:animal]}! Key present? #{opts.key?(:animal)}!" end end out, err = capture_io_while do cmd.run(%w[]) end assert_equal ['Animal = cow! Key present? false!'], lines(out) assert_equal [], lines(err) end def test_optional_option_defaults_to_given_value cmd = Cri::Command.define do name 'a' option :a, :animal, 'specify animal', argument: :optional, default: 'cow' run do |opts, _args, _cmd| puts "Animal = #{opts[:animal]}" end end out, err = capture_io_while do cmd.run(%w[]) end assert_equal ['Animal = cow'], lines(out) assert_equal [], lines(err) end def test_required_option_defaults_to_given_value_with_transform cmd = Cri::Command.define do name 'a' option :a, :animal, 'specify animal', argument: :required, transform: ->(a) { a.upcase }, default: 'cow' run do |opts, _args, _cmd| puts "Animal = #{opts[:animal]}" end end out, err = capture_io_while do cmd.run(%w[]) end assert_equal ['Animal = cow'], lines(out) assert_equal [], lines(err) end def test_option_definitions_are_not_shared_across_commands root_cmd = Cri::Command.define do name 'root' option :r, :rrr, 'Rrr!', argument: :required end subcmd_a = root_cmd.define_command do name 'a' option :a, :aaa, 'Aaa!', argument: :required run do |_opts, _args, cmd| puts cmd.all_opt_defns.map(&:long).sort.join(',') end end subcmd_b = root_cmd.define_command do name 'b' option :b, :bbb, 'Bbb!', argument: :required run do |_opts, _args, cmd| puts cmd.all_opt_defns.map(&:long).sort.join(',') end end out, err = capture_io_while do subcmd_a.run(%w[]) end assert_equal ['aaa,rrr'], lines(out) assert_equal [], lines(err) out, err = capture_io_while do subcmd_b.run(%w[]) end assert_equal ['bbb,rrr'], lines(out) assert_equal [], lines(err) end end end cri-2.15.11/test/test_help_renderer.rb0000644000004100000410000000340414004133336017645 0ustar www-datawww-data# frozen_string_literal: true require 'helper' module Cri class HelpRendererTestCase < Cri::TestCase # NOTE: Additional test cases are in test_command.rb def help_for(cmd) io = StringIO.new Cri::HelpRenderer.new(cmd, io: io).render end def test_simple expected = <<~HELP NAME help - show help USAGE help [command_name] DESCRIPTION Show help for the given command, or show general help. When no command is given, a list of available commands is displayed, as well as a list of global command-line options. When a command is given, a command description, as well as command-specific command-line options, are shown. OPTIONS -v --verbose show more detailed help HELP cmd = Cri::Command.new_basic_help assert_equal(expected, help_for(cmd)) end def test_with_defaults cmd = Cri::Command.define do name 'build' optional nil, :'with-animal', 'Add animal', default: 'giraffe' end help = help_for(cmd) assert_match(/^ --with-animal\[=\] Add animal \(default: giraffe\)$/, help) end def test_with_summary cmd = Cri::Command.define do name 'build' summary 'do some buildage' optional nil, :'with-animal', 'Add animal', default: 'giraffe' end help = help_for(cmd) assert_match(/^NAME\n build - do some buildage\n$/, help) end def test_without_summary cmd = Cri::Command.define do name 'build' optional nil, :'with-animal', 'Add animal', default: 'giraffe' end help = help_for(cmd) assert_match(/^NAME\n build\n$/, help) end end end cri-2.15.11/test/test_basic_help.rb0000644000004100000410000000277714004133336017134 0ustar www-datawww-data# frozen_string_literal: true require 'helper' module Cri class BasicHelpTestCase < Cri::TestCase def test_run_without_supercommand cmd = Cri::Command.new_basic_help assert_raises Cri::NoHelpAvailableError do cmd.run([]) end end def test_run_with_supercommand cmd = Cri::Command.define do name 'meh' end help_cmd = Cri::Command.new_basic_help cmd.add_command(help_cmd) help_cmd.run([]) end def test_run_with_chain_of_commands cmd = Cri::Command.define do name 'root' summary 'I am root!' subcommand do name 'foo' summary 'I am foo!' subcommand do name 'subsubby' summary 'I am subsubby!' end end end help_cmd = Cri::Command.new_basic_help cmd.add_command(help_cmd) # Simple call stdout, stderr = capture_io_while do help_cmd.run(['foo']) end assert_match(/I am foo!/m, stdout) assert_equal('', stderr) # Subcommand stdout, stderr = capture_io_while do help_cmd.run(%w[foo subsubby]) end assert_match(/I am subsubby!/m, stdout) assert_equal('', stderr) # Non-existing subcommand stdout, stderr = capture_io_while do assert_raises SystemExit do help_cmd.run(%w[foo mysterycmd]) end end assert_equal '', stdout assert_match(/foo: unknown command 'mysterycmd'/, stderr) end end end cri-2.15.11/test/test_basic_root.rb0000644000004100000410000000121614004133336017152 0ustar www-datawww-data# frozen_string_literal: true require 'helper' module Cri class BasicRootTestCase < Cri::TestCase def test_run_with_help cmd = Cri::Command.new_basic_root stdout, _stderr = capture_io_while do err = assert_raises SystemExit do cmd.run(%w[-h]) end assert_equal 0, err.status end assert stdout =~ /COMMANDS.*\n.*help.*show help/ end def test_run_with_help_no_exit cmd = Cri::Command.new_basic_root stdout, _stderr = capture_io_while do cmd.run(%w[-h], {}, hard_exit: false) end assert stdout =~ /COMMANDS.*\n.*help.*show help/ end end end cri-2.15.11/test/test_parser.rb0000644000004100000410000004260214004133336016326 0ustar www-datawww-data# frozen_string_literal: true require 'helper' module Cri class ParserTestCase < Cri::TestCase def test_parse_without_options input = %w[foo bar baz] opt_defns = [] parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({}, parser.options) assert_equal(%w[foo bar baz], parser.gen_argument_list.to_a) end def test_parse_with_invalid_option input = %w[foo -x] opt_defns = [] assert_raises(Cri::Parser::IllegalOptionError) do Cri::Parser.new(input, opt_defns, [], false).run end end def test_parse_with_unused_options input = %w[foo] opt_defns = [ { long: 'aaa', short: 'a', argument: :forbidden }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert(!parser.options[:aaa]) end def test_parse_with_long_valueless_option input = %w[foo --aaa bar] opt_defns = [ { long: 'aaa', short: 'a', argument: :forbidden }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert(parser.options[:aaa]) assert_equal(%w[foo bar], parser.gen_argument_list.to_a) end def test_parse_with_long_valueful_option input = %w[foo --aaa xxx bar] opt_defns = [ { long: 'aaa', short: 'a', argument: :required }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ aaa: 'xxx' }, parser.options) assert_equal(%w[foo bar], parser.gen_argument_list.to_a) end def test_parse_with_long_valueful_equalsign_option input = %w[foo --aaa=xxx bar] opt_defns = [ { long: 'aaa', short: 'a', argument: :required }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ aaa: 'xxx' }, parser.options) assert_equal(%w[foo bar], parser.gen_argument_list.to_a) end def test_parse_with_long_valueful_option_with_missing_value input = %w[foo --aaa] opt_defns = [ { long: 'aaa', short: 'a', argument: :required }, ].map { |hash| make_opt_defn(hash) } assert_raises(Cri::Parser::OptionRequiresAnArgumentError) do Cri::Parser.new(input, opt_defns, [], false).run end end def test_parse_with_two_long_valueful_options input = %w[foo --all --port 2] opt_defns = [ { long: 'all', short: 'a', argument: :required }, { long: 'port', short: 'p', argument: :required }, ].map { |hash| make_opt_defn(hash) } assert_raises(Cri::Parser::OptionRequiresAnArgumentError) do Cri::Parser.new(input, opt_defns, [], false).run end end def test_parse_with_long_valueless_option_with_optional_value input = %w[foo --aaa] opt_defns = [ { long: 'aaa', short: 'a', argument: :optional }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert(parser.options[:aaa]) assert_equal(['foo'], parser.gen_argument_list.to_a) end def test_parse_with_long_valueful_option_with_optional_value input = %w[foo --aaa xxx] opt_defns = [ { long: 'aaa', short: 'a', argument: :optional }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ aaa: 'xxx' }, parser.options) assert_equal(['foo'], parser.gen_argument_list.to_a) end def test_parse_with_long_valueless_option_with_optional_value_and_more_options input = %w[foo --aaa -b -c] opt_defns = [ { long: 'aaa', short: 'a', argument: :optional }, { long: 'bbb', short: 'b', argument: :forbidden }, { long: 'ccc', short: 'c', argument: :forbidden }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert(parser.options[:aaa]) assert(parser.options[:bbb]) assert(parser.options[:ccc]) assert_equal(['foo'], parser.gen_argument_list.to_a) end def test_parse_with_short_valueless_options input = %w[foo -a bar] opt_defns = [ { long: 'aaa', short: 'a', argument: :forbidden }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert(parser.options[:aaa]) assert_equal(%w[foo bar], parser.gen_argument_list.to_a) end def test_parse_with_short_valueful_option_with_missing_value input = %w[foo -a] opt_defns = [ { long: 'aaa', short: 'a', argument: :required }, ].map { |hash| make_opt_defn(hash) } assert_raises(Cri::Parser::OptionRequiresAnArgumentError) do Cri::Parser.new(input, opt_defns, [], false).run end end def test_parse_with_short_combined_valueless_options input = %w[foo -abc bar] opt_defns = [ { long: 'aaa', short: 'a', argument: :forbidden }, { long: 'bbb', short: 'b', argument: :forbidden }, { long: 'ccc', short: 'c', argument: :forbidden }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert(parser.options[:aaa]) assert(parser.options[:bbb]) assert(parser.options[:ccc]) assert_equal(%w[foo bar], parser.gen_argument_list.to_a) end def test_parse_with_short_combined_valueful_options_with_missing_value input = %w[foo -abc bar qux] opt_defns = [ { long: 'aaa', short: 'a', argument: :required }, { long: 'bbb', short: 'b', argument: :forbidden }, { long: 'ccc', short: 'c', argument: :forbidden }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal('bar', parser.options[:aaa]) assert(parser.options[:bbb]) assert(parser.options[:ccc]) assert_equal(%w[foo qux], parser.gen_argument_list.to_a) end def test_parse_with_two_short_valueful_options input = %w[foo -a -p 2] opt_defns = [ { long: 'all', short: 'a', argument: :required }, { long: 'port', short: 'p', argument: :required }, ].map { |hash| make_opt_defn(hash) } assert_raises(Cri::Parser::OptionRequiresAnArgumentError) do Cri::Parser.new(input, opt_defns, [], false).run end end def test_parse_with_short_valueless_option_with_optional_value input = %w[foo -a] opt_defns = [ { long: 'aaa', short: 'a', argument: :optional }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert(parser.options[:aaa]) assert_equal(['foo'], parser.gen_argument_list.to_a) end def test_parse_with_short_valueful_option_with_optional_value input = %w[foo -a xxx] opt_defns = [ { long: 'aaa', short: 'a', argument: :optional }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ aaa: 'xxx' }, parser.options) assert_equal(['foo'], parser.gen_argument_list.to_a) end def test_parse_with_short_valueless_option_with_optional_value_and_more_options input = %w[foo -a -b -c] opt_defns = [ { long: 'aaa', short: 'a', argument: :optional }, { long: 'bbb', short: 'b', argument: :forbidden }, { long: 'ccc', short: 'c', argument: :forbidden }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert(parser.options[:aaa]) assert(parser.options[:bbb]) assert(parser.options[:ccc]) assert_equal(['foo'], parser.gen_argument_list.to_a) end def test_parse_with_single_hyphen input = %w[foo - bar] opt_defns = [] parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({}, parser.options) assert_equal(['foo', '-', 'bar'], parser.gen_argument_list.to_a) end def test_parse_with_end_marker input = %w[foo bar -- -x --yyy -abc] opt_defns = [] parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({}, parser.options) assert_equal(['foo', 'bar', '-x', '--yyy', '-abc'], parser.gen_argument_list.to_a) end def test_parse_with_end_marker_between_option_key_and_value input = %w[foo --aaa -- zzz] opt_defns = [ { long: 'aaa', short: 'a', argument: :required }, ].map { |hash| make_opt_defn(hash) } assert_raises(Cri::Parser::OptionRequiresAnArgumentError) do Cri::Parser.new(input, opt_defns, [], false).run end end def test_parse_with_multiple_options input = %w[foo -o test -o test2 -v -v -v] opt_defns = [ { long: 'long', short: 'o', argument: :required, multiple: true }, { long: 'verbose', short: 'v', multiple: true }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal(%w[test test2], parser.options[:long]) assert_equal(3, parser.options[:verbose].size) end def test_parse_with_default_required_no_value input = %w[foo -a] opt_defns = [ { long: 'animal', short: 'a', argument: :required, default: 'donkey' }, ].map { |hash| make_opt_defn(hash) } assert_raises(Cri::Parser::OptionRequiresAnArgumentError) do Cri::Parser.new(input, opt_defns, [], false).run end end def test_parse_with_default_required_value input = %w[foo -a giraffe] opt_defns = [ { long: 'animal', short: 'a', argument: :required, default: 'donkey' }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ animal: 'giraffe' }, parser.options) assert_equal(['foo'], parser.gen_argument_list.to_a) end def test_parse_with_default_optional_no_value input = %w[foo -a] opt_defns = [ { long: 'animal', short: 'a', argument: :optional, default: 'donkey' }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ animal: 'donkey' }, parser.options) assert_equal(['foo'], parser.gen_argument_list.to_a) end def test_parse_with_default_optional_value input = %w[foo -a giraffe] opt_defns = [ { long: 'animal', short: 'a', argument: :optional, default: 'donkey' }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ animal: 'giraffe' }, parser.options) assert_equal(['foo'], parser.gen_argument_list.to_a) end def test_parse_with_default_optional_value_and_arg input = %w[foo -a gi raffe] opt_defns = [ { long: 'animal', short: 'a', argument: :optional, default: 'donkey' }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ animal: 'gi' }, parser.options) assert_equal(%w[foo raffe], parser.gen_argument_list.to_a) end def test_parse_with_combined_required_options input = %w[foo -abc xxx yyy zzz] opt_defns = [ { long: 'aaa', short: 'a', argument: :forbidden }, { long: 'bbb', short: 'b', argument: :required }, { long: 'ccc', short: 'c', argument: :required }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ aaa: true, bbb: 'xxx', ccc: 'yyy' }, parser.options) assert_equal(%w[foo zzz], parser.gen_argument_list.to_a) end def test_parse_with_combined_optional_options input = %w[foo -abc xxx yyy zzz] opt_defns = [ { long: 'aaa', short: 'a', argument: :forbidden }, { long: 'bbb', short: 'b', argument: :optional }, { long: 'ccc', short: 'c', argument: :required }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ aaa: true, bbb: 'xxx', ccc: 'yyy' }, parser.options) assert_equal(%w[foo zzz], parser.gen_argument_list.to_a) end def test_parse_with_combined_optional_options_with_missing_value input = %w[foo -abc xxx] opt_defns = [ { long: 'aaa', short: 'a', argument: :forbidden }, { long: 'bbb', short: 'b', argument: :required }, { long: 'ccc', short: 'c', argument: :optional, default: 'c default' }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ aaa: true, bbb: 'xxx', ccc: 'c default' }, parser.options) assert_equal(%w[foo], parser.gen_argument_list.to_a) end def test_parse_with_transform_proc input = %w[--port 123] opt_defns = [ { long: 'port', short: 'p', argument: :required, transform: ->(x) { Integer(x) } }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ port: 123 }, parser.options) assert_equal([], parser.gen_argument_list.to_a) end def test_parse_with_transform_method input = %w[--port 123] opt_defns = [ { long: 'port', short: 'p', argument: :required, transform: method(:Integer) }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ port: 123 }, parser.options) assert_equal([], parser.gen_argument_list.to_a) end def test_parse_with_transform_object port = Class.new do def call(str) Integer(str) end end.new input = %w[--port 123] opt_defns = [ { long: 'port', short: 'p', argument: :required, transform: port }, ].map { |hash| make_opt_defn(hash) } parser = Cri::Parser.new(input, opt_defns, [], false).run assert_equal({ port: 123 }, parser.options) assert_equal([], parser.gen_argument_list.to_a) end def test_parse_with_transform_exception input = %w[--port one_hundred_and_twenty_three] opt_defns = [ { long: 'port', short: 'p', argument: :required, transform: method(:Integer) }, ].map { |hash| make_opt_defn(hash) } exception = assert_raises(Cri::Parser::IllegalOptionValueError) do Cri::Parser.new(input, opt_defns, [], false).run end assert_equal('invalid value "one_hundred_and_twenty_three" for --port option', exception.message) end def test_parse_with_param_defns input = %w[localhost] param_defns = [ { name: 'host', transform: nil }, ].map { |hash| Cri::ParamDefinition.new(**hash) } parser = Cri::Parser.new(input, [], param_defns, false).run assert_equal({}, parser.options) assert_equal('localhost', parser.gen_argument_list[0]) assert_equal('localhost', parser.gen_argument_list[:host]) end def test_parse_with_param_defns_too_few_args input = [] param_defns = [ { name: 'host', transform: nil }, ].map { |hash| Cri::ParamDefinition.new(**hash) } parser = Cri::Parser.new(input, [], param_defns, false).run exception = assert_raises(Cri::ArgumentList::ArgumentCountMismatchError) do parser.gen_argument_list end assert_equal('incorrect number of arguments given: expected 1, but got 0', exception.message) end def test_parse_with_param_defns_too_many_args input = %w[localhost oink] param_defns = [ { name: 'host', transform: nil }, ].map { |hash| Cri::ParamDefinition.new(**hash) } parser = Cri::Parser.new(input, [], param_defns, false).run exception = assert_raises(Cri::ArgumentList::ArgumentCountMismatchError) do parser.gen_argument_list end assert_equal('incorrect number of arguments given: expected 1, but got 2', exception.message) end def test_parse_with_param_defns_invalid_key input = %w[localhost] param_defns = [ { name: 'host', transform: nil }, ].map { |hash| Cri::ParamDefinition.new(**hash) } parser = Cri::Parser.new(input, [], param_defns, false).run exception = assert_raises(ArgumentError) do parser.gen_argument_list['oink'] end assert_equal('argument lists can be indexed using a Symbol or an Integer, but not a String', exception.message) end def test_parse_with_param_defns_two_params input = %w[localhost example.com] param_defns = [ { name: 'source', transform: nil }, { name: 'target', transform: nil }, ].map { |hash| Cri::ParamDefinition.new(**hash) } parser = Cri::Parser.new(input, [], param_defns, false).run assert_equal({}, parser.options) assert_equal('localhost', parser.gen_argument_list[0]) assert_equal('localhost', parser.gen_argument_list[:source]) assert_equal('example.com', parser.gen_argument_list[1]) assert_equal('example.com', parser.gen_argument_list[:target]) end def make_opt_defn(hash) Cri::OptionDefinition.new( short: hash.fetch(:short, nil), long: hash.fetch(:long, nil), desc: hash.fetch(:desc, nil), argument: hash.fetch(:argument, nil), multiple: hash.fetch(:multiple, nil), block: hash.fetch(:block, nil), hidden: hash.fetch(:hidden, nil), default: hash.fetch(:default, nil), transform: hash.fetch(:transform, nil), ) end end end cri-2.15.11/test/test_command_dsl.rb0000644000004100000410000002522714004133336017316 0ustar www-datawww-data# frozen_string_literal: true require 'helper' module Cri class CommandDSLTestCase < Cri::TestCase def test_create_command # Define dsl = Cri::CommandDSL.new dsl.instance_eval do name 'moo' usage 'dunno whatever' summary 'does stuff' description 'This command does a lot of stuff.' option :a, :aaa, 'opt a', argument: :optional, multiple: true required :b, :bbb, 'opt b' optional :c, :ccc, 'opt c' flag :d, :ddd, 'opt d' forbidden :e, :eee, 'opt e' flag :f, :fff, 'opt f', hidden: true run do |_opts, _args| $did_it_work = :probably end end command = dsl.command # Run $did_it_work = :sadly_not command.run(%w[-a x -b y -c -d -e]) assert_equal :probably, $did_it_work # Check assert_equal 'moo', command.name assert_equal 'dunno whatever', command.usage assert_equal 'does stuff', command.summary assert_equal 'This command does a lot of stuff.', command.description # Check options expected_option_definitions = Set.new( [ { short: 'a', long: 'aaa', desc: 'opt a', argument: :optional, multiple: true, hidden: false, block: nil, default: nil, transform: nil }, { short: 'b', long: 'bbb', desc: 'opt b', argument: :required, multiple: false, hidden: false, block: nil, default: nil, transform: nil }, { short: 'c', long: 'ccc', desc: 'opt c', argument: :optional, multiple: false, hidden: false, block: nil, default: nil, transform: nil }, { short: 'd', long: 'ddd', desc: 'opt d', argument: :forbidden, multiple: false, hidden: false, block: nil, default: false, transform: nil }, { short: 'e', long: 'eee', desc: 'opt e', argument: :forbidden, multiple: false, hidden: false, block: nil, default: false, transform: nil }, { short: 'f', long: 'fff', desc: 'opt f', argument: :forbidden, multiple: false, hidden: true, block: nil, default: false, transform: nil }, ], ) actual_option_definitions = Set.new(command.option_definitions.map(&:to_h)) assert_equal expected_option_definitions, actual_option_definitions end def test_optional_options # Define dsl = Cri::CommandDSL.new dsl.instance_eval do name 'moo' usage 'dunno whatever' summary 'does stuff' description 'This command does a lot of stuff.' flag :s, nil, 'short' flag nil, :long, 'long' run do |_opts, _args| $did_it_work = :probably end end command = dsl.command # Run $did_it_work = :sadly_not command.run(%w[-s --long]) assert_equal :probably, $did_it_work # Check options expected_option_definitions = Set.new( [ { short: 's', long: nil, desc: 'short', argument: :forbidden, multiple: false, hidden: false, block: nil, default: false, transform: nil }, { short: nil, long: 'long', desc: 'long', argument: :forbidden, multiple: false, hidden: false, block: nil, default: false, transform: nil }, ], ) actual_option_definitions = Set.new(command.option_definitions.map(&:to_h)) assert_equal expected_option_definitions, actual_option_definitions end def test_multiple # Define dsl = Cri::CommandDSL.new dsl.instance_eval do flag :f, :flag, 'flag', multiple: true required :r, :required, 'req', multiple: true optional :o, :optional, 'opt', multiple: true run { |_opts, _args| } end command = dsl.command # Check options expected_option_definitions = Set.new( [ { short: 'f', long: 'flag', desc: 'flag', argument: :forbidden, multiple: true, hidden: false, block: nil, default: false, transform: nil }, { short: 'r', long: 'required', desc: 'req', argument: :required, multiple: true, hidden: false, block: nil, default: nil, transform: nil }, { short: 'o', long: 'optional', desc: 'opt', argument: :optional, multiple: true, hidden: false, block: nil, default: nil, transform: nil }, ], ) actual_option_definitions = Set.new(command.option_definitions.map(&:to_h)) assert_equal expected_option_definitions, actual_option_definitions end def test_hidden # Define dsl = Cri::CommandDSL.new dsl.instance_eval do flag :f, :flag, 'flag', hidden: true required :r, :required, 'req', hidden: true optional :o, :optional, 'opt', hidden: true run { |_opts, _args| } end command = dsl.command # Check options expected_option_definitions = Set.new( [ { short: 'f', long: 'flag', desc: 'flag', argument: :forbidden, multiple: false, hidden: true, block: nil, default: false, transform: nil }, { short: 'r', long: 'required', desc: 'req', argument: :required, multiple: false, hidden: true, block: nil, default: nil, transform: nil }, { short: 'o', long: 'optional', desc: 'opt', argument: :optional, multiple: false, hidden: true, block: nil, default: nil, transform: nil }, ], ) actual_option_definitions = Set.new(command.option_definitions.map(&:to_h)) assert_equal expected_option_definitions, actual_option_definitions end def test_raises_on_unrecognized_option # Define dsl = Cri::CommandDSL.new assert_raises ArgumentError do dsl.option :s, :long, 'desc', unrecognized: true end end def test_required_short_and_long # Define dsl = Cri::CommandDSL.new assert_raises ArgumentError do dsl.instance_eval do option nil, nil, 'meh' end end assert_raises ArgumentError do dsl.instance_eval do flag nil, nil, 'meh' end end assert_raises ArgumentError do dsl.instance_eval do required nil, nil, 'meh' end end assert_raises ArgumentError do dsl.instance_eval do optional nil, nil, 'meh' end end end def test_default_value_with_equiredness_is_required dsl = Cri::CommandDSL.new dsl.instance_eval do required 'a', 'animal', 'Specify animal', default: 'giraffe' end end def test_default_value_errors_when_requiredness_is_forbidden dsl = Cri::CommandDSL.new err = assert_raises ArgumentError do dsl.instance_eval do flag 'a', 'animal', 'Allow animal', default: 'giraffe' end end assert_equal('a default value cannot be specified for flag options', err.message) end def test_subcommand # Define dsl = Cri::CommandDSL.new dsl.instance_eval do name 'super' subcommand do |c| c.name 'sub' end end command = dsl.command # Check assert_equal 'super', command.name assert_equal 1, command.subcommands.size assert_equal 'sub', command.subcommands.to_a[0].name end def test_aliases # Define dsl = Cri::CommandDSL.new dsl.instance_eval do aliases :moo, :aah end command = dsl.command # Check assert_equal %w[aah moo], command.aliases.sort end def test_run_arity dsl = Cri::CommandDSL.new assert_raises ArgumentError do dsl.instance_eval do run do |_a, _b, _c, _d, _e| end end end end def test_runner # Define dsl = Cri::CommandDSL.new dsl.instance_eval(<<-CMD, __FILE__, __LINE__ + 1) class Cri::CommandDSLTestCaseCommandRunner < Cri::CommandRunner def run $did_it_work = arguments[0] end end runner Cri::CommandDSLTestCaseCommandRunner CMD command = dsl.command # Check $did_it_work = false command.run(%w[certainly]) assert_equal 'certainly', $did_it_work end def test_params # Define dsl = Cri::CommandDSL.new dsl.instance_eval do name 'moo' usage 'dunno whatever' summary 'does stuff' description 'This command does a lot of stuff.' param :foo param :bar param :qux run do |_opts, args| $args_num = { foo: args[0], bar: args[1], qux: args[2] } $args_sym = { foo: args[:foo], bar: args[:bar], qux: args[:qux] } end end command = dsl.command # Run $args_num = '???' $args_sym = '???' command.run(%w[a b c]) assert_equal({ foo: 'a', bar: 'b', qux: 'c' }, $args_num) assert_equal({ foo: 'a', bar: 'b', qux: 'c' }, $args_sym) end def test_params_transform # Define dsl = Cri::CommandDSL.new dsl.instance_eval do name 'moo' usage 'dunno whatever' summary 'does stuff' description 'This command does a lot of stuff.' param :foo, transform: ->(a) { a.upcase } run do |_opts, args| $args_num = { foo: args[0] } $args_sym = { foo: args[:foo] } end end command = dsl.command # Run $args_num = '???' $args_sym = '???' command.run(%w[abc]) assert_equal({ foo: 'ABC' }, $args_num) assert_equal({ foo: 'ABC' }, $args_sym) end def test_no_params_with_one_param_specified dsl = Cri::CommandDSL.new err = assert_raises Cri::CommandDSL::AlreadySpecifiedWithParams do dsl.instance_eval do name 'moo' usage 'dunno whatever' summary 'does stuff' description 'This command does a lot of stuff.' param :oink no_params end end assert_equal('Attempted to declare the command "moo" as taking no parameters, but some parameters are already declared for this command. Suggestion: remove the #no_params call.', err.message) end def test_one_param_with_no_params_specified dsl = Cri::CommandDSL.new err = assert_raises Cri::CommandDSL::AlreadySpecifiedAsNoParams do dsl.instance_eval do name 'moo' usage 'dunno whatever' summary 'does stuff' description 'This command does a lot of stuff.' no_params param :oink end end assert_equal('Attempted to specify a parameter :oink to the command "moo", which is already specified as taking no params. Suggestion: remove the #no_params call.', err.message) end end end cri-2.15.11/test/test_command_runner.rb0000644000004100000410000000142514004133336020037 0ustar www-datawww-data# frozen_string_literal: true require 'helper' module Cri class CommandRunnerTestCase < Cri::TestCase def setup super @options = { vehicle: 'pig' } @arguments = %w[baby_monkey] @command = Cri::Command.new end def test_initialize runner = Cri::CommandRunner.new(@options, @arguments, @command) assert_equal @options, runner.options assert_equal @arguments, runner.arguments assert_equal @command, runner.command end def test_call_run assert_raises(Cri::NotImplementedError) do Cri::CommandRunner.new(@options, @arguments, @command).call end assert_raises(Cri::NotImplementedError) do Cri::CommandRunner.new(@options, @arguments, @command).run end end end end cri-2.15.11/test/test_argument_list.rb0000644000004100000410000000640314004133336017706 0ustar www-datawww-data# frozen_string_literal: true require 'helper' module Cri class ArgumentListTestCase < Cri::TestCase def test_empty args = Cri::ArgumentList.new([], false, []) assert_equal([], args.to_a) assert(args.empty?) assert_equal(0, args.size) assert_equal(nil, args[0]) assert_equal(nil, args[:abc]) end def test_no_param_defns args = Cri::ArgumentList.new(%w[a b c], false, []) assert_equal(%w[a b c], args.to_a) refute(args.empty?) assert_equal(3, args.size) assert_equal('a', args[0]) assert_equal('b', args[1]) assert_equal('c', args[2]) assert_equal(nil, args[3]) assert_equal(nil, args[:abc]) end def test_enum args = Cri::ArgumentList.new(%w[a b c], false, []) assert_equal(%w[A B C], args.map(&:upcase)) end def test_enum_without_block args = Cri::ArgumentList.new(%w[a b c], false, []) assert_equal(%w[A B C], args.each.map(&:upcase)) end def test_no_method_error args = Cri::ArgumentList.new(%w[a b c], false, []) refute args.respond_to?(:oink) assert_raises(NoMethodError, 'x') do args.oink end end def test_dash_dash args = Cri::ArgumentList.new(%w[a -- b -- c], false, []) assert_equal(%w[a b c], args.to_a) end def test_one_param_defn_matched param_defns = [Cri::ParamDefinition.new(name: 'filename', transform: nil)] args = Cri::ArgumentList.new(%w[notbad.jpg], false, param_defns) assert_equal(['notbad.jpg'], args.to_a) assert_equal(1, args.size) assert_equal('notbad.jpg', args[0]) assert_equal('notbad.jpg', args[:filename]) end def test_one_param_defn_too_many param_defns = [Cri::ParamDefinition.new(name: 'filename', transform: nil)] exception = assert_raises(Cri::ArgumentList::ArgumentCountMismatchError) do Cri::ArgumentList.new(%w[notbad.jpg verybad.jpg], false, param_defns) end assert_equal('incorrect number of arguments given: expected 1, but got 2', exception.message) end def test_one_param_defn_too_few param_defns = [Cri::ParamDefinition.new(name: 'filename', transform: nil)] exception = assert_raises(Cri::ArgumentList::ArgumentCountMismatchError) do Cri::ArgumentList.new(%w[], false, param_defns) end assert_equal('incorrect number of arguments given: expected 1, but got 0', exception.message) end def test_zero_params_zero_args args = Cri::ArgumentList.new(%w[], false, []) assert_equal([], args.to_a) assert args.empty? assert_equal(0, args.size) end def test_zero_params_one_arg exception = assert_raises(Cri::ArgumentList::ArgumentCountMismatchError) do Cri::ArgumentList.new(%w[a], true, []) end assert_equal('incorrect number of arguments given: expected 0, but got 1', exception.message) end def test_transform param_defns = [Cri::ParamDefinition.new(name: 'filename', transform: ->(a) { a.upcase })] args = Cri::ArgumentList.new(%w[notbad.jpg], false, param_defns) assert_equal(['NOTBAD.JPG'], args.to_a) assert_equal(1, args.size) assert_equal('NOTBAD.JPG', args[0]) assert_equal('NOTBAD.JPG', args[:filename]) end end end cri-2.15.11/README.md0000644000004100000410000004176214004133336013754 0ustar www-datawww-data# Cri [![Gem](http://img.shields.io/gem/v/cri.svg)](http://rubygems.org/gems/cri) [![Gem downloads](https://img.shields.io/gem/dt/cri.svg)](http://rubygems.org/gems/cri) [![Travis](http://img.shields.io/travis/ddfreyne/cri.svg)](https://travis-ci.org/ddfreyne/cri) [![Coveralls](http://img.shields.io/coveralls/ddfreyne/cri.svg)](https://coveralls.io/r/ddfreyne/cri) [![Codeclimate](http://img.shields.io/codeclimate/github/ddfreyne/cri.svg)](https://codeclimate.com/github/ddfreyne/cri) [![Inch](http://inch-ci.org/github/ddfreyne/cri.svg)](http://inch-ci.org/github/ddfreyne/cri/) Cri is a library for building easy-to-use command-line tools with support for nested commands. ## Requirements Cri requires Ruby 2.5 or newer (including Ruby 3.x). ## Compatibility policy Cri is guaranteed to be supported on any [officially supported Ruby version](https://www.ruby-lang.org/en/downloads/branches/), as well as the version of Ruby that comes by default on - the last two [Ubuntu LTS releases](https://wiki.ubuntu.com/Releases) - the last two major [macOS releases](https://en.wikipedia.org/wiki/MacOS_version_history) ## Usage The central concept in Cri is the _command_, which has option definitions as well as code for actually executing itself. In Cri, the command-line tool itself is a command as well. Here’s a sample command definition: ```ruby command = Cri::Command.define do name 'dostuff' usage 'dostuff [options]' aliases :ds, :stuff summary 'does stuff' description 'This command does a lot of stuff. I really mean a lot.' flag :h, :help, 'show help for this command' do |value, cmd| puts cmd.help exit 0 end flag nil, :more, 'do even more stuff' option :s, :stuff, 'specify stuff to do', argument: :required run do |opts, args, cmd| stuff = opts.fetch(:stuff, 'generic stuff') puts "Doing #{stuff}!" if opts[:more] puts 'Doing it even more!' end end end ``` To run this command, invoke the `#run` method with the raw arguments. For example, for a root command (the command-line tool itself), the command could be called like this: ```ruby command.run(ARGV) ``` Each command has automatically generated help. This help can be printed using `Cri::Command#help`; something like this will be shown: ``` usage: dostuff [options] does stuff This command does a lot of stuff. I really mean a lot. options: -h --help show help for this command --more do even more stuff -s --stuff specify stuff to do ``` ### General command metadata Let’s disect the command definition and start with the first five lines: ```ruby name 'dostuff' usage 'dostuff [options]' aliases :ds, :stuff summary 'does stuff' description 'This command does a lot of stuff. I really mean a lot.' ``` These lines of the command definition specify the name of the command (or the command-line tool, if the command is the root command), the usage, a list of aliases that can be used to call this command, a one-line summary and a (long) description. The usage should not include a “usage:” prefix nor the name of the supercommand, because the latter will be automatically prepended. Aliases don’t make sense for root commands, but for subcommands they do. ### Command-line options The next few lines contain the command’s option definitions: ```ruby flag :h, :help, 'show help for this command' do |value, cmd| puts cmd.help exit 0 end flag nil, :more, 'do even more stuff' option :s, :stuff, 'specify stuff to do', argument: :required ``` The most generic way of definition an option is using either `#option` or `#opt`. It takes the following arguments: 1. a short option name 2. a long option name 3. a description 4. optional extra parameters - `argument:` (default: `:forbidden`) - `transform:` - `default:` - `multiple:` (default: `false`) 5. optionally, a block In more detail: - The short option name is a symbol containing one character, to be used in single-dash options, e.g. `:f` (corresponds to `-f`). The long option name is a symbol containing a string, to be used in double-dash options, e.g. `:force` (corresponds to `--force`). Either the short or the long option name can be nil, but not both. - The description is a short, one-line text that shows up in the command’s help. For example, the `-v`/`--version` option might have the description `show version information and quit`. - The extra parameters, `argument:`, `multiple:`, `default:`, and `transform:`, are described in the sections below. - The block, if given, will be executed when the option is found. The arguments to the block are the option value (`true` in case the option does not have an argument) and the command. There are several convenience methods that are alternatives to `#option`/`#opt`: - `#flag` sets `argument:` to `:forbidden` - (**deprecated**) `#required` sets `argument:` to `:required` -- deprecated because `#required` suggests that the option is required, wich is incorrect; the _argument_ is required.) - (**deprecated**) `#optional` sets `argument:` to `:optional` -- deprecated because `#optional` looks too similar to `#option`. #### Forbidden, required, and optional arguments (`argument:`) The `:argument` parameter can be set to `:forbidden`, `:required`, or `:optional`. - `:forbidden` means that when the option is present, the value will be set to `true`, and `false` otherwise. For example: ```ruby option :f, :force, 'push with force', argument: :forbidden run do |opts, args, cmd| puts "Force? #{opts[:force]}" end ``` ```sh % ./push mypackage.zip Force? false % ./push --force mypackage.zip Force? true ``` `:argument` is set to `:forbidden` by default. - `:required` means that the option must be followed by an argument, which will then be treated as the value for the option. It does not mean that the option itself is required. For example: ```ruby option :o, :output, 'specify output file', argument: :required option :f, :fast, 'fetch faster', argument: :forbidden run do |opts, args, cmd| puts "Output file: #{opts[:output]}" end ``` ```sh % ./fetch http://example.com/source.zip Output file: nil % ./fetch --output example.zip http://example.com/source.zip Output file: example.zip % ./fetch http://example.com/source.zip --output fetch: option requires an argument -- output % ./fetch --output --fast http://example.com/source.zip fetch: option requires an argument -- output ``` - `:optional` means that the option can be followed by an argument. If it is, then the argument is treated as the value for the option; if it isn’t, the value for the option will be `true`. For example: ```ruby option :o, :output, 'specify output file', argument: :optional option :f, :fast, 'fetch faster', argument: :forbidden run do |opts, args, cmd| puts "Output file: #{opts[:output]}" end ``` ```sh % ./fetch http://example.com/source.zip Output file: nil % ./fetch --output example.zip http://example.com/source.zip Output file: example.zip % ./fetch http://example.com/source.zip --output Output file: true % ./fetch --output --fast http://example.com/source.zip Output file: true ``` #### Transforming options (`transform:`) The `:transform` parameter specifies how the value should be transformed. It takes any object that responds to `#call`: ```ruby option :p, :port, 'set port', argument: :required, transform: -> (x) { Integer(x) } ``` The following example uses `#Integer` to transform a string into an integer: ```ruby option :p, :port, 'set port', argument: :required, transform: method(:Integer) ``` The following example uses a custom object to perform transformation, as well as validation: ```ruby class PortTransformer def call(str) raise ArgumentError unless str.is_a?(String) Integer(str).tap do |int| raise unless (0x0001..0xffff).include?(int) end end end option :p, :port, 'set port', argument: :required, transform: PortTransformer.new ``` Default values are not transformed: ```ruby option :p, :port, 'set port', argument: :required, default: 8080, transform: PortTransformer.new ``` #### Options with default values (`default:`) The `:default` parameter sets the option value that will be used if the option is passed without an argument or isn't passed at all: ```ruby option :a, :animal, 'add animal', default: 'giraffe', argument: :optional ``` In the example above, the value for the `--animal` option will be the string `"giraffe"`, unless otherwise specified: ``` OPTIONS -a --animal[=] add animal (default: giraffe) ``` If the option is not given on the command line, the `options` hash will not have key for this option, but will still have a default value: ```ruby option :a, :animal, 'add animal', default: 'giraffe', argument: :required run do |opts, args, cmd| puts "Animal = #{opts[:animal]}" puts "Option given? #{opts.key?(:animal)}" end ``` ```sh % ./run --animal=donkey Animal = donkey Option given? true % ./run --animal=giraffe Animal = giraffe Option given? true % ./run Animal = giraffe Option given? false ``` This can be useful to distinguish between an explicitly-passed-in value and a default value. In the example above, the `animal` option is set to `giraffe` in the second and third cases, but it is possible to detect whether the value is a default or not. #### Multivalued options (`multiple:`) The `:multiple` parameter allows an option to be specified more than once on the command line. When set to `true`, multiple option valus are accepted, and the option values will be stored in an array. For example, to parse the command line options string `-o foo.txt -o bar.txt` into an array, so that `options[:output]` contains `[ 'foo.txt', 'bar.txt' ]`, you can use an option definition like this: ```ruby option :o, :output, 'specify output paths', argument: :required, multiple: true ``` This can also be used for flags (options without arguments). In this case, the length of the options array is relevant. For example, you can allow setting the verbosity level using `-v -v -v`. The value of `options[:verbose].size` would then be the verbosity level (three in this example). The option definition would then look like this: ```ruby flag :v, :verbose, 'be verbose (use up to three times)', multiple: true ``` #### Skipping option parsing If you want to skip option parsing for your command or subcommand, you can add the `skip_option_parsing` method to your command definition and everything on your command line after the command name will be passed to your command as arguments. ```ruby command = Cri::Command.define do name 'dostuff' usage 'dostuff [args]' aliases :ds, :stuff summary 'does stuff' description 'This command does a lot of stuff, but not option parsing.' skip_option_parsing run do |opts, args, cmd| puts args.inspect end end ``` When executing this command with `dostuff --some=value -f yes`, the `opts` hash that is passed to your `run` block will be empty and the `args` array will be `["--some=value", "-f", "yes"]`. ### Argument parsing Cri supports parsing arguments, as well as parsing options. To define the parameters of a command, use `#param`, which takes a symbol containing the name of the parameter. For example: ```ruby command = Cri::Command.define do name 'publish' usage 'publish filename' summary 'publishes the given file' description 'This command does a lot of stuff, but not option parsing.' flag :q, :quick, 'publish quicker' param :filename run do |opts, args, cmd| puts "Publishing #{args[:filename]}…" end end ``` The command in this example has one parameter named `filename`. This means that the command takes a single argument, named `filename`. As with options, parameter definitions take `transform:`, which can be used for transforming and validating arguments: ```ruby param :port, transform: method(:Integer) ``` (_Why the distinction between argument and parameter?_ A parameter is a name, e.g. `filename`, while an argument is a value for a parameter, e.g. `kitten.jpg`.) ### Allowing arbitrary arguments If no parameters are specified, Cri performs no argument parsing or validation; any number of arguments is allowed. ```ruby command = Cri::Command.define do name 'publish' usage 'publish [filename...]' summary 'publishes the given file(s)' description 'This command does a lot of stuff, but not option parsing.' flag :q, :quick, 'publish quicker' run do |opts, args, cmd| args.each do |arg| puts "Publishing #{arg}…" end end end ``` ```bash % my-tool publish foo.zip bar.zip Publishing foo.zip… Publishing bar.zip… % ``` ### Forbidding any arguments To explicitly specify that a command has no parameters, use `#no_params`: ```ruby name 'reset' usage 'reset' summary 'resets the site' description '…' no_params run do |opts, args, cmd| puts "Resetting…" end ``` ``` % my-tool reset x reset: incorrect number of arguments given: expected 0, but got 1 % my-tool reset Resetting… % ``` A future version of Cri will likely make `#no_params` the default behavior. ### The run block The last part of the command defines the execution itself: ```ruby run do |opts, args, cmd| stuff = opts.fetch(:stuff, 'generic stuff') puts "Doing #{stuff}!" if opts[:more] puts 'Doing it even more!' end end ``` The +Cri::CommandDSL#run+ method takes a block with the actual code to execute. This block takes three arguments: the options, any arguments passed to the command, and the command itself. ### The command runner Instead of defining a run block, it is possible to declare a class, the _command runner_ class that will perform the actual execution of the command. This makes it easier to break up large run blocks into manageable pieces. ```ruby name 'push' option :f, :force, 'force' param :filename class MyRunner < Cri::CommandRunner def run puts "Pushing #{arguments[:filename]}…" puts "… with force!" if options[:force] end end runner MyRunner ``` To create a command runner, subclass `Cri::CommandRunner`, and define a `#run` method with no params. Inside the `#run` block, you can access `options` and `arguments`. Lastly, to connect the command to the command runner, call `#runner` with the class of the command runner. Here is an example interaction with the example command, defined above: ``` % push push: incorrect number of arguments given: expected 1, but got 0 % push a Pushing a… % push -f push: incorrect number of arguments given: expected 1, but got 0 % push -f a Pushing a… … with force! ``` ### Subcommands Commands can have subcommands. For example, the `git` command-line tool would be represented by a command that has subcommands named `commit`, `add`, and so on. Commands with subcommands do not use a run block; execution will always be dispatched to a subcommand (or none, if no subcommand is found). To add a command as a subcommand to another command, use the `Cri::Command#add_command` method, like this: ```ruby root_cmd.add_command(cmd_add) root_cmd.add_command(cmd_commit) root_cmd.add_command(cmd_init) ``` You can also define a subcommand on the fly without creating a class first using `Cri::Command#define_command` (name can be skipped if you set it inside the block instead): ```ruby root_cmd.define_command('add') do # option ... run do |opts, args, cmd| # ... end end ``` You can specify a default subcommand. This subcommand will be executed when the command has subcommands, and no subcommands are otherwise explicitly specified: ```ruby default_subcommand 'compile' ``` ### Loading commands from separate files You can use `Cri::Command.load_file` to load a command from a file. For example, given the file _commands/check.rb_ with the following contents: ```ruby name 'check' usage 'check' summary 'runs all checks' description '…' run do |opts, args, cmd| puts "Running checks…" end ``` To load this command: ```ruby Cri::Command.load_file('commands/check.rb') ``` `Cri::Command.load_file` expects the file to be in UTF-8. You can also use it to load subcommands: ```ruby root_cmd = Cri::Command.load_file('commands/nanoc.rb') root_cmd.add_command(Cri::Command.load_file('commands/comile.rb')) root_cmd.add_command(Cri::Command.load_file('commands/view.rb')) root_cmd.add_command(Cri::Command.load_file('commands/check.rb')) ``` #### Automatically inferring command names Pass `infer_name: true` to `Cri::Command.load_file` to use the file basename as the name of the command. For example, given a file _commands/check.rb_ with the following contents: ```ruby usage 'check' summary 'runs all checks' description '…' run do |opts, args, cmd| puts "Running checks…" end ``` To load this command and infer the name: ```ruby cmd = Cri::Command.load_file('commands/check.rb', infer_name: true) ``` `cmd.name` will be `check`, derived from the filename. ## Contributors - Bart Mesuere - Ken Coar - Tim Sharpe - Toon Willems Thanks for Lee “injekt” Jarvis for [Slop](https://github.com/injekt/slop), which has inspired the design of Cri 2.0. cri-2.15.11/CODE_OF_CONDUCT.md0000644000004100000410000000626014004133336015266 0ustar www-datawww-data# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at denis+coc@denis.ws. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org cri-2.15.11/LICENSE0000644000004100000410000000206314004133336013471 0ustar www-datawww-dataCopyright (c) 2015 Denis Defreyne and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. cri-2.15.11/cri.gemspec0000644000004100000410000000133214004133336014604 0ustar www-datawww-data# frozen_string_literal: true require_relative 'lib/cri/version' Gem::Specification.new do |s| s.name = 'cri' s.version = Cri::VERSION s.homepage = 'https://github.com/ddfreyne/cri' s.summary = 'a library for building easy-to-use command-line tools' s.description = 'Cri allows building easy-to-use command-line interfaces with support for subcommands.' s.license = 'MIT' s.author = 'Denis Defreyne' s.email = 'denis.defreyne@stoneship.org' s.files = Dir['[A-Z]*'] + Dir['{lib,test}/**/*'] + ['cri.gemspec'] s.require_paths = ['lib'] s.required_ruby_version = '>= 2.5' s.rdoc_options = ['--main', 'README.md'] s.extra_rdoc_files = ['LICENSE', 'README.md', 'NEWS.md'] end cri-2.15.11/Rakefile0000644000004100000410000000110314004133336014123 0ustar www-datawww-data# frozen_string_literal: true require 'rake/testtask' require 'rubocop/rake_task' require 'yard' YARD::Rake::YardocTask.new(:doc) do |yard| yard.files = Dir['lib/**/*.rb'] yard.options = [ '--markup', 'markdown', '--readme', 'README.md', '--files', 'NEWS.md,LICENSE', '--output-dir', 'doc/yardoc' ] end Rake::TestTask.new(:test_unit) do |t| t.test_files = Dir['test/**/*_spec.rb'] + Dir['test/**/test_*.rb'] t.libs << 'test' end RuboCop::RakeTask.new(:test_style) task test: %i[test_unit test_style] task default: :test cri-2.15.11/lib/0000755000004100000410000000000014004133336013231 5ustar www-datawww-datacri-2.15.11/lib/cri/0000755000004100000410000000000014004133336014006 5ustar www-datawww-datacri-2.15.11/lib/cri/help_renderer.rb0000644000004100000410000001413514004133336017155 0ustar www-datawww-data# frozen_string_literal: true module Cri # The {HelpRenderer} class is responsible for generating a string containing # the help for a given command, intended to be printed on the command line. class HelpRenderer # The line width of the help output LINE_WIDTH = 78 # The indentation of descriptions DESC_INDENT = 4 # The spacing between an option name and option description OPT_DESC_SPACING = 6 # Creates a new help renderer for the given command. # # @param [Cri::Command] cmd The command to generate the help for # # @option params [Boolean] :verbose true if the help output should be # verbose, false otherwise. def initialize(cmd, **params) @cmd = cmd @is_verbose = params.fetch(:verbose, false) @io = params.fetch(:io, $stdout) end # @return [String] The help text for this command def render text = +'' append_summary(text) append_usage(text) append_description(text) append_subcommands(text) append_options(text) text end private def fmt @fmt ||= Cri::StringFormatter.new end def append_summary(text) return if @cmd.name.nil? text << fmt.format_as_title('name', @io) << "\n" text << ' ' << fmt.format_as_command(@cmd.name, @io) if @cmd.summary text << ' - ' << @cmd.summary end text << "\n" unless @cmd.aliases.empty? text << ' aliases: ' << @cmd.aliases.map { |a| fmt.format_as_command(a, @io) }.join(' ') << "\n" end end def append_usage(text) return if @cmd.usage.nil? path = [@cmd.supercommand] path.unshift(path[0].supercommand) until path[0].nil? formatted_usage = @cmd.usage.gsub(/^([^\s]+)/) { |m| fmt.format_as_command(m, @io) } full_usage = path[1..-1].map { |c| fmt.format_as_command(c.name, @io) + ' ' }.join + formatted_usage text << "\n" text << fmt.format_as_title('usage', @io) << "\n" text << fmt.wrap_and_indent(full_usage, LINE_WIDTH, DESC_INDENT) << "\n" end def append_description(text) return if @cmd.description.nil? text << "\n" text << fmt.format_as_title('description', @io) << "\n" text << fmt.wrap_and_indent(@cmd.description, LINE_WIDTH, DESC_INDENT) + "\n" end def append_subcommands(text) return if @cmd.subcommands.empty? text << "\n" text << fmt.format_as_title(@cmd.supercommand ? 'subcommands' : 'commands', @io) text << "\n" shown_subcommands = @cmd.subcommands.select { |c| !c.hidden? || @is_verbose } length = shown_subcommands.map { |c| fmt.format_as_command(c.name, @io).size }.max # Command shown_subcommands.sort_by(&:name).each do |cmd| text << format( " %-#{length + DESC_INDENT}s %s\n", name: fmt.format_as_command(cmd.name, @io), summary: cmd.summary, ) end # Hidden notice unless @is_verbose diff = @cmd.subcommands.size - shown_subcommands.size if diff == 1 text << " (1 hidden command omitted; show it with --verbose)\n" elsif diff > 1 text << " (#{diff} hidden commands omitted; show them with --verbose)\n" end end end def length_for_opt_defns(opt_defns) opt_defns.map do |opt_defn| string = +'' # Always pretend there is a short option string << '-X' if opt_defn.long string << ' --' + opt_defn.long end case opt_defn.argument when :required string << '=' when :optional string << '=[]' end string.size end.max end def append_options(text) groups = { 'options' => @cmd.option_definitions } if @cmd.supercommand groups["options for #{@cmd.supercommand.name}"] = @cmd.supercommand.global_option_definitions end length = length_for_opt_defns(groups.values.inject(&:+)) groups.keys.sort.each do |name| defs = groups[name] append_option_group(text, name, defs, length) end end def append_option_group(text, name, defs, length) return if defs.empty? text << "\n" text << fmt.format_as_title(name.to_s, @io) text << "\n" ordered_defs = defs.sort_by { |x| x.short || x.long } ordered_defs.reject(&:hidden).each do |opt_defn| text << format_opt_defn(opt_defn, length) desc = opt_defn.desc + (opt_defn.default ? " (default: #{opt_defn.default})" : '') text << fmt.wrap_and_indent(desc, LINE_WIDTH, length + OPT_DESC_SPACING + DESC_INDENT, true) << "\n" end end def short_value_postfix_for(opt_defn) value_postfix = case opt_defn.argument when :required '' when :optional '[]' end if value_postfix opt_defn.long ? '' : ' ' + value_postfix else '' end end def long_value_postfix_for(opt_defn) value_postfix = case opt_defn.argument when :required '=' when :optional '[=]' end if value_postfix opt_defn.long ? value_postfix : '' else '' end end def format_opt_defn(opt_defn, length) short_value_postfix = short_value_postfix_for(opt_defn) long_value_postfix = long_value_postfix_for(opt_defn) opt_text = +'' opt_text_len = 0 if opt_defn.short opt_text << fmt.format_as_option('-' + opt_defn.short, @io) opt_text << short_value_postfix opt_text << ' ' opt_text_len += 1 + opt_defn.short.size + short_value_postfix.size + 1 else opt_text << ' ' opt_text_len += 3 end opt_text << fmt.format_as_option('--' + opt_defn.long, @io) if opt_defn.long opt_text << long_value_postfix opt_text_len += 2 + opt_defn.long.size if opt_defn.long opt_text_len += long_value_postfix.size ' ' + opt_text + ' ' * (length + OPT_DESC_SPACING - opt_text_len) end end end cri-2.15.11/lib/cri/version.rb0000644000004100000410000000014114004133336016014 0ustar www-datawww-data# frozen_string_literal: true module Cri # The current Cri version. VERSION = '2.15.11' end cri-2.15.11/lib/cri/option_definition.rb0000644000004100000410000000245614004133336020062 0ustar www-datawww-data# frozen_string_literal: true module Cri # The definition of an option. class OptionDefinition attr_reader :short attr_reader :long attr_reader :desc attr_reader :argument attr_reader :multiple attr_reader :block attr_reader :hidden attr_reader :default attr_reader :transform def initialize(short:, long:, desc:, argument:, multiple:, block:, hidden:, default:, transform:) @short = short @long = long @desc = desc @argument = argument @multiple = multiple @block = block @hidden = hidden @default = default @transform = transform if @short.nil? && @long.nil? raise ArgumentError, 'short and long options cannot both be nil' end if @default && @argument == :forbidden raise ArgumentError, 'a default value cannot be specified for flag options' end @default = false if @argument == :forbidden end def to_h { short: @short, long: @long, desc: @desc, argument: @argument, multiple: @multiple, block: @block, hidden: @hidden, default: @default, transform: @transform, } end def formatted_name @long ? '--' + @long : '-' + @short end end end cri-2.15.11/lib/cri/command_dsl.rb0000644000004100000410000002247114004133336016621 0ustar www-datawww-data# frozen_string_literal: true module Cri # The command DSL is a class that is used for building and modifying # commands. class CommandDSL # Error that will be raised when specifying a parameter after the command is # already declared as taken no params. class AlreadySpecifiedAsNoParams < Cri::Error def initialize(param, command) super("Attempted to specify a parameter #{param.inspect} to the command #{command.name.inspect}, which is already specified as taking no params. Suggestion: remove the #no_params call.") end end # Error that will be raised when declaring the command as taking no # parameters, when the command is already declared with parameters. class AlreadySpecifiedWithParams < Cri::Error def initialize(command) super("Attempted to declare the command #{command.name.inspect} as taking no parameters, but some parameters are already declared for this command. Suggestion: remove the #no_params call.") end end # @return [Cri::Command] The built command attr_reader :command # Creates a new DSL, intended to be used for building a single command. A # {CommandDSL} instance is not reusable; create a new instance if you want # to build another command. # # @param [Cri::Command, nil] command The command to modify, or nil if a # new command should be created def initialize(command = nil) @command = command || Cri::Command.new end # Adds a subcommand to the current command. The command can either be # given explicitly, or a block can be given that defines the command. # # @param [Cri::Command, nil] command The command to add as a subcommand, # or nil if the block should be used to define the command that will be # added as a subcommand # # @return [void] def subcommand(command = nil, &block) if command.nil? command = Cri::Command.define(&block) end @command.add_command(command) end # Sets the name of the default subcommand, i.e. the subcommand that will # be executed if no subcommand is explicitly specified. This is `nil` by # default, and will typically only be set for the root command. # # @param [String, nil] name The name of the default subcommand # # @return [void] def default_subcommand(name) @command.default_subcommand_name = name end # Sets the command name. # # @param [String] arg The new command name # # @return [void] def name(arg) @command.name = arg end # Sets the command aliases. # # @param [String, Symbol, Array] args The new command aliases # # @return [void] def aliases(*args) @command.aliases = args.flatten.map(&:to_s) end # Sets the command summary. # # @param [String] arg The new command summary # # @return [void] def summary(arg) @command.summary = arg end # Sets the command description. # # @param [String] arg The new command description # # @return [void] def description(arg) @command.description = arg end # Sets the command usage. The usage should not include the “usage:” # prefix, nor should it include the command names of the supercommand. # # @param [String] arg The new command usage # # @return [void] def usage(arg) @command.usage = arg end # Marks the command as hidden. Hidden commands do not show up in the list of # subcommands of the parent command, unless --verbose is passed (or # `:verbose => true` is passed to the {Cri::Command#help} method). This can # be used to mark commands as deprecated. # # @return [void] def be_hidden @command.hidden = true end # Skips option parsing for the command. Allows option-like arguments to be # passed in, avoiding the {Cri::Parser} validation. # # @return [void] def skip_option_parsing @command.all_opts_as_args = true end # Adds a new option to the command. If a block is given, it will be # executed when the option is successfully parsed. # # @param [String, Symbol, nil] short The short option name # # @param [String, Symbol, nil] long The long option name # # @param [String] desc The option description # # @option params [:forbidden, :required, :optional] :argument Whether the # argument is forbidden, required or optional # # @option params [Boolean] :multiple Whether or not the option should # be multi-valued # # @option params [Boolean] :hidden Whether or not the option should # be printed in the help output # # @return [void] def option(short, long, desc, argument: :forbidden, multiple: false, hidden: false, default: nil, transform: nil, &block) @command.option_definitions << Cri::OptionDefinition.new( short: short&.to_s, long: long&.to_s, desc: desc, argument: argument, multiple: multiple, hidden: hidden, default: default, transform: transform, block: block, ) end alias opt option # Defines a new parameter for the command. # # @param [Symbol] name The name of the parameter def param(name, transform: nil) if @command.explicitly_no_params? raise AlreadySpecifiedAsNoParams.new(name, @command) end @command.parameter_definitions << Cri::ParamDefinition.new( name: name, transform: transform, ) end def no_params if @command.parameter_definitions.any? raise AlreadySpecifiedWithParams.new(@command) end @command.explicitly_no_params = true end # Adds a new option with a required argument to the command. If a block is # given, it will be executed when the option is successfully parsed. # # @param [String, Symbol, nil] short The short option name # # @param [String, Symbol, nil] long The long option name # # @param [String] desc The option description # # @option params [Boolean] :multiple Whether or not the option should # be multi-valued # # @option params [Boolean] :hidden Whether or not the option should # be printed in the help output # # @return [void] # # @deprecated # # @see #option def required(short, long, desc, **params, &block) params = params.merge(argument: :required) option(short, long, desc, **params, &block) end # Adds a new option with a forbidden argument to the command. If a block # is given, it will be executed when the option is successfully parsed. # # @param [String, Symbol, nil] short The short option name # # @param [String, Symbol, nil] long The long option name # # @param [String] desc The option description # # @option params [Boolean] :multiple Whether or not the option should # be multi-valued # # @option params [Boolean] :hidden Whether or not the option should # be printed in the help output # # @return [void] # # @see #option def flag(short, long, desc, **params, &block) params = params.merge(argument: :forbidden) option(short, long, desc, **params, &block) end alias forbidden flag # Adds a new option with an optional argument to the command. If a block # is given, it will be executed when the option is successfully parsed. # # @param [String, Symbol, nil] short The short option name # # @param [String, Symbol, nil] long The long option name # # @param [String] desc The option description # # @option params [Boolean] :multiple Whether or not the option should # be multi-valued # # @option params [Boolean] :hidden Whether or not the option should # be printed in the help output # # @return [void] # # @deprecated # # @see #option def optional(short, long, desc, **params, &block) params = params.merge(argument: :optional) option(short, long, desc, **params, &block) end # Sets the run block to the given block. The given block should have two # or three arguments (options, arguments, and optionally the command). # Calling this will override existing run block or runner declarations # (using {#run} and {#runner}, respectively). # # @yieldparam [Hash] opts A map of option names, as defined # in the option definitions, onto strings (when single-valued) or arrays # (when multi-valued) # # @yieldparam [Array] args A list of arguments # # @return [void] def run(&block) unless [2, 3].include?(block.arity) raise ArgumentError, 'The block given to Cri::Command#run expects two or three args' end @command.block = block end # Defines the runner class for this command. Calling this will override # existing run block or runner declarations (using {#run} and {#runner}, # respectively). # # @param [Class] klass The command runner class (subclass # of {CommandRunner}) that is used for executing this command. # # @return [void] def runner(klass) run do |opts, args, cmd| klass.new(opts, args, cmd).call end end end end cri-2.15.11/lib/cri/param_definition.rb0000644000004100000410000000037714004133336017652 0ustar www-datawww-data# frozen_string_literal: true module Cri # The definition of a parameter. class ParamDefinition attr_reader :name attr_reader :transform def initialize(name:, transform:) @name = name @transform = transform end end end cri-2.15.11/lib/cri/platform.rb0000644000004100000410000000145014004133336016157 0ustar www-datawww-data# frozen_string_literal: true module Cri # Provides tools to detect platform and environment configuration (e.g. is # color support available?) # # @api private module Platform # @return [Boolean] true if the current platform is Windows, false # otherwise. def self.windows? RUBY_PLATFORM =~ /windows|bccwin|cygwin|djgpp|mingw|mswin|wince/i end # Checks whether colors can be enabled. For colors to be enabled, the given # IO should be a TTY, and, when on Windows, ::Win32::Console::ANSI needs to # be defined. # # @return [Boolean] True if colors should be enabled, false otherwise. def self.color?(io) if !io.tty? false elsif windows? defined?(::Win32::Console::ANSI) else true end end end end cri-2.15.11/lib/cri/string_formatter.rb0000644000004100000410000000631314004133336017727 0ustar www-datawww-data# frozen_string_literal: true module Cri # Used for formatting strings (e.g. converting to paragraphs, wrapping, # formatting as title) # # @api private class StringFormatter # Extracts individual paragraphs (separated by two newlines). # # @param [String] str The string to format # # @return [Array] A list of paragraphs in the string def to_paragraphs(str) lines = str.scan(/([^\n]+\n|[^\n]*$)/).map { |l| l[0].strip } paragraphs = [[]] lines.each do |line| if line.empty? paragraphs << [] else paragraphs.last << line end end paragraphs.reject(&:empty?).map { |p| p.join(' ') } end # Word-wraps and indents the string. # # @param [String] str The string to format # # @param [Number] width The maximal width of each line. This also includes # indentation, i.e. the actual maximal width of the text is # `width`-`indentation`. # # @param [Number] indentation The number of spaces to indent each line. # # @param [Boolean] first_line_already_indented Whether or not the first # line is already indented # # @return [String] The word-wrapped and indented string def wrap_and_indent(str, width, indentation, first_line_already_indented = false) indented_width = width - indentation indent = ' ' * indentation # Split into paragraphs paragraphs = to_paragraphs(str) # Wrap and indent each paragraph text = paragraphs.map do |paragraph| # Initialize lines = [] line = '' # Split into words paragraph.split(/\s/).each do |word| # Begin new line if it's too long if (line + ' ' + word).length >= indented_width lines << line line = '' end # Add word to line line += (line == '' ? '' : ' ') + word end lines << line # Join lines lines.map { |l| indent + l }.join("\n") end.join("\n\n") if first_line_already_indented text[indentation..-1] else text end end # @param [String] str The string to format # # @return [String] The string, formatted to be used as a title in a section # in the help def format_as_title(str, io) if Cri::Platform.color?(io) bold(red(str.upcase)) else str.upcase end end # @param [String] str The string to format # # @return [String] The string, formatted to be used as the name of a command # in the help def format_as_command(str, io) if Cri::Platform.color?(io) green(str) else str end end # @param [String] str The string to format # # @return [String] The string, formatted to be used as an option definition # of a command in the help def format_as_option(str, io) if Cri::Platform.color?(io) yellow(str) else str end end def red(str) "\e[31m#{str}\e[0m" end def green(str) "\e[32m#{str}\e[0m" end def yellow(str) "\e[33m#{str}\e[0m" end def bold(str) "\e[1m#{str}\e[0m" end end end cri-2.15.11/lib/cri/command.rb0000644000004100000410000003454514004133336015764 0ustar www-datawww-data# frozen_string_literal: true module Cri # Cri::Command represents a command that can be executed on the command line. # It is also used for the command-line tool itself. class Command # Delegate used for partitioning the list of arguments and options. This # delegate will stop the parser as soon as the first argument, i.e. the # command, is found. # # @api private class ParserPartitioningDelegate # Returns the last parsed argument, which, in this case, will be the # first argument, which will be either nil or the command name. # # @return [String] The last parsed argument. attr_reader :last_argument # Called when an option is parsed. # # @param [Symbol] _key The option key (derived from the long format) # # @param _value The option value # # @param [Cri::Parser] _parser The option parser # # @return [void] def option_added(_key, _value, _parser); end # Called when an argument is parsed. # # @param [String] argument The argument # # @param [Cri::Parser] parser The option parser # # @return [void] def argument_added(argument, parser) @last_argument = argument parser.stop end end # Signals that Cri should abort execution. Unless otherwise specified using the `hard_exit` # param, this exception will cause Cri to exit the running process. # # @api private class CriExitException < StandardError def initialize(is_error:) super('exit requested') @is_error = is_error end def error? @is_error end end # @return [Cri::Command, nil] This command’s supercommand, or nil if the # command has no supercommand attr_accessor :supercommand # @return [Set] This command’s subcommands attr_accessor :commands alias subcommands commands # @return [Symbol] The name of the default subcommand attr_accessor :default_subcommand_name # @return [String] The name attr_accessor :name # @return [Array] A list of aliases for this command that can be # used to invoke this command attr_accessor :aliases # @return [String] The short description (“summary”) attr_accessor :summary # @return [String] The long description (“description”) attr_accessor :description # @return [String] The usage, without the “usage:” prefix and without the # supercommands’ names. attr_accessor :usage # @return [Boolean] true if the command is hidden (e.g. because it is # deprecated), false otherwise attr_accessor :hidden alias hidden? hidden # @return [Array] The list of option definitions attr_accessor :option_definitions # @return [Array] The list of parameter definitions attr_accessor :parameter_definitions # @return [Boolean] Whether or not this command has parameters attr_accessor :explicitly_no_params alias explicitly_no_params? explicitly_no_params # @return [Proc] The block that should be executed when invoking this # command (ignored for commands with subcommands) attr_accessor :block # @return [Boolean] true if the command should skip option parsing and # treat all options as arguments. attr_accessor :all_opts_as_args alias all_opts_as_args? all_opts_as_args # Creates a new command using the DSL. If a string is given, the command # will be defined using the string; if a block is given, the block will be # used instead. # # If the block has one parameter, the block will be executed in the same # context with the command DSL as its parameter. If the block has no # parameters, the block will be executed in the context of the DSL. # # @param [String, nil] string The command definition as a string # # @param [String, nil] filename The filename corresponding to the string parameter (only useful if a string is given) # # @return [Cri::Command] The newly defined command def self.define(string = nil, filename = nil, &block) dsl = Cri::CommandDSL.new if string args = filename ? [string, filename] : [string] dsl.instance_eval(*args) elsif [-1, 0].include? block.arity dsl.instance_eval(&block) else block.call(dsl) end dsl.command end # Creates a new command using a DSL, from code defined in the given filename. # # @param [String] filename The filename that contains the command definition # as a string # # @return [Cri::Command] The newly defined command def self.load_file(filename, infer_name: false) code = File.read(filename, encoding: 'UTF-8') define(code, filename).tap do |cmd| if infer_name command_name = File.basename(filename, '.rb') cmd.modify { name command_name } end end end # Returns a new command that has support for the `-h`/`--help` option and # also has a `help` subcommand. It is intended to be modified (adding # name, summary, description, other subcommands, …) # # @return [Cri::Command] A basic root command def self.new_basic_root filename = File.dirname(__FILE__) + '/commands/basic_root.rb' define(File.read(filename)) end # Returns a new command that implements showing help. # # @return [Cri::Command] A basic help command def self.new_basic_help filename = File.dirname(__FILE__) + '/commands/basic_help.rb' define(File.read(filename)) end def initialize @aliases = Set.new @commands = Set.new @option_definitions = Set.new @parameter_definitions = [] @explicitly_no_params = false @default_subcommand_name = nil end # Modifies the command using the DSL. # # If the block has one parameter, the block will be executed in the same # context with the command DSL as its parameter. If the block has no # parameters, the block will be executed in the context of the DSL. # # @return [Cri::Command] The command itself def modify(&block) dsl = Cri::CommandDSL.new(self) if [-1, 0].include? block.arity dsl.instance_eval(&block) else yield(dsl) end self end # @return [Enumerable] The option definitions for the # command itself and all its ancestors def global_option_definitions res = Set.new res.merge(option_definitions) res.merge(supercommand.global_option_definitions) if supercommand res end # Adds the given command as a subcommand to the current command. # # @param [Cri::Command] command The command to add as a subcommand # # @return [void] def add_command(command) @commands << command command.supercommand = self end # Defines a new subcommand for the current command using the DSL. # # @param [String, nil] name The name of the subcommand, or nil if no name # should be set (yet) # # @return [Cri::Command] The subcommand def define_command(name = nil, &block) # Execute DSL dsl = Cri::CommandDSL.new dsl.name name unless name.nil? if [-1, 0].include? block.arity dsl.instance_eval(&block) else yield(dsl) end # Create command cmd = dsl.command add_command(cmd) cmd end # Returns the commands that could be referred to with the given name. If # the result contains more than one command, the name is ambiguous. # # @param [String] name The full, partial or aliases name of the command # # @return [Array] A list of commands matching the given name def commands_named(name) # Find by exact name or alias @commands.each do |cmd| found = cmd.name == name || cmd.aliases.include?(name) return [cmd] if found end # Find by approximation @commands.select do |cmd| cmd.name[0, name.length] == name end end # Returns the command with the given name. This method will display error # messages and exit in case of an error (unknown or ambiguous command). # # The name can be a full command name, a partial command name (e.g. “com” # for “commit”) or an aliased command name (e.g. “ci” for “commit”). # # @param [String] name The full, partial or aliases name of the command # # @return [Cri::Command] The command with the given name def command_named(name, hard_exit: true) commands = commands_named(name) if commands.empty? warn "#{self.name}: unknown command '#{name}'\n" raise CriExitException.new(is_error: true) elsif commands.size > 1 warn "#{self.name}: '#{name}' is ambiguous:" warn " #{commands.map(&:name).sort.join(' ')}" raise CriExitException.new(is_error: true) else commands[0] end rescue CriExitException => e exit(e.error? ? 1 : 0) if hard_exit end # Runs the command with the given command-line arguments, possibly invoking # subcommands and passing on the options and arguments. # # @param [Array] opts_and_args A list of unparsed arguments # # @param [Hash] parent_opts A hash of options already handled by the # supercommand # # @return [void] def run(opts_and_args, parent_opts = {}, hard_exit: true) # Parse up to command name stuff = partition(opts_and_args) opts_before_subcmd, subcmd_name, opts_and_args_after_subcmd = *stuff if subcommands.empty? || (subcmd_name.nil? && !block.nil?) run_this(opts_and_args, parent_opts) else # Handle options handle_options(opts_before_subcmd) # Get command if subcmd_name.nil? if default_subcommand_name subcmd_name = default_subcommand_name else warn "#{name}: no command given" raise CriExitException.new(is_error: true) end end subcommand = command_named(subcmd_name, hard_exit: hard_exit) return if subcommand.nil? # Run subcommand.run(opts_and_args_after_subcmd, parent_opts.merge(opts_before_subcmd), hard_exit: hard_exit) end rescue CriExitException => e exit(e.error? ? 1 : 0) if hard_exit end # Runs the actual command with the given command-line arguments, not # invoking any subcommands. If the command does not have an execution # block, an error ir raised. # # @param [Array] opts_and_args A list of unparsed arguments # # @param [Hash] parent_opts A hash of options already handled by the # supercommand # # @raise [NotImplementedError] if the command does not have an execution # block # # @return [void] def run_this(opts_and_args, parent_opts = {}) if all_opts_as_args? args = opts_and_args global_opts = parent_opts else # Parse parser = Cri::Parser.new( opts_and_args, global_option_definitions, parameter_definitions, explicitly_no_params?, ) handle_errors_while { parser.run } local_opts = parser.options global_opts = parent_opts.merge(parser.options) global_opts = add_defaults(global_opts) # Handle options handle_options(local_opts) args = handle_errors_while { parser.gen_argument_list } end # Execute if block.nil? raise NotImplementedError, "No implementation available for '#{name}'" end block.call(global_opts, args, self) end def all_opt_defns if supercommand supercommand.all_opt_defns | option_definitions else option_definitions end end # @return [String] The help text for this command # # @option params [Boolean] :verbose true if the help output should be # verbose, false otherwise. # # @option params [IO] :io ($stdout) the IO the help text is intended for. # This influences the decision to enable/disable colored output. def help(**params) HelpRenderer.new(self, **params).render end # Compares this command's name to the other given command's name. # # @param [Cri::Command] other The command to compare with # # @return [-1, 0, 1] The result of the comparison between names # # @see Object<=> def <=>(other) name <=> other.name end private def handle_options(opts) opts.each_pair do |key, value| opt_defn = global_option_definitions.find { |o| (o.long || o.short) == key.to_s } block = opt_defn.block block&.call(value, self) end end def partition(opts_and_args) return [{}, opts_and_args.first, opts_and_args] if all_opts_as_args? # Parse delegate = Cri::Command::ParserPartitioningDelegate.new parser = Cri::Parser.new( opts_and_args, global_option_definitions, parameter_definitions, explicitly_no_params?, ) parser.delegate = delegate handle_errors_while { parser.run } # Extract [ parser.options, delegate.last_argument, parser.unprocessed_arguments_and_options, ] end def handle_errors_while yield rescue Cri::Parser::IllegalOptionError => e warn "#{name}: unrecognised option -- #{e}" raise CriExitException.new(is_error: true) rescue Cri::Parser::OptionRequiresAnArgumentError => e warn "#{name}: option requires an argument -- #{e}" raise CriExitException.new(is_error: true) rescue Cri::Parser::IllegalOptionValueError => e warn "#{name}: #{e.message}" raise CriExitException.new(is_error: true) rescue Cri::ArgumentList::ArgumentCountMismatchError => e warn "#{name}: #{e.message}" raise CriExitException.new(is_error: true) end def add_defaults(options) all_opt_defns_by_key = all_opt_defns.each_with_object({}) do |opt_defn, hash| key = (opt_defn.long || opt_defn.short).to_sym hash[key] = opt_defn end new_options = Hash.new do |hash, key| hash.fetch(key) { all_opt_defns_by_key[key]&.default } end options.each do |key, value| new_options[key] = value end new_options end end end cri-2.15.11/lib/cri/parser.rb0000644000004100000410000001571114004133336015634 0ustar www-datawww-data# frozen_string_literal: true module Cri # Cri::Parser is used for parsing command-line options and arguments. class Parser # Error that will be raised when an unknown option is encountered. class IllegalOptionError < Cri::Error end # Error that will be raised when an option with an invalid or # non-transformable value is encountered. class IllegalOptionValueError < Cri::Error attr_reader :definition attr_reader :value def initialize(definition, value) super("invalid value #{value.inspect} for #{definition.formatted_name} option") @value = value @definition = definition end end # Error that will be raised when an option without argument is # encountered. class OptionRequiresAnArgumentError < Cri::Error end # The delegate to which events will be sent. The following methods will # be send to the delegate: # # * `option_added(key, value, cmd)` # * `argument_added(argument, cmd)` # # @return [#option_added, #argument_added] The delegate attr_accessor :delegate # The options that have already been parsed. # # If the parser was stopped before it finished, this will not contain all # options and `unprocessed_arguments_and_options` will contain what is # left to be processed. # # @return [Hash] The already parsed options. attr_reader :options # The options and arguments that have not yet been processed. If the # parser wasn’t stopped (using {#stop}), this list will be empty. # # @return [Array] The not yet parsed options and arguments. attr_reader :unprocessed_arguments_and_options # Creates a new parser with the given options/arguments and definitions. # # @param [Array] arguments_and_options An array containing the # command-line arguments (will probably be `ARGS` for a root command) # # @param [Array] option_defns An array of option # definitions # # @param [Array] param_defns An array of parameter # definitions def initialize(arguments_and_options, option_defns, param_defns, explicitly_no_params) @unprocessed_arguments_and_options = arguments_and_options.dup @option_defns = option_defns @param_defns = param_defns @explicitly_no_params = explicitly_no_params @options = {} @raw_arguments = [] @running = false @no_more_options = false end # @return [Boolean] true if the parser is running, false otherwise. def running? @running end # Stops the parser. The parser will finish its current parse cycle but # will not start parsing new options and/or arguments. # # @return [void] def stop @running = false end # Parses the command-line arguments into options and arguments. # # During parsing, two errors can be raised: # # @raise IllegalOptionError if an unrecognised option was encountered, # i.e. an option that is not present in the list of option definitions # # @raise OptionRequiresAnArgumentError if an option was found that did not # have a value, even though this value was required. # # @return [Cri::Parser] The option parser self def run @running = true while running? # Get next item e = @unprocessed_arguments_and_options.shift break if e.nil? if e == '--' handle_dashdash(e) elsif e =~ /^--./ && !@no_more_options handle_dashdash_option(e) elsif e =~ /^-./ && !@no_more_options handle_dash_option(e) else add_argument(e) end end self ensure @running = false end # @return [Cri::ArgumentList] The list of arguments that have already been # parsed, excluding the -- separator. def gen_argument_list ArgumentList.new(@raw_arguments, @explicitly_no_params, @param_defns) end private def handle_dashdash(elem) add_argument(elem) @no_more_options = true end def handle_dashdash_option(elem) # Get option key, and option value if included if elem =~ /^--([^=]+)=(.+)$/ option_key = Regexp.last_match[1] option_value = Regexp.last_match[2] else option_key = elem[2..-1] option_value = nil end # Find definition option_defn = @option_defns.find { |d| d.long == option_key } raise IllegalOptionError.new(option_key) if option_defn.nil? if %i[required optional].include?(option_defn.argument) # Get option value if necessary if option_value.nil? option_value = find_option_value(option_defn, option_key) end # Store option add_option(option_defn, option_value) else # Store option add_option(option_defn, true) end end def handle_dash_option(elem) # Get option keys option_keys = elem[1..-1].scan(/./) # For each key option_keys.each do |option_key| # Find definition option_defn = @option_defns.find { |d| d.short == option_key } raise IllegalOptionError.new(option_key) if option_defn.nil? if %i[required optional].include?(option_defn.argument) # Get option value option_value = find_option_value(option_defn, option_key) # Store option add_option(option_defn, option_value) else # Store option add_option(option_defn, true) end end end def find_option_value(option_defn, option_key) option_value = @unprocessed_arguments_and_options.shift if option_value.nil? || option_value =~ /^-/ if option_defn.argument == :optional && option_defn.default option_value = option_defn.default elsif option_defn.argument == :required raise OptionRequiresAnArgumentError.new(option_key) else @unprocessed_arguments_and_options.unshift(option_value) option_value = true end end option_value end def add_option(option_defn, value, transform: true) key = key_for(option_defn) value = transform ? transform_value(option_defn, value) : value if option_defn.multiple options[key] ||= [] options[key] << value else options[key] = value end delegate&.option_added(key, value, self) end def transform_value(option_defn, value) transformer = option_defn.transform if transformer begin transformer.call(value) rescue StandardError raise IllegalOptionValueError.new(option_defn, value) end else value end end def key_for(option_defn) (option_defn.long || option_defn.short).to_sym end def add_argument(value) @raw_arguments << value unless value == '--' delegate&.argument_added(value, self) end end end end cri-2.15.11/lib/cri/argument_list.rb0000644000004100000410000000405514004133336017214 0ustar www-datawww-data# frozen_string_literal: true module Cri # A list of arguments, which can be indexed using either a number or a symbol. class ArgumentList # Error that will be raised when an incorrect number of arguments is given. class ArgumentCountMismatchError < Cri::Error def initialize(expected_count, actual_count) super("incorrect number of arguments given: expected #{expected_count}, but got #{actual_count}") end end include Enumerable def initialize(raw_arguments, explicitly_no_params, param_defns) @raw_arguments = raw_arguments @explicitly_no_params = explicitly_no_params @param_defns = param_defns load end def [](key) case key when Symbol @arguments_hash[key] when Integer @arguments_array[key] else raise ArgumentError, "argument lists can be indexed using a Symbol or an Integer, but not a #{key.class}" end end def each return to_enum(__method__) unless block_given? @arguments_array.each { |e| yield(e) } self end def method_missing(sym, *args, &block) if @arguments_array.respond_to?(sym) @arguments_array.send(sym, *args, &block) else super end end def respond_to_missing?(sym, include_private = false) @arguments_array.respond_to?(sym) || super end def load @arguments_array = [] @arguments_hash = {} arguments_array = @raw_arguments.reject { |a| a == '--' }.freeze if !@explicitly_no_params && @param_defns.empty? # No parameters defined; ignore @arguments_array = arguments_array return end if arguments_array.size != @param_defns.size raise ArgumentCountMismatchError.new(@param_defns.size, arguments_array.size) end arguments_array.zip(@param_defns).each do |(arg, param_defn)| arg = param_defn.transform ? param_defn.transform.call(arg) : arg @arguments_hash[param_defn.name.to_sym] = arg @arguments_array << arg end end end end cri-2.15.11/lib/cri/commands/0000755000004100000410000000000014004133336015607 5ustar www-datawww-datacri-2.15.11/lib/cri/commands/basic_root.rb0000644000004100000410000000030714004133336020260 0ustar www-datawww-data# frozen_string_literal: true flag :h, :help, 'show help for this command' do |_value, cmd| puts cmd.help raise CriExitException.new(is_error: false) end subcommand Cri::Command.new_basic_help cri-2.15.11/lib/cri/commands/basic_help.rb0000644000004100000410000000151014004133336020222 0ustar www-datawww-data# frozen_string_literal: true name 'help' usage 'help [command_name]' summary 'show help' description <<~DESC Show help for the given command, or show general help. When no command is given, a list of available commands is displayed, as well as a list of global command-line options. When a command is given, a command description, as well as command-specific command-line options, are shown. DESC flag :v, :verbose, 'show more detailed help' run do |opts, args, cmd| if cmd.supercommand.nil? raise NoHelpAvailableError, 'No help available because the help command has no supercommand' end is_verbose = opts.fetch(:verbose, false) resolved_cmd = args.inject(cmd.supercommand) do |acc, name| acc.command_named(name) end puts resolved_cmd.help(verbose: is_verbose, io: $stdout) end cri-2.15.11/lib/cri/command_runner.rb0000644000004100000410000000241314004133336017342 0ustar www-datawww-data# frozen_string_literal: true module Cri # A command runner is responsible for the execution of a command. Using it # is optional, but it is useful for commands whose execution block is large. class CommandRunner # @return [Hash] A hash contain the options and their values attr_reader :options # @return [Array] The list of arguments attr_reader :arguments # @return [Command] The command attr_reader :command # Creates a command runner from the given options, arguments and command. # # @param [Hash] options A hash contain the options and their values # # @param [Array] arguments The list of arguments # # @param [Cri::Command] command The Cri command def initialize(options, arguments, command) @options = options @arguments = arguments @command = command end # Runs the command. By default, this simply does the actual execution, but # subclasses may choose to add error handling around the actual execution. # # @return [void] def call run end # Performs the actual execution of the command. # # @return [void] # # @abstract def run raise NotImplementedError, 'Cri::CommandRunner subclasses must implement #run' end end end cri-2.15.11/lib/cri.rb0000644000004100000410000000200414004133336014327 0ustar www-datawww-data# frozen_string_literal: true # The namespace for Cri, a library for building easy-to-use command-line tools # with support for nested commands. module Cri # A generic error class for all Cri-specific errors. class Error < ::StandardError end # Error that will be raised when an implementation for a method or command # is missing. For commands, this may mean that a run block is missing. class NotImplementedError < Error end # Error that will be raised when no help is available because the help # command has no supercommand for which to show help. class NoHelpAvailableError < Error end end require 'set' require_relative 'cri/version' require_relative 'cri/argument_list' require_relative 'cri/command' require_relative 'cri/string_formatter' require_relative 'cri/command_dsl' require_relative 'cri/command_runner' require_relative 'cri/help_renderer' require_relative 'cri/option_definition' require_relative 'cri/parser' require_relative 'cri/param_definition' require_relative 'cri/platform' cri-2.15.11/NEWS.md0000644000004100000410000001223714004133336013566 0ustar www-datawww-data# Cri News ## 2.15.11 Fixes: - Added support for Ruby 3.0 (#111) Changes: - Dropped support for Ruby 2.3 and 2.4 (#112) ## 2.15.10 Fixes: - Fixed warnings appearing in Ruby 2.7 (9a3d810) ## 2.15.9 Fixes: - Fixed bug which could cause options from one command appear in other commands (#101, #102) ## 2.15.8 Fixes: - Don’t explicitly set default values for options (#99) This release reverts a backwards-incompatible change introduced in 2.15.7. To illustrate this, compare the behavior of the following command in recent versions of Cri: ```ruby option :f, :force, 'use force', argument: :forbidden run do |opts, args, cmd| puts "Options = #{opts.inspect}" puts "Force? #{opts[:force]}" puts "Option given? #{opts.key?(:force)}" end ``` In Cri 2.15.6, the default is not set in the options hash, so the value is `nil` and `#key?` returns false: ```sh % ./run Options = {} Force? nil Option given? false ``` This behavior was inconsistent with what was documented: flag options were (and still are) documented to default to `false` rather than `nil`. In Cri 2.15.7, the default value is `false`, and explicitly set in the options hash (`#key?` returns `true`): ```sh % ./run Options = {:force=>false} Force? false Option given? true ``` This change made it impossible to detect options that were not explicitly specified, because the behavior of `#key?` also changed. In Cri 2.15.8, the default value is also `false` (as in 2.15.7), but not explicitly set in the options hash (`#key?` returns `false`, as in 2.15.6): ```sh % ./run Options = {} Force? false Option given? false ``` This backwards-incompatible change was not intentional. To fix issue #94, a change in behavior was needed, but this change also affected other, previously-undefined behavior. The new behavior in 2.15.8 should fix the bug fixed in 2.15.7 (#94, #96), without causing the problems introduced in that version. ## 2.15.7 Fixes: - Options with a forbidden argument now default to false, rather than nil (#94, #96) ## 2.15.6 Fixes: - Fixed problem with help header not being shown if the summary is missing (#93) ## 2.15.5 Fixes: - Restored compatibility with Ruby 2.3. (#91) ## 2.15.4 Fixes: - Removed dependency on `colored`, which restores functionality to gems that `colored` breaks (e.g. `awesome_print`) (#89, #90) ## 2.15.3 Fixes: - Made `ArgumentList#each` callable without a block, in which case it returns an `Enumerator` (mimicking `Array`) (#87, #88) ## 2.15.2 Fixes: - Fixed option propagation for two levels or more (#85, #86) ## 2.15.1 Fixes: - Made -h/--help not fail when parameters are defined for the command that -h/--help is called on (#76, #78) Enhancements: - Made `#option` raise an error when unrecognised parameters are passed to it (#77) [Marc-André Lafortune] ## 2.15.0 Features: - Added support for parameter transformation (#72) ## 2.14.0 Features: - Added `Cri::Command.load_file` ## 2.13.0 Features: - Added support for explicitly specifying zero parameters using `#no_params` (#71) ## 2.12.0 Features: - Added support for parameter naming and validation (#70) ## 2.11.0 Features: - Added support for transforming option values (#68) ## 2.10.1 Fixes: - Restored Ruby 2.1 compatibility (for now) ## 2.10.0 Features: - Added support for skipping option parsing (#62) [Tim Sharpe] This release drops support for Ruby 2.1, which is no longer supported. ## 2.9.1 Fixes: - Made default values be always returned, even when not explicitly specified (#57, #58) ## 2.9.0 Features: - Allowed specifying default option value (#55) Enhancements: - Added support for specifying values for combined options (#56) ## 2.8.0 Features: - Allowed passing `hard_exit: false` to `Command#run` to prevent `SystemExit` (#51) - Allowed specifying the default subcommand (#54) ## 2.7.1 Fixes: - Fixed some grammatical mistakes ## 2.7.0 Features: - Added support for hidden options (#43, #44) [Bart Mesuere] Enhancements: - Added option values to help output (#37, #40, #41) - Made option descriptions wrap (#36, #45) [Bart Mesuere] ## 2.6.1 - Disable ANSI color codes when not supported (#31, #32) ## 2.6.0 - Added support for multi-valued options (#29) [Toon Willems] ## 2.5.0 - Made the default help command handle subcommands (#27) - Added `#raw` method to argument arrays, returning all arguments including `--` (#22) ## 2.4.1 - Fixed ordering of option groups on Ruby 1.8.x (#14, #15) - Fixed ordering of commands when --verbose is passed (#16, #18) ## 2.4.0 - Allowed either short or long option to be, eh, optional (#9, #10) [Ken Coar] - Fixed wrap-and-indent behavior (#12) [Ken Coar] - Moved version information into `cri/version` ## 2.3.0 - Added colors (#1) - Added support for marking commands as hidden ## 2.2.1 - Made command help sort subcommands ## 2.2.0 - Allowed commands with subcommands to have a run block ## 2.1.0 - Added support for runners - Split up local/global command options ## 2.0.2 - Added command filename to stack traces ## 2.0.1 - Sorted ambiguous command names - Restored compatibility with Ruby 1.8.x ## 2.0.0 - Added DSL - Added support for nested commands ## 1.0.1 - Made gem actually include code. D'oh. ## 1.0.0 - Initial release! cri-2.15.11/Gemfile0000644000004100000410000000033714004133336013761 0ustar www-datawww-data# frozen_string_literal: true source 'https://rubygems.org' gemspec gem 'coveralls' gem 'm', '~> 1.5' gem 'minitest' gem 'rake' gem 'rubocop' gem 'rubocop-minitest', '~> 0.10.2' gem 'rubocop-rake', '~> 0.5.1' gem 'yard'