cri-2.7.0/0000755000004100000410000000000012530447725012336 5ustar www-datawww-datacri-2.7.0/Rakefile0000644000004100000410000000111512530447725014001 0ustar www-datawww-data# encoing: utf-8 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.adoc', '--files', 'NEWS.md,LICENSE', '--output-dir', 'doc/yardoc' ] end task :test_unit do require './test/helper.rb' FileList['./test/**/test_*.rb', './test/**/*_spec.rb'].each do |fn| require fn end end RuboCop::RakeTask.new(:test_style) task :test => [:test_unit, :test_style] task :default => :test cri-2.7.0/NEWS.md0000644000004100000410000000264012530447725013436 0ustar www-datawww-dataCri News ======== 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.7.0/Gemfile.lock0000644000004100000410000000260012530447725014556 0ustar www-datawww-dataPATH remote: . specs: cri (2.7.0) colored (~> 1.2) GEM remote: https://rubygems.org/ specs: asciidoctor (1.5.2) ast (2.0.0) astrolabe (1.3.0) parser (>= 2.2.0.pre.3, < 3.0) colored (1.2) coveralls (0.8.1) json (~> 1.8) rest-client (>= 1.6.8, < 2) simplecov (~> 0.10.0) term-ansicolor (~> 1.3) thor (~> 0.19.1) docile (1.1.5) domain_name (0.5.24) unf (>= 0.0.5, < 1.0.0) http-cookie (1.0.2) domain_name (~> 0.5) json (1.8.2) mime-types (2.5) minitest (5.6.1) netrc (0.10.3) parser (2.2.2.2) ast (>= 1.1, < 3.0) powerpack (0.1.0) rainbow (2.0.0) rake (10.4.2) rest-client (1.8.0) http-cookie (>= 1.0.2, < 2.0) mime-types (>= 1.16, < 3.0) netrc (~> 0.7) rubocop (0.30.1) astrolabe (~> 1.3) parser (>= 2.2.2.1, < 3.0) powerpack (~> 0.1) rainbow (>= 1.99.1, < 3.0) ruby-progressbar (~> 1.4) ruby-progressbar (1.7.5) simplecov (0.10.0) docile (~> 1.1.0) json (~> 1.8) simplecov-html (~> 0.10.0) simplecov-html (0.10.0) term-ansicolor (1.3.0) tins (~> 1.0) thor (0.19.1) tins (1.5.1) unf (0.1.4) unf_ext unf_ext (0.0.7.1) yard (0.8.7.6) PLATFORMS ruby DEPENDENCIES asciidoctor bundler (~> 1.6) coveralls cri! minitest rake rubocop yard cri-2.7.0/Gemfile0000644000004100000410000000017512530447725013634 0ustar www-datawww-datasource 'https://rubygems.org' gemspec gem 'coveralls' gem 'rake' gem 'minitest' gem 'yard' gem 'asciidoctor' gem 'rubocop' cri-2.7.0/lib/0000755000004100000410000000000012530447725013104 5ustar www-datawww-datacri-2.7.0/lib/cri.rb0000644000004100000410000000203612530447725014207 0ustar www-datawww-data# encoding: utf-8 require 'cri/version' # The namespace for Cri, a library for building easy-to-use commandline 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 autoload 'Command', 'cri/command' autoload 'StringFormatter', 'cri/string_formatter' autoload 'CommandDSL', 'cri/command_dsl' autoload 'CommandRunner', 'cri/command_runner' autoload 'HelpRenderer', 'cri/help_renderer' autoload 'OptionParser', 'cri/option_parser' autoload 'Platform', 'cri/platform' end require 'set' require 'cri/core_ext' require 'cri/argument_array' cri-2.7.0/lib/cri/0000755000004100000410000000000012530447725013661 5ustar www-datawww-datacri-2.7.0/lib/cri/core_ext.rb0000644000004100000410000000022712530447725016017 0ustar www-datawww-data# encoding: utf-8 module Cri module CoreExtensions end end require 'cri/core_ext/string' class String include Cri::CoreExtensions::String end cri-2.7.0/lib/cri/help_renderer.rb0000644000004100000410000001402312530447725017024 0ustar www-datawww-data# encoding: utf-8 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 @_formatter ||= Cri::StringFormatter.new end def append_summary(text) return if @cmd.summary.nil? text << fmt.format_as_title('name', @io) << "\n" text << " #{fmt.format_as_command(@cmd.name, @io)} - #{@cmd.summary}" << "\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 { |cmd| cmd.name }.each do |cmd| text << format( " %-#{length + DESC_INDENT}s %s\n", fmt.format_as_command(cmd.name, @io), cmd.summary) end # Hidden notice unless @is_verbose diff = @cmd.subcommands.size - shown_subcommands.size case diff when 0 when 1 text << " (1 hidden command omitted; show it with --verbose)\n" else text << " (#{diff} hidden commands omitted; show them with --verbose)\n" end end end def length_for_opt_defs(opt_defs) opt_defs.map do |opt_def| string = '' # Always pretend there is a short option string << '-X' if opt_def[:long] string << ' --' + opt_def[:long] end case opt_def[: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_defs(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}", @io) text << "\n" ordered_defs = defs.sort_by { |x| x[:short] || x[:long] } ordered_defs.each do |opt_def| unless opt_def[:hidden] text << format_opt_def(opt_def, length) text << fmt.wrap_and_indent(opt_def[:desc], LINE_WIDTH, length + OPT_DESC_SPACING + DESC_INDENT, true) << "\n" end end end def short_value_postfix_for(opt_def) value_postfix = case opt_def[:argument] when :required '' when :optional '[]' else nil end if value_postfix opt_def[:long] ? '' : ' ' + value_postfix else '' end end def long_value_postfix_for(opt_def) value_postfix = case opt_def[:argument] when :required '=' when :optional '[=]' else nil end if value_postfix opt_def[:long] ? value_postfix : '' else '' end end def format_opt_def(opt_def, length) short_value_postfix = short_value_postfix_for(opt_def) long_value_postfix = long_value_postfix_for(opt_def) opt_text = '' opt_text_len = 0 if opt_def[:short] opt_text << fmt.format_as_option('-' + opt_def[:short], @io) opt_text << short_value_postfix opt_text << ' ' opt_text_len += 1 + opt_def[:short].size + short_value_postfix.size + 1 else opt_text << ' ' opt_text_len += 3 end opt_text << fmt.format_as_option('--' + opt_def[:long], @io) if opt_def[:long] opt_text << long_value_postfix opt_text_len += 2 + opt_def[:long].size if opt_def[:long] opt_text_len += long_value_postfix.size ' ' + opt_text + ' ' * (length + OPT_DESC_SPACING - opt_text_len) end end end cri-2.7.0/lib/cri/core_ext/0000755000004100000410000000000012530447725015471 5ustar www-datawww-datacri-2.7.0/lib/cri/core_ext/string.rb0000644000004100000410000000156612530447725017334 0ustar www-datawww-data# encoding: utf-8 require 'colored' module Cri module CoreExtensions # @deprecated module String # @see Cri::StringFormatter#to_paragraphs def to_paragraphs Cri::StringFormatter.new.to_paragraphs(self) end # @see Cri::StringFormatter#to_paragraphs def wrap_and_indent(width, indentation) Cri::StringFormatter.new.wrap_and_indent(self, width, indentation) end # @see Cri::StringFormatter#format_as_title def formatted_as_title Cri::StringFormatter.new.format_as_title(self) end # @see Cri::StringFormatter#format_as_command def formatted_as_command Cri::StringFormatter.new.format_as_command(self) end # @see Cri::StringFormatter#format_as_option def formatted_as_option Cri::StringFormatter.new.format_as_option(self) end end end end cri-2.7.0/lib/cri/string_formatter.rb0000644000004100000410000000554412530447725017607 0ustar www-datawww-data# encoding: utf-8 require 'colored' module Cri class StringFormatter # Extracts individual paragraphs (separated by two newlines). # # @param [String] s The string to format # # @return [Array] A list of paragraphs in the string def to_paragraphs(s) lines = s.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 { |p| p.empty? }.map { |p| p.join(' ') } end # Word-wraps and indents the string. # # @param [String] s 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(s, width, indentation, first_line_already_indented = false) indented_width = width - indentation indent = ' ' * indentation # Split into paragraphs paragraphs = to_paragraphs(s) # 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] s 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(s, io) if Cri::Platform.color?(io) s.upcase.red.bold else s.upcase end end # @param [String] s 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(s, io) if Cri::Platform.color?(io) s.green else s end end # @param [String] s 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(s, io) if Cri::Platform.color?(io) s.yellow else s end end end end cri-2.7.0/lib/cri/argument_array.rb0000644000004100000410000000135012530447725017225 0ustar www-datawww-data# encoding: utf-8 module Cri # Represents an array of arguments. It is an array that strips separator # arguments (`--`) but provides a `#raw` method to get the raw arguments # array, i.e. an array that includes the separator `--` arguments. class ArgumentArray < Array # Initializes the array using the given raw arguments. # # @param [Array] raw_arguments A list of raw arguments, i.e. # including any separator arguments (`--`). def initialize(raw_arguments) super(raw_arguments.reject { |a| '--' == a }) @raw_arguments = raw_arguments end # @return [Array] The arguments, including any separator arguments # (`--`) def raw @raw_arguments end end end cri-2.7.0/lib/cri/commands/0000755000004100000410000000000012530447725015462 5ustar www-datawww-datacri-2.7.0/lib/cri/commands/basic_root.rb0000644000004100000410000000022612530447725020133 0ustar www-datawww-data# encoding: utf-8 flag :h, :help, 'show help for this command' do |_value, cmd| puts cmd.help exit 0 end subcommand Cri::Command.new_basic_help cri-2.7.0/lib/cri/commands/basic_help.rb0000644000004100000410000000146212530447725020103 0ustar www-datawww-data# encoding: utf-8 name 'help' usage 'help [command_name]' summary 'show help' description <<-EOS 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 commandline options. When a command is given, a command description as well as command-specific commandline options are shown. EOS flag :v, :verbose, 'show more detailed help' run do |opts, args, cmd| if cmd.supercommand.nil? fail 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.7.0/lib/cri/command.rb0000644000004100000410000002605312530447725015632 0ustar www-datawww-data# encoding: utf-8 module Cri # Cri::Command represents a command that can be executed on the commandline. # It is also used for the commandline 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 OptionParserPartitioningDelegate # 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::OptionParser] option_parser The option parser # # @return [void] def option_added(_key, _value, _option_parser) end # Called when an argument is parsed. # # @param [String] argument The argument # # @param [Cri::OptionParser] option_parser The option parser # # @return [void] def argument_added(argument, option_parser) @last_argument = argument option_parser.stop 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_method :subcommands, :commands # @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_method :hidden?, :hidden # @return [Array] The list of option definitions attr_accessor :option_definitions # @return [Proc] The block that should be executed when invoking this # command (ignored for commands with subcommands) attr_accessor :block # 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 # 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 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 block.call(dsl) end self end # @return [Hash] 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 block.call(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) commands = commands_named(name) if commands.size < 1 $stderr.puts "#{self.name}: unknown command '#{name}'\n" exit 1 elsif commands.size > 1 $stderr.puts "#{self.name}: '#{name}' is ambiguous:" $stderr.puts " #{commands.map { |c| c.name }.sort.join(' ') }" exit 1 else commands[0] end end # Runs the command with the given commandline 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 = {}) # 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? $stderr.puts "#{name}: no command given" exit 1 end subcommand = command_named(subcmd_name) # Run subcommand.run(opts_and_args_after_subcmd, opts_before_subcmd) end end # Runs the actual command with the given commandline 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 = {}) # Parse parser = Cri::OptionParser.new( opts_and_args, global_option_definitions) handle_parser_errors_while { parser.run } local_opts = parser.options global_opts = parent_opts.merge(parser.options) args = parser.arguments # Handle options handle_options(local_opts) # Execute if block.nil? fail NotImplementedError, "No implementation available for '#{name}'" end block.call(global_opts, args, self) 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_def = global_option_definitions.find { |o| (o[:long] || o[:short]) == key.to_s } block = opt_def[:block] block.call(value, self) if block end end def partition(opts_and_args) # Parse delegate = Cri::Command::OptionParserPartitioningDelegate.new parser = Cri::OptionParser.new(opts_and_args, global_option_definitions) parser.delegate = delegate handle_parser_errors_while { parser.run } # Extract [ parser.options, delegate.last_argument, parser.unprocessed_arguments_and_options, ] end def handle_parser_errors_while(&block) block.call rescue Cri::OptionParser::IllegalOptionError => e $stderr.puts "#{name}: illegal option -- #{e}" exit 1 rescue Cri::OptionParser::OptionRequiresAnArgumentError => e $stderr.puts "#{name}: option requires an argument -- #{e}" exit 1 end end end cri-2.7.0/lib/cri/command_dsl.rb0000644000004100000410000001655212530447725016477 0ustar www-datawww-data# encoding: utf-8 module Cri # The command DSL is a class that is used for building and modifying # commands. class CommandDSL # @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 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 { |a| a.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 # 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, params = {}, &block) requiredness = params.fetch(:argument, :forbidden) multiple = params.fetch(:multiple, false) hidden = params.fetch(:hidden, false) if short.nil? && long.nil? fail ArgumentError, 'short and long options cannot both be nil' end @command.option_definitions << { :short => short.nil? ? nil : short.to_s, :long => long.nil? ? nil : long.to_s, :desc => desc, :argument => requiredness, :multiple => multiple, :block => block, :hidden => hidden, } end alias_method :opt, :option # 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] # # @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_method :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] # # @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) fail 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.7.0/lib/cri/version.rb0000644000004100000410000000012312530447725015667 0ustar www-datawww-data# encoding: utf-8 module Cri # The current Cri version. VERSION = '2.7.0' end cri-2.7.0/lib/cri/option_parser.rb0000644000004100000410000002146612530447725017103 0ustar www-datawww-data# encoding: utf-8 module Cri # Cri::OptionParser is used for parsing commandline options. # # Option definitions are hashes with the keys `:short`, `:long` and # `:argument` (optionally `:description` but this is not used by the # option parser, only by the help generator). `:short` is the short, # one-character option, without the `-` prefix. `:long` is the long, # multi-character option, without the `--` prefix. `:argument` can be # :required (if an argument should be provided to the option), :optional # (if an argument may be provided) or :forbidden (if an argument should # not be provided). # # A sample array of definition hashes could look like this: # # [ # { :short => 'a', :long => 'all', :argument => :forbidden, :multiple => true }, # { :short => 'p', :long => 'port', :argument => :required, :multiple => false }, # ] # # For example, the following commandline options (which should not be # passed as a string, but as an array of strings): # # foo -xyz -a hiss -s -m please --level 50 --father=ani -n luke squeak # # with the following option definitions: # # [ # { :short => 'x', :long => 'xxx', :argument => :forbidden }, # { :short => 'y', :long => 'yyy', :argument => :forbidden }, # { :short => 'z', :long => 'zzz', :argument => :forbidden }, # { :short => 'a', :long => 'all', :argument => :forbidden }, # { :short => 's', :long => 'stuff', :argument => :optional }, # { :short => 'm', :long => 'more', :argument => :optional }, # { :short => 'l', :long => 'level', :argument => :required }, # { :short => 'f', :long => 'father', :argument => :required }, # { :short => 'n', :long => 'name', :argument => :required } # ] # # will be translated into: # # { # :arguments => [ 'foo', 'hiss', 'squeak' ], # :options => { # :xxx => true, # :yyy => true, # :zzz => true, # :all => true, # :stuff => true, # :more => 'please', # :level => '50', # :father => 'ani', # :name => 'luke' # } # } class OptionParser # Error that will be raised when an unknown option is encountered. class IllegalOptionError < Cri::Error 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 # @return [Array] The arguments that have already been parsed, including # the -- separator. attr_reader :raw_arguments # 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 # Parses the commandline arguments. See the instance `parse` method for # details. # # @param [Array] arguments_and_options An array containing the # commandline arguments (will probably be `ARGS` for a root command) # # @param [Array] definitions An array of option definitions # # @return [Cri::OptionParser] The option parser self def self.parse(arguments_and_options, definitions) new(arguments_and_options, definitions).run end # Creates a new parser with the given options/arguments and definitions. # # @param [Array] arguments_and_options An array containing the # commandline arguments (will probably be `ARGS` for a root command) # # @param [Array] definitions An array of option definitions def initialize(arguments_and_options, definitions) @unprocessed_arguments_and_options = arguments_and_options.dup @definitions = definitions @options = {} @raw_arguments = [] @running = false @no_more_options = false end # Returns the arguments 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 [Array] The already parsed arguments. def arguments ArgumentArray.new(@raw_arguments).freeze 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 commandline 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::OptionParser] 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 private def handle_dashdash(e) add_argument(e) @no_more_options = true end def handle_dashdash_option(e) # Get option key, and option value if included if e =~ /^--([^=]+)=(.+)$/ option_key = Regexp.last_match[1] option_value = Regexp.last_match[2] else option_key = e[2..-1] option_value = nil end # Find definition definition = @definitions.find { |d| d[:long] == option_key } fail IllegalOptionError.new(option_key) if definition.nil? if [:required, :optional].include?(definition[:argument]) # Get option value if necessary if option_value.nil? option_value = find_option_value(definition, option_key) end # Store option add_option(definition, option_value) else # Store option add_option(definition, true) end end def handle_dash_option(e) # Get option keys option_keys = e[1..-1].scan(/./) # For each key option_keys.each do |option_key| # Find definition definition = @definitions.find { |d| d[:short] == option_key } fail IllegalOptionError.new(option_key) if definition.nil? if option_keys.length > 1 && definition[:argument] == :required # This is a combined option and it requires an argument, so complain fail OptionRequiresAnArgumentError.new(option_key) elsif [:required, :optional].include?(definition[:argument]) # Get option value option_value = find_option_value(definition, option_key) # Store option add_option(definition, option_value) else # Store option add_option(definition, true) end end end def find_option_value(definition, option_key) option_value = @unprocessed_arguments_and_options.shift if option_value.nil? || option_value =~ /^-/ if definition[:argument] == :required fail OptionRequiresAnArgumentError.new(option_key) else @unprocessed_arguments_and_options.unshift(option_value) option_value = true end end option_value end def add_option(definition, value) key = (definition[:long] || definition[:short]).to_sym if definition[:multiple] options[key] ||= [] options[key] << value else options[key] = value end delegate.option_added(key, value, self) unless delegate.nil? end def add_argument(value) @raw_arguments << value unless '--' == value delegate.argument_added(value, self) unless delegate.nil? end end end end cri-2.7.0/lib/cri/platform.rb0000644000004100000410000000123412530447725016032 0ustar www-datawww-data# encoding: utf-8 module Cri 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.7.0/lib/cri/command_runner.rb0000644000004100000410000000237612530447725017225 0ustar www-datawww-data# encoding: utf-8 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 fail NotImplementedError, 'Cri::CommandRunner subclasses must implement #run' end end end cri-2.7.0/metadata.yml0000644000004100000410000000467612530447725014656 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: cri version: !ruby/object:Gem::Version version: 2.7.0 platform: ruby authors: - Denis Defreyne autorequire: bindir: bin cert_chain: [] date: 2015-04-29 00:00:00.000000000 Z dependencies: - !ruby/object:Gem::Dependency name: colored requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.2' type: :runtime prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.2' - !ruby/object:Gem::Dependency name: bundler requirement: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.6' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - "~>" - !ruby/object:Gem::Version version: '1.6' description: Cri allows building easy-to-use commandline interfaces with support for subcommands. email: denis.defreyne@stoneship.org executables: [] extensions: [] extra_rdoc_files: - LICENSE - README.adoc - NEWS.md files: - Gemfile - Gemfile.lock - LICENSE - NEWS.md - README.adoc - Rakefile - cri.gemspec - lib/cri.rb - lib/cri/argument_array.rb - lib/cri/command.rb - lib/cri/command_dsl.rb - lib/cri/command_runner.rb - lib/cri/commands/basic_help.rb - lib/cri/commands/basic_root.rb - lib/cri/core_ext.rb - lib/cri/core_ext/string.rb - lib/cri/help_renderer.rb - lib/cri/option_parser.rb - lib/cri/platform.rb - lib/cri/string_formatter.rb - lib/cri/version.rb - test/helper.rb - test/test_argument_array.rb - test/test_base.rb - test/test_basic_help.rb - test/test_basic_root.rb - test/test_command.rb - test/test_command_dsl.rb - test/test_command_runner.rb - test/test_option_parser.rb - test/test_string_formatter.rb homepage: http://stoneship.org/software/cri/ licenses: - MIT metadata: {} post_install_message: rdoc_options: - "--main" - README.adoc require_paths: - lib required_ruby_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' required_rubygems_version: !ruby/object:Gem::Requirement requirements: - - ">=" - !ruby/object:Gem::Version version: '0' requirements: [] rubyforge_project: rubygems_version: 2.4.6 signing_key: specification_version: 4 summary: a library for building easy-to-use commandline tools test_files: [] has_rdoc: cri-2.7.0/test/0000755000004100000410000000000012530447725013315 5ustar www-datawww-datacri-2.7.0/test/test_command_dsl.rb0000644000004100000410000001570112530447725017165 0ustar www-datawww-data# encoding: utf-8 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 }, { :short => 'b', :long => 'bbb', :desc => 'opt b', :argument => :required, :multiple => false, :hidden => false, :block => nil }, { :short => 'c', :long => 'ccc', :desc => 'opt c', :argument => :optional, :multiple => false, :hidden => false, :block => nil }, { :short => 'd', :long => 'ddd', :desc => 'opt d', :argument => :forbidden, :multiple => false, :hidden => false, :block => nil }, { :short => 'e', :long => 'eee', :desc => 'opt e', :argument => :forbidden, :multiple => false, :hidden => false, :block => nil }, { :short => 'f', :long => 'fff', :desc => 'opt f', :argument => :forbidden, :multiple => false, :hidden => true, :block => nil }, ]) actual_option_definitions = Set.new(command.option_definitions) 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 }, { :short => nil, :long => 'long', :desc => 'long', :argument => :forbidden, :multiple => false, :hidden => false, :block => nil }, ]) actual_option_definitions = Set.new(command.option_definitions) 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 }, { :short => 'r', :long => 'required', :desc => 'req', :argument => :required, :multiple => true, :hidden => false, :block => nil }, { :short => 'o', :long => 'optional', :desc => 'opt', :argument => :optional, :multiple => true, :hidden => false, :block => nil }, ]) actual_option_definitions = Set.new(command.option_definitions) 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 }, { :short => 'r', :long => 'required', :desc => 'req', :argument => :required, :multiple => false, :hidden => true, :block => nil }, { :short => 'o', :long => 'optional', :desc => 'opt', :argument => :optional, :multiple => false, :hidden => true, :block => nil }, ]) actual_option_definitions = Set.new(command.option_definitions) assert_equal expected_option_definitions, actual_option_definitions 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_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 <<-EOS class Cri::CommandDSLTestCaseCommandRunner < Cri::CommandRunner def run $did_it_work = arguments[0] end end runner Cri::CommandDSLTestCaseCommandRunner EOS command = dsl.command # Check $did_it_work = false command.run(%w( certainly )) assert_equal 'certainly', $did_it_work end end end cri-2.7.0/test/helper.rb0000644000004100000410000000167212530447725015127 0ustar www-datawww-data# encoding: utf-8 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(&block) orig_io = capture_io block.call [$stdout.string, $stderr.string] ensure uncapture_io(*orig_io) end def lines(string) string.scan(/^.*\n/).map { |s| s.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.7.0/test/test_basic_help.rb0000644000004100000410000000274112530447725016776 0ustar www-datawww-data# encoding: utf-8 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.7.0/test/test_string_formatter.rb0000644000004100000410000000767112530447725020305 0ustar www-datawww-data# encoding: utf-8 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.7.0/test/test_argument_array.rb0000644000004100000410000000042612530447725017723 0ustar www-datawww-data# encoding: utf-8 module Cri class ArgumentArrayTestCase < Cri::TestCase def test_initialize arr = Cri::ArgumentArray.new(['foo', 'bar', '--', 'baz']) assert_equal %w(foo bar baz), arr assert_equal ['foo', 'bar', '--', 'baz'], arr.raw end end end cri-2.7.0/test/test_option_parser.rb0000644000004100000410000002133712530447725017573 0ustar www-datawww-data# encoding: utf-8 module Cri class OptionParserTestCase < Cri::TestCase def test_parse_without_options input = %w( foo bar baz ) definitions = [] parser = Cri::OptionParser.parse(input, definitions) assert_equal({}, parser.options) assert_equal(%w(foo bar baz), parser.arguments) end def test_parse_with_invalid_option input = %w( foo -x ) definitions = [] assert_raises(Cri::OptionParser::IllegalOptionError) do Cri::OptionParser.parse(input, definitions) end end def test_parse_with_unused_options input = %w( foo ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :forbidden }, ] parser = Cri::OptionParser.parse(input, definitions) assert(!parser.options[:aaa]) end def test_parse_with_long_valueless_option input = %w( foo --aaa bar ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :forbidden }, ] parser = Cri::OptionParser.parse(input, definitions) assert(parser.options[:aaa]) assert_equal(%w(foo bar), parser.arguments) end def test_parse_with_long_valueful_option input = %w( foo --aaa xxx bar ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :required }, ] parser = Cri::OptionParser.parse(input, definitions) assert_equal({ :aaa => 'xxx' }, parser.options) assert_equal(%w(foo bar), parser.arguments) end def test_parse_with_long_valueful_equalsign_option input = %w( foo --aaa=xxx bar ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :required }, ] parser = Cri::OptionParser.parse(input, definitions) assert_equal({ :aaa => 'xxx' }, parser.options) assert_equal(%w(foo bar), parser.arguments) end def test_parse_with_long_valueful_option_with_missing_value input = %w( foo --aaa ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :required }, ] assert_raises(Cri::OptionParser::OptionRequiresAnArgumentError) do Cri::OptionParser.parse(input, definitions) end end def test_parse_with_two_long_valueful_options input = %w( foo --all --port 2 ) definitions = [ { :long => 'all', :short => 'a', :argument => :required }, { :long => 'port', :short => 'p', :argument => :required }, ] assert_raises(Cri::OptionParser::OptionRequiresAnArgumentError) do Cri::OptionParser.parse(input, definitions) end end def test_parse_with_long_valueless_option_with_optional_value input = %w( foo --aaa ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :optional }, ] parser = Cri::OptionParser.parse(input, definitions) assert(parser.options[:aaa]) assert_equal(['foo'], parser.arguments) end def test_parse_with_long_valueful_option_with_optional_value input = %w( foo --aaa xxx ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :optional }, ] parser = Cri::OptionParser.parse(input, definitions) assert_equal({ :aaa => 'xxx' }, parser.options) assert_equal(['foo'], parser.arguments) end def test_parse_with_long_valueless_option_with_optional_value_and_more_options input = %w( foo --aaa -b -c ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :optional }, { :long => 'bbb', :short => 'b', :argument => :forbidden }, { :long => 'ccc', :short => 'c', :argument => :forbidden }, ] parser = Cri::OptionParser.parse(input, definitions) assert(parser.options[:aaa]) assert(parser.options[:bbb]) assert(parser.options[:ccc]) assert_equal(['foo'], parser.arguments) end def test_parse_with_short_valueless_options input = %w( foo -a bar ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :forbidden }, ] parser = Cri::OptionParser.parse(input, definitions) assert(parser.options[:aaa]) assert_equal(%w(foo bar), parser.arguments) end def test_parse_with_short_valueful_option_with_missing_value input = %w( foo -a ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :required }, ] assert_raises(Cri::OptionParser::OptionRequiresAnArgumentError) do Cri::OptionParser.parse(input, definitions) end end def test_parse_with_short_combined_valueless_options input = %w( foo -abc bar ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :forbidden }, { :long => 'bbb', :short => 'b', :argument => :forbidden }, { :long => 'ccc', :short => 'c', :argument => :forbidden }, ] parser = Cri::OptionParser.parse(input, definitions) assert(parser.options[:aaa]) assert(parser.options[:bbb]) assert(parser.options[:ccc]) assert_equal(%w(foo bar), parser.arguments) end def test_parse_with_short_combined_valueful_options_with_missing_value input = %w( foo -abc bar ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :required }, { :long => 'bbb', :short => 'b', :argument => :forbidden }, { :long => 'ccc', :short => 'c', :argument => :forbidden }, ] assert_raises(Cri::OptionParser::OptionRequiresAnArgumentError) do Cri::OptionParser.parse(input, definitions) end end def test_parse_with_two_short_valueful_options input = %w( foo -a -p 2 ) definitions = [ { :long => 'all', :short => 'a', :argument => :required }, { :long => 'port', :short => 'p', :argument => :required }, ] assert_raises(Cri::OptionParser::OptionRequiresAnArgumentError) do Cri::OptionParser.parse(input, definitions) end end def test_parse_with_short_valueless_option_with_optional_value input = %w( foo -a ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :optional }, ] parser = Cri::OptionParser.parse(input, definitions) assert(parser.options[:aaa]) assert_equal(['foo'], parser.arguments) end def test_parse_with_short_valueful_option_with_optional_value input = %w( foo -a xxx ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :optional }, ] parser = Cri::OptionParser.parse(input, definitions) assert_equal({ :aaa => 'xxx' }, parser.options) assert_equal(['foo'], parser.arguments) end def test_parse_with_short_valueless_option_with_optional_value_and_more_options input = %w( foo -a -b -c ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :optional }, { :long => 'bbb', :short => 'b', :argument => :forbidden }, { :long => 'ccc', :short => 'c', :argument => :forbidden }, ] parser = Cri::OptionParser.parse(input, definitions) assert(parser.options[:aaa]) assert(parser.options[:bbb]) assert(parser.options[:ccc]) assert_equal(['foo'], parser.arguments) end def test_parse_with_single_hyphen input = %w( foo - bar ) definitions = [] parser = Cri::OptionParser.parse(input, definitions) assert_equal({}, parser.options) assert_equal(['foo', '-', 'bar'], parser.arguments) end def test_parse_with_end_marker input = %w( foo bar -- -x --yyy -abc ) definitions = [] parser = Cri::OptionParser.parse(input, definitions) assert_equal({}, parser.options) assert_equal(['foo', 'bar', '-x', '--yyy', '-abc'], parser.arguments) assert_equal(['foo', 'bar', '--', '-x', '--yyy', '-abc'], parser.arguments.raw) end def test_parse_with_end_marker_between_option_key_and_value input = %w( foo --aaa -- zzz ) definitions = [ { :long => 'aaa', :short => 'a', :argument => :required }, ] assert_raises(Cri::OptionParser::OptionRequiresAnArgumentError) do Cri::OptionParser.parse(input, definitions) end end def test_parse_with_multiple_options input = %w( foo -o test -o test2 -v -v -v) definitions = [ { :long => 'long', :short => 'o', :argument => :required, :multiple => true }, { :long => 'verbose', :short => 'v', :multiple => true }, ] parser = Cri::OptionParser.parse(input, definitions) assert_equal(%w(test test2), parser.options[:long]) assert_equal(3, parser.options[:verbose].size) end end end cri-2.7.0/test/test_command_runner.rb0000644000004100000410000000137412530447725017715 0ustar www-datawww-data# encoding: utf-8 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.7.0/test/test_base.rb0000644000004100000410000000014712530447725015615 0ustar www-datawww-data# encoding: utf-8 module Cri class BaseTestCase < Cri::TestCase def test_stub end end end cri-2.7.0/test/test_basic_root.rb0000644000004100000410000000053012530447725017023 0ustar www-datawww-data# encoding: utf-8 module Cri class BasicRootTestCase < Cri::TestCase def test_run_with_help cmd = Cri::Command.new_basic_root stdout, _stderr = capture_io_while do assert_raises SystemExit do cmd.run(%w( -h )) end end assert stdout =~ /COMMANDS.*\n.*help.*show help/ end end end cri-2.7.0/test/test_command.rb0000644000004100000410000003577412530447725016337 0ustar www-datawww-data# encoding: utf-8 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' 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 assert_raises SystemExit do simple_cmd.run(%w( -b )) end 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 assert_raises SystemExit do simple_cmd.run(%w( -z )) end end assert_equal [], lines(out) assert_equal ['moo: illegal option -- z'], 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 assert_raises SystemExit do nested_cmd.run(%w()) end 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 assert_raises SystemExit do nested_cmd.run(%w( oogabooga )) end 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 assert_raises SystemExit do nested_cmd.run(%w( s )) end 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_def = cmd.option_definitions.to_a[0] assert_equal 'help', opt_def[: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(',')} args.raw=#{args.raw.join(',')}" end end out, _err = capture_io_while do cmd.run(%w( foo -- bar )) end assert_equal "args=foo,bar args.raw=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(',')} args.raw=#{arguments.raw.join(',')}" end end) end out, _err = capture_io_while do cmd.run(%w( foo -- bar )) end assert_equal "args=foo,bar args.raw=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 end end cri-2.7.0/LICENSE0000644000004100000410000000206312530447725013344 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.7.0/README.adoc0000644000004100000410000001671312530447725014133 0ustar www-datawww-data= Cri = link:http://rubygems.org/gems/cri[image:http://img.shields.io/gem/v/cri.svg[]] link:https://travis-ci.org/ddfreyne/cri[image:http://img.shields.io/travis/ddfreyne/cri.svg[]] link:https://coveralls.io/r/ddfreyne/cri[image:http://img.shields.io/coveralls/ddfreyne/cri.svg[]] link:https://codeclimate.com/github/ddfreyne/cri[image:http://img.shields.io/codeclimate/github/ddfreyne/cri.svg[]] link:http://inch-ci.org/github/ddfreyne/cri/[image:http://inch-ci.org/github/ddfreyne/cri.svg[]] Cri is a library for building easy-to-use commandline tools with support for nested commands. == Usage == The central concept in Cri is the _command_, which has option definitions as well as code for actually executing itself. In Cri, the commandline tool itself is a command as well. Here’s a sample command definition: [source,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 commandline tool itself), the command could be called like this: [source,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: [source,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 commandline 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: [source,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 -------------------------------------------------------------------------------- Options can be defined using the following methods: * `Cri::CommandDSL#option` or `Cri::CommandDSL#opt` * `Cri::CommandDSL#flag` (implies no arguments passed to option) * `Cri::CommandDSL#required` (implies required argument) * `Cri::CommandDSL#optional` (implies optional argument) All these methods take the short option form as their first argument, and a long option form as their second argument. Either the short or the long form can be nil, but not both (because that would not make any sense). In the example above, the `--more` option has no short form. Each of the above methods also take a block, which will be executed when the option is found. The argument to the block are the option value (`true` in case the option does not have an argument) and the command. ==== Multivalued options ==== Each of these four methods take a `:multiple` option. 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: [source,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: [source,ruby] -------------------------------------------------------------------------------- flag :v, :verbose, 'be verbose (use up to three times)', multiple: true -------------------------------------------------------------------------------- === The run block === The last part of the command defines the execution itself: [source,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. Instead of defining a run block, it is possible to declare a class, the _command runner_ class (`Cri::CommandRunner`) that will perform the actual execution of the command. This makes it easier to break up large run blocks into manageable pieces. === Subcommands === Commands can have subcommands. For example, the `git` commandline 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: [source,ruby] -------------------------------------------------------------------------------- root_cmd.add_command(cmd_add) root_cmd.add_command(cmd_commit) root.cmd.add_command(cmd_init) -------------------------------------------------------------------------------- == Contributors == * Toon Willems * Ken Coar Thanks for Lee “injekt” Jarvis for link:https://github.com/injekt/slop[Slop], which has inspired the design of Cri 2.0. cri-2.7.0/cri.gemspec0000644000004100000410000000154312530447725014463 0ustar www-datawww-data# encoding: utf-8 $LOAD_PATH.unshift(File.expand_path('../lib/', __FILE__)) require 'cri/version' Gem::Specification.new do |s| s.name = 'cri' s.version = Cri::VERSION s.homepage = 'http://stoneship.org/software/cri/' # TODO: CREATE A WEB SITE YOU SILLY PERSON s.summary = 'a library for building easy-to-use commandline tools' s.description = 'Cri allows building easy-to-use commandline 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.add_dependency('colored', '~> 1.2') s.add_development_dependency('bundler', '~> 1.6') s.rdoc_options = ['--main', 'README.adoc'] s.extra_rdoc_files = ['LICENSE', 'README.adoc', 'NEWS.md'] end