cri-2.4.1/0000755000004100000410000000000012261202372012320 5ustar www-datawww-datacri-2.4.1/Rakefile0000644000004100000410000000142712261202372013771 0ustar www-datawww-data# encoing: utf-8 ##### Requirements # Rake etc require 'rake' require 'minitest' require 'minitest/unit' # Cri itself $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__) + '/lib')) require 'cri' ##### Documentation 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 ##### Testing desc 'Runs all tests' task :test do ENV['QUIET'] ||= 'true' $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__))) MiniTest.autorun require 'test/helper.rb' test_files = Dir['test/test_*.rb'] test_files.each { |f| require f } end task :default => :test cri-2.4.1/NEWS.md0000644000004100000410000000160512261202372013420 0ustar www-datawww-dataCri News ======== 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 [Ken Coar] * Fixed wrap-and-indent behavior [Ken Coar] * Moved version information into `cri/version` 2.3.0 ----- * Added colors * 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.4.1/Gemfile.lock0000644000004100000410000000037112261202372014543 0ustar www-datawww-dataPATH remote: . specs: cri (2.4.1) colored (>= 1.2) GEM remote: https://rubygems.org/ specs: colored (1.2) minitest (5.0.6) rake (10.1.0) yard (0.8.7) PLATFORMS ruby DEPENDENCIES cri! minitest rake yard cri-2.4.1/Gemfile0000644000004100000410000000004612261202372013613 0ustar www-datawww-datasource 'https://rubygems.org' gemspec cri-2.4.1/lib/0000755000004100000410000000000012261202372013066 5ustar www-datawww-datacri-2.4.1/lib/cri.rb0000644000004100000410000000136612261202372014176 0ustar www-datawww-data# encoding: utf-8 require 'cri/version' 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 'CommandDSL', 'cri/command_dsl' autoload 'CommandRunner', 'cri/command_runner' autoload 'OptionParser', 'cri/option_parser' end require 'set' require 'cri/core_ext' cri-2.4.1/lib/cri/0000755000004100000410000000000012261202372013643 5ustar www-datawww-datacri-2.4.1/lib/cri/core_ext.rb0000644000004100000410000000021112261202372015772 0ustar www-datawww-data# encoding: utf-8 module Cri::CoreExtensions end require 'cri/core_ext/string' class String include Cri::CoreExtensions::String end cri-2.4.1/lib/cri/core_ext/0000755000004100000410000000000012261202372015453 5ustar www-datawww-datacri-2.4.1/lib/cri/core_ext/string.rb0000644000004100000410000000423212261202372017307 0ustar www-datawww-data# encoding: utf-8 require 'colored' module Cri::CoreExtensions module String # Extracts individual paragraphs (separated by two newlines). # # @return [Array] A list of paragraphs in the string def to_paragraphs lines = self.scan(/([^\n]+\n|[^\n]*$)/).map { |s| s[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 [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. # # @return [String] The word-wrapped and indented string def wrap_and_indent(width, indentation) indented_width = width - indentation indent = ' ' * indentation # Split into paragraphs paragraphs = self.to_paragraphs # Wrap and indent each paragraph 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") end # @return [String] The string, formatted to be used as a title in a section # in the help def formatted_as_title self.upcase.red.bold end # @return [String] The string, formatted to be used as the name of a command # in the help def formatted_as_command self.green end # @return [String] The string, formatted to be used as an option definition # of a command in the help def formatted_as_option self.yellow end end end cri-2.4.1/lib/cri/commands/0000755000004100000410000000000012261202372015444 5ustar www-datawww-datacri-2.4.1/lib/cri/commands/basic_root.rb0000644000004100000410000000022512261202372020114 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.4.1/lib/cri/commands/basic_help.rb0000644000004100000410000000155712261202372020072 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? raise NoHelpAvailableError, "No help available because the help command has no supercommand" end is_verbose = opts.fetch(:verbose, false) if args.empty? puts cmd.supercommand.help(:verbose => is_verbose) elsif args.size == 1 puts cmd.supercommand.command_named(args[0]).help(:verbose => is_verbose) else $stderr.puts cmd.usage exit 1 end end cri-2.4.1/lib/cri/command.rb0000644000004100000410000003303312261202372015610 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' self.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' self.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 self.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? && !self.block.nil?) run_this(opts_and_args, parent_opts) else # Handle options self.handle_options(opts_before_subcmd) # Get command if subcmd_name.nil? $stderr.puts "#{name}: no command given" exit 1 end subcommand = self.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, self.global_option_definitions) self.handle_parser_errors_while { parser.run } local_opts = parser.options global_opts = parent_opts.merge(parser.options) args = parser.arguments # Handle options self.handle_options(local_opts) # Execute if self.block.nil? raise NotImplementedError, "No implementation available for '#{self.name}'" end self.block.call(global_opts, args, self) end # @return [String] The help text for this command def help(params={}) is_verbose = params.fetch(:verbose, false) text = '' # Append name and summary if summary text << "name".formatted_as_title << "\n" text << " #{name.formatted_as_command} - #{summary}" << "\n" unless aliases.empty? text << " aliases: " << aliases.map { |a| a.formatted_as_command }.join(' ') << "\n" end end # Append usage if usage path = [ self.supercommand ] path.unshift(path[0].supercommand) until path[0].nil? formatted_usage = usage.gsub(/^([^\s]+)/) { |m| m.formatted_as_command } full_usage = path[1..-1].map { |c| c.name.formatted_as_command + ' ' }.join + formatted_usage text << "\n" text << "usage".formatted_as_title << "\n" text << full_usage.wrap_and_indent(78, 4) << "\n" end # Append long description if description text << "\n" text << "description".formatted_as_title << "\n" text << description.wrap_and_indent(78, 4) + "\n" end # Append subcommands unless self.subcommands.empty? text << "\n" text << (self.supercommand ? 'subcommands' : 'commands').formatted_as_title text << "\n" shown_subcommands = self.subcommands.select { |c| !c.hidden? || is_verbose } length = shown_subcommands.map { |c| c.name.formatted_as_command.size }.max # Command shown_subcommands.sort_by { |cmd| cmd.name }.each do |cmd| text << sprintf(" %-#{length+4}s %s\n", cmd.name.formatted_as_command, cmd.summary) end # Hidden notice if !is_verbose diff = self.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 # Append options groups = { 'options' => self.option_definitions } if self.supercommand groups["options for #{self.supercommand.name}"] = self.supercommand.global_option_definitions end length = groups.values.inject(&:+).map { |o| o[:long].to_s.size }.max groups.keys.sort.each do |name| defs = groups[name] unless defs.empty? text << "\n" text << "#{name}".formatted_as_title text << "\n" ordered_defs = defs.sort_by { |x| x[:short] || x[:long] } ordered_defs.each do |opt_def| text << sprintf( " %-2s %-#{length+6}s", opt_def[:short] ? ('-' + opt_def[:short]) : '', opt_def[:long] ? ('--' + opt_def[:long]) : '').formatted_as_option text << opt_def[:desc] << "\n" end end end text end # Compares this command's name to the other given command's name. def <=>(other) self.name <=> other.name end protected 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 self.handle_parser_errors_while { parser.run } parser # Extract [ parser.options, delegate.last_argument, parser.unprocessed_arguments_and_options ] end def handle_parser_errors_while(&block) begin 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 end cri-2.4.1/lib/cri/command_dsl.rb0000644000004100000410000001361212261202372016453 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 # @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 # @return [Cri::Command] The built command def command @command 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 # # @return [void] def option(short, long, desc, params={}, &block) requiredness = params[:argument] || :forbidden self.add_option(short, long, desc, requiredness, block) 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 # # @return [void] # # @see {#option} def required(short, long, desc, &block) self.add_option(short, long, desc, :required, 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 # # @return [void] # # @see {#option} def flag(short, long, desc, &block) self.add_option(short, long, desc, :forbidden, 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 # # @return [void] # # @see {#option} def optional(short, long, desc, &block) self.add_option(short, long, desc, :optional, 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). # # @return [void] def run(&block) unless block.arity != 2 || block.arity != 3 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 protected def add_option(short, long, desc, argument, block) if short.nil? && long.nil? raise 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 => argument, :block => block } end end end cri-2.4.1/lib/cri/version.rb0000644000004100000410000000010212261202372015646 0ustar www-datawww-datamodule Cri # The current Cri version. VERSION = '2.4.1' end cri-2.4.1/lib/cri/option_parser.rb0000644000004100000410000002137212261202372017061 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 }, # { :short => 'p', :long => 'port', :argument => :required }, # ] # # 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 # 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. attr_reader :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) self.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 = {} @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 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? # Handle end-of-options marker if e == '--' @no_more_options = true # Handle incomplete options elsif e =~ /^--./ and !@no_more_options # Get option key, and option value if included if e =~ /^--([^=]+)=(.+)$/ option_key = $1 option_value = $2 else option_key = e[2..-1] option_value = nil end # Find definition definition = @definitions.find { |d| d[:long] == option_key } raise IllegalOptionError.new(option_key) if definition.nil? if [ :required, :optional ].include?(definition[:argument]) # Get option value if necessary if option_value.nil? option_value = @unprocessed_arguments_and_options.shift if option_value.nil? || option_value =~ /^-/ if definition[:argument] == :required raise OptionRequiresAnArgumentError.new(option_key) else @unprocessed_arguments_and_options.unshift(option_value) option_value = true end end end # Store option add_option(definition, option_value) else # Store option add_option(definition, true) end # Handle -xyz options elsif e =~ /^-./ and !@no_more_options # 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 } raise IllegalOptionError.new(option_key) if definition.nil? if option_keys.length > 1 and definition[:argument] == :required # This is a combined option and it requires an argument, so complain raise OptionRequiresAnArgumentError.new(option_key) elsif [ :required, :optional ].include?(definition[:argument]) # Get option value option_value = @unprocessed_arguments_and_options.shift if option_value.nil? || option_value =~ /^-/ if definition[:argument] == :required raise OptionRequiresAnArgumentError.new(option_key) else @unprocessed_arguments_and_options.unshift(option_value) option_value = true end end # Store option add_option(definition, option_value) else # Store option add_option(definition, true) end end # Handle normal arguments else add_argument(e) end end self ensure @running = false end private def add_option(definition, value) key = (definition[:long] || definition[:short]).to_sym options[key] = value delegate.option_added(key, value, self) unless delegate.nil? end def add_argument(value) arguments << value delegate.argument_added(value, self) unless delegate.nil? end end end cri-2.4.1/lib/cri/command_runner.rb0000644000004100000410000000226412261202372017203 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 # @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 self.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.4.1/metadata.yml0000644000004100000410000000574112261202372014632 0ustar www-datawww-data--- !ruby/object:Gem::Specification name: cri version: !ruby/object:Gem::Version version: 2.4.1 platform: ruby authors: - Denis Defreyne autorequire: bindir: bin cert_chain: [] date: 2013-11-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: rake requirement: !ruby/object:Gem::Requirement requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: minitest requirement: !ruby/object:Gem::Requirement requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' - !ruby/object:Gem::Dependency name: yard requirement: !ruby/object:Gem::Requirement requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' type: :development prerelease: false version_requirements: !ruby/object:Gem::Requirement requirements: - - ! '>=' - !ruby/object:Gem::Version version: '0' 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.md - NEWS.md files: - Gemfile - Gemfile.lock - LICENSE - NEWS.md - Rakefile - README.md - 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/string.rb - lib/cri/core_ext.rb - lib/cri/option_parser.rb - lib/cri/version.rb - lib/cri.rb - test/helper.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_core_ext.rb - test/test_option_parser.rb - cri.gemspec homepage: http://stoneship.org/software/cri/ licenses: - MIT metadata: {} post_install_message: rdoc_options: - --main - README.md 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.1.11 signing_key: specification_version: 4 summary: a library for building easy-to-use commandline tools test_files: [] has_rdoc: cri-2.4.1/test/0000755000004100000410000000000012261202372013277 5ustar www-datawww-datacri-2.4.1/test/test_command_dsl.rb0000644000004100000410000000733112261202372017147 0ustar www-datawww-data# encoding: utf-8 class Cri::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 required :b, :bbb, 'opt b' optional :c, :ccc, 'opt c' flag :d, :ddd, 'opt d' forbidden :e, :eee, 'opt e' 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, :block => nil }, { :short => 'b', :long => 'bbb', :desc => 'opt b', :argument => :required, :block => nil }, { :short => 'c', :long => 'ccc', :desc => 'opt c', :argument => :optional, :block => nil }, { :short => 'd', :long => 'ddd', :desc => 'opt d', :argument => :forbidden, :block => nil }, { :short => 'e', :long => 'eee', :desc => 'opt e', :argument => :forbidden, :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, :block => nil }, { :short => nil, :long => 'long', :desc => 'long', :argument => :forbidden, :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 assert_raises ArgumentError do dsl.instance_eval do flag 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_runner # Define dsl = Cri::CommandDSL.new dsl.instance_eval <<-EOS class Cri::CommandDSLTestCaseCommandRunner < Cri::CommandRunner def run $works = arguments[0] end end runner Cri::CommandDSLTestCaseCommandRunner EOS command = dsl.command # Check $works = false command.run(%w( certainly )) assert_equal 'certainly', $works end end cri-2.4.1/test/helper.rb0000644000004100000410000000142512261202372015105 0ustar www-datawww-data# encoding: utf-8 require 'stringio' class Cri::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 # Unexpected system exit is unexpected ::MiniTest::Unit::TestCase::PASSTHROUGH_EXCEPTIONS.delete(SystemExit) cri-2.4.1/test/test_basic_help.rb0000644000004100000410000000064212261202372016756 0ustar www-datawww-data# encoding: utf-8 class Cri::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 end cri-2.4.1/test/test_core_ext.rb0000644000004100000410000000614312261202372016477 0ustar www-datawww-data# encoding: utf-8 class Cri::CoreExtTestCase < Cri::TestCase 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 = original.to_paragraphs 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 = original.wrap_and_indent(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 = original.wrap_and_indent(36, 4) 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 = original.wrap_and_indent(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 = original.wrap_and_indent(36, 4) assert_equal expected, actual end end cri-2.4.1/test/test_option_parser.rb0000644000004100000410000001773012261202372017557 0ustar www-datawww-data# encoding: utf-8 class Cri::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([ 'foo', 'bar', 'baz' ], parser.arguments) end def test_parse_with_invalid_option input = %w( foo -x ) definitions = [] result = nil assert_raises(Cri::OptionParser::IllegalOptionError) do parser = Cri::OptionParser.parse(input, definitions) end end def test_parse_without_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([ '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([ '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([ '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 } ] result = nil assert_raises(Cri::OptionParser::OptionRequiresAnArgumentError) do parser = 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 } ] result = nil assert_raises(Cri::OptionParser::OptionRequiresAnArgumentError) do parser = 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([ '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 } ] result = nil assert_raises(Cri::OptionParser::OptionRequiresAnArgumentError) do parser = 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([ '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 } ] result = nil assert_raises(Cri::OptionParser::OptionRequiresAnArgumentError) do parser = 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 } ] result = nil assert_raises(Cri::OptionParser::OptionRequiresAnArgumentError) do parser = 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) 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 parser = Cri::OptionParser.parse(input, definitions) end end end cri-2.4.1/test/test_base.rb0000644000004100000410000000012712261202372015575 0ustar www-datawww-data# encoding: utf-8 class Cri::BaseTestCase < Cri::TestCase def test_stub end end cri-2.4.1/test/test_basic_root.rb0000644000004100000410000000047112261202372017011 0ustar www-datawww-data# encoding: utf-8 class Cri::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 cri-2.4.1/test/test_command.rb0000644000004100000410000002475412261202372016315 0ustar www-datawww-data# encoding: utf-8 class Cri::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| $stdout.puts "Sinking!" 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 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_for_bare_cmd bare_cmd.help end def test_help_with_optional_options 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\[0mlong$/, help) assert_match(/^\e\[33m -s \e\[0mshort$/, 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_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 end cri-2.4.1/LICENSE0000644000004100000410000000206312261202372013326 0ustar www-datawww-dataCopyright (c) 2009 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.4.1/checksums.yaml.gz0000444000004100000410000000065112261202372015610 0ustar www-datawww-dataHURMk0޳$})ĵa$#fnx=z(bNZz[E 23Z׫<5y$^n]|NZ$&çX)` :required run do |opts, args, cmd| stuff = opts[: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: command.run(ARGS) 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 Let’s disect the command definition and start with the first five lines: 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. The next few lines contain the command’s option definitions: 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 forbidden argument) * {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. The last part of the command defines the execution itself: run do |opts, args, cmd| stuff = opts[: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. 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: 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 [Slop][1], which has inspired the design of Cri 2.0. [1]: https://github.com/injekt/slop cri-2.4.1/cri.gemspec0000644000004100000410000000175712261202372014454 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('rake') s.add_development_dependency('minitest') s.add_development_dependency('yard') s.rdoc_options = [ '--main', 'README.md' ] s.extra_rdoc_files = [ 'LICENSE', 'README.md', 'NEWS.md' ] end