gli-2.14.0/0000755000175000017500000000000013017317740011300 5ustar micahmicahgli-2.14.0/.ruby-version0000644000175000017500000000000613017317740013741 0ustar micahmicah2.2.2 gli-2.14.0/lib/0000755000175000017500000000000013017317740012046 5ustar micahmicahgli-2.14.0/lib/gli/0000755000175000017500000000000013017317740012621 5ustar micahmicahgli-2.14.0/lib/gli/argument.rb0000644000175000017500000000046413017317740014774 0ustar micahmicahmodule GLI class Argument #:nodoc: attr_reader :name attr_reader :options def initialize(name,options = []) @name = name @options = options end def optional? @options.include? :optional end def multiple? @options.include? :multiple end end end gli-2.14.0/lib/gli/command_line_token.rb0000644000175000017500000000350413017317740016775 0ustar micahmicahmodule GLI # Abstract base class for a logical element of a command line, mostly so that subclasses can have similar # initialization and interface class CommandLineToken attr_reader :name #:nodoc: attr_reader :aliases #:nodoc: attr_reader :description #:nodoc: attr_reader :long_description #:nodoc: def initialize(names,description,long_description=nil) #:nodoc: @description = description @long_description = long_description @name,@aliases,@names = parse_names(names) end # Sort based on primary name def <=>(other) self.name.to_s <=> other.name.to_s end # Array of the name and aliases, as string def names_and_aliases [self.name,self.aliases].flatten.compact.map(&:to_s) end private # Returns a string of all possible forms # of this flag. Mostly intended for printing # to the user. def all_forms(joiner=', ') forms = all_forms_a forms.join(joiner) end # Handles dealing with the "names" param, parsing # it into the primary name and aliases list def parse_names(names) # Allow strings; convert to symbols names = [names].flatten.map { |name| name.to_sym } names_hash = {} names.each do |name| raise ArgumentError.new("#{name} has spaces; they are not allowed") if name.to_s =~ /\s/ names_hash[self.class.name_as_string(name)] = true end name = names.shift aliases = names.length > 0 ? names : nil [name,aliases,names_hash] end def negatable? false; end def all_forms_a forms = [self.class.name_as_string(name,negatable?)] if aliases forms |= aliases.map { |one_alias| self.class.name_as_string(one_alias,negatable?) }.sort { |one,two| one.length <=> two.length } end forms end end end gli-2.14.0/lib/gli/app.rb0000644000175000017500000003266413017317740013741 0ustar micahmicahrequire 'etc' require 'optparse' require 'gli/dsl' require 'pathname' module GLI # A means to define and parse a command line interface that works as # Git's does, in that you specify global options, a command name, command # specific options, and then command arguments. module App include DSL include AppSupport # Loads ruby files in the load path that start with # +path+, which are presumed to be commands for your executable. # This is useful for decomposing your bin file into different classes, but # can also be used as a plugin mechanism, allowing users to provide additional # commands for your app at runtime. All that being said, it's basically # a glorified +require+. # # path:: a path from which to load .rb files that, presumably, contain commands. If this is an absolute path, # any files in that path are loaded. If not, it is interpretted as relative to somewhere # in the LOAD_PATH. # # == Example: # # # loads *.rb from your app's install - great for decomposing your bin file # commands_from "my_app/commands" # # # loads *.rb files from the user's home dir - great and an extension/plugin mechanism # commands_from File.join(ENV["HOME"],".my_app","plugins") def commands_from(path) if Pathname.new(path).absolute? and File.exist?(path) load_commands(path) else $LOAD_PATH.each do |load_path| commands_path = File.join(load_path,path) load_commands(commands_path) end end end # Describe the overall application/programm. This should be a one-sentence summary # of what your program does that will appear in the help output. # # +description+:: A String of the short description of your program's purpose def program_desc(description=nil) if description @program_desc = description end @program_desc end # Provide a longer description of the program. This can be as long as needed, and use double-newlines # for paragraphs. This will show up in the help output. # # description:: A String for the description def program_long_desc(description=nil) if description @program_long_desc = description end @program_long_desc end # Provide a flag to choose whether to hide or not from the help the undescribed commands. # By default the undescribed commands will be shown in the help. # # hide:: A Bool for hide the undescribed commands def hide_commands_without_desc(hide=nil) unless hide.nil? @hide_commands_without_desc = hide end @hide_commands_without_desc || false end # Use this if the following command should not have the pre block executed. # By default, the pre block is executed before each command and can result in # aborting the call. Using this will avoid that behavior for the following command def skips_pre @skips_pre = true end # Use this if the following command should not have the post block executed. # By default, the post block is executed after each command. # Using this will avoid that behavior for the following command def skips_post @skips_post = true end # Use this if the following command should not have the around block executed. # By default, the around block is executed, but for commands that might not want the # setup to happen, this can be handy def skips_around @skips_around = true end # Sets that this app uses a config file as well as the name of the config file. # # +filename+:: A String representing the path to the file to use for the config file. If it's an absolute # path, this is treated as the path to the file. If it's *not*, it's treated as relative to the user's home # directory as produced by File.expand_path('~'). def config_file(filename) if filename =~ /^\// @config_file = filename else @config_file = File.join(File.expand_path(ENV['HOME']),filename) end commands[:initconfig] = InitConfig.new(@config_file,commands,flags,switches) @commands_declaration_order << commands[:initconfig] @config_file end # Define a block to run after command line arguments are parsed # but before any command is run. If this block raises an exception # the command specified will not be executed. # The block will receive the global-options,command,options, and arguments # If this block evaluates to true, the program will proceed; otherwise # the program will end immediately and exit nonzero def pre(&a_proc) @pre_block = a_proc end # Define a block to run after the command was executed, only # if there was not an error. # The block will receive the global-options,command,options, and arguments def post(&a_proc) @post_block = a_proc end # This inverts the pre/post concept. This is useful when you have a global shared resource that is governed by a block # instead of separate open/close methods. The block you pass here will be given four parameters: # # global options:: the parsed global options # command:: The GLI::Command that the user is going to invoke # options:: the command specific options # args:: unparsed command-line args # code:: a block that you must +call+ to execute the command. # # #help_now! and #exit_now! work as expected; you can abort the command call by simply not calling it. # # You can declare as many #around blocks as you want. They will be called in the order in which they are defined. # # Note that if you declare #around blocks, #pre and #post blocks will still work. The #pre is called first, followed by # the around, followed by the #post. # # Call #skips_around before a command that should not have this hook fired def around(&a_proc) @around_blocks ||= [] @around_blocks << a_proc end # Define a block to run if an error occurs. # The block will receive any Exception that was caught. # It should evaluate to false to avoid the built-in error handling (which basically just # prints out a message). GLI uses a variety of exceptions that you can use to find out what # errors might've occurred during command-line parsing: # * GLI::CustomExit # * GLI::UnknownCommandArgument # * GLI::UnknownGlobalArgument # * GLI::UnknownCommand # * GLI::BadCommandLine def on_error(&a_proc) @error_block = a_proc end # Indicate the version of your application # # +version+:: String containing the version of your application. def version(version) @version = version desc 'Display the program version' switch :version, :negatable => false end # By default, GLI mutates the argument passed to it. This is # consistent with +OptionParser+, but be less than ideal. Since # that value, for scaffolded apps, is +ARGV+, you might want to # refer to the entire command-line via +ARGV+ and thus not want it mutated. def preserve_argv(preserve=true) @preserve_argv = preserve end # Call this with +true+ will cause the +global_options+ and # +options+ passed to your code to be wrapped in # Options, which is a subclass of +OpenStruct+ that adds # [] and []= methods. # # +use_openstruct+:: a Boolean indicating if we should use OpenStruct instead of Hashes def use_openstruct(use_openstruct) @use_openstruct = use_openstruct end # Configure a type conversion not already provided by the underlying OptionParser. # This works more or less like the OptionParser version. It's global. # # object:: the class (or whatever) that triggers the type conversion # block:: the block that will be given the string argument and is expected # to return the converted value # # Example # # accept(Hash) do |value| # result = {} # value.split(/,/).each do |pair| # k,v = pair.split(/:/) # result[k] = v # end # result # end # # flag :properties, :type => Hash def accept(object,&block) accepts[object] = block end # Simpler means of exiting with a custom exit code. This will # raise a CustomExit with the given message and exit code, which will ultimatley # cause your application to exit with the given exit_code as its exit status # Use #help_now! if you want to show the help in addition to the error message # # message:: message to show the user # exit_code:: exit code to exit as, defaults to 1 def exit_now!(message,exit_code=1) raise CustomExit.new(message,exit_code) end # Exit now, showing the user help for the command they executed. Use #exit_now! to just show the error message # # message:: message to indicate how the user has messed up the CLI invocation or nil to just simply show help def help_now!(message=nil) exception = OptionParser::ParseError.new(message) class << exception def exit_code; 64; end end raise exception end # Control how commands and options are sorted in help output. By default, they are sorted alphabetically. # # sort_type:: How you want help commands/options sorted: # +:manually+:: help commands/options are ordered in the order declared. # +:alpha+:: sort alphabetically (default) def sort_help(sort_type) @help_sort_type = sort_type end # Set how help text is wrapped. # # wrap_type:: Symbol indicating how you'd like text wrapped: # +:to_terminal+:: Wrap text based on the width of the terminal (default) # +:verbatim+:: Format text exactly as it was given to the various methods. This is useful if your output has # formatted output, e.g. ascii tables and you don't want it messed with. # +:one_line+:: Do not wrap text at all. This will bring all help content onto one line, removing any newlines # +:tty_only+:: Wrap like +:to_terminal+ if this output is going to a TTY, otherwise don't wrap (like +:one_line+) def wrap_help_text(wrap_type) @help_text_wrap_type = wrap_type end # Control how the SYNOPSIS is formatted. # # format:: one of: # +:full+:: the default, show subcommand options and flags inline # +:terminal+:: if :full would be wider than the terminal, use :compact # +:compact+:: use a simpler and shorter SYNOPSIS. Useful if your app has a lot of options and showing them in the SYNOPSIS makes things more confusing def synopsis_format(format) @synopsis_format_type = format end def program_name(override=nil) #:nodoc: warn "#program_name has been deprecated" end # Sets a default command to run when none is specified on the command line. Note that # if you use this, you won't be able to pass arguments, flags, or switches # to the command when run in default mode. All flags and switches are treated # as global, and any argument will be interpretted as the command name and likely # fail. # # +command+:: Command as a Symbol to run as default def default_command(command) @default_command = command.to_sym end # How to handle subcommand options. In general, you want to set this to +:normal+, which # treats each subcommand as establishing its own namespace for options. This is what # the scaffolding should generate, but it is *not* what GLI 2.5.x and lower apps had as a default. # To maintain backwards compatibility, the default is +:legacy+, which is that all subcommands of # a particular command share a namespace for options, making it impossible for two subcommands # to have options of the same name. def subcommand_option_handling(handling_strategy) @subcommand_option_handling_strategy = handling_strategy end # How to handle argument validation. # # handling_strategy:: One of: # +:loose+:: no argument validation. Use of `arg` or `arg_name` is for documentation purposes only. (Default) # +:strict+:: arguments are validated according to their specification. +action+ blocks may assume # the value of `arguments` matches the specification provided in `arg`. Note that to use # this strategy, you must also be sure that +subcommand_option_handling+ is set. def arguments(handling_strategy) @argument_handling_strategy = handling_strategy end # Enables/Disables command autocomplete, where partially spelled commands are automatically expanded to their full form # # Example: # When enabled, executing 'shake' would execute 'shake_hand' (if no 'shake' command is defined). # When disabled, executing 'shake' would throw an UnknownCommand error # # +boolean+:: Boolean value to enable or disable autocomplete, respectively. True by default. def autocomplete_commands(boolean) @autocomplete = boolean end private def load_commands(path) if File.exist? path Dir.entries(path).sort.each do |entry| file = File.join(path,entry) if file =~ /\.rb$/ require file end end end end end end gli-2.14.0/lib/gli/version.rb0000644000175000017500000000011513017317740014630 0ustar micahmicahmodule GLI unless const_defined? :VERSION VERSION = '2.14.0' end end gli-2.14.0/lib/gli/option_parser_factory.rb0000644000175000017500000000514713017317740017570 0ustar micahmicahmodule GLI # Factory for creating an OptionParser based on app configuration and DSL calls class OptionParserFactory # Create an option parser factory for a command. This has the added # feature of setting up -h and --help on the command if those # options aren't otherwise configured, e.g. to allow todo add --help as an # alternate to todo help add def self.for_command(command,accepts) self.new(command.flags,command.switches,accepts).tap { |factory| add_help_switches_to_command(factory.option_parser,command) } end # Create an OptionParserFactory for the given # flags, switches, and accepts def initialize(flags,switches,accepts) @flags = flags @switches = switches @options_hash = {} @option_parser = OptionParser.new do |opts| self.class.setup_accepts(opts,accepts) self.class.setup_options(opts,@switches,@options_hash) self.class.setup_options(opts,@flags,@options_hash) end end attr_reader :option_parser attr_reader :options_hash def options_hash_with_defaults_set! set_defaults(@flags,@options_hash) set_defaults(@switches,@options_hash) @options_hash end private def set_defaults(options_by_name,options_hash) options_by_name.values.each do |option| option.names_and_aliases.each do |option_name| [option_name,option_name.to_sym].each do |name| options_hash[name] = option.default_value if options_hash[name].nil? end end end end def self.setup_accepts(opts,accepts) accepts.each do |object,block| opts.accept(object) do |arg_as_string| block.call(arg_as_string) end end end def self.setup_options(opts,tokens,options) tokens.each do |ignore,token| opts.on(*token.arguments_for_option_parser) do |arg| token.names_and_aliases.each do |name| if token.kind_of?(Flag) && token.multiple? options[name] ||= [] options[name.to_sym] ||= [] options[name] << arg options[name.to_sym] << arg else options[name] = arg options[name.to_sym] = arg end end end end end def self.add_help_switches_to_command(option_parser,command) help_args = %w(-h --help).reject { |_| command.has_option?(_) } unless help_args.empty? help_args << "Get help for #{command.name}" option_parser.on(*help_args) do raise RequestHelp.new(command) end end end end end gli-2.14.0/lib/gli/command.rb0000644000175000017500000001543313017317740014572 0ustar micahmicahrequire 'gli/command_line_token.rb' require 'gli/dsl.rb' module GLI # A command to be run, in context of global flags and switches. You are given an instance of this class # to the block you use for GLI::DSL#command. This class mixes in GLI::DSL so all of those methods are available # to describe the command, in addition to the methods documented here, most importantly # #action. # # Example: # # command :list do |c| # <- c is an instance of GLI::Command # c.desc 'use long form' # c.switch :l # # c.action do |global,options,args| # # list things here # end # # c.command :tasks do |t| # <- t is an instance of GLI::Command # # this is a "subcommand" of list # # t.action do |global,options,args| # # do whatever list tasks should do # end # end # end # class Command < CommandLineToken include DSL include CommandSupport # Key in an options hash to find the parent's parsed options PARENT = Object.new # Create a new command. # # options:: Keys should be: # +names+:: A String, Symbol, or Array of String or Symbol that represents the name(s) of this command (required). # +description+:: short description of this command as a String # +arguments_name+:: description of the arguments as a String, or nil if this command doesn't take arguments # +long_desc+:: a longer description of the command, possibly with multiple lines. A double line-break is treated # as a paragraph break. No other formatting is respected, though inner whitespace is maintained. # +skips_pre+:: if true, this command advertises that it doesn't want the pre block called first # +skips_post+:: if true, this command advertises that it doesn't want the post block called after it # +skips_around+:: if true, this command advertises that it doesn't want the around block called # +hide_commands_without_desc+:: if true and there isn't a description the command is not going to be shown in the help def initialize(options) super(options[:names],options[:description],options[:long_desc]) @arguments_description = options[:arguments_name] || '' @arguments_options = Array(options[:arguments_options]).flatten @arguments = options[:arguments] || [] @skips_pre = options[:skips_pre] @skips_post = options[:skips_post] @skips_around = options[:skips_around] @hide_commands_without_desc = options[:hide_commands_without_desc] @commands_declaration_order = [] @flags_declaration_order = [] @switches_declaration_order = [] clear_nexts end # Set the default command if this command has subcommands and the user doesn't # provide a subcommand when invoking THIS command. When nil, this will show an error and the help # for this command; when set, the command with this name will be executed. # # +command_name+:: The primary name of the subcommand of this command that should be run by default as a String or Symbol. def default_command(command_name) @default_command = command_name end # Define the action to take when the user executes this command. Every command should either define this # action block, or have subcommands (or both). # # +block+:: A block of code to execute. The block will be given 3 arguments: # +global_options+:: A Hash of the _global_ options specified # by the user, with defaults set and config file values used (if using a config file, see # GLI::App#config_file) # +options+:: A Hash of the command-specific options specified by the # user, with defaults set and config file values used (if using a config file, see # GLI::App#config_file). # +arguments+:: An Array of Strings representing the unparsed command line arguments # The block's result value is not used; raise an exception or use GLI#exit_now! if you need an early exit based # on an error condition # def action(&block) @action = block end # Describes this commands action block when it *also* has subcommands. # In this case, the GLI::DSL#desc value is the general description of the commands # that this command groups, and the value for *this* method documents what # will happen if you omit a subcommand. # # Note that if you omit the action block and specify a subcommand, that subcommand's # description will be used to describe what happens by default. # # desc:: the description of what this command's action block does. # # Example # # desc 'list things' # command :list do |c| # # c.desc 'list tasks' # c.command :tasks do |t| # t.action do |global,options,args| # end # end # # c.desc 'list contexts' # c.command :contexts do |t| # t.action do |global,options,args| # end # end # # c.default_desc 'list both tasks and contexts' # c.action do |global,options,args| # # list everything # end # end # # # > todo help list # NAME # list - List things # # SYNOPSIS # todo [global options] list [command options] # todo [global options] list [command options] tasks # todo [global options] list [command options] contexts # # COMMANDS # - list both tasks and contexts # tasks - list tasks # contexts - list contexts # def default_desc(desc) @default_desc = desc end # Returns true if this command has the given option defined def has_option?(option) #:nodoc: option = option.gsub(/^\-+/,'') ((flags.values.map { |_| [_.name,_.aliases] }) + (switches.values.map { |_| [_.name,_.aliases] })).flatten.map(&:to_s).include?(option) end # Returns full name for help command including parents # # Example # # command :remote do |t| # t.command :add do |global,options,args| # end # end # # @add_command.name_for_help # => ["remote", "add"] # def name_for_help name_array = [name.to_s] command_parent = parent while(command_parent.is_a?(GLI::Command)) do name_array.unshift(command_parent.name.to_s) command_parent = command_parent.parent end name_array end def self.name_as_string(name,negatable=false) #:nodoc: name.to_s end end end gli-2.14.0/lib/gli/flag.rb0000644000175000017500000000606013017317740014061 0ustar micahmicahrequire 'gli/command_line_option.rb' module GLI # Defines a flag, which is to say a switch that takes an argument class Flag < CommandLineOption # :nodoc: # Regexp that is used to see if the flag's argument matches attr_reader :must_match # Type to which we want to cast the values attr_reader :type # Name of the argument that user configured attr_reader :argument_name # Creates a new option # # names:: Array of symbols or strings representing the names of this switch # options:: hash of options: # :desc:: the short description # :long_desc:: the long description # :default_value:: the default value of this option # :arg_name:: the name of the flag's argument, default is "arg" # :must_match:: a regexp that the flag's value must match # :type:: a class to convert the value to # :required:: if true, this flag must be specified on the command line # :multiple:: if true, flag may be used multiple times and values are stored in an array # :mask:: if true, the default value of this flag will not be output in the help. # This is useful for password flags where you might not want to show it # on the command-line. def initialize(names,options) super(names,options) @argument_name = options[:arg_name] || "arg" @must_match = options[:must_match] @type = options[:type] @mask = options[:mask] @required = options[:required] @multiple = options[:multiple] end # True if this flag is required on the command line def required? @required end # True if the flag may be used multiple times. def multiple? @multiple end def safe_default_value if @mask "********" else # This uses @default_value instead of the `default_value` method because # this method is only used for display, and for flags that may be passed # multiple times, we want to display whatever is set in the code as the # the default, or the string "none" rather than displaying an empty # array. @default_value end end # The default value for this flag. Uses the value passed if one is set; # otherwise uses `[]` if the flag support multiple arguments and `nil` if # it does not. def default_value if @default_value @default_value elsif @multiple [] end end def arguments_for_option_parser args = all_forms_a.map { |name| "#{name} VAL" } args << @must_match if @must_match args << @type if @type args end # Returns a string of all possible forms # of this flag. Mostly intended for printing # to the user. def all_forms(joiner=', ') forms = all_forms_a string = forms.join(joiner) if forms[-1] =~ /^\-\-/ string += '=' else string += ' ' end string += @argument_name return string end end end gli-2.14.0/lib/gli/exceptions.rb0000644000175000017500000000563713017317740015342 0ustar micahmicahmodule GLI # Mixed into all exceptions that GLI handles; you can use this to catch # anything that came from GLI intentionally. You can also mix this into non-GLI # exceptions to get GLI's exit behavior. module StandardException def exit_code; 1; end end # Hack to request help from within a command # Will *not* be rethrown when GLI_DEBUG is ON class RequestHelp < StandardError include StandardException def exit_code; 0; end # The command for which the argument was unknown attr_reader :command_in_context def initialize(command_in_context) @command_in_context = command_in_context end end # Indicates that the command line invocation was bad class BadCommandLine < StandardError include StandardException def exit_code; 64; end end class PreconditionFailed < StandardError include StandardException def exit_code; 65; end end # Indicates the bad command line was an unknown command class UnknownCommand < BadCommandLine end # The command issued partially matches more than one command class AmbiguousCommand < BadCommandLine end # Indicates the bad command line was an unknown global argument class UnknownGlobalArgument < BadCommandLine end class CommandException < BadCommandLine # The command for which the argument was unknown attr_reader :command_in_context # +message+:: the error message to show the user # +command+:: the command we were using to parse command-specific options def initialize(message,command_in_context,exit_code=nil) super(message) @command_in_context = command_in_context @exit_code = exit_code end def exit_code @exit_code || super end end class MissingRequiredArgumentsException < BadCommandLine # The command for which the argument was unknown attr_reader :command_in_context # +message+:: the error message to show the user # +command+:: the command we were using to parse command-specific options def initialize(message,command) super(message) @command_in_context = command end end # Indicates the bad command line was an unknown command argument class UnknownCommandArgument < CommandException end # Raise this if you want to use an exit status that isn't the default # provided by GLI. Note that GLI::App#exit_now! might be a bit more to your liking. # # Example: # # raise CustomExit.new("Not connected to DB",-5) unless connected? # raise CustomExit.new("Bad SQL",-6) unless valid_sql?(args[0]) # class CustomExit < StandardError include StandardException attr_reader :exit_code #:nodoc: # Create a custom exit exception # # +message+:: String containing error message to show the user # +exit_code+:: the exit code to use (as an Int), overridding GLI's default def initialize(message,exit_code) super(message) @exit_code = exit_code end end end gli-2.14.0/lib/gli/command_finder.rb0000644000175000017500000000376013017317740016121 0ustar micahmicahmodule GLI class CommandFinder attr_accessor :options DEFAULT_OPTIONS = { :default_command => nil, :autocomplete => true } def initialize(commands, options = {}) self.options = DEFAULT_OPTIONS.merge(options) self.commands_with_aliases = expand_with_aliases(commands) end def find_command(name) name = String(name || options[:default_command]).strip raise UnknownCommand.new("No command name given nor default available") if name == '' command_found = commands_with_aliases.fetch(name) do |command_to_match| if options[:autocomplete] found_match = find_command_by_partial_name(commands_with_aliases, command_to_match) if found_match.kind_of? GLI::Command if ENV["GLI_DEBUG"] == 'true' $stderr.puts "Using '#{name}' as it's is short for #{found_match.name}." $stderr.puts "Set autocomplete false for any command you don't want matched like this" end elsif found_match.kind_of?(Array) && !found_match.empty? raise AmbiguousCommand.new("Ambiguous command '#{name}'. It matches #{found_match.sort.join(',')}") end found_match end end raise UnknownCommand.new("Unknown command '#{name}'") if Array(command_found).empty? command_found end private attr_accessor :commands_with_aliases def expand_with_aliases(commands) expanded = {} commands.each do |command_name, command| expanded[command_name.to_s] = command Array(command.aliases).each do |command_alias| expanded[command_alias.to_s] = command end end expanded end def find_command_by_partial_name(commands_with_aliases, command_to_match) partial_matches = commands_with_aliases.keys.select { |command_name| command_name =~ /^#{command_to_match}/ } return commands_with_aliases[partial_matches[0]] if partial_matches.size == 1 partial_matches end end end gli-2.14.0/lib/gli/terminal.rb0000644000175000017500000000673613017317740014775 0ustar micahmicahmodule GLI # Class to encapsulate stuff about the terminal. This is useful to application developers # as a canonical means to get information about the user's current terminal configuraiton. # GLI uses this to determine the number of columns to use when printing to the screen. # # To access it, use Terminal.instance. This is a singleton mostly to facilitate testing, but # it seems reasonable enough, since there's only one terminal in effect # # Example: # # Terminal.instance.size[0] # => columns in the terminal # Terminal.default_size = [128,24] # => change default when we can't figure it out # raise "no ls?!?!?" unless Terminal.instance.command_exists?("ls") # class Terminal @@default_size = [80,24] # Get the default size of the terminal when we can't figure it out # # Returns an array of int [cols,rows] def self.default_size @@default_size end # Set the default size of the terminal to use when we can't figure it out # # +size+:: array of two int [cols,rows] def self.default_size=(size) @@default_size = size end # Provide access to the shared instance. def self.instance; @@instance ||= Terminal.new; end # Call this to cause methods to throw exceptions rather than return a sane default. You # probably don't want to call this unless you are writing tests def make_unsafe! #:nodoc: @unsafe = true end # Returns true if the given command exists on this system # # +command+:: The command, as a String, to check for, without any path information. def self.command_exists?(command) ENV['PATH'].split(File::PATH_SEPARATOR).any? {|dir| File.exist? File.join(dir, command) } end def command_exists?(command) self.class.command_exists?(command) end SIZE_DETERMINERS = [ [ lambda { (ENV['COLUMNS'] =~ /^\d+$/) && (ENV['LINES'] =~ /^\d+$/) }, lambda { [ENV['COLUMNS'].to_i, ENV['LINES'].to_i] } ], [ lambda { (jruby? || (!STDIN.tty? && ENV['TERM'])) && command_exists?('tput') }, lambda { [run_command('tput cols').to_i, run_command('tput lines').to_i] } ], [ lambda { (solaris? && STDIN.tty? && command_exists?('stty')) }, lambda { run_command('stty').split("\n")[1].scan(/\d+/)[0..1].map { |size_element| size_element.to_i }.reverse } ], [ lambda { STDIN.tty? && command_exists?('stty') }, lambda { run_command('stty size').scan(/\d+/).map { |size_element| size_element.to_i }.reverse } ], [ lambda { true }, lambda { Terminal.default_size }, ], ] # Get the size of the current terminal. # Ripped from hirb[https://github.com/cldwalker/hirb/blob/master/lib/hirb/util.rb] # # Returns an Array of size two Ints representing the terminal width and height def size SIZE_DETERMINERS.each do |predicate, get_size| next unless predicate.call size = get_size.call return size unless size == [0, 0] end rescue Exception => ex raise ex if @unsafe Terminal.default_size end private # Runs a command using backticks. Extracted to allow for testing def self.run_command(command) `#{command}` end # True if we are JRuby; exposed to allow for testing def self.jruby?; RUBY_PLATFORM =~ /java/; end # True if this is running under Solaris Sparc def self.solaris?; RUBY_PLATFORM =~ /solaris/; end end end gli-2.14.0/lib/gli/command_line_option.rb0000644000175000017500000000175313017317740017171 0ustar micahmicahrequire 'gli/command_line_token.rb' module GLI # An option, not a command or argument, on the command line class CommandLineOption < CommandLineToken #:nodoc: attr_accessor :default_value # Command to which this option "belongs", nil if it's a global option attr_accessor :associated_command # Creates a new option # # names - Array of symbols or strings representing the names of this switch # options - hash of options: # :desc - the short description # :long_desc - the long description # :default_value - the default value of this option def initialize(names,options = {}) super(names,options[:desc],options[:long_desc]) @default_value = options[:default_value] end def self.name_as_string(name,negatable=true) string = name.to_s if string.length == 1 "-#{string}" elsif negatable "--[no-]#{string}" else "--#{string}" end end end end gli-2.14.0/lib/gli/commands/0000755000175000017500000000000013017317740014422 5ustar micahmicahgli-2.14.0/lib/gli/commands/help.rb0000644000175000017500000001106413017317740015701 0ustar micahmicahrequire 'erb' require 'gli/command' require 'gli/terminal' require 'gli/commands/help_modules/list_formatter' require 'gli/commands/help_modules/text_wrapper' require 'gli/commands/help_modules/one_line_wrapper' require 'gli/commands/help_modules/verbatim_wrapper' require 'gli/commands/help_modules/tty_only_wrapper' require 'gli/commands/help_modules/options_formatter' require 'gli/commands/help_modules/global_help_format' require 'gli/commands/help_modules/command_help_format' require 'gli/commands/help_modules/help_completion_format' require 'gli/commands/help_modules/command_finder' require 'gli/commands/help_modules/arg_name_formatter' require 'gli/commands/help_modules/full_synopsis_formatter' require 'gli/commands/help_modules/compact_synopsis_formatter' require 'gli/commands/help_modules/terminal_synopsis_formatter' module GLI module Commands SORTERS = { :manually => lambda { |list| list }, :alpha => lambda { |list| list.sort }, } WRAPPERS = { :to_terminal => HelpModules::TextWrapper, :never => HelpModules::OneLineWrapper, :one_line => HelpModules::OneLineWrapper, :tty_only => HelpModules::TTYOnlyWrapper, :none => HelpModules::VerbatimWrapper, :verbatim => HelpModules::VerbatimWrapper, } SYNOPSIS_FORMATTERS = { :full => HelpModules::FullSynopsisFormatter, :compact => HelpModules::CompactSynopsisFormatter, :terminal => HelpModules::TerminalSynopsisFormatter, } # The help command used for the two-level interactive help system class Help < Command @@skips_pre = true @@skips_post = true @@skips_around = true # Configure help to explicitly skip or not skip the pre block when the help command runs. # This is here because the creation of the help command is outside of the client programmer's control def self.skips_pre=(skips_pre) ; @@skips_pre = skips_pre ; end # Configure help to explicitly skip or not skip the post block when the help command runs. # This is here because the creation of the help command is outside of the client programmer's control def self.skips_post=(skips_post) ; @@skips_post = skips_post ; end # Configure help to explicitly skip or not skip the around block when the help command runs. # This is here because the creation of the help command is outside of the client programmer's control def self.skips_around=(skips_around) ; @@skips_around = skips_around ; end def initialize(app,output=$stdout,error=$stderr) super(:names => :help, :description => 'Shows a list of commands or help for one command', :arguments_name => 'command', :long_desc => 'Gets help for the application or its commands. Can also list the commands in a way helpful to creating a bash-style completion function', :arguments => [Argument.new(:command_name, [:multiple, :optional])]) @app = app @parent = app @sorter = SORTERS[@app.help_sort_type] @text_wrapping_class = WRAPPERS[@app.help_text_wrap_type] @synopsis_formatter_class = SYNOPSIS_FORMATTERS[@app.synopsis_format_type] desc 'List commands one per line, to assist with shell completion' switch :c action do |global_options,options,arguments| if global_options[:version] && !global_options[:help] puts "#{@app.exe_name} version #{@app.version_string}" else show_help(global_options,options,arguments,output,error) end end end def skips_pre ; @@skips_pre ; end def skips_post ; @@skips_post ; end def skips_around ; @@skips_around ; end private def show_help(global_options,options,arguments,out,error) command_finder = HelpModules::CommandFinder.new(@app,arguments,error) if options[:c] help_output = HelpModules::HelpCompletionFormat.new(@app,command_finder,arguments).format out.puts help_output unless help_output.nil? elsif arguments.empty? || options[:c] out.puts HelpModules::GlobalHelpFormat.new(@app,@sorter,@text_wrapping_class).format else name = arguments.shift command = command_finder.find_command(name) unless command.nil? out.puts HelpModules::CommandHelpFormat.new( command, @app, @sorter, @synopsis_formatter_class, @text_wrapping_class).format end end end end end end gli-2.14.0/lib/gli/commands/rdoc_document_listener.rb0000644000175000017500000000615413017317740021507 0ustar micahmicahrequire 'stringio' require 'gli/commands/help_modules/arg_name_formatter' module GLI module Commands class RdocDocumentListener def initialize(global_options,options,arguments,app) @io = File.new("#{app.exe_name}.rdoc",'w') @nest = '' @arg_name_formatter = GLI::Commands::HelpModules::ArgNameFormatter.new end def beginning end # Called when processing has completed def ending @io.close end # Gives you the program description def program_desc(desc) @io.puts "== #{@app.exe_name} - #{desc}" @io.puts end def program_long_desc(desc) @io.puts desc @io.puts end # Gives you the program version def version(version) @io.puts "v#{version}" @io.puts end def options if @nest.size == 0 @io.puts "=== Global Options" else @io.puts "#{@nest}=== Options" end end # Gives you a flag in the current context def flag(name,aliases,desc,long_desc,default_value,arg_name,must_match,type) invocations = ([name] + Array(aliases)).map { |_| add_dashes(_) }.join('|') usage = "#{invocations} #{arg_name || 'arg'}" @io.puts "#{@nest}=== #{usage}" @io.puts @io.puts String(desc).strip @io.puts @io.puts "[Default Value] #{default_value || 'None'}" @io.puts "[Must Match] #{must_match.to_s}" unless must_match.nil? @io.puts String(long_desc).strip @io.puts end # Gives you a switch in the current context def switch(name,aliases,desc,long_desc,negatable) if negatable name = "[no-]#{name}" if name.to_s.length > 1 aliases = aliases.map { |_| _.to_s.length > 1 ? "[no-]#{_}" : _ } end invocations = ([name] + aliases).map { |_| add_dashes(_) }.join('|') @io.puts "#{@nest}=== #{invocations}" @io.puts String(desc).strip @io.puts @io.puts String(long_desc).strip @io.puts end def end_options end def commands @io.puts "#{@nest}=== Commands" @nest = "#{@nest}=" end # Gives you a command in the current context and creates a new context of this command def command(name,aliases,desc,long_desc,arg_name,arg_options) @io.puts "#{@nest}=== Command: #{([name] + aliases).join('|')} #{@arg_name_formatter.format(arg_name,arg_options,[])}" @io.puts String(desc).strip @io.puts @io.puts String(long_desc).strip @nest = "#{@nest}=" end # Ends a command, and "pops" you back up one context def end_command(name) @nest.gsub!(/=$/,'') end # Gives you the name of the current command in the current context def default_command(name) @io.puts "[Default Command] #{name}" unless name.nil? end def end_commands @nest.gsub!(/=$/,'') end private def add_dashes(name) name = "-#{name}" name = "-#{name}" if name.length > 2 name end end end end gli-2.14.0/lib/gli/commands/initconfig.rb0000644000175000017500000000514313017317740017103 0ustar micahmicahrequire 'gli' require 'gli/command' require 'yaml' require 'fileutils' module GLI # Command that initializes the configuration file for apps that use it. class InitConfig < Command # :nodoc: COMMANDS_KEY = 'commands' def initialize(config_file_name,commands,flags,switches) @filename = config_file_name super(:names => :initconfig, :description => "Initialize the config file using current global options", :long_desc => 'Initializes a configuration file where you can set default options for command line flags, both globally and on a per-command basis. These defaults override the built-in defaults and allow you to omit commonly-used command line flags when invoking this program', :skips_pre => true,:skips_post => true, :skips_around => true) @app_commands = commands @app_flags = flags @app_switches = switches self.desc 'force overwrite of existing config file' self.switch :force action do |global_options,options,arguments| if options[:force] || !File.exist?(@filename) create_config(global_options,options,arguments) else raise "Not overwriting existing config file #{@filename}, use --force to override" end end end private def create_config(global_options,options,arguments) config = Hash[(@app_switches.keys + @app_flags.keys).map { |option_name| option_value = global_options[option_name] if option_value.kind_of?(String) && option_value.respond_to?(:force_encoding) [option_name,option_value.force_encoding("utf-8")] else [option_name,option_value] end }] config[COMMANDS_KEY] = {} @app_commands.each do |name,command| if (command != self) && (name != :rdoc) && (name != :help) if command != self config[COMMANDS_KEY][name.to_sym] = config_for_command(@app_commands,name.to_sym) end end end FileUtils.mkdir_p(File.dirname(@filename)) unless File.dirname(@filename) == '.' File.open(@filename,'w', 0600) do |file| YAML.dump(config,file) puts "Configuration file '#{@filename}' written." end end def config_for_command(commands,command_name) {}.tap do |hash| subcommands = commands[command_name].commands subcommands.each do |name,subcommand| next unless name.kind_of? Symbol hash[COMMANDS_KEY] ||= {} puts "#{command_name}:#{name}" hash[COMMANDS_KEY][name.to_sym] = config_for_command(subcommands,name) end end end end end gli-2.14.0/lib/gli/commands/help_modules/0000755000175000017500000000000013017317740017102 5ustar micahmicahgli-2.14.0/lib/gli/commands/help_modules/full_synopsis_formatter.rb0000644000175000017500000000674113017317740024433 0ustar micahmicahmodule GLI module Commands module HelpModules class FullSynopsisFormatter def initialize(app,flags_and_switches) @app = app @basic_invocation = @app.exe_name.to_s @flags_and_switches = flags_and_switches end def synopses_for_command(command) synopses = [] one_line_usage = basic_usage(command) one_line_usage << ArgNameFormatter.new.format(command.arguments_description,command.arguments_options,command.arguments).strip if command.commands.empty? synopses << one_line_usage else synopses = sorted_synopses(command) if command.has_action? synopses.unshift(one_line_usage) end end synopses end protected def sub_options_doc(sub_options) sub_options_doc = sub_options.map { |_,option| doc = option.names_and_aliases.map { |name| CommandLineOption.name_as_string(name,false) + (option.kind_of?(Flag) ? " #{option.argument_name }" : '') }.join('|') option.required?? doc : "[#{doc}]" }.sort.join(' ').strip end private def path_to_command(command) path = [] c = command while c.kind_of? Command path.unshift(c.name) c = c.parent end path.join(' ') end def basic_usage(command) usage = @basic_invocation.dup usage << " [global options]" unless global_flags_and_switches.empty? usage << " #{path_to_command(command)}" usage << " [command options]" unless @flags_and_switches.empty? usage << " " usage end def command_with_subcommand_usage(command,sub,is_default_command) usage = basic_usage(command) sub_options = if @app.subcommand_option_handling_strategy == :legacy command.flags.merge(command.switches).select { |_,o| o.associated_command == sub } else sub.flags.merge(sub.switches) end if is_default_command usage << "[#{sub.name}]" else usage << sub.name.to_s end sub_options_doc = sub_options_doc(sub_options) if sub_options_doc.length > 0 usage << ' ' usage << sub_options_doc end arg_name_doc = ArgNameFormatter.new.format(sub.arguments_description,sub.arguments_options,sub.arguments).strip if arg_name_doc.length > 0 usage << ' ' usage << arg_name_doc end usage end def sorted_synopses(command) synopses_command = {} command.commands.each do |name,sub| default = command.get_default_command == name synopsis = command_with_subcommand_usage(command,sub,default) synopses_command[synopsis] = sub end synopses = synopses_command.keys.sort { |one,two| if synopses_command[one].name == command.get_default_command -1 elsif synopses_command[two].name == command.get_default_command 1 else synopses_command[one] <=> synopses_command[two] end } end def global_flags_and_switches @app.flags.merge(@app.switches) end end end end end gli-2.14.0/lib/gli/commands/help_modules/text_wrapper.rb0000644000175000017500000000322613017317740022156 0ustar micahmicahmodule GLI module Commands module HelpModules # Handles wrapping text class TextWrapper # Create a text_wrapper wrapping at the given width, # and indent. def initialize(width,indent) @width = width @indent = indent end # Return a wrapped version of text, assuming that the first line has already been # indented by @indent characters. Resulting text does NOT have a newline in it. def wrap(text) return text if text.nil? wrapped_text = '' current_graf = '' paragraphs = text.split(/\n\n+/) paragraphs.each do |graf| current_line = '' current_line_length = @indent words = graf.split(/\s+/) current_line = words.shift || '' current_line_length += current_line.length words.each do |word| if current_line_length + word.length + 1 > @width current_graf << current_line << "\n" current_line = '' current_line << ' ' * @indent << word current_line_length = @indent + word.length else if current_line == '' current_line << word else current_line << ' ' << word end current_line_length += (word.length + 1) end end current_graf << current_line wrapped_text << current_graf << "\n\n" << ' ' * @indent current_graf = '' end wrapped_text.gsub(/[\n\s]*\Z/,'') end end end end end gli-2.14.0/lib/gli/commands/help_modules/help_completion_format.rb0000644000175000017500000000151613017317740024163 0ustar micahmicahmodule GLI module Commands module HelpModules class HelpCompletionFormat def initialize(app,command_finder,args) @app = app @command_finder = command_finder @command_finder.squelch_stderr = true @args = args end def format name = @args.shift base = @command_finder.find_command(name) base = @command_finder.last_found_command if base.nil? base = @app if base.nil? prefix_to_match = @command_finder.last_unknown_command base.commands.values.map { |command| [command.name,command.aliases] }.flatten.compact.map(&:to_s).sort.select { |command_name| prefix_to_match.nil? || command_name =~ /^#{prefix_to_match}/ }.join("\n") end end end end end gli-2.14.0/lib/gli/commands/help_modules/list_formatter.rb0000644000175000017500000000134313017317740022466 0ustar micahmicahmodule GLI module Commands module HelpModules # Given a list of two-element lists, formats on the terminal class ListFormatter def initialize(list,wrapper_class=TextWrapper) @list = list @wrapper_class = wrapper_class end # Output the list to the output_device def output(output_device) return if @list.empty? max_width = @list.map { |_| _[0].length }.max wrapper = @wrapper_class.new(Terminal.instance.size[0],4 + max_width + 3) @list.each do |(name,description)| output_device.printf(" %-#{max_width}s - %s\n",name,wrapper.wrap(String(description).strip)) end end end end end end gli-2.14.0/lib/gli/commands/help_modules/tty_only_wrapper.rb0000644000175000017500000000135413017317740023053 0ustar micahmicahmodule GLI module Commands module HelpModules # Formats text in one line, stripping newlines and NOT wrapping class TTYOnlyWrapper # Args are ignored entirely; this keeps it consistent with the TextWrapper interface def initialize(width,indent) @proxy = if STDOUT.tty? TextWrapper.new(width,indent) else OneLineWrapper.new(width,indent) end end # Return a wrapped version of text, assuming that the first line has already been # indented by @indent characters. Resulting text does NOT have a newline in it. def wrap(text) @proxy.wrap(text) end end end end end gli-2.14.0/lib/gli/commands/help_modules/command_finder.rb0000644000175000017500000000361713017317740022403 0ustar micahmicahmodule GLI module Commands module HelpModules # Finds commands from the application/command data structures class CommandFinder attr_reader :last_unknown_command attr_reader :last_found_command attr_writer :squelch_stderr def initialize(app,arguments,error) @app = app @arguments = arguments @error = error @squelch_stderr = false @last_unknown_command = nil end def find_command(name) command = find_command_from_base(name,@app) return if unknown_command?(command,name,@error) @last_found_command = command while !@arguments.empty? name = @arguments.shift command = find_command_from_base(name,command) return if unknown_command?(command,name,@error) @last_found_command = command end command end private # Given the name of a command to find, and a base, either the app or another command, returns # the command object or nil. def find_command_from_base(command_name,base) base.commands.values.select { |command| if [command.name,Array(command.aliases)].flatten.map(&:to_s).any? { |_| _ == command_name } command end }.first end # Checks if the return from find_command was unknown and, if so, prints an error # for the user on the error device, returning true or false if the command was unknown. def unknown_command?(command,name,error) if command.nil? @last_unknown_command = name unless @squelch_stderr error.puts "error: Unknown command '#{name}'. Use '#{@app.exe_name} help' for a list of commands." end true else false end end end end end end gli-2.14.0/lib/gli/commands/help_modules/verbatim_wrapper.rb0000644000175000017500000000061713017317740023004 0ustar micahmicahmodule GLI module Commands module HelpModules # Leaves text formatting exactly as it was received. Doesn't strip anything. class VerbatimWrapper # Args are ignored entirely; this keeps it consistent with the TextWrapper interface def initialize(width,indent) end def wrap(text) return String(text) end end end end end gli-2.14.0/lib/gli/commands/help_modules/options_formatter.rb0000644000175000017500000000342013017317740023204 0ustar micahmicahmodule GLI module Commands module HelpModules class OptionsFormatter def initialize(flags_and_switches,sorter,wrapper_class) @flags_and_switches = sorter.call(flags_and_switches) @wrapper_class = wrapper_class end def format list_formatter = ListFormatter.new(@flags_and_switches.map { |option| if option.respond_to? :argument_name [option_names_for_help_string(option,option.argument_name),description_with_default(option)] else [option_names_for_help_string(option),description_with_default(option)] end },@wrapper_class) stringio = StringIO.new list_formatter.output(stringio) stringio.string end private def description_with_default(option) if option.kind_of? Flag required = option.required? ? 'required, ' : '' multiple = option.multiple? ? 'may be used more than once, ' : '' String(option.description) + " (#{required}#{multiple}default: #{option.safe_default_value || 'none'})" else String(option.description) + (option.default_value ? " (default: enabled)" : "") end end def option_names_for_help_string(option,arg_name=nil) names = [option.name,Array(option.aliases)].flatten names = names.map { |name| CommandLineOption.name_as_string(name,option.kind_of?(Switch) ? option.negatable? : false) } if arg_name.nil? names.join(', ') else if names[-1] =~ /^--/ names.join(', ') + "=#{arg_name}" else names.join(', ') + " #{arg_name}" end end end end end end end gli-2.14.0/lib/gli/commands/help_modules/global_help_format.rb0000644000175000017500000000366513017317740023261 0ustar micahmicahrequire 'erb' module GLI module Commands module HelpModules class GlobalHelpFormat def initialize(app,sorter,wrapper_class) @app = app @sorter = sorter @wrapper_class = wrapper_class end def format program_desc = @app.program_desc program_long_desc = @app.program_long_desc if program_long_desc wrapper = @wrapper_class.new(Terminal.instance.size[0],4) program_long_desc = "\n #{wrapper.wrap(program_long_desc)}\n\n" if program_long_desc else program_long_desc = "\n" end command_formatter = ListFormatter.new(@sorter.call(@app.commands_declaration_order.reject(&:nodoc)).map { |command| [[command.name,Array(command.aliases)].flatten.join(', '),command.description] }, @wrapper_class) stringio = StringIO.new command_formatter.output(stringio) commands = stringio.string global_option_descriptions = OptionsFormatter.new(global_flags_and_switches,@sorter,@wrapper_class).format GLOBAL_HELP.result(binding) end private GLOBAL_HELP = ERB.new(%q(NAME <%= @app.exe_name %> - <%= program_desc %> <%= program_long_desc %> SYNOPSIS <%= usage_string %> <% unless @app.version_string.nil? %> VERSION <%= @app.version_string %> <% end %> <% unless global_flags_and_switches.empty? %> GLOBAL OPTIONS <%= global_option_descriptions %> <% end %> COMMANDS <%= commands %>),nil,'<>') def global_flags_and_switches @app.flags_declaration_order + @app.switches_declaration_order end def usage_string "#{@app.exe_name} ".tap do |string| string << "[global options] " unless global_flags_and_switches.empty? string << "command " string << "[command options] [arguments...]" end end end end end end gli-2.14.0/lib/gli/commands/help_modules/terminal_synopsis_formatter.rb0000644000175000017500000000126113017317740025274 0ustar micahmicahmodule GLI module Commands module HelpModules class TerminalSynopsisFormatter def initialize(app,flags_and_switches) @app = app @basic_invocation = @app.exe_name.to_s @flags_and_switches = flags_and_switches end def synopses_for_command(command) synopses = FullSynopsisFormatter.new(@app,@flags_and_switches).synopses_for_command(command) if synopses.any? { |synopsis| synopsis.length > Terminal.instance.size[0] } CompactSynopsisFormatter.new(@app,@flags_and_switches).synopses_for_command(command) else synopses end end end end end end gli-2.14.0/lib/gli/commands/help_modules/arg_name_formatter.rb0000644000175000017500000000256313017317740023271 0ustar micahmicahmodule GLI module Commands module HelpModules # Handles wrapping text class ArgNameFormatter def format(arguments_description,arguments_options,arguments) # Select which format to use: argname or arguments # Priority to old way: argname desc = format_argname(arguments_description, arguments_options) desc = format_arguments(arguments) if desc.strip == '' desc end def format_arguments(arguments) return '' if arguments.empty? desc = "" # Go through the arguments, building the description string arguments.each do |arg| arg_desc = "#{arg.name}" if arg.optional? arg_desc = "[#{arg_desc}]" end if arg.multiple? arg_desc = "#{arg_desc}[, #{arg_desc}]*" end desc = desc + " " + arg_desc end desc end def format_argname(arguments_description,arguments_options) return '' if String(arguments_description).strip == '' desc = arguments_description if arguments_options.include? :optional desc = "[#{desc}]" end if arguments_options.include? :multiple desc = "#{desc}[, #{desc}]*" end " " + desc end end end end end gli-2.14.0/lib/gli/commands/help_modules/one_line_wrapper.rb0000644000175000017500000000111213017317740022752 0ustar micahmicahmodule GLI module Commands module HelpModules # Formats text in one line, stripping newlines and NOT wrapping class OneLineWrapper # Args are ignored entirely; this keeps it consistent with the TextWrapper interface def initialize(width,indent) end # Return a wrapped version of text, assuming that the first line has already been # indented by @indent characters. Resulting text does NOT have a newline in it. def wrap(text) return String(text).gsub(/\n+/,' ').strip end end end end end gli-2.14.0/lib/gli/commands/help_modules/compact_synopsis_formatter.rb0000644000175000017500000000050513017317740025107 0ustar micahmicahmodule GLI module Commands module HelpModules class CompactSynopsisFormatter < FullSynopsisFormatter protected def sub_options_doc(sub_options) if sub_options.empty? '' else '[subcommand options]' end end end end end end gli-2.14.0/lib/gli/commands/help_modules/command_help_format.rb0000644000175000017500000000504013017317740023424 0ustar micahmicahrequire 'erb' module GLI module Commands module HelpModules class CommandHelpFormat def initialize(command,app,sorter,synopsis_formatter_class,wrapper_class=TextWrapper) @app = app @command = command @sorter = sorter @wrapper_class = wrapper_class @synopsis_formatter = synopsis_formatter_class.new(@app,flags_and_switches(@command,@app)) end def format command_wrapper = @wrapper_class.new(Terminal.instance.size[0],4 + @command.name.to_s.size + 3) wrapper = @wrapper_class.new(Terminal.instance.size[0],4) options_description = OptionsFormatter.new(flags_and_switches(@command,@app),@sorter,@wrapper_class).format commands_description = format_subcommands(@command) synopses = @synopsis_formatter.synopses_for_command(@command) COMMAND_HELP.result(binding) end private COMMAND_HELP = ERB.new(%q(NAME <%= @command.name %> - <%= command_wrapper.wrap(@command.description) %> SYNOPSIS <% synopses.each do |s| %> <%= s %> <% end %> <% unless @command.long_description.nil? %> DESCRIPTION <%= wrapper.wrap(@command.long_description) %> <% end %> <% if options_description.strip.length != 0 %> COMMAND OPTIONS <%= options_description %> <% end %> <% unless @command.commands.empty? %> COMMANDS <%= commands_description %> <% end %>),nil,'<>') def flags_and_switches(command,app) if app.subcommand_option_handling_strategy == :legacy ( command.topmost_ancestor.flags_declaration_order + command.topmost_ancestor.switches_declaration_order ).select { |option| option.associated_command == command } else ( command.flags_declaration_order + command.switches_declaration_order ) end end def format_subcommands(command) commands_array = @sorter.call(command.commands_declaration_order).map { |cmd| if command.get_default_command == cmd.name [cmd.names,String(cmd.description) + " (default)"] else [cmd.names,cmd.description] end } if command.has_action? commands_array.unshift(["",command.default_description]) end formatter = ListFormatter.new(commands_array,@wrapper_class) StringIO.new.tap { |io| formatter.output(io) }.string end end end end end gli-2.14.0/lib/gli/commands/scaffold.rb0000644000175000017500000002701713017317740016537 0ustar micahmicahrequire 'gli' require 'fileutils' module GLI module Commands class Scaffold #:nodoc: def self.create_scaffold(root_dir, create_test_dir, create_ext_dir, project_name, commands, force=false, dry_run=false, create_rvmrc=false) dirs = [File.join(root_dir,project_name,'lib')] dirs << File.join(root_dir,project_name,'bin') dirs << File.join(root_dir,project_name,'test') if create_test_dir dirs << File.join(root_dir,project_name,'ext') if create_ext_dir if mkdirs(dirs,force,dry_run) mk_binfile(root_dir,create_ext_dir,force,dry_run,project_name,commands) mk_readme(root_dir,dry_run,project_name) mk_gemspec(root_dir,dry_run,project_name) mk_rakefile(root_dir,dry_run,project_name,create_test_dir) mk_lib_files(root_dir,dry_run,project_name) if create_rvmrc rvmrc = File.join(root_dir,project_name,".rvmrc") File.open(rvmrc,'w') do |file| file.puts "rvm use #{ENV['rvm_ruby_string']}@#{project_name} --create" end puts "Created #{rvmrc}" end end end def self.mk_readme(root_dir,dry_run,project_name) return if dry_run File.open("#{root_dir}/#{project_name}/README.rdoc",'w') do |file| file << "= #{project_name}\n\n" file << "Describe your project here\n\n" file << ":include:#{project_name}.rdoc\n\n" end puts "Created #{root_dir}/#{project_name}/README.rdoc" File.open("#{root_dir}/#{project_name}/#{project_name}.rdoc",'w') do |file| file << "= #{project_name}\n\n" file << "Generate this with\n #{project_name} rdoc\nAfter you have described your command line interface" end puts "Created #{root_dir}/#{project_name}/#{project_name}.rdoc" end def self.mk_gemspec(root_dir,dry_run,project_name) return if dry_run File.open("#{root_dir}/#{project_name}/#{project_name}.gemspec",'w') do |file| file.puts < :features task 'cucumber:wip' => 'features:wip' task :wip => 'features:wip' EOS end if create_test_dir file.puts < [:test,:features] EOS File.open("#{root_dir}/#{project_name}/test/default_test.rb",'w') do |test_file| test_file.puts < :package\n" end end puts "Created #{root_dir}/#{project_name}/Rakefile" File.open("#{root_dir}/#{project_name}/Gemfile",'w') do |bundler_file| bundler_file.puts "source 'https://rubygems.org'" bundler_file.puts "gemspec" end puts "Created #{root_dir}/#{project_name}/Gemfile" if create_test_dir features_dir = File.join(root_dir,project_name,'features') FileUtils.mkdir features_dir FileUtils.mkdir File.join(features_dir,"step_definitions") FileUtils.mkdir File.join(features_dir,"support") File.open(File.join(features_dir,"#{project_name}.feature"),'w') do |file| file.puts < [name])) command_names = configuration[name] check_for_unknown_commands!(base,command_names) @wrapped_commands = command_names.map { |name| self.class.find_command(base,name) } end def execute(global_options,options,arguments) #:nodoc: @wrapped_commands.each do |command| command.execute(global_options,options,arguments) end end private def check_for_unknown_commands!(base,command_names) known_commands = base.commands.keys.map(&:to_s) unknown_commands = command_names.map(&:to_s) - known_commands unless unknown_commands.empty? raise "Unknown commands #{unknown_commands.join(',')}" end end def self.find_command(base,name) base.commands.values.find { |command| command.name == name } end end end end gli-2.14.0/lib/gli/commands/doc.rb0000644000175000017500000001773413017317740015530 0ustar micahmicahmodule GLI module Commands # Takes a DocListener which will be called with all of the meta-data and documentation # about your app, so as to create documentation in whatever format you want class Doc < Command FORMATS = { 'rdoc' => GLI::Commands::RdocDocumentListener, } # Create the Doc generator based on the GLI app passed in def initialize(app) super(:names => "_doc", :description => "Generate documentation of your application's UI", :long_desc => "Introspects your application's UI meta-data to generate documentation in a variety of formats. This is intended to be extensible via the DocumentListener interface, so that you can provide your own documentation formats without them being a part of GLI", :skips_pre => true, :skips_post => true, :skips_around => true, :hidden => true) @app = app @parent = @app @subcommand_option_handling_strategy = @app.subcommand_option_handling_strategy desc 'The format name of the documentation to generate or the class name to use to generate it' default_value 'rdoc' arg_name 'name_or_class' flag :format action do |global_options,options,arguments| self.document(format_class(options[:format]).new(global_options,options,arguments,app)) end end def nodoc true end # Generates documentation using the listener def document(document_listener) document_listener.beginning document_listener.program_desc(@app.program_desc) unless @app.program_desc.nil? document_listener.program_long_desc(@app.program_long_desc) unless @app.program_long_desc.nil? document_listener.version(@app.version_string) if any_options?(@app) document_listener.options end document_flags_and_switches(document_listener, @app.flags.values.sort(&by_name), @app.switches.values.sort(&by_name)) if any_options?(@app) document_listener.end_options end document_listener.commands document_commands(document_listener,@app) document_listener.end_commands document_listener.ending end # Interface for a listener that is called during various parts of the doc process class DocumentListener def initialize(global_options,options,arguments,app) @global_options = global_options @options = options @arguments = arguments @app = app end # Called before processing begins def beginning abstract! end # Called when processing has completed def ending abstract! end # Gives you the program description def program_desc(desc) abstract! end # Gives you the program long description def program_long_desc(desc) abstract! end # Gives you the program version def version(version) abstract! end # Called at the start of options for the current context def options abstract! end # Called when all options for the current context have been vended def end_options abstract! end # Called at the start of commands for the current context def commands abstract! end # Called when all commands for the current context have been vended def end_commands abstract! end # Gives you a flag in the current context def flag(name,aliases,desc,long_desc,default_value,arg_name,must_match,type) abstract! end # Gives you a switch in the current context def switch(name,aliases,desc,long_desc,negatable) abstract! end # Gives you the name of the current command in the current context def default_command(name) abstract! end # Gives you a command in the current context and creates a new context of this command def command(name,aliases,desc,long_desc,arg_name,arg_options) abstract! end # Ends a command, and "pops" you back up one context def end_command(name) abstract! end private def abstract! raise "Subclass must implement" end end private def format_class(format_name) FORMATS.fetch(format_name) { begin return format_name.split(/::/).reduce(Kernel) { |context,part| context.const_get(part) } rescue => ex raise IndexError,"Couldn't find formatter or class named #{format_name}" end } end def document_commands(document_listener,context) context.commands.values.reject {|_| _.nodoc }.sort(&by_name).each do |command| call_command_method_being_backwards_compatible(document_listener,command) document_listener.options if any_options?(command) document_flags_and_switches(document_listener,command_flags(command),command_switches(command)) document_listener.end_options if any_options?(command) document_listener.commands if any_commands?(command) document_commands(document_listener,command) document_listener.end_commands if any_commands?(command) document_listener.end_command(command.name) end document_listener.default_command(context.get_default_command) end def call_command_method_being_backwards_compatible(document_listener,command) command_args = [command.name, Array(command.aliases), command.description, command.long_description, command.arguments_description] if document_listener.method(:command).arity == 6 command_args << command.arguments_options end document_listener.command(*command_args) end def by_name lambda { |a,b| a.name.to_s <=> b.name.to_s } end def command_flags(command) if @subcommand_option_handling_strategy == :legacy command.topmost_ancestor.flags.values.select { |flag| flag.associated_command == command }.sort(&by_name) else command.flags.values.sort(&by_name) end end def command_switches(command) if @subcommand_option_handling_strategy == :legacy command.topmost_ancestor.switches.values.select { |switch| switch.associated_command == command }.sort(&by_name) else command.switches.values.sort(&by_name) end end def document_flags_and_switches(document_listener,flags,switches) flags.each do |flag| document_listener.flag(flag.name, Array(flag.aliases), flag.description, flag.long_description, flag.safe_default_value, flag.argument_name, flag.must_match, flag.type) end switches.each do |switch| document_listener.switch(switch.name, Array(switch.aliases), switch.description, switch.long_description, switch.negatable) end end def any_options?(context) options = if context.kind_of?(Command) command_flags(context) + command_switches(context) else context.flags.values + context.switches.values end !options.empty? end def any_commands?(command) !command.commands.empty? end end end end gli-2.14.0/lib/gli/gli_option_block_parser.rb0000644000175000017500000000543113017317740020042 0ustar micahmicahmodule GLI # An "option block" is a set of parseable options, starting from the beginning of # the argument list, stopping with the first unknown command-line element. # This class handles parsing that block class GLIOptionBlockParser # Create the parser using the given +OptionParser+ instance and exception handling # strategy. # # option_parser_factory:: An +OptionParserFactory+ instance, configured to parse wherever you are on the command line # exception_klass_or_block:: means of handling exceptions from +OptionParser+. One of: # an exception class:: will be raised on errors with a message # lambda/block:: will be called with a single argument - the error message. def initialize(option_parser_factory,exception_klass_or_block) @option_parser_factory = option_parser_factory @extra_error_context = nil @exception_handler = if exception_klass_or_block.kind_of?(Class) lambda { |message,extra_error_context| raise exception_klass_or_block,message } else exception_klass_or_block end end # Parse the given argument list, returning the unparsed arguments and options hash of parsed arguments. # Exceptions from +OptionParser+ are given to the handler configured in the constructor # # args:: argument list. This will be mutated # # Returns unparsed args def parse!(args) do_parse(args) rescue OptionParser::InvalidOption => ex @exception_handler.call("Unknown option #{ex.args.join(' ')}",@extra_error_context) rescue OptionParser::InvalidArgument => ex @exception_handler.call("#{ex.reason}: #{ex.args.join(' ')}",@extra_error_context) end protected def do_parse(args) first_non_option = nil @option_parser_factory.option_parser.order!(args) do |non_option| first_non_option = non_option break end args.unshift(first_non_option) end end class CommandOptionBlockParser < GLIOptionBlockParser def command=(command_being_parsed) @extra_error_context = command_being_parsed end protected def break_on_non_option? true end def do_parse(args) unknown_options = [] @option_parser_factory.option_parser.order!(args) do |non_option| unknown_options << non_option break if break_on_non_option? end unknown_options.reverse.each do |unknown_option| args.unshift(unknown_option) end args end end class LegacyCommandOptionBlockParser < CommandOptionBlockParser protected def break_on_non_option? false end end end gli-2.14.0/lib/gli/option_parsing_result.rb0000644000175000017500000000100113017317740017567 0ustar micahmicahmodule GLI class OptionParsingResult attr_accessor :global_options attr_accessor :command attr_accessor :command_options attr_accessor :arguments def convert_to_openstruct! @global_options = Options.new(@global_options) @command_options = Options.new(@command_options) self end # Allows us to splat this object into blocks and methods expecting parameters in this order def to_a [@global_options,@command,@command_options,@arguments] end end end gli-2.14.0/lib/gli/gli_option_parser.rb0000644000175000017500000002173213017317740016672 0ustar micahmicahmodule GLI # Parses the command-line options using an actual +OptionParser+ class GLIOptionParser attr_accessor :options DEFAULT_OPTIONS = { :default_command => nil, :autocomplete => true, :subcommand_option_handling_strategy => :legacy, :argument_handling_strategy => :loose } def initialize(commands,flags,switches,accepts, options={}) self.options = DEFAULT_OPTIONS.merge(options) command_finder = CommandFinder.new(commands, :default_command => (options[:default_command] || :help), :autocomplete => options[:autocomplete]) @global_option_parser = GlobalOptionParser.new(OptionParserFactory.new(flags,switches,accepts),command_finder,flags) @accepts = accepts if options[:argument_handling_strategy] == :strict && options[:subcommand_option_handling_strategy] != :normal raise ArgumentError, "To use strict argument handling, you must enable normal subcommand_option_handling, e.g. subcommand_option_handling :normal" end end # Given the command-line argument array, returns an OptionParsingResult def parse_options(args) # :nodoc: option_parser_class = self.class.const_get("#{options[:subcommand_option_handling_strategy].to_s.capitalize}CommandOptionParser") OptionParsingResult.new.tap { |parsing_result| parsing_result.arguments = args parsing_result = @global_option_parser.parse!(parsing_result) option_parser_class.new(@accepts).parse!(parsing_result, options[:argument_handling_strategy]) } end private class GlobalOptionParser def initialize(option_parser_factory,command_finder,flags) @option_parser_factory = option_parser_factory @command_finder = command_finder @flags = flags end def parse!(parsing_result) parsing_result.arguments = GLIOptionBlockParser.new(@option_parser_factory,UnknownGlobalArgument).parse!(parsing_result.arguments) parsing_result.global_options = @option_parser_factory.options_hash_with_defaults_set! command_name = if parsing_result.global_options[:help] "help" else parsing_result.arguments.shift end parsing_result.command = @command_finder.find_command(command_name) unless command_name == 'help' verify_required_options!(@flags, parsing_result.command, parsing_result.global_options) end parsing_result end protected def verify_arguments!(arguments, command) # Lets assume that if the user sets a 'arg_name' for the command it is for a complex scenario # and we should not validate the arguments return unless command.arguments_description.empty? # Go through all declared arguments for the command, counting the min and max number # of arguments min_number_of_arguments = 0 max_number_of_arguments = 0 command.arguments.each do |arg| if arg.optional? max_number_of_arguments = max_number_of_arguments + 1 else min_number_of_arguments = min_number_of_arguments + 1 max_number_of_arguments = max_number_of_arguments + 1 end # Special case, as soon as we have a 'multiple' arguments, all bets are off for the # maximum number of arguments ! if arg.multiple? max_number_of_arguments = 99999 end end # Now validate the number of arguments if arguments.size < min_number_of_arguments raise MissingRequiredArgumentsException.new("Not enough arguments for command", command) end if arguments.size > max_number_of_arguments raise MissingRequiredArgumentsException.new("Too many arguments for command", command) end end def verify_required_options!(flags, command, options) missing_required_options = flags.values. select(&:required?). reject { |option| options[option.name] != nil } unless missing_required_options.empty? missing_required_options.sort! raise MissingRequiredArgumentsException.new(missing_required_options.map { |option| "#{option.name} is required" }.join(', '), command) end end end class NormalCommandOptionParser < GlobalOptionParser def initialize(accepts) @accepts = accepts end def error_handler lambda { |message,extra_error_context| raise UnknownCommandArgument.new(message,extra_error_context) } end def parse!(parsing_result,argument_handling_strategy) parsed_command_options = {} command = parsing_result.command arguments = nil loop do option_parser_factory = OptionParserFactory.for_command(command,@accepts) option_block_parser = CommandOptionBlockParser.new(option_parser_factory, self.error_handler) option_block_parser.command = command arguments = parsing_result.arguments arguments = option_block_parser.parse!(arguments) parsed_command_options[command] = option_parser_factory.options_hash_with_defaults_set! command_finder = CommandFinder.new(command.commands, :default_command => command.get_default_command) next_command_name = arguments.shift verify_required_options!(command.flags, command, parsed_command_options[command]) begin command = command_finder.find_command(next_command_name) rescue AmbiguousCommand arguments.unshift(next_command_name) break rescue UnknownCommand arguments.unshift(next_command_name) # Although command finder could certainy know if it should use # the default command, it has no way to put the "unknown command" # back into the argument stack. UGH. unless command.get_default_command.nil? command = command_finder.find_command(command.get_default_command) end break end end parsed_command_options[command] ||= {} command_options = parsed_command_options[command] this_command = command.parent child_command_options = command_options while this_command.kind_of?(command.class) this_command_options = parsed_command_options[this_command] || {} child_command_options[GLI::Command::PARENT] = this_command_options this_command = this_command.parent child_command_options = this_command_options end parsing_result.command_options = command_options parsing_result.command = command parsing_result.arguments = Array(arguments.compact) # Lets validate the arguments now that we know for sure the command that is invoked verify_arguments!(parsing_result.arguments, parsing_result.command) if argument_handling_strategy == :strict parsing_result end end class LegacyCommandOptionParser < NormalCommandOptionParser def parse!(parsing_result,argument_handling_strategy) command = parsing_result.command option_parser_factory = OptionParserFactory.for_command(command,@accepts) option_block_parser = LegacyCommandOptionBlockParser.new(option_parser_factory, self.error_handler) option_block_parser.command = command parsing_result.arguments = option_block_parser.parse!(parsing_result.arguments) parsing_result.command_options = option_parser_factory.options_hash_with_defaults_set! subcommand,args = find_subcommand(command,parsing_result.arguments) parsing_result.command = subcommand parsing_result.arguments = args verify_required_options!(command.flags, parsing_result.command, parsing_result.command_options) end private def find_subcommand(command,arguments) arguments = Array(arguments) command_name = if arguments.empty? nil else arguments.first end default_command = command.get_default_command finder = CommandFinder.new(command.commands, :default_command => default_command.to_s) begin results = [finder.find_command(command_name),arguments[1..-1]] find_subcommand(results[0],results[1]) rescue UnknownCommand, AmbiguousCommand begin results = [finder.find_command(default_command.to_s),arguments] find_subcommand(results[0],results[1]) rescue UnknownCommand, AmbiguousCommand [command,arguments] end end end end end end gli-2.14.0/lib/gli/app_support.rb0000644000175000017500000002155513017317740015532 0ustar micahmicahmodule GLI # Internals for make App work module AppSupport # Override the device of stderr; exposed only for testing def error_device=(e) #:nodoc: @stderr = e end def context_description "in global context" end # Reset the GLI module internal data structures; mostly useful for testing def reset # :nodoc: switches.clear flags.clear @commands = nil @commands_declaration_order = [] @flags_declaration_order = [] @switches_declaration_order = [] @version = nil @config_file = nil @use_openstruct = false @prog_desc = nil @error_block = false @pre_block = false @post_block = false @default_command = :help @autocomplete = false @around_block = nil @subcommand_option_handling_strategy = :legacy @argument_handling_strategy = :loose clear_nexts end def exe_name File.basename($0) end # Get an array of commands, ordered by when they were declared def commands_declaration_order # :nodoc: @commands_declaration_order end # Get the version string def version_string #:nodoc: @version end # Get the default command for the entire app def get_default_command @default_command end # Runs whatever command is needed based on the arguments. # # +args+:: the command line ARGV array # # Returns a number that would be a reasonable exit code def run(args) #:nodoc: args = args.dup if @preserve_argv the_command = nil begin override_defaults_based_on_config(parse_config) add_help_switch_if_needed(self) gli_option_parser = GLIOptionParser.new(commands, flags, switches, accepts, :default_command => @default_command, :autocomplete => autocomplete, :subcommand_option_handling_strategy => subcommand_option_handling_strategy, :argument_handling_strategy => argument_handling_strategy) parsing_result = gli_option_parser.parse_options(args) parsing_result.convert_to_openstruct! if @use_openstruct the_command = parsing_result.command if proceed?(parsing_result) call_command(parsing_result) 0 else raise PreconditionFailed, "preconditions failed" end rescue Exception => ex if the_command.nil? && ex.respond_to?(:command_in_context) the_command = ex.command_in_context end handle_exception(ex,the_command) end end # Return the name of the config file; mostly useful for generating help docs def config_file_name #:nodoc: @config_file end def accepts #:nodoc: @accepts ||= {} end def parse_config # :nodoc: config = { 'commands' => {}, } if @config_file && File.exist?(@config_file) require 'yaml' config.merge!(File.open(@config_file) { |file| YAML::load(file) }) end config end def clear_nexts # :nodoc: super @skips_post = false @skips_pre = false @skips_around = false end def stderr @stderr ||= STDERR end def self.included(klass) @stderr = $stderr end def flags # :nodoc: @flags ||= {} end def switches # :nodoc: @switches ||= {} end def commands # :nodoc: if !@commands @commands = { :help => GLI::Commands::Help.new(self), :_doc => GLI::Commands::Doc.new(self) } @commands_declaration_order ||= [] @commands_declaration_order << @commands[:help] @commands_declaration_order << @commands[:_doc] end @commands end def pre_block @pre_block ||= Proc.new do true end end def post_block @post_block ||= Proc.new do end end def around_blocks @around_blocks || [] end def help_sort_type @help_sort_type || :alpha end def help_text_wrap_type @help_text_wrap_type || :to_terminal end def synopsis_format_type @synopsis_format_type || :full end # Sets the default values for flags based on the configuration def override_defaults_based_on_config(config) override_default(flags,config) override_default(switches,config) override_command_defaults(commands,config) end def override_command_defaults(command_list,config) command_list.each do |command_name,command| next if command_name == :initconfig || command.nil? command_config = (config['commands'] || {})[command_name] || {} if @subcommand_option_handling_strategy == :legacy override_default(command.topmost_ancestor.flags,command_config) override_default(command.topmost_ancestor.switches,command_config) else override_default(command.flags,command_config) override_default(command.switches,command_config) end override_command_defaults(command.commands,command_config) end end def override_default(tokens,config) tokens.each do |name,token| token.default_value=config[name] unless config[name].nil? end end def argument_handling_strategy @argument_handling_strategy || :loose end def subcommand_option_handling_strategy @subcommand_option_handling_strategy || :legacy end def autocomplete @autocomplete.nil? ? true : @autocomplete end private def handle_exception(ex,command) if regular_error_handling?(ex) output_error_message(ex) if ex.kind_of?(OptionParser::ParseError) || ex.kind_of?(BadCommandLine) || ex.kind_of?(RequestHelp) if commands[:help] command_for_help = command.nil? ? [] : command.name_for_help commands[:help].execute({},{},command_for_help) end end elsif ENV['GLI_DEBUG'] == 'true' stderr.puts "Custom error handler exited false, skipping normal error handling" end raise ex if ENV['GLI_DEBUG'] == 'true' && !ex.kind_of?(RequestHelp) ex.extend(GLI::StandardException) ex.exit_code end def output_error_message(ex) stderr.puts error_message(ex) unless no_message_given?(ex) if ex.kind_of?(OptionParser::ParseError) || ex.kind_of?(BadCommandLine) stderr.puts unless no_message_given?(ex) end end def no_message_given?(ex) ex.message == ex.class.name end def add_help_switch_if_needed(target) help_switch_exists = target.switches.values.find { |switch| switch.names_and_aliases.map(&:to_s).find { |an_alias| an_alias == 'help' } } unless help_switch_exists target.desc 'Show this message' target.switch :help, :negatable => false end end # True if we should proceed with executing the command; this calls # the pre block if it's defined def proceed?(parsing_result) #:nodoc: if parsing_result.command && parsing_result.command.skips_pre true else pre_block.call(*parsing_result) end end # Returns true if we should proceed with GLI's basic error handling. # This calls the error block if the user provided one def regular_error_handling?(ex) #:nodoc: if @error_block return true if (ex.respond_to?(:exit_code) && ex.exit_code == 0) @error_block.call(ex) else true end end # Returns a String of the error message to show the user # +ex+:: The exception we caught that launched the error handling routines def error_message(ex) #:nodoc: "error: #{ex.message}" end def call_command(parsing_result) command = parsing_result.command global_options = parsing_result.global_options options = parsing_result.command_options arguments = parsing_result.arguments.map { |arg| arg.dup } # unfreeze code = lambda { command.execute(global_options,options,arguments) } nested_arounds = unless command.skips_around around_blocks.inject do |outer_around, inner_around| lambda { |go,c,o,a, code1| inner = lambda { inner_around.call(go,c,o,a, code1) } outer_around.call(go,c,o,a, inner) } end end if nested_arounds nested_arounds.call(global_options,command, options, arguments, code) else code.call end unless command.skips_post post_block.call(global_options,command,options,arguments) end end end end gli-2.14.0/lib/gli/switch.rb0000644000175000017500000000212713017317740014451 0ustar micahmicahrequire 'gli/command_line_option.rb' module GLI # Defines a command line switch class Switch < CommandLineOption #:nodoc: attr_accessor :default_value attr_reader :negatable # Creates a new switch # # names - Array of symbols or strings representing the names of this switch # options - hash of options: # :desc - the short description # :long_desc - the long description # :negatable - true or false if this switch is negatable; defaults to true # :default_value - default value if the switch is omitted def initialize(names,options = {}) super(names,options) @default_value = false if options[:default_value].nil? @negatable = options[:negatable].nil? ? true : options[:negatable] if @default_value != false && @negatable == false raise "A switch with default #{@default_value} that isn't negatable is useless" end end def arguments_for_option_parser all_forms_a end def negatable? @negatable end def required? false end end end gli-2.14.0/lib/gli/dsl.rb0000644000175000017500000002427213017317740013737 0ustar micahmicahmodule GLI # The primary DSL for GLI. This represents the methods shared between your top-level app and # the commands. See GLI::Command for additional methods that apply only to command objects. module DSL # Describe the next switch, flag, or command. This should be a # short, one-line description # # +description+:: A String of the short descripiton of the switch, flag, or command following def desc(description); @next_desc = description; end alias :d :desc # Provide a longer, more detailed description. This # will be reformatted and wrapped to fit in the terminal's columns # # +long_desc+:: A String that is s longer description of the switch, flag, or command following. def long_desc(long_desc); @next_long_desc = long_desc; end # Describe the argument name of the next flag. It's important to keep # this VERY short and, ideally, without any spaces (see Example). # # +name+:: A String that *briefly* describes the argument given to the following command or flag. # +options+:: Symbol or array of symbols to annotate this argument. This doesn't affect parsing, just # the help output. Values recognized are: # +:optional+:: indicates this argument is optional; will format it with square brackets # +:multiple+:: indicates multiple values are accepted; will format appropriately # # Example: # desc 'Set the filename' # arg_name 'file_name' # flag [:f,:filename] # # Produces: # -f, --filename=file_name Set the filename def arg_name(name,options=[]) @next_arg_name = name @next_arg_options = options end # Describes one of the arguments of the next command # # +name+:: A String that *briefly* describes the argument given to the following command. # +options+:: Symbol or array of symbols to annotate this argument. This doesn't affect parsing, just # the help output. Values recognized are: # +:optional+:: indicates this argument is optional; will format it with square brackets # +:multiple+:: indicates multiple values are accepted; will format appropriately # # Example: # arg :output # arg :input, :multiple # command :pack do ... # # Produces the synopsis: # app.rb [global options] pack output input[, input]* def arg(name, options=[]) @next_arguments ||= [] @next_arguments << Argument.new(name, Array(options).flatten) end # set the default value of the next flag or switch # # +val+:: The default value to be used for the following flag if the user doesn't specify one # and, when using a config file, the config also doesn't specify one. For a switch, this is # the value to be used if the switch isn't specified on the command-line. Note that if you # set a switch to have a default of true, using the switch on the command-line has no effect. # To disable a switch where the default is true, use the --no- form. def default_value(val); @next_default_value = val; end # Create a flag, which is a switch that takes an argument # # +names+:: a String or Symbol, or an Array of String or Symbol that represent all the different names # and aliases for this flag. The last element can be a hash of options: # +:desc+:: the description, instead of using #desc # +:long_desc+:: the long_description, instead of using #long_desc # +:default_value+:: the default value, instead of using #default_value # +:arg_name+:: the arg name, instead of using #arg_name # +:must_match+:: A regexp that the flag's value must match or an array of allowable values # +:type+:: A Class (or object you passed to GLI::App#accept) to trigger type coversion # +:multiple+:: if true, flag may be used multiple times and values are stored in an array # # Example: # # desc 'Set the filename' # flag [:f,:filename,'file-name'] # # flag :ipaddress, :desc => "IP Address", :must_match => /\d+\.\d+\.\d+\.\d+/ # # flag :names, :desc => "list of names", :type => Array # # Produces: # # -f, --filename, --file-name=arg Set the filename def flag(*names) options = extract_options(names) names = [names].flatten verify_unused(names) flag = Flag.new(names,options) flags[flag.name] = flag clear_nexts flags_declaration_order << flag flag end alias :f :flag # Create a switch, which is a command line flag that takes no arguments (thus, it _switches_ something on) # # +names+:: a String or Symbol, or an Array of String or Symbol that represent all the different names # and aliases for this switch. The last element can be a hash of options: # +:desc+:: the description, instead of using #desc # +:long_desc+:: the long_description, instead of using #long_desc # +:default_value+:: if the switch is omitted, use this as the default value. By default, switches default to off, or +false+ # +:negatable+:: if true, this switch will get a negatable form (e.g. --[no-]switch, false it will not. Default is true def switch(*names) options = extract_options(names) names = [names].flatten verify_unused(names) switch = Switch.new(names,options) switches[switch.name] = switch clear_nexts switches_declaration_order << switch switch end alias :s :switch def clear_nexts # :nodoc: @next_desc = nil @next_arg_name = nil @next_arg_options = nil @next_default_value = nil @next_long_desc = nil end # Define a new command. This can be done in a few ways, but the most common method is # to pass a symbol (or Array of symbols) representing the command name (or names) and a block. # The block will be given an instance of the Command that was created. # You then may call methods on this object to define aspects of that Command. # # Alternatively, you can call this with a one element Hash, where the key is the symbol representing the name # of the command, and the value being an Array of symbols representing the commands to call in order, as a # chained or compound command. Note that these commands must exist already, and that only those command-specific # options defined in *this* command will be parsed and passed to the chained commands. This might not be what you expect # # +names+:: a String or Symbol, or an Array of String or Symbol that represent all the different names and aliases # for this command *or* a Hash, as described above. # # ==Examples # # # Make a command named list # command :list do |c| # c.action do |global_options,options,args| # # your command code # end # end # # # Make a command named list, callable by ls as well # command [:list,:ls] do |c| # c.action do |global_options,options,args| # # your command code # end # end # # # Make a command named all, that calls list and list_contexts # command :all => [ :list, :list_contexts ] # # # Make a command named all, aliased as :a:, that calls list and list_contexts # command [:all,:a] => [ :list, :list_contexts ] # def command(*names) command_options = { :description => @next_desc, :arguments_name => @next_arg_name, :arguments_options => @next_arg_options, :arguments => @next_arguments, :long_desc => @next_long_desc, :skips_pre => @skips_pre, :skips_post => @skips_post, :skips_around => @skips_around, :hide_commands_without_desc => @hide_commands_without_desc, } @commands_declaration_order ||= [] if names.first.kind_of? Hash command = GLI::Commands::CompoundCommand.new(self, names.first, command_options) command.parent = self commands[command.name] = command @commands_declaration_order << command else new_command = Command.new(command_options.merge(:names => [names].flatten)) command = commands[new_command.name] if command.nil? command = new_command command.parent = self commands[command.name] = command @commands_declaration_order << command end yield command end clear_nexts @next_arguments = [] end alias :c :command def flags_declaration_order # :nodoc: @flags_declaration_order ||= [] end def switches_declaration_order # :nodoc: @switches_declaration_order ||= [] end private # Checks that the names passed in have not been used in another flag or option def verify_unused(names) # :nodoc: names.each do |name| verify_unused_in_option(name,flags,"flag") verify_unused_in_option(name,switches,"switch") end end def verify_unused_in_option(name,option_like,type) # :nodoc: return if name.to_s == 'help' raise ArgumentError.new("#{name} has already been specified as a #{type} #{context_description}") if option_like[name] option_like.each do |one_option_name,one_option| if one_option.aliases if one_option.aliases.include? name raise ArgumentError.new("#{name} has already been specified as an alias of #{type} #{one_option_name} #{context_description}") end end end end # Extract the options hash out of the argument to flag/switch and # set the values if using classic style def extract_options(names) options = {} options = names.pop if names.last.kind_of? Hash options = { :desc => @next_desc, :long_desc => @next_long_desc, :default_value => @next_default_value, :arg_name => @next_arg_name}.merge(options) end end end gli-2.14.0/lib/gli/options.rb0000644000175000017500000000075513017317740014650 0ustar micahmicahrequire 'ostruct' module GLI # Subclass of +OpenStruct+ that provides hash-like methods for #[] and #[]=. Note that is is *not* a Hash. # By using GLI::App#use_openstruct, your options will be coerced into one of these. class Options < OpenStruct # Return the value of an attribute def[](k) @table[k.to_sym] end # Set the value of an attribute def[]=(k, v) @table[k.to_sym] = v end def map(&block) @table.map(&block) end end end gli-2.14.0/lib/gli/command_support.rb0000644000175000017500000001073313017317740016364 0ustar micahmicahmodule GLI # Things unrelated to the true public interface of Command that are needed for bookkeeping # and help support. Generally, you shouldn't be calling these methods; they are technically public # but are essentially part of GLI's internal implementation and subject to change module CommandSupport # The parent of this command, either the GLI app, or another command attr_accessor :parent def context_description "in the command #{name}" end # Return true to avoid including this command in your help strings # Will honor the hide_commands_without_desc flag def nodoc @hide_commands_without_desc and description.nil? end # Return the arguments description def arguments_description @arguments_description end def arguments_options @arguments_options end def arguments @arguments end # If true, this command doesn't want the pre block run before it executes def skips_pre @skips_pre end # If true, this command doesn't want the post block run before it executes def skips_post @skips_post end # If true, this command doesn't want the around block called def skips_around @skips_around end # Return the Array of the command's names def names all_forms end # Get an array of commands, ordered by when they were declared def commands_declaration_order # :nodoc: @commands_declaration_order end def flag(*names) if send_declarations_to_parent? new_flag = if parent.kind_of? Command super(*names) parent.flag(*names) else super(*names) end new_flag.associated_command = self new_flag else super(*names) end end def switch(*names) if send_declarations_to_parent? new_switch = if parent.kind_of? Command super(*names) parent.switch(*names) else super(*names) end new_switch.associated_command = self new_switch else super(*names) end end def desc(d) parent.desc(d) if parent.kind_of?(Command) && send_declarations_to_parent? super(d) end def long_desc(d) parent.long_desc(d) if parent.kind_of?(Command) && send_declarations_to_parent? super(d) end def arg_name(d,options=[]) parent.arg_name(d,options) if parent.kind_of?(Command) && send_declarations_to_parent? super(d,options) end def default_value(d) parent.default_value(d) if parent.kind_of?(Command) && send_declarations_to_parent? super(d) end # Return the flags as a Hash def flags @flags ||= {} end # Return the switches as a Hash def switches @switches ||= {} end def commands # :nodoc: @commands ||= {} end def default_description @default_desc end # Executes the command def execute(global_options,options,arguments) get_action(arguments).call(global_options,options,arguments) end def topmost_ancestor some_command = self top = some_command while some_command.kind_of? self.class top = some_command some_command = some_command.parent end top end def has_action? !!@action end def get_default_command @default_command end private def send_declarations_to_parent? app = topmost_ancestor.parent app.nil? ? true : (app.subcommand_option_handling_strategy == :legacy) end def get_action(arguments) if @action @action else generate_error_action(arguments) end end def generate_error_action(arguments) lambda { |global_options,options,arguments| if am_subcommand? if arguments.size > 0 raise UnknownCommand,"Unknown command '#{arguments[0]}'" else raise BadCommandLine,"Command '#{name}' requires a subcommand" end elsif have_subcommands? raise BadCommandLine,"Command '#{name}' requires a subcommand #{self.commands.keys.join(',')}" else raise "Command '#{name}' has no action block" end } end def am_subcommand? parent.kind_of?(Command) end def have_subcommands? !self.commands.empty? end end end gli-2.14.0/lib/gli.rb0000644000175000017500000000177413017317740013157 0ustar micahmicahrequire 'gli/command_finder.rb' require 'gli/gli_option_block_parser.rb' require 'gli/option_parser_factory.rb' require 'gli/option_parsing_result.rb' require 'gli/gli_option_parser.rb' require 'gli/app_support.rb' require 'gli/app.rb' require 'gli/command_support.rb' require 'gli/command.rb' require 'gli/command_line_token.rb' require 'gli/command_line_option.rb' require 'gli/exceptions.rb' require 'gli/flag.rb' require 'gli/options.rb' require 'gli/switch.rb' require 'gli/argument.rb' require 'gli/dsl.rb' require 'gli/version.rb' require 'gli/commands/help' require 'gli/commands/compound_command' require 'gli/commands/initconfig' require 'gli/commands/rdoc_document_listener' require 'gli/commands/doc' module GLI include GLI::App def self.included(klass) warn "You should include GLI::App instead" end def self.run(*args) warn "GLI.run no longer works for GLI-2, you must just call `run(ARGV)' instead" warn "either fix your app, or use the latest GLI in the 1.x family" 1 end end gli-2.14.0/.gitignore0000644000175000017500000000014613017317740013271 0ustar micahmicahdoc html *.sw? pkg coverage .bundle tmp cruddo.rdoc gli.wiki Gemfile.lock results.html .rbx .DS_Store gli-2.14.0/CONTRIBUTING.md0000644000175000017500000000241713017317740013535 0ustar micahmicahContributions are welcome as long as they are part of my vision for GLI (or can be treated as optional to the user). I am obsessive about backwards-compatibility, so you may need to default things to disable your features. Sorry, not ready to bump a major version any time soon. 1. Fork my Repository 2. Create a branch off of gli-2 (**master is no longer the canonical branch**) 3. Make your changes: * Please include tests and watch out for reek and roodi; i.e. keep your code clean * If you make changes to the gli executable or the scaffolding, please update the cucumber features * Please rubydoc any new methods and update the rubydoc to methods you change in the following format: ```ruby # Short description # # Longer description if needed # # +args+:: get documented using this syntax # +args+:: please state the TYPE of every arg # # Returns goes here, please state the TYPE of what's returned, if anything ``` * Use # :nodoc: for methods that a _user_ of GLI should not call (but still please do document all methods) 4. Make sure your branch will merge with my gli-2 branch (or just rebase your branch from my gli-2 branch) 5. Create a pull request explaining your change gli-2.14.0/test/0000755000175000017500000000000013017317740012257 5ustar micahmicahgli-2.14.0/test/tc_options.rb0000644000175000017500000000165313017317740014772 0ustar micahmicahrequire 'test_helper' class TC_testOptions < Clean::Test::TestCase include TestHelper def test_by_method o = GLI::Options.new o.name = 'verbose' assert_equal 'verbose', o.name assert_equal 'verbose', o[:name] assert_equal 'verbose', o['name'] end def test_by_string o = GLI::Options.new o['name'] = 'verbose' assert_equal 'verbose', o.name assert_equal 'verbose', o[:name] assert_equal 'verbose', o['name'] end def test_by_symbol o = GLI::Options.new o[:name] = 'verbose' assert_equal 'verbose', o.name assert_equal 'verbose', o[:name] assert_equal 'verbose', o['name'] end def test_map_defers_to_underlying_map o = GLI::Options.new o[:foo] = 'bar' o[:blah] = 'crud' result = Hash[o.map { |k,v| [k,v.upcase] }] assert_equal 2,result.size assert_equal "BAR",result[:foo] assert_equal "CRUD",result[:blah] end end gli-2.14.0/test/tc_compound_command.rb0000644000175000017500000000123113017317740016611 0ustar micahmicahrequire 'test_helper' require 'tempfile' class TC_testCompounCommand < Clean::Test::TestCase include TestHelper test_that "when we create a CompoundCommand where some commands are missing, we get an exception" do Given { @name = any_string @unknown_name = any_string @existing_command = OpenStruct.new(:name => @name) @base = OpenStruct.new( :commands => { @name => @existing_command }) } When { @code = lambda { GLI::Commands::CompoundCommand.new(@base,{:foo => [@name,@unknown_name]}) } } Then { ex = assert_raises(RuntimeError,&@code) assert_match /#{@unknown_name}/,ex.message } end end gli-2.14.0/test/fake_std_out.rb0000644000175000017500000000067013017317740015256 0ustar micahmicahclass FakeStdOut attr_reader :strings def initialize @strings = [] end def puts(string=nil) @strings << string unless string.nil? end def write(x) puts(x) end def printf(*args) puts(Kernel.printf(*args)) end # Returns true if the regexp matches anything in the output def contained?(regexp) strings.find{ |x| x =~ regexp } end def flush; end def to_s @strings.join("\n") end end gli-2.14.0/test/tc_switch.rb0000644000175000017500000000242713017317740014600 0ustar micahmicahrequire 'test_helper' class TC_testSwitch < Clean::Test::TestCase include TestHelper def test_basics_simple Given switch_with_names(:filename) Then attributes_should_be_set And name_should_be(:filename) And aliases_should_be(nil) end def test_basics_kinda_complex Given switch_with_names([:f]) Then attributes_should_be_set And name_should_be(:f) And aliases_should_be(nil) end def test_basics_complex Given switch_with_names([:f,:file,:filename]) Then attributes_should_be_set And name_should_be(:f) And aliases_should_be([:file,:filename]) And { assert_equal ["-f","--[no-]file","--[no-]filename"],@switch.arguments_for_option_parser } end def test_includes_negatable assert_equal '-a',GLI::Switch.name_as_string('a') assert_equal '--[no-]foo',GLI::Switch.name_as_string('foo') end private def switch_with_names(names) lambda do @options = { :desc => 'Filename', :long_desc => 'The Filename', } @switch = GLI::Switch.new(names,@options) @cli_option = @switch end end def attributes_should_be_set lambda { assert_equal(@options[:desc],@switch.description) assert_equal(@options[:long_desc],@switch.long_description) } end end gli-2.14.0/test/config.yaml0000644000175000017500000000021013017317740014401 0ustar micahmicah--- commands: :command: :g: bar :s: true :other_command: :g: foobar :f: barfoo :f: foo :bleorgh: true :t: false gli-2.14.0/test/apps/0000755000175000017500000000000013017317740013222 5ustar micahmicahgli-2.14.0/test/apps/todo_legacy/0000755000175000017500000000000013017317740015513 5ustar micahmicahgli-2.14.0/test/apps/todo_legacy/lib/0000755000175000017500000000000013017317740016261 5ustar micahmicahgli-2.14.0/test/apps/todo_legacy/lib/todo/0000755000175000017500000000000013017317740017226 5ustar micahmicahgli-2.14.0/test/apps/todo_legacy/lib/todo/version.rb0000644000175000017500000000004413017317740021236 0ustar micahmicahmodule Todo VERSION = '0.0.1' end gli-2.14.0/test/apps/todo_legacy/lib/todo/commands/0000755000175000017500000000000013017317740021027 5ustar micahmicahgli-2.14.0/test/apps/todo_legacy/lib/todo/commands/ls.rb0000644000175000017500000000225713017317740022000 0ustar micahmicah# This is copied so I can have two slightly different versions of the same thing desc "LS things, such as tasks or contexts" long_desc %( List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases. ) command [:ls] do |c| c.desc "Show long form" c.switch [:l,:long] c.desc "List tasks" c.long_desc %( Lists all of your tasks that you have, in varying orders, and all that stuff. Yes, this is long, but I need a long description. ) c.command :tasks do |tasks| tasks.desc "blah blah crud x whatever" tasks.flag [:x] tasks.action do |global,options,args| puts "ls tasks: #{args.join(',')}" end end c.desc "List contexts" c.long_desc %( Lists all of your contexts, which are places you might be where you can do stuff and all that. ) c.command :contexts do |contexts| contexts.desc "Foobar" contexts.switch [:f,'foobar'] contexts.desc "Blah" contexts.switch [:b] contexts.action do |global,options,args| puts "ls contexts: #{args.join(',')}" end end end gli-2.14.0/test/apps/todo_legacy/lib/todo/commands/create.rb0000644000175000017500000000077613017317740022631 0ustar micahmicahdesc "Create a new task or context" command [:create,:new] do |c| c.desc "Make a new task" c.arg_name 'task_name', :multiple c.command :tasks do |tasks| tasks.action do |global,options,args| puts "#{args}" end end c.desc "Make a new context" c.arg_name 'context_name', :optional c.command :contexts do |contexts| contexts.action do |global,options,args| puts "#{args}" end end c.default_desc "Makes a new task" c.action do puts "default action" end end gli-2.14.0/test/apps/todo_legacy/lib/todo/commands/list.rb0000644000175000017500000000266013017317740022333 0ustar micahmicahdesc "List things, such as tasks or contexts" long_desc %( List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases. ) command [:list] do |c| c.default_command :tasks c.desc "Show long form" c.switch [:l,:long] c.desc "List tasks" c.long_desc %( Lists all of your tasks that you have, in varying orders, and all that stuff. Yes, this is long, but I need a long description. ) c.command :tasks do |tasks| tasks.desc "blah blah crud x whatever" tasks.flag [:x], :must_match => Array tasks.flag :flag tasks.action do |global,options,args| puts options[:x].inspect puts "list tasks: #{args.join(',')}" end tasks.desc 'list open tasks' tasks.command :open do |open| open.action do |global,options,args| puts "tasks open" end end tasks.default_desc 'list all tasks' end c.desc "List contexts" c.long_desc %( Lists all of your contexts, which are places you might be where you can do stuff and all that. ) c.command :contexts do |contexts| contexts.desc "Foobar" contexts.switch [:f,'foobar'] contexts.desc "Blah" contexts.switch [:b] contexts.flag :otherflag contexts.action do |global,options,args| puts "list contexts: #{args.join(',')}" end end end gli-2.14.0/test/apps/todo_legacy/todo.gemspec0000644000175000017500000000144213017317740020026 0ustar micahmicah# Ensure we require the local version and not one we might have installed already require File.join([File.dirname(__FILE__),'lib','todo','version.rb']) spec = Gem::Specification.new do |s| s.name = 'todo' s.version = Todo::VERSION s.author = 'Your Name Here' s.email = 'your@email.address.com' s.homepage = 'http://your.website.com' s.platform = Gem::Platform::RUBY s.summary = 'A description of your project' # Add your other files here if you make them s.files = %w( bin/todo ) s.require_paths << 'lib' s.has_rdoc = true s.extra_rdoc_files = ['README.rdoc','todo.rdoc'] s.rdoc_options << '--title' << 'todo' << '--main' << 'README.rdoc' << '-ri' s.bindir = 'bin' s.executables << 'todo' s.add_development_dependency('rake') s.add_development_dependency('rdoc') end gli-2.14.0/test/apps/todo_legacy/test/0000755000175000017500000000000013017317740016472 5ustar micahmicahgli-2.14.0/test/apps/todo_legacy/test/tc_nothing.rb0000644000175000017500000000023213017317740021150 0ustar micahmicahrequire 'test/unit' class TC_testNothing < Test::Unit::TestCase def setup end def teardown end def test_the_truth assert true end end gli-2.14.0/test/apps/todo_legacy/todo.rdoc0000644000175000017500000000013513017317740017330 0ustar micahmicah= todo Generate this with todo rdoc After you have described your command line interfacegli-2.14.0/test/apps/todo_legacy/bin/0000755000175000017500000000000013017317740016263 5ustar micahmicahgli-2.14.0/test/apps/todo_legacy/bin/todo0000755000175000017500000000314713017317740017163 0ustar micahmicah#!/usr/bin/env ruby # These would not be in a real GLI app; we do this so we can easily run this on the command line $: << File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','lib')) $: << File.expand_path(File.join(File.dirname(__FILE__),'..','lib')) require 'gli' require 'todo/version' if ENV['GLI1_COMPATIBILITY'] include GLI else include GLI::App end sort_help (ENV['TODO_SORT_HELP'] || 'alpha').to_sym wrap_help_text (ENV['TODO_WRAP_HELP_TEXT'] || 'to_terminal').to_sym program_desc 'Manages tasks' program_long_desc "A test program that has a sophisticated UI that can be used to exercise a lot of GLI's power" config_file "gli_test_todo.rc" flag :flag switch :switch switch :otherswitch, :negatable => true version Todo::VERSION commands_from 'todo/commands' commands_from File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'todo_plugins', 'commands')) command :first do |c| c.action { |g,o,a| puts "first: #{a.join(',')}" } end command :second do |c| c.action { |g,o,a| puts "second: #{a.join(',')}" } end command :chained => [ :first, :second ] command [:chained2,:ch2] => [ :second, :first ] pre do |global,command,options,args| # Pre logic here # Return true to proceed; false to abort and not call the # chosen command # Use skips_pre before a command to skip this block # on that command only true end post do |global,command,options,args| # Post logic here # Use skips_post before a command to skip this # block on that command only end on_error do |exception| # Error logic here # return false to skip default error handling true end exit run(ARGV) gli-2.14.0/test/apps/todo_legacy/README.rdoc0000644000175000017500000000007013017317740017316 0ustar micahmicah= todo Describe your project here :include:todo.rdoc gli-2.14.0/test/apps/todo_legacy/Gemfile0000644000175000017500000000003113017317740017000 0ustar micahmicahsource :rubygems gemspec gli-2.14.0/test/apps/todo_legacy/Rakefile0000644000175000017500000000073313017317740017163 0ustar micahmicahrequire 'rake/clean' require 'rubygems' require 'rake/gempackagetask' require 'rdoc/task' Rake::RDocTask.new do |rd| rd.main = "README.rdoc" rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*") rd.title = 'Your application title' end spec = eval(File.read('todo.gemspec')) Rake::GemPackageTask.new(spec) do |pkg| end require 'rake/testtask' Rake::TestTask.new do |t| t.libs << "test" t.test_files = FileList['test/tc_*.rb'] end task :default => :test gli-2.14.0/test/apps/README.md0000644000175000017500000000014713017317740014503 0ustar micahmicahSpecial-built applications for use in tests that will hopefully reveal various features and edge cases gli-2.14.0/test/apps/todo_plugins/0000755000175000017500000000000013017317740015730 5ustar micahmicahgli-2.14.0/test/apps/todo_plugins/commands/0000755000175000017500000000000013017317740017531 5ustar micahmicahgli-2.14.0/test/apps/todo_plugins/commands/third.rb0000644000175000017500000000011413017317740021164 0ustar micahmicahcommand :third do |c| c.action { |g,o,a| puts "third: #{a.join(',')}" } end gli-2.14.0/test/apps/todo/0000755000175000017500000000000013017317740014167 5ustar micahmicahgli-2.14.0/test/apps/todo/lib/0000755000175000017500000000000013017317740014735 5ustar micahmicahgli-2.14.0/test/apps/todo/lib/todo/0000755000175000017500000000000013017317740015702 5ustar micahmicahgli-2.14.0/test/apps/todo/lib/todo/version.rb0000644000175000017500000000004413017317740017712 0ustar micahmicahmodule Todo VERSION = '0.0.1' end gli-2.14.0/test/apps/todo/lib/todo/commands/0000755000175000017500000000000013017317740017503 5ustar micahmicahgli-2.14.0/test/apps/todo/lib/todo/commands/make.rb0000644000175000017500000000262513017317740020752 0ustar micahmicahcommand [:make] do |c| c.desc "Show long form" c.flag [:l,:long] c.desc 'make a new task' c.command :task do |task| task.desc 'make the task a long task' task.flag [:l,:long] task.action do |g,o,a| puts 'new task' puts a.join(',') puts o[:long] end task.desc 'make a bug' task.arg :argument, [:multiple, :optional] task.command :bug do |bug| bug.desc 'make this bug in the legacy system' bug.flag [:l,:legacy] bug.action do |g,o,a| puts 'new task bug' puts a.join(',') # All this .to_s is to make sure 1.8.7/REE don't convert nil to the string "nil" puts o[:legacy].to_s puts o[:long].to_s puts o[:l].to_s puts o[GLI::Command::PARENT][:l].to_s puts o[GLI::Command::PARENT][:long].to_s puts o[GLI::Command::PARENT][:legacy].to_s puts o[GLI::Command::PARENT][GLI::Command::PARENT][:l].to_s puts o[GLI::Command::PARENT][GLI::Command::PARENT][:long].to_s puts o[GLI::Command::PARENT][GLI::Command::PARENT][:legacy].to_s end end end c.desc 'make a new context' c.command :context do |context| context.desc 'make the context a local context' context.flag [:l,:local] context.action do |g,o,a| puts 'new context' puts a.join(',') puts o[:local].to_s puts o[:long].to_s puts o[:l].to_s end end end gli-2.14.0/test/apps/todo/lib/todo/commands/ls.rb0000644000175000017500000000225713017317740020454 0ustar micahmicah# This is copied so I can have two slightly different versions of the same thing desc "LS things, such as tasks or contexts" long_desc %( List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases. ) command [:ls] do |c| c.desc "Show long form" c.switch [:l,:long] c.desc "List tasks" c.long_desc %( Lists all of your tasks that you have, in varying orders, and all that stuff. Yes, this is long, but I need a long description. ) c.command :tasks do |tasks| tasks.desc "blah blah crud x whatever" tasks.flag [:x] tasks.action do |global,options,args| puts "ls tasks: #{args.join(',')}" end end c.desc "List contexts" c.long_desc %( Lists all of your contexts, which are places you might be where you can do stuff and all that. ) c.command :contexts do |contexts| contexts.desc "Foobar" contexts.switch [:f,'foobar'] contexts.desc "Blah" contexts.switch [:b] contexts.action do |global,options,args| puts "ls contexts: #{args.join(',')}" end end end gli-2.14.0/test/apps/todo/lib/todo/commands/create.rb0000644000175000017500000000220013017317740021265 0ustar micahmicahdesc "Create a new task or context" command [:create,:new] do |c| c.desc "Make a new task" c.arg_name 'task_name', :multiple c.arg :should_ignore_this c.command :tasks do |tasks| tasks.action do |global,options,args| puts "#{args}" end end c.desc "Make a new context" c.arg :should_ignore_this c.arg_name 'context_name', :optional c.command :contexts do |contexts| contexts.action do |global,options,args| puts "#{args}" end end c.default_desc "Makes a new task" c.action do puts "default action" end c.arg "first" c.arg "second" c.arg "name", :optional c.command :"relation_1-1" do |remote| remote.action do |global,options,args| puts "relation: #{args}" end end c.arg "first", :multiple c.arg "second" c.arg "name", :optional c.command :"relation_n-1" do |remote| remote.action do |global,options,args| puts "relation: #{args}" end end c.arg "first" c.arg "second", :multiple c.arg "name", :optional c.command :"relation_1-n" do |remote| remote.action do |global,options,args| puts "relation: #{args}" end end end gli-2.14.0/test/apps/todo/lib/todo/commands/list.rb0000644000175000017500000000306013017317740021002 0ustar micahmicahdesc "List things, such as tasks or contexts" long_desc %( List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases. ) command [:list] do |c| c.default_command :tasks c.desc "Show long form" c.switch [:l,:long] c.flag :required_flag, :required => true c.flag :required_flag2, :required => true c.desc "List tasks" c.long_desc %( Lists all of your tasks that you have, in varying orders, and all that stuff. Yes, this is long, but I need a long description. ) c.arg :task, [:optional, :multiple] c.command :tasks do |tasks| tasks.desc "blah blah crud x whatever" tasks.flag [:x], :must_match => Array tasks.flag :flag tasks.action do |global,options,args| puts options[:x].inspect puts "list tasks: #{args.join(',')}" end tasks.desc 'list open tasks' tasks.command :open do |open| open.action do |global,options,args| puts "tasks open" end end tasks.default_desc 'list all tasks' end c.desc "List contexts" c.long_desc %( Lists all of your contexts, which are places you might be where you can do stuff and all that. ) c.command :contexts do |contexts| contexts.desc "Foobar" contexts.switch [:f,'foobar'] contexts.desc "Blah" contexts.switch [:b] contexts.flag :otherflag contexts.action do |global,options,args| puts "list contexts: #{args.join(',')}" end end end gli-2.14.0/test/apps/todo/todo.gemspec0000644000175000017500000000144213017317740016502 0ustar micahmicah# Ensure we require the local version and not one we might have installed already require File.join([File.dirname(__FILE__),'lib','todo','version.rb']) spec = Gem::Specification.new do |s| s.name = 'todo' s.version = Todo::VERSION s.author = 'Your Name Here' s.email = 'your@email.address.com' s.homepage = 'http://your.website.com' s.platform = Gem::Platform::RUBY s.summary = 'A description of your project' # Add your other files here if you make them s.files = %w( bin/todo ) s.require_paths << 'lib' s.has_rdoc = true s.extra_rdoc_files = ['README.rdoc','todo.rdoc'] s.rdoc_options << '--title' << 'todo' << '--main' << 'README.rdoc' << '-ri' s.bindir = 'bin' s.executables << 'todo' s.add_development_dependency('rake') s.add_development_dependency('rdoc') end gli-2.14.0/test/apps/todo/test/0000755000175000017500000000000013017317740015146 5ustar micahmicahgli-2.14.0/test/apps/todo/test/tc_nothing.rb0000644000175000017500000000023213017317740017624 0ustar micahmicahrequire 'test/unit' class TC_testNothing < Test::Unit::TestCase def setup end def teardown end def test_the_truth assert true end end gli-2.14.0/test/apps/todo/todo.rdoc0000644000175000017500000000013513017317740016004 0ustar micahmicah= todo Generate this with todo rdoc After you have described your command line interfacegli-2.14.0/test/apps/todo/bin/0000755000175000017500000000000013017317740014737 5ustar micahmicahgli-2.14.0/test/apps/todo/bin/todo0000755000175000017500000000357713017317740015646 0ustar micahmicah#!/usr/bin/env ruby # These would not be in a real GLI app; we do this so we can easily run this on the command line $: << File.expand_path(File.join(File.dirname(__FILE__),'..','..','..','lib')) $: << File.expand_path(File.join(File.dirname(__FILE__),'..','lib')) require 'gli' require 'todo/version' if ENV['GLI1_COMPATIBILITY'] include GLI else include GLI::App end sort_help (ENV['TODO_SORT_HELP'] || 'alpha').to_sym wrap_help_text (ENV['TODO_WRAP_HELP_TEXT'] || 'to_terminal').to_sym synopsis_format (ENV['SYNOPSES'] || 'full').to_sym hide_commands_without_desc ENV['HIDE_COMMANDS_WITHOUT_DESC'] === 'true' subcommand_option_handling :normal arguments :strict program_desc 'Manages tasks' program_long_desc "A test program that has a sophisticated UI that can be used to exercise a lot of GLI's power" config_file "gli_test_todo.rc" flag :flag switch :switch switch :otherswitch, :negatable => true version Todo::VERSION commands_from 'todo/commands' commands_from File.expand_path(File.join(File.dirname(__FILE__), '..', '..', 'todo_plugins', 'commands')) arg_name :argument, :optional command :first do |c| c.action { |g,o,a| puts "first: #{a.join(',')}" } end arg :argument, :optional command :second do |c| c.action { |g,o,a| puts "second: #{a.join(',')}" } end arg :first arg :second command :chained => [ :first, :second ] arg :first arg :second command [:chained2,:ch2] => [ :second, :first ] pre do |global,command,options,args| # Pre logic here # Return true to proceed; false to abort and not call the # chosen command # Use skips_pre before a command to skip this block # on that command only true end post do |global,command,options,args| # Post logic here # Use skips_post before a command to skip this # block on that command only end on_error do |exception| # Error logic here # return false to skip default error handling true end exit run(ARGV) gli-2.14.0/test/apps/todo/README.rdoc0000644000175000017500000000007013017317740015772 0ustar micahmicah= todo Describe your project here :include:todo.rdoc gli-2.14.0/test/apps/todo/Gemfile0000644000175000017500000000003113017317740015454 0ustar micahmicahsource :rubygems gemspec gli-2.14.0/test/apps/todo/Rakefile0000644000175000017500000000073313017317740015637 0ustar micahmicahrequire 'rake/clean' require 'rubygems' require 'rake/gempackagetask' require 'rdoc/task' Rake::RDocTask.new do |rd| rd.main = "README.rdoc" rd.rdoc_files.include("README.rdoc","lib/**/*.rb","bin/**/*") rd.title = 'Your application title' end spec = eval(File.read('todo.gemspec')) Rake::GemPackageTask.new(spec) do |pkg| end require 'rake/testtask' Rake::TestTask.new do |t| t.libs << "test" t.test_files = FileList['test/tc_*.rb'] end task :default => :test gli-2.14.0/test/tc_help.rb0000644000175000017500000002564013017317740014231 0ustar micahmicahrequire 'test_helper' class TC_testHelp < Clean::Test::TestCase include TestHelper def setup @option_index = 0 @real_columns = ENV['COLUMNS'] ENV['COLUMNS'] = '1024' @output = StringIO.new @error = StringIO.new @command_names_used = [] # Reset help command to its default state GLI::Commands::Help.skips_pre = true GLI::Commands::Help.skips_post = true GLI::Commands::Help.skips_around = true end def teardown ENV['COLUMNS'] = @real_columns end class TestApp include GLI::App end test_that "the help command is configured properly when created" do Given { app = TestApp.new app.subcommand_option_handling :normal @command = GLI::Commands::Help.new(app,@output,@error) } Then { assert_equal 'help',@command.name.to_s assert_nil @command.aliases assert_equal 'command',@command.arguments_description assert_not_nil @command.description assert_not_nil @command.long_description assert @command.skips_pre assert @command.skips_post assert @command.skips_around } end test_that "the help command can be configured to skip things declaratively" do Given { app = TestApp.new app.subcommand_option_handling :normal @command = GLI::Commands::Help.new(app,@output,@error) GLI::Commands::Help.skips_pre = false GLI::Commands::Help.skips_post = false GLI::Commands::Help.skips_around = false } Then { assert !@command.skips_pre assert !@command.skips_post assert !@command.skips_around } end test_that "the help command can be configured to skip things declaratively regardless of when it the object was created" do Given { GLI::Commands::Help.skips_pre = false GLI::Commands::Help.skips_post = false GLI::Commands::Help.skips_around = false app = TestApp.new app.subcommand_option_handling :normal @command = GLI::Commands::Help.new(app,@output,@error) } Then { assert !@command.skips_pre assert !@command.skips_post assert !@command.skips_around } end test_that "invoking help with no arguments results in listing all commands and global options" do Given a_GLI_app And { @command = GLI::Commands::Help.new(@app,@output,@error) } When { @command.execute({},{},[]) } Then { assert_top_level_help_output } end test_that "invoking help with a command that doesn't exist shows an error" do Given a_GLI_app And { @command = GLI::Commands::Help.new(@app,@output,@error) @unknown_command_name = any_command_name } When { @command.execute({},{},[@unknown_command_name]) } Then { assert_error_contained(/error: Unknown command '#{@unknown_command_name}'./) } end test_that "invoking help with a known command shows help for that command" do Given a_GLI_app And { @command_name = cm = any_command_name @desc = d = any_desc @long_desc = ld = any_desc @switch = s = any_option @switch_desc = sd = any_desc @flag = f = any_option @flag_desc = fd = any_desc @app.instance_eval do desc d long_desc ld command cm do |c| c.desc sd c.switch s c.desc fd c.flag f c.action {} end end @command = GLI::Commands::Help.new(@app,@output,@error) } When { @command.execute({},{},[@command_name]) } Then { assert_output_contained(@command_name,"Name of the command") assert_output_contained(@desc,"Short description") assert_output_contained(@long_desc,"Long description") assert_output_contained("-" + @switch,"command switch") assert_output_contained(@switch_desc,"switch description") assert_output_contained("-" + @flag,"command flag") assert_output_contained(@flag_desc,"flag description") } end test_that 'invoking help for an app with no global options omits [global options] from the usage string' do Given a_GLI_app(:no_options) And { @command = GLI::Commands::Help.new(@app,@output,@error) } When { @command.execute({},{},[]) } Then { refute_output_contained(/\[global options\] command \[command options\] \[arguments\.\.\.\]/) refute_output_contained('GLOBAL OPTIONS') assert_output_contained(/command \[command options\] \[arguments\.\.\.\]/) } end test_that "invoking help with a known command when no global options are present omits [global options] from the usage string" do Given a_GLI_app(:no_options) And { @command_name = cm = any_command_name @desc = d = any_desc @long_desc = ld = any_desc @switch = s = any_option @switch_desc = sd = any_desc @flag = f = any_option @flag_desc = fd = any_desc @app.instance_eval do desc d long_desc ld command cm do |c| c.desc sd c.switch s c.desc fd c.flag f c.action {} end end @command = GLI::Commands::Help.new(@app,@output,@error) } When { @command.execute({},{},[@command_name]) } Then { refute_output_contained(/\[global options\]/) assert_output_contained(/\[command options\]/) assert_output_contained('COMMAND OPTIONS') } end test_that "invoking help with a known command when no global options nor command options are present omits [global options] and [command options] from the usage string" do Given a_GLI_app(:no_options) And { @command_name = cm = any_command_name @desc = d = any_desc @long_desc = ld = any_desc @app.instance_eval do desc d long_desc ld command cm do |c| c.action {} end end @command = GLI::Commands::Help.new(@app,@output,@error) } When { @command.execute({},{},[@command_name]) } Then { refute_output_contained(/\[global options\]/) refute_output_contained(/\[command options\]/) refute_output_contained('COMMAND OPTIONS') } end test_that "invoking help with a known command that has no command options omits [command options] from the usage string" do Given a_GLI_app And { @command_name = cm = any_command_name @desc = d = any_desc @long_desc = ld = any_desc @app.instance_eval do desc d long_desc ld command cm do |c| c.action {} end end @command = GLI::Commands::Help.new(@app,@output,@error) } When { @command.execute({},{},[@command_name]) } Then { assert_output_contained(/\[global options\]/) refute_output_contained(/\[command options\]/) refute_output_contained('COMMAND OPTIONS') } end test_that "omitting default_description doesn't blow up" do Given { app = TestApp.new app.instance_eval do subcommand_option_handling :normal command :top do |top| top.command :list do |list| list.action do |g,o,a| end end top.command :new do |new| new.action do |g,o,a| end end top.default_command :list end end @command = GLI::Commands::Help.new(app,@output,@error) } When { @code = lambda { @command.execute({},{},['top']) } } Then { assert_nothing_raised(&@code) } end private def a_GLI_app(omit_options=false) lambda { @program_description = program_description = any_desc @flags = flags = [ [any_desc.strip,any_arg_name,[any_option]], [any_desc.strip,any_arg_name,[any_option,any_long_option]], ] @switches = switches = [ [any_desc.strip,[any_option]], [any_desc.strip,[any_option,any_long_option]], ] @commands = commands = [ [any_desc.strip,[any_command_name]], [any_desc.strip,[any_command_name,any_command_name]], ] @app = TestApp.new @app.instance_eval do program_desc program_description subcommand_option_handling :normal unless omit_options flags.each do |(description,arg,flag_names)| desc description arg_name arg flag flag_names end switches.each do |(description,switch_names)| desc description switch switch_names end end commands.each do |(description,command_names)| desc description command command_names do |c| c.action {} end end end } end def assert_top_level_help_output assert_output_contained(@program_description) @commands.each do |(description,command_names)| assert_output_contained(/#{command_names.join(', ')}\s+-\s+#{description}/,"For command #{command_names.join(',')}") end assert_output_contained(/help\s+-\s+#{@command.description}/) @switches.each do |(description,switch_names)| expected_switch_names = switch_names.map { |_| _.length == 1 ? "-#{_}" : "--\\[no-\\]#{_}" }.join(', ') assert_output_contained(/#{expected_switch_names}\s+-\s+#{description}/,"For switch #{switch_names.join(',')}") end @flags.each do |(description,arg,flag_names)| expected_flag_names = flag_names.map { |_| _.length == 1 ? "-#{_}" : "--#{_}" }.join(', ') assert_output_contained(/#{expected_flag_names}[ =]#{arg}\s+-\s+#{description}/,"For flag #{flag_names.join(',')}") end assert_output_contained('GLOBAL OPTIONS') assert_output_contained('COMMANDS') assert_output_contained(/\[global options\] command \[command options\] \[arguments\.\.\.\]/) end def assert_error_contained(string_or_regexp,desc='') string_or_regexp = /#{string_or_regexp}/ if string_or_regexp.kind_of?(String) assert_match string_or_regexp,@error.string,desc end def assert_output_contained(string_or_regexp,desc='') string_or_regexp = /#{string_or_regexp}/ if string_or_regexp.kind_of?(String) assert_match string_or_regexp,@output.string,desc end def refute_output_contained(string_or_regexp,desc='') string_or_regexp = /#{string_or_regexp}/ if string_or_regexp.kind_of?(String) assert_no_match string_or_regexp,@output.string,desc end def any_option ('a'..'z').to_a[@option_index].tap { @option_index += 1 } end def any_long_option Faker::Lorem.words(10)[rand(10)] end def any_arg_name any_string :max => 20 end def any_desc Faker::Lorem.words(10).join(' ')[0..30].gsub(/\s*$/,'') end def any_command_name command_name = Faker::Lorem.words(10)[rand(10)] while @command_names_used.include?(command_name) command_name = Faker::Lorem.words(10)[rand(10)] end @command_names_used << command_name command_name end end gli-2.14.0/test/init_simplecov.rb0000644000175000017500000000016213017317740015627 0ustar micahmicahbegin require 'simplecov' SimpleCov.start do add_filter "/test" end rescue LoadError # Don't care end gli-2.14.0/test/tc_verbatim_wrapper.rb0000644000175000017500000000140613017317740016644 0ustar micahmicahrequire 'test_helper' class TC_testVerbatimWrapper < Clean::Test::TestCase include TestHelper test_that "verbatim wrapper handles nil" do Given { @wrapper = GLI::Commands::HelpModules::VerbatimWrapper.new(any_int,any_int) } When { @result = @wrapper.wrap(nil) } Then { assert_equal '',@result } end test_that "verbatim wrapper doesn't touch the input" do Given { @wrapper = GLI::Commands::HelpModules::VerbatimWrapper.new(any_int,any_int) @input = < {:f => true}, :args => %w(foo bar)) end end test_that "with subcommands, but not using one on the command-line, we run the base action" do Given we_have_a_command_with_two_subcommands When run_app('remote','foo','bar') Then assert_command_ran_with(:base, :command_options => {:f => false}, :args => %w(foo bar)) end test_that "switches and flags defined on a subcommand are available" do Given we_have_a_command_with_two_subcommands(:switches => [:addswitch], :flags => [:addflag]) When run_app('remote','add','--addswitch','--addflag','foo','bar') Then assert_command_ran_with(:add,:command_options => { :addswitch => true, :addflag => 'foo', :f => false }, :args => ['bar']) end test_that "--help works for subcommands in :normal handling mode" do Given { @app.subcommand_option_handling :normal } And we_have_a_command_with_two_subcommands When { @app.run(["remote", "add", "--help"]) rescue nil } Then { assert_no_match /^error/, @fake_stderr.to_s, "should not output an error message" } end test_that "--help works for subcommands in :legacy handling mode" do Given { @app.subcommand_option_handling :legacy } And we_have_a_command_with_two_subcommands When { @app.run(["remote", "add", "--help"]) rescue nil } Then { assert_no_match /^error/, @fake_stderr.to_s, "should not output an error message" } end test_that "we can reopen commands to add new subcommands" do Given { @app.command :remote do |p| p.command :add do |c| c.action do |global_options,command_options,args| @ran_command = :add end end end @app.command :remote do |p| p.command :new do |c| c.action do |global_options,command_options,args| @ran_command = :new end end end } When run_app('remote','new') Then { assert_equal(@ran_command, :new) } When run_app('remote', 'add') Then { assert_equal(@ran_command, :add) } end test_that "reopening commands doesn't re-add them to the output" do Given { @app.command :remote do |p| p.command(:add) { } end @app.command :remote do |p| p.command(:new) { } end } command_names = @app.instance_variable_get("@commands_declaration_order").collect { |c| c.name } assert_equal 1, command_names.grep(:remote).size end test_that "we can reopen commands doesn't cause conflicts" do Given { @app.command :remote do |p| p.command :add do |c| c.action do |global_options,command_options,args| @ran_command = :remote_add end end end @app.command :local do |p| p.command :add do |c| c.action do |global_options,command_options,args| @ran_command = :local_add end end end } When run_app('remote','add') Then { assert_equal(@ran_command, :remote_add) } When run_app('local', 'add') Then { assert_equal(@ran_command, :local_add) } end test_that "we can nest subcommands very deep" do Given { @run_results = { :add => nil, :rename => nil, :base => nil } @app.command :remote do |c| c.switch :f c.command :add do |add| add.command :some do |some| some.command :cmd do |cmd| cmd.switch :s cmd.action do |global_options,command_options,args| @run_results[:cmd] = [global_options,command_options,args] end end end end end ENV['GLI_DEBUG'] = 'true' } When run_app('remote','add','some','cmd','-s','blah') Then assert_command_ran_with(:cmd, :command_options => {:s => true, :f => false},:args => ['blah']) end test_that "when any command in the chain has no action, but there's still arguments, indicate we have an unknown command" do Given a_very_deeply_nested_command_structure Then { assert_raises GLI::UnknownCommand do When run_app('remote','add','some','foo') end assert_match /Unknown command 'foo'/,@fake_stderr.to_s } end test_that "when a command in the chain has no action, but there's NO additional arguments, indicate we need a subcommand" do Given a_very_deeply_nested_command_structure Then { assert_raises GLI::BadCommandLine do When run_app('remote','add','some') end assert_match /Command 'some' requires a subcommand/,@fake_stderr.to_s } end private def run_app(*args) lambda { @exit_code = @app.run(args) } end def a_very_deeply_nested_command_structure lambda { @run_results = { :add => nil, :rename => nil, :base => nil } @app.command :remote do |c| c.switch :f c.command :add do |add| add.command :some do |some| some.command :cmd do |cmd| cmd.switch :s cmd.action do |global_options,command_options,args| @run_results[:cmd] = [global_options,command_options,args] end end end end end ENV['GLI_DEBUG'] = 'true' } end # expected_command - name of command exepcted to have been run # options: # - global_options => hash of expected options # - command_options => hash of expected command options # - args => array of expected args def assert_command_ran_with(expected_command,options) lambda { global_options = options[:global_options] || { :help => false } @run_results.each do |command,results| if command == expected_command assert_equal(indifferent_hash(global_options),results[0]) assert_equal(indifferent_hash(options[:command_options]),results[1]) assert_equal(options[:args],results[2]) else assert_nil results end end } end def indifferent_hash(possibly_nil_hash) return {} if possibly_nil_hash.nil? possibly_nil_hash.keys.each do |key| if key.kind_of? Symbol possibly_nil_hash[key.to_s] = possibly_nil_hash[key] unless possibly_nil_hash.has_key?(key.to_s) elsif key.kind_of? String possibly_nil_hash[key.to_sym] = possibly_nil_hash[key] unless possibly_nil_hash.has_key?(key.to_sym) end end possibly_nil_hash end # options - # :flags => flags to add to :add # :switiches => switiches to add to :add def we_have_a_command_with_two_subcommands(options = {}) lambda { @run_results = { :add => nil, :rename => nil, :base => nil } @app.command :remote do |c| c.switch :f c.desc "add a remote" c.command [:add,:new] do |add| Array(options[:flags]).each { |_| add.flag _ } Array(options[:switches]).each { |_| add.switch _ } add.action do |global_options,command_options,args| @run_results[:add] = [global_options,command_options,args] end end c.desc "rename a remote" c.command :rename do |rename| rename.action do |global_options,command_options,args| @run_results[:rename] = [global_options,command_options,args] end end c.action do |global_options,command_options,args| @run_results[:base] = [global_options,command_options,args] end end ENV['GLI_DEBUG'] = 'true' } end end gli-2.14.0/test/tc_command.rb0000644000175000017500000003503213017317740014713 0ustar micahmicahrequire 'test_helper' require 'tempfile' class TC_testCommand < Clean::Test::TestCase include TestHelper def setup @fake_stdout = FakeStdOut.new @fake_stderr = FakeStdOut.new @original_stdout = $stdout $stdout = @fake_stdout @original_stderr = $stderr $stderr = @fake_stderr ENV.delete('GLI_DEBUG') create_app end def teardown $stdout = @original_stdout $stderr = @original_stderr FileUtils.rm_f "cruddo.rdoc" end def test_names command = GLI::Command.new(:names => [:ls,:list,:'list-them-all'],:description => "List") assert_equal "ls, list, list-them-all",command.names end def test_command_sort commands = [GLI::Command.new(:names => :foo)] commands << GLI::Command.new(:names => :bar) commands << GLI::Command.new(:names => :zazz) commands << GLI::Command.new(:names => :zaz) sorted = commands.sort assert_equal :bar,sorted[0].name assert_equal :foo,sorted[1].name assert_equal :zaz,sorted[2].name assert_equal :zazz,sorted[3].name end def test_basic_command [false,true].each do |openstruct| end [true].each do |openstruct| create_app(openstruct) openstruct_message = openstruct ? ", with use_openstruct" : "" args_args = [%w(-g basic -v -c foo bar baz quux), %w(-g basic -v --configure=foo bar baz quux)] args_args.each do |args| args_orig = args.clone @app.run(args) assert_equal('true',@glob,"For args #{args_orig}#{openstruct_message}") assert_equal('true',@glob_long_form,"For args #{args_orig}#{openstruct_message}") assert_equal('true',@verbose,"For args #{args_orig}#{openstruct_message}") assert_equal('false',@glob_verbose,"For args #{args_orig}#{openstruct_message}") assert_equal('foo',@configure,"For args #{args_orig}#{openstruct_message}") assert_equal(%w(bar baz quux),@args,"For args #{args_orig}#{openstruct_message}") assert(@pre_called,"Pre block should have been called for args #{args_orig}#{openstruct_message}") assert(@post_called,"Post block should have been called for args #{args_orig}#{openstruct_message}") assert(!@error_called,"Error block should not have been called for args #{args_orig}#{openstruct_message}") end end end def test_around_filter @around_block_called = false @app.around do |global_options, command, options, arguments, code| @around_block_called = true code.call end @app.run(['bs']) assert(@around_block_called, "Wrapper block should have been called") end def test_around_filter_can_be_skipped # Given @around_block_called = false @action_called = false @app.skips_around @app.command :skips_around_filter do |c| c.action do |g,o,a| @action_called = true end end @app.command :uses_around_filter do |c| c.action do |g,o,a| @action_called = true end end @app.around do |global_options, command, options, arguments, code| @around_block_called = true code.call end # When exit_status = @app.run(['skips_around_filter']) # Then assert_equal 0,exit_status assert(!@around_block_called, "Wrapper block should have been skipped") assert(@action_called,"Action should have been called") # When @around_block_called = false @action_called = false exit_status = @app.run(['uses_around_filter']) # Then assert_equal 0, exit_status assert(@around_block_called, "Wrapper block should have been called") assert(@action_called,"Action should have been called") end def test_around_filter_can_be_nested @calls = [] @app.around do |global_options, command, options, arguments, code| @calls << "first_pre" code.call @calls << "first_post" end @app.around do |global_options, command, options, arguments, code| @calls << "second_pre" code.call @calls << "second_post" end @app.around do |global_options, command, options, arguments, code| @calls << "third_pre" code.call @calls << "third_post" end @app.command :with_multiple_around_filters do |c| c.action do |g,o,a| @calls << "action!" end end @app.run(['with_multiple_around_filters']) assert_equal ["first_pre", "second_pre", "third_pre", "action!", "third_post", "second_post", "first_post"], @calls end def test_skipping_nested_around_filters @calls = [] @app.around do |global_options, command, options, arguments, code| begin @calls << "first_pre" code.call ensure @calls << "first_post" end end @app.around do |global_options, command, options, arguments, code| @calls << "second_pre" code.call @calls << "second_post" end @app.around do |global_options, command, options, arguments, code| @calls << "third_pre" code.call exit_now! @calls << "third_post" end @app.command :with_multiple_around_filters do |c| c.action do |g,o,a| @calls << "action!" end end @app.run(['with_multiple_around_filters']) assert_equal ["first_pre", "second_pre", "third_pre", "action!", "first_post"], @calls end def test_around_filter_handles_exit_now @around_block_called = false @error_message = "OH NOES" @app.around do |global_options, command, options, arguments, code| @app.exit_now! @error_message code.call end exit_code = @app.run(['bs']) assert exit_code != 0 assert_contained(@fake_stderr,/#{@error_message}/) assert_not_contained(@fake_stdout,/SYNOPSIS/) end def test_around_filter_handles_help_now @around_block_called = false @error_message = "OH NOES" @app.around do |global_options, command, options, arguments, code| @app.help_now! @error_message code.call end exit_code = @app.run(['bs']) assert exit_code != 0 assert_contained(@fake_stderr,/#{@error_message}/) assert_contained(@fake_stdout,/SYNOPSIS/) end def test_error_handler_prints_that_its_skipping_when_gli_debug_is_set ENV["GLI_DEBUG"] = 'true' @app.on_error do false end @app.command :blah do |c| c.action do |*| raise 'wtf' end end assert_raises(RuntimeError) { @app.run(['blah']) } assert_contained(@fake_stderr,/Custom error handler exited false, skipping normal error handling/) end def test_error_handler_should_be_called_on_help_now @app.command :blah do |c| c.action do |*| help_now! end end @app.run(["blah"]) assert @error_called end def test_error_handler_shouldnt_be_called_on_help_from_command_line @app.command :blah do |c| c.action do |*| end end [ ["--help", "blah"], ["blah", "--help"], ].each do |args| args_copy = args.clone @app.run(args) assert !@error_called, "for args #{args_copy.inspect}" end end def test_command_skips_pre @app.skips_pre @app.skips_post skips_pre_called = false runs_pre_called = false @app.command [:skipspre] do |c| c.action do |g,o,a| skips_pre_called = true end end # Making sure skips_pre doesn't leak to other commands @app.command [:runspre] do |c| c.action do |g,o,a| runs_pre_called = true end end @app.run(['skipspre']) assert(skips_pre_called,"'skipspre' should have been called") assert(!@pre_called,"Pre block should not have been called") assert(!@post_called,"Post block should not have been called") assert(!@error_called,"Error block should not have been called") @app.run(['runspre']) assert(runs_pre_called,"'runspre' should have been called") assert(@pre_called,"Pre block should not have been called") assert(@post_called,"Post block SHOULD have been called") assert(!@error_called,"Error block should not have been called") end def test_command_no_globals args = %w(basic -c foo bar baz quux) @app.run(args) assert_equal('foo',@configure) assert_equal(%w(bar baz quux),@args) end def test_defaults_get_set args = %w(basic bar baz quux) @app.run(args) assert_equal('false',@glob) assert_equal('false',@verbose) assert_equal('crud',@configure) assert_equal(%w(bar baz quux),@args) end def test_negatable_gets_created @app.command [:foo] do |c| c.action do |g,o,a| assert !g[:blah] end end exit_status = @app.run(%w(--no-blah foo)) assert_equal 0,exit_status end def test_arguments_are_not_frozen @args = [] @app.command [:foo] do |c| c.action do |g,o,a| @args = a end end exit_status = @app.run(%w(foo a b c d e).map { |arg| arg.freeze }) assert_equal 0,exit_status assert_equal 5,@args.length,"Action block was not called" @args.each_with_index do |arg,index| assert !arg.frozen?,"Expected argument at index #{index} to not be frozen" end end def test_no_arguments args = %w(basic -v) @app.run(args) assert_equal('true',@verbose) assert_equal('crud',@configure) assert_equal([],@args) end def test_unknown_command args = %w(blah) @app.run(args) assert(!@post_called) assert(@error_called) assert_contained(@fake_stderr,/Unknown command 'blah'/) end def test_unknown_global_option args = %w(--quux basic) @app.run(args) assert(!@post_called) assert(@error_called,"Expected error callback to be called") assert_contained(@fake_stderr,/Unknown option --quux/) end def test_unknown_argument args = %w(basic --quux) @app.run(args) assert(!@post_called) assert(@error_called) assert_contained(@fake_stderr,/ Unknown option --quux/) end def test_forgot_action_block @app.reset @app.command :foo do end ENV['GLI_DEBUG'] = 'true' assert_raises RuntimeError do @app.run(['foo']) end assert_match /Command 'foo' has no action block/,@fake_stderr.to_s end def test_command_create @app.desc 'single symbol' @app.command :single do |c|; end command = @app.commands[:single] assert_equal :single, command.name assert_equal nil, command.aliases description = 'implicit array' @app.desc description @app.command :foo, :bar do |c|; end command = @app.commands[:foo] assert_equal :foo, command.name assert_equal [:bar], command.aliases description = 'explicit array' @app.desc description @app.command [:baz, :blah] do |c|; end command = @app.commands[:baz] assert_equal :baz, command.name assert_equal [:blah], command.aliases end def test_pre_exiting_false_causes_nonzero_exit @app.pre { |*| false } assert_equal 65,@app.run(["bs"]) # BSD for "input data incorrect in some way" assert_equal 'error: preconditions failed',@fake_stderr.to_s assert_equal '',@fake_stdout.to_s end def test_name_for_help_with_top_command @app.subcommand_option_handling :normal @app.command :remote do |c|; end command = @app.commands[:remote] assert_equal ["remote"], command.name_for_help end def test_name_for_help_with_sub_command @app.subcommand_option_handling :normal @app.command :remote do |c| c.command :add do |s|; end end sub_command = @app.commands[:remote].commands[:add] assert_equal ["remote", "add"], sub_command.name_for_help end def test_name_for_help_with_sub_sub_command @app.subcommand_option_handling :normal @app.command :remote do |c| c.command :add do |s| s.command :sub do |ss|; end end end sub_command = @app.commands[:remote].commands[:add].commands[:sub] assert_equal ["remote", "add", "sub"], sub_command.name_for_help end private def assert_contained(output,regexp) assert_not_nil output.contained?(regexp), "Expected output to contain #{regexp.inspect}, output was:\n#{output}" end def assert_not_contained(output,regexp) assert_nil output.contained?(regexp), "Didn't expected output to contain #{regexp.inspect}, output was:\n#{output}" end def create_app(use_openstruct=false) @app = CLIApp.new @app.error_device=@fake_stderr @app.reset if use_openstruct @app.use_openstruct true end @app.program_desc 'A super awesome program' @app.desc 'Some Global Option' @app.switch [:g,:global] @app.switch :blah @app.long_desc 'This is a very long description for a flag' @app.flag [:y,:yes] @pre_called = false @post_called = false @error_called = false @app.pre { |g,c,o,a| @pre_called = true } @app.post { |g,c,o,a| @post_called = true } @app.on_error { |g,c,o,a| @error_called = true } @glob = nil @verbose = nil @glob_verbose = nil @configure = nil @args = nil @app.desc 'Some Basic Command that potentially has a really really really really really really really long description and stuff, but you know, who cares?' @app.long_desc 'This is the long description: "Some Basic Command that potentially has a really really really really really really really long description and stuff, but you know, who cares?"' @app.arg_name 'first_file second_file' @app.command [:basic,:bs] do |c| c.desc 'be verbose' c.switch :v c.desc 'configure something or other, in some way that requires a lot of verbose text and whatnot' c.default_value 'crud' c.flag [:c,:configure] c.action do |global_options,options,arguments| if use_openstruct @glob = global_options.g ? 'true' : 'false' @glob_long_form = global_options.global ? 'true' : 'false' @verbose = options.v ? 'true' : 'false' @glob_verbose = global_options.v ? 'true' : 'false' @configure = options.c else @glob = global_options[:g] ? 'true' : 'false' @glob_long_form = global_options[:global] ? 'true' : 'false' @verbose = options[:v] ? 'true' : 'false' @glob_verbose = global_options[:v] ? 'true' : 'false' @configure = options[:c] end @args = arguments end end @app.desc "Testing long help wrapping" @app.long_desc <<-EOS This will create a scaffold command line project that uses @app for command line processing. Specifically, this will create an executable ready to go, as well as a lib and test directory, all inside the directory named for your project EOS @app.command [:test_wrap] do |c| c.action {} end end end gli-2.14.0/test/tc_subcommand_parsing.rb0000644000175000017500000001664213017317740017156 0ustar micahmicahrequire 'test_helper' require 'pp' class TC_testSubCommandParsing < Clean::Test::TestCase include TestHelper def setup @fake_stdout = FakeStdOut.new @fake_stderr = FakeStdOut.new @original_stdout = $stdout $stdout = @fake_stdout @original_stderr = $stderr $stderr = @fake_stderr @app = CLIApp.new @app.reset @app.subcommand_option_handling :legacy @app.error_device=@fake_stderr ENV.delete('GLI_DEBUG') @results = {} @exit_code = 0 end def teardown $stdout = @original_stdout $stderr = @original_stderr end test_that "commands options may clash with globals and it gets sorted out" do Given :app_with_subcommands_storing_results When { @app.run(%w(-f global command1 -f command -s foo)) } Then { assert_equal 'command1',@results[:command_name] assert_equal 'global', @results[:global_options][:f],'global' assert !@results[:global_options][:s] assert_equal 'command', @results[:command_options][:f] assert @results[:command_options][:s] } end test_that "in legacy mode, subcommand options all share a namespace" do Given :app_with_subcommands_storing_results When { @app.run(%w(-f global command1 -f command -s subcommand10 -f sub)) } Then { with_clue { assert_equal 'subcommand10',@results[:command_name] assert_equal 'global', @results[:global_options][:f],'global' assert !@results[:global_options][:s] assert_equal 'sub', @results[:command_options][:f] assert @results[:command_options][:s] assert_nil @results[:command_options][GLI::Command::PARENT] assert_nil @results[:command_options][GLI::Command::PARENT] } } end test_that "in normal mode, each subcommand has its own namespace" do Given :app_with_subcommands_storing_results, :normal When { @app.run(%w(-f global command1 -f command -s subcommand10 -f sub)) } Then { with_clue { assert_equal 'subcommand10',@results[:command_name] assert_equal 'global', @results[:global_options][:f],'global' assert !@results[:global_options][:s] assert_equal 'sub', @results[:command_options][:f] assert !@results[:command_options][:s] assert_equal 'command',@results[:command_options][GLI::Command::PARENT][:f] assert @results[:command_options][GLI::Command::PARENT][:s] } } end test_that "in loose mode, argument validation is ignored" do Given :app_with_arguments, 1, 1, false, :loose When :run_app_with_X_arguments, 0 Then { with_clue { assert_equal 0, @results[:number_of_args_give_to_action] assert_equal 0, @exit_code } } end test_that "in strict mode, subcommand_option_handling must be normal" do Given :app_with_arguments, 1, 1, false, :strict, :legacy When :run_app_with_X_arguments, 1 Then { with_clue { assert_nil @results[:number_of_args_give_to_action] assert_equal 1, @exit_code assert @fake_stderr.contained?(/you must enable normal subcommand_option_handling/) } } end ix = -1 [ [1 , 1 , false , 0 , :not_enough] , [1 , 1 , false , 1 , :success] , [1 , 1 , false , 2 , :success] , [1 , 1 , false , 3 , :too_many] , [1 , 1 , true , 0 , :not_enough] , [1 , 1 , true , 1 , :success] , [1 , 1 , true , 2 , :success] , [1 , 1 , true , 3 , :success] , [1 , 1 , true , 30 , :success] , [0 , 0 , false , 0 , :success] , [0 , 0 , false , 1 , :too_many] , [0 , 1 , false , 1 , :success] , [0 , 1 , false , 0 , :success] , [1 , 0 , false , 1 , :success] , [1 , 0 , false , 0 , :not_enough] , [0 , 0 , true , 0 , :success] , [0 , 0 , true , 10 , :success] ].each do |number_required, number_optional, has_multiple, number_generated, status| ix = ix + 1 test_that "in strict mode, with #{number_required} required, #{number_optional} optional, #{ has_multiple ? 'multiple' : 'not multiple' } and #{number_generated} generated, it should be #{status}" do Given :app_with_arguments, number_required, number_optional, has_multiple, :strict When :run_app_with_X_arguments, number_generated Then { with_clue { if status == :success then assert_equal number_generated, @results[:number_of_args_give_to_action] assert_equal 0, @exit_code assert !@fake_stderr.contained?(/Not enough arguments for command/) assert !@fake_stderr.contained?(/Too many arguments for command/) elsif status == :not_enough then assert_equal nil, @results[:number_of_args_give_to_action] assert_equal 64, @exit_code assert @fake_stderr.contained?(/Not enough arguments for command/) elsif status == :too_many then assert_equal nil, @results[:number_of_args_give_to_action] assert_equal 64, @exit_code assert @fake_stderr.contained?(/Too many arguments for command/) else assert false end } } end end private def with_clue(&block) block.call rescue Exception dump = "" PP.pp "\nRESULTS---#{@results}", dump unless @results.empty? PP.pp "\nSTDERR---\n#{@fake_stderr.to_s}", dump PP.pp "\nSTDOUT---\n#{@fake_stdout.to_s}", dump @original_stdout.puts dump raise end def app_with_subcommands_storing_results(subcommand_option_handling_strategy = :legacy) @app.subcommand_option_handling subcommand_option_handling_strategy @app.flag ['f','flag'] @app.switch ['s','switch'] 2.times do |i| @app.command "command#{i}" do |c| c.flag ['f','flag'] c.switch ['s','switch'] c.action do |global,options,args| @results = { :command_name => "command#{i}", :global_options => global, :command_options => options, :args => args } end 2.times do |j| c.command "subcommand#{i}#{j}" do |subcommand| subcommand.flag ['f','flag'] subcommand.flag ['foo'] subcommand.switch ['s','switch'] subcommand.action do |global,options,args| @results = { :command_name => "subcommand#{i}#{j}", :global_options => global, :command_options => options, :args => args } end end end end end end def app_with_arguments(number_required_arguments, number_optional_arguments, has_argument_multiple, arguments_handling_strategy = :loose, subcommand_option_handling_strategy = :normal) @app.arguments arguments_handling_strategy @app.subcommand_option_handling subcommand_option_handling_strategy number_required_arguments.times { |i| @app.arg("needed#{i}") } number_optional_arguments.times { |i| @app.arg("optional#{i}", :optional) } @app.arg :multiple, [:multiple, :optional] if has_argument_multiple @app.command :cmd do |c| c.action do |g,o,a| @results = { :number_of_args_give_to_action => a.size } end end end def run_app_with_X_arguments(number_arguments) @exit_code = @app.run [].tap{|args| args << "cmd"; number_arguments.times {|i| args << "arg#{i}"}} end end gli-2.14.0/test/tc_terminal.rb0000644000175000017500000000643113017317740015111 0ustar micahmicahrequire 'test_helper' class TC_testTerminal < Clean::Test::TestCase include TestHelper def test_command_exists assert GLI::Terminal.instance.command_exists?('ls') assert !GLI::Terminal.instance.command_exists?('asdfasfasdf') end def setup @old_columns = ENV['COLUMNS'] @old_lines = ENV['LINES'] end def teardown ENV['COLUMNS'] = @old_columns ENV['LINES'] = @old_lines GLI::Terminal.default_size = [80,24] end def test_shared_instance_is_same assert_equal GLI::Terminal.instance,GLI::Terminal.instance end def test_size_based_on_columns ENV['COLUMNS'] = '666' ENV['LINES'] = '777' assert_equal [666,777],GLI::Terminal.instance.size end def test_size_using_tput terminal = GLI::Terminal.new terminal.make_unsafe! GLI::Terminal.instance_eval do def run_command(command) if command == 'tput cols' return '888' elsif command == 'tput lines' return '999' else raise "Unexpected command called: #{command}" end end def command_exists?(command); true; end def jruby?; true; end end ENV['COLUMNS'] = 'foo' assert_equal [888,999],terminal.size end def test_size_using_stty terminal = GLI::Terminal.new terminal.make_unsafe! GLI::Terminal.instance_eval do def run_command(command) if RUBY_PLATFORM == 'java' return '5678' if command == 'tput cols' return '1234' if command == 'tput lines' else return '1234 5678' if command == 'stty size' return '1234 5678' if command == 'stty' end raise "Unexpected command called: #{command} for #{RUBY_PLATFORM}" end def command_exists?(command); true; end def jruby?; false; end def solaris?; false; end end ENV['COLUMNS'] = 'foo' assert_equal [5678,1234],terminal.size end def test_size_using_stty_but_returns_0 terminal = GLI::Terminal.new terminal.make_unsafe! GLI::Terminal.instance_eval do def run_command(command) if RUBY_PLATFORM == 'java' return '0' if command == 'tput cols' return '0' if command == 'tput lines' else return '0 0' if command == 'stty size' return '0 0' if command == 'stty' end raise "Unexpected command called: #{command} for #{RUBY_PLATFORM}" end def command_exists?(command); true; end def jruby?; false; end def solaris?; false; end end ENV['COLUMNS'] = 'foo' assert_equal [80,24],terminal.size end def test_size_using_default terminal = GLI::Terminal.new terminal.make_unsafe! GLI::Terminal.instance_eval do def command_exists?(command); false; end def jruby?; false; end def solaris?; false; end end ENV['COLUMNS'] = 'foo' assert_equal [80,24],terminal.size # While we have this set up, lets make sure the default change falls through GLI::Terminal.default_size = [90,45] assert_equal [90,45],terminal.size end def test_size_using_default_when_exception terminal = GLI::Terminal.new GLI::Terminal.instance_eval do def jruby?; raise "Problem"; end def solaris?; false; end end ENV['COLUMNS'] = 'foo' assert_equal [80,24],terminal.size end end gli-2.14.0/test/tc_doc.rb0000644000175000017500000002113013017317740014034 0ustar micahmicahrequire 'test_helper' require 'pp' class String def blank? self.strip.length == 0 end end class NilClass def blank? true end end class Object def blank? false end end class TC_testDoc < Clean::Test::TestCase include TestHelper class TestApp include GLI::App end class TestListener @@last = nil def self.last @@last end def initialize(*ignored) @stringio = StringIO.new @indent = '' @@last = self end def options end def end_options end def commands end def end_commands end def beginning @stringio << 'BEGIN' << "\n" end def ending @stringio << 'END' << "\n" end def program_desc(desc) @stringio << desc << "\n" end def program_long_desc(desc) @stringio << desc << "\n" end def version(version) @stringio << version << "\n" end def default_command(name) @stringio << @indent << "default_command: " << name << "\n" end def flag(name,aliases,desc,long_desc,default_value,arg_name,must_match,type) @stringio << @indent << "flag: " << name << "\n" @indent += ' ' @stringio << @indent << "aliases: " << aliases.join(',') << "\n" unless aliases.empty? @stringio << @indent << "desc: " << desc << "\n" unless desc.blank? @stringio << @indent << "long_desc: " << long_desc << "\n" unless long_desc.blank? @stringio << @indent << "default_value: " << default_value << "\n" unless default_value.blank? @stringio << @indent << "arg_name: " << arg_name << "\n" unless arg_name.blank? @indent.gsub!(/ $/,'') end def switch(name,aliases,desc,long_desc,negatable) @stringio << @indent << "switch: " << name << "\n" @indent += ' ' @stringio << @indent << "aliases: " << aliases.join(',') << "\n" unless aliases.empty? @stringio << @indent << "desc: " << desc << "\n" unless desc.blank? @stringio << @indent << "long_desc: " << long_desc << "\n" unless long_desc.blank? @stringio << @indent << "negatable: " << negatable << "\n" unless negatable.blank? @indent.gsub!(/ $/,'') end def command(name,aliases,desc,long_desc,arg_name) @stringio << @indent << "command: " << name << "\n" @indent += ' ' @stringio << @indent << "aliases: " << aliases.join(',') << "\n" unless aliases.empty? @stringio << @indent << "desc: " << desc << "\n" unless desc.blank? @stringio << @indent << "long_desc: " << long_desc << "\n" unless long_desc.blank? @stringio << @indent << "arg_name: " << arg_name << "\n" unless arg_name.blank? end def end_command(name) @indent.gsub!(/ $/,'') @stringio << @indent << "end #{name}" << "\n" end def to_s @stringio.string end end def setup @@counter = -1 # we pre-increment so this makes 0 first end test_that "a GLI app with documentation gets the callbacks for each element" do Given :the_test_app And :the_expected_output And { @documenter = GLI::Commands::Doc.new(@app) @listener = TestListener.new } When { @documenter.document(@listener) } Then { lines_expected = @string.split(/\n/) lines_got = @listener.to_s.split(/\n/) lines_expected.zip(lines_got).each_with_index do |(expected,got),index| assert_equal expected,got,"At index #{index}" end } end test_that "the doc command works as a GLI command" do Given :the_test_app And :the_expected_output And { @documenter = GLI::Commands::Doc.new(@app) @listener = TestListener.new } When { @documenter.execute({},{:format => "TC_testDoc::TestListener"},[]) } Then { lines_expected = @string.split(/\n/) lines_got = TestListener.last.to_s.split(/\n/) lines_expected.zip(lines_got).each_with_index do |(expected,got),index| assert_equal expected,got,"At index #{index}" end } end private @@counter = 1 def self.counter @@counter += 1 @@counter end def the_test_app @app = TestApp.new @app.instance_eval do program_desc "program desc" program_long_desc "program long desc" version "1.3.4" TC_testDoc.flag_with_everything_specified(self) TC_testDoc.flag_with_everything_omitted(self) TC_testDoc.switch_with_everything_specified(self) TC_testDoc.switch_with_everything_omitted(self) desc "command desc" long_desc "command long desc" arg_name "cmd_arg_name" command [:command1,:com1] do |c| TC_testDoc.flag_with_everything_specified(c) TC_testDoc.flag_with_everything_omitted(c) TC_testDoc.switch_with_everything_specified(c) TC_testDoc.switch_with_everything_omitted(c) c.desc "subcommand desc" c.long_desc "subcommand long desc" c.arg_name "subcmd_arg_name" c.action { |g,o,a| } c.command [:sub,:subcommand] do |sub| TC_testDoc.flag_with_everything_specified(sub,:subflag) TC_testDoc.flag_with_everything_omitted(sub,:subflag2) TC_testDoc.switch_with_everything_specified(sub,:subswitch) TC_testDoc.switch_with_everything_omitted(sub,:subswitch2) sub.action { |g,o,a| } end c.command [:default] do |sub| sub.action { |g,o,a| } end c.default_command :default end command [:command2,:com2] do |c| c.action { |g,o,a| } c.command [:sub2,:subcommand2] do |sub| sub.action { |g,o,a| } end end end end def self.flag_with_everything_specified(on,name=[:f,:flag]) on.flag name,:desc => "flag desc #{counter}", :long_desc => "flag long_desc #{counter}", :default_value => "flag default_value #{counter}", :arg_name => "flag_arg_name_#{counter}", :must_match => /foo.*bar/, :type => Array end def self.flag_with_everything_omitted(on,name=[:F,:flag2]) on.flag name end def self.switch_with_everything_specified(on,name=[:s,:switch]) on.switch name, :desc => "switch desc #{counter}", :long_desc => "switch long_desc #{counter}", :negatable => false end def self.switch_with_everything_omitted(on,name=[:S,:switch2]) on.switch name end def the_expected_output # Oh yeah. Creating a string representing the structure of the calls. @string =< true) Then attributes_should_be_set(:safe_default_value => "********") end def flag_with_names(names,options = {}) lambda do @options = { :desc => 'Filename', :long_desc => 'The Filename', :arg_name => 'file', :default_value => '~/.blah.rc', :safe_default_value => '~/.blah.rc', :must_match => /foobar/, :type => Float, }.merge(options) @flag = GLI::Flag.new(names,@options) @cli_option = @flag end end def attributes_should_be_set(override={}) lambda { expected = @options.merge(override) assert_equal(expected[:desc],@flag.description) assert_equal(expected[:long_desc],@flag.long_description) assert_equal(expected[:default_value],@flag.default_value) assert_equal(expected[:safe_default_value],@flag.safe_default_value) assert_equal(expected[:must_match],@flag.must_match) assert_equal(expected[:type],@flag.type) } end end gli-2.14.0/test/option_test_helper.rb0000644000175000017500000000034513017317740016514 0ustar micahmicahmodule OptionTestHelper def name_should_be(name) lambda { assert_equal(name,@cli_option.name) } end def aliases_should_be(aliases) lambda { assert_equal(aliases,@cli_option.aliases) } end end gli-2.14.0/test/tc_command_finder.rb0000644000175000017500000000301013017317740016231 0ustar micahmicahrequire 'test_helper' class TC_testCommandFinder < Clean::Test::TestCase include TestHelper def setup @app = CLIApp.new [:status, :deployable, :some_command, :some_similar_command].each do |command| @app.commands[command] = GLI::Command.new(:names => command) end end def teardown end def test_unknown_command_name assert_raise(GLI::UnknownCommand) do GLI::CommandFinder.new(@app.commands, :default_command => :status).find_command(:unfindable_command) end end def test_no_command_name_without_default assert_raise(GLI::UnknownCommand) do GLI::CommandFinder.new(@app.commands).find_command(nil) end end def test_no_command_name_with_default actual = GLI::CommandFinder.new(@app.commands, :default_command => :status).find_command(nil) expected = @app.commands[:status] assert_equal(actual, expected) end def test_ambigous_command assert_raise(GLI::AmbiguousCommand) do GLI::CommandFinder.new(@app.commands, :default_command => :status).find_command(:some) end end def test_partial_name_with_autocorrect_enabled actual = GLI::CommandFinder.new(@app.commands, :default_command => :status).find_command(:deploy) expected = @app.commands[:deployable] assert_equal(actual, expected) end def test_partial_name_with_autocorrect_disabled assert_raise(GLI::UnknownCommand) do GLI::CommandFinder.new(@app.commands, :default_command => :status, :autocomplete => false) .find_command(:deploy) end end end gli-2.14.0/test/tc_gli.rb0000644000175000017500000005643313017317740014060 0ustar micahmicah# 1.9 adds realpath to resolve symlinks; 1.8 doesn't # have this method, so we add it so we get resolved symlinks # and compatibility unless File.respond_to? :realpath class File def self.realpath path return realpath(File.readlink(path)) if symlink?(path) path end end end require 'test_helper' class TC_testGLI < Clean::Test::TestCase include TestHelper def setup @fake_stdout = FakeStdOut.new @fake_stderr = FakeStdOut.new @original_stdout = $stdout $stdout = @fake_stdout @original_stderr = $stderr $stderr = @fake_stderr @app = CLIApp.new @config_file = File.expand_path(File.dirname(File.realpath(__FILE__)) + '/new_config.yaml') @gli_debug = ENV['GLI_DEBUG'] @app.error_device=@fake_stderr ENV.delete('GLI_DEBUG') end def teardown File.delete(@config_file) if File.exist?(@config_file) ENV['GLI_DEBUG'] = @gli_debug @app.error_device=$stderr $stdout = @original_stdout $stderr = @original_stderr end def test_flag_create @app.reset do_test_flag_create(@app) do_test_flag_create(GLI::Command.new(:names => :f)) end def test_create_commands_using_strings @app.reset @app.flag ['f','flag'] @app.switch ['s','some-switch'] @app.command 'command','command-with-dash' do |c| end assert @app.commands.include? :command assert @app.flags.include? :f assert @app.switches.include? :s assert @app.commands[:command].aliases.include? :'command-with-dash' assert @app.flags[:f].aliases.include? :flag assert @app.switches[:s].aliases.include? :'some-switch' end def test_default_command @app.reset @called = false @app.command :foo do |c| c.action do |global, options, arguments| @called = true end end @app.default_command(:foo) assert_equal 0, @app.run([]), "Expected exit status to be 0" assert @called, "Expected default command to be executed" end def test_command_options_can_be_required @app.reset @called = false @app.command :foo do |c| c.flag :flag, :required => true c.flag :other_flag, :required => true c.action do |global, options, arguments| @called = true end end assert_equal 64, @app.run(['foo']), "Expected exit status to be 64" assert @fake_stderr.contained?(/flag is required/), @fake_stderr.strings.inspect assert @fake_stderr.contained?(/other_flag is required/), @fake_stderr.strings.inspect assert @fake_stderr.contained?(/flag is required, other_flag is required/), @fake_stderr.strings.inspect assert !@called assert_equal 0, @app.run(['foo','--flag=bar','--other_flag=blah']), "Expected exit status to be 0 #{@fake_stderr.strings.join(',')}" assert @called end def test_global_options_can_be_required @app.reset @called = false @app.flag :flag, :required => true @app.flag :other_flag, :required => true @app.command :foo do |c| c.action do |global, options, arguments| @called = true end end assert_equal 64, @app.run(['foo']), "Expected exit status to be 64" assert @fake_stderr.contained?(/flag is required/), @fake_stderr.strings.inspect assert @fake_stderr.contained?(/other_flag is required/), @fake_stderr.strings.inspect assert @fake_stderr.contained?(/flag is required, other_flag is required/), @fake_stderr.strings.inspect assert !@called assert_equal 0, @app.run(['--flag=bar','--other_flag=blah','foo']), "Expected exit status to be 0 #{@fake_stderr.strings.join(',')}" assert @called end def test_global_required_options_are_ignored_on_help @app.reset @called = false @app.flag :flag, :required => true @app.flag :other_flag, :required => true @app.command :foo do |c| c.action do |global, options, arguments| @called = true end end assert_equal 0, @app.run(['help']), "Expected exit status to be 64" assert !@fake_stderr.contained?(/flag is required/), @fake_stderr.strings.inspect assert !@fake_stderr.contained?(/other_flag is required/), @fake_stderr.strings.inspect end def test_flag_with_space_barfs @app.reset assert_raises(ArgumentError) { @app.flag ['some flag'] } assert_raises(ArgumentError) { @app.flag ['f','some flag'] } assert_raises(ArgumentError) { @app.switch ['some switch'] } assert_raises(ArgumentError) { @app.switch ['f','some switch'] } assert_raises(ArgumentError) { @app.command ['some command'] } assert_raises(ArgumentError) { @app.command ['f','some command'] } end def test_init_from_config failure = nil @app.reset @app.config_file(File.expand_path(File.dirname(File.realpath(__FILE__)) + '/config.yaml')) @app.flag :f @app.switch :s @app.flag :g @app.default_value true @app.switch :t called = false @app.command :command do |c| c.flag :f c.switch :s c.flag :g c.action do |g,o,a| begin called = true assert_equal "foo",g[:f] assert_equal "bar",o[:g] assert !g[:g] assert !o[:f] assert !g[:s] assert o[:s] assert !g[:t] rescue Exception => ex failure = ex end end end @app.run(['command']) assert called raise failure if !failure.nil? end def test_command_line_overrides_config failure = nil @app.reset @app.config_file(File.expand_path(File.dirname(File.realpath(__FILE__)) + '/config.yaml')) @app.flag :f @app.switch :s @app.flag :g @app.switch :bleorgh called = false @app.command :command do |c| c.flag :f c.switch :s c.flag :g c.action do |g,o,a| begin called = true assert_equal "baaz",o[:g] assert_equal "bar",g[:f] assert !g[:g],o.inspect assert !o[:f],o.inspect assert !g[:s],o.inspect assert o[:s],o.inspect assert g[:bleorgh] != nil,"Expected :bleorgh to have a value" assert g[:bleorgh] == false,"Expected :bleorgh to be false" rescue Exception => ex failure = ex end end end assert_equal 0,@app.run(%w(-f bar --no-bleorgh command -g baaz)),@fake_stderr.to_s assert called raise failure if !failure.nil? end def test_no_overwrite_config config_file = File.expand_path(File.dirname(File.realpath(__FILE__)) + '/config.yaml') config_file_contents = File.read(config_file) @app.reset @app.config_file(config_file) assert_equal 1,@app.run(['initconfig']) assert @fake_stderr.strings.grep(/--force/),@fake_stderr.strings.inspect assert !@fake_stdout.contained?(/written/), @fake_stdout.strings.inspect config_file_contents_after = File.read(config_file) assert_equal(config_file_contents,config_file_contents_after) end def test_config_file_name @app.reset file = @app.config_file("foo") assert_equal(Etc.getpwuid.dir + "/foo",file) file = @app.config_file("/foo") assert_equal "/foo",file init_command = @app.commands[:initconfig] assert init_command end def test_initconfig_command @app.reset @app.config_file(@config_file) @app.flag :f @app.switch :s, :salias @app.switch :w @app.flag :bigflag, :bigalias @app.flag :biggestflag @app.command :foo do |c| end @app.command :bar do |c| end @app.command :blah do |c| end @app.on_error do |ex| raise ex end @app.run(['-f','foo','-s','--bigflag=bleorgh','initconfig']) assert @fake_stdout.contained?(/written/), @fake_stdout.strings.inspect written_config = File.open(@config_file) { |f| YAML::load(f) } assert_equal 'foo',written_config[:f] assert_equal 'bleorgh',written_config[:bigflag] assert !written_config[:bigalias] assert written_config[:s] assert !written_config[:salias] assert !written_config[:w] assert_nil written_config[:biggestflag] assert written_config[GLI::InitConfig::COMMANDS_KEY] assert written_config[GLI::InitConfig::COMMANDS_KEY][:foo] assert written_config[GLI::InitConfig::COMMANDS_KEY][:bar] assert written_config[GLI::InitConfig::COMMANDS_KEY][:blah] end def test_initconfig_permissions @app.reset @app.config_file(@config_file) @app.run(['initconfig']) oct_mode = "%o" % File.stat(@config_file).mode assert_match /0600$/, oct_mode end def do_test_flag_create(object) description = 'this is a description' long_desc = 'this is a very long description' object.desc description object.long_desc long_desc object.arg_name 'filename' object.default_value '~/.blah.rc' object.flag :f assert (object.flags[:f] ) assert_equal(description,object.flags[:f].description) assert_equal(long_desc,object.flags[:f].long_description) assert(nil != object.flags[:f].usage) assert(object.usage != nil) if object.respond_to? :usage; end def test_switch_create @app.reset do_test_switch_create(@app) do_test_switch_create(GLI::Command.new(:names => :f)) end def test_switch_create_twice @app.reset do_test_switch_create_twice(@app) do_test_switch_create_twice(GLI::Command.new(:names => :f)) end def test_all_aliases_in_options @app.reset @app.on_error { |ex| raise ex } @app.flag [:f,:flag,:'big-flag-name'] @app.switch [:s,:switch,:'big-switch-name'] @app.command [:com,:command] do |c| c.flag [:g,:gflag] c.switch [:h,:hswitch] c.action do |global,options,args| assert_equal 'foo',global[:f] assert_equal global[:f],global[:flag] assert_equal global[:f],global['f'] assert_equal global[:f],global['flag'] assert_equal global[:f],global['big-flag-name'] assert_equal global[:f],global[:'big-flag-name'] assert global[:s] assert global[:switch] assert global[:'big-switch-name'] assert global['s'] assert global['switch'] assert global['big-switch-name'] assert_equal 'bar',options[:g] assert_equal options[:g],options['g'] assert_equal options[:g],options['gflag'] assert_equal options[:g],options[:gflag] assert options[:h] assert options['h'] assert options[:hswitch] assert options['hswitch'] end end @app.run(%w(-f foo -s command -g bar -h some_arg)) end def test_use_hash_by_default @app.reset @app.switch :g @app.command :command do |c| c.switch :f c.action do |global,options,args| assert_equal Hash,global.class assert_equal Hash,options.class end end @app.run(%w(-g command -f)) end def test_flag_array_of_options_global @app.reset @app.flag :foo, :must_match => ['bar','blah','baz'] @app.command :command do |c| c.action do end end assert_equal 64,@app.run(%w(--foo=cruddo command)),@fake_stderr.to_s assert @fake_stderr.contained?(/error: invalid argument: --foo=cruddo/),"STDERR was:\n" + @fake_stderr.to_s assert_equal 0,@app.run(%w(--foo=blah command)),@fake_stderr.to_s end def test_flag_hash_of_options_global @app.reset @app.flag :foo, :must_match => { 'bar' => "BAR", 'blah' => "BLAH" } @foo_arg_value = nil @app.command :command do |c| c.action do |g,o,a| @foo_arg_value = g[:foo] end end assert_equal 64,@app.run(%w(--foo=cruddo command)),@fake_stderr.to_s assert @fake_stderr.contained?(/error: invalid argument: --foo=cruddo/),"STDERR was:\n" + @fake_stderr.to_s assert_equal 0,@app.run(%w(--foo=blah command)),@fake_stderr.to_s assert_equal 'BLAH',@foo_arg_value end def test_flag_regexp_global @app.reset @app.flag :foo, :must_match => /bar/ @app.command :command do |c| c.action do end end assert_equal 64,@app.run(%w(--foo=cruddo command)),@fake_stderr.to_s assert @fake_stderr.contained?(/error: invalid argument: --foo=cruddo/),"STDERR was:\n" + @fake_stderr.to_s end def test_flag_regexp_global_short_form @app.reset @app.flag :f, :must_match => /bar/ @app.command :command do |c| c.action do end end assert_equal 64,@app.run(%w(-f cruddo command)),@fake_stderr.to_s assert @fake_stderr.contained?(/error: invalid argument: -f cruddo/),"STDERR was:\n" + @fake_stderr.to_s end def test_flag_regexp_command @app.reset @app.command :command do |c| c.flag :foo, :must_match => /bar/ c.action do end end assert_equal 64,@app.run(%w(command --foo=cruddo)),@fake_stderr.to_s assert @fake_stderr.contained?(/error: invalid argument: --foo=cruddo/),"STDERR was:\n" + @fake_stderr.to_s end def test_use_openstruct @app.reset @app.switch :g @app.use_openstruct true @app.command :command do |c| c.switch :f c.action do |global,options,args| assert_equal GLI::Options,global.class assert_equal GLI::Options,options.class end end @app.run(%w(-g command -f)) end def test_repeated_option_names @app.reset @app.on_error { |ex| raise ex } @app.flag [:f,:flag] assert_raises(ArgumentError) { @app.switch [:foo,:flag] } assert_raises(ArgumentError) { @app.switch [:f] } @app.switch [:x,:y] assert_raises(ArgumentError) { @app.flag [:x] } assert_raises(ArgumentError) { @app.flag [:y] } # This shouldn't raise; :help is special @app.switch :help end def test_repeated_option_names_on_command @app.reset @app.on_error { |ex| raise ex } @app.command :command do |c| c.flag [:f,:flag] assert_raises(ArgumentError) { c.switch [:foo,:flag] } assert_raises(ArgumentError) { c.switch [:f] } assert_raises(ArgumentError) { c.flag [:foo,:flag] } assert_raises(ArgumentError) { c.flag [:f] } end @app.command :command3 do |c| c.switch [:s,:switch] assert_raises(ArgumentError) { c.switch [:switch] } assert_raises(ArgumentError) { c.switch [:s] } assert_raises(ArgumentError) { c.flag [:switch] } assert_raises(ArgumentError) { c.flag [:s] } end end def test_two_flags @app.reset @app.on_error do |ex| raise ex end @app.command [:foo] do |c| c.flag :i c.flag :s c.action do |g,o,a| assert_equal "5", o[:i] assert_equal "a", o[:s] end end @app.run(['foo', '-i','5','-s','a']) end def test_two_flags_with_a_default @app.reset @app.on_error do |ex| raise ex end @app.command [:foo] do |c| c.default_value "1" c.flag :i c.flag :s c.action do |g,o,a| assert_equal "1", o[:i] assert_equal nil, o[:s] end end @app.run(['foo','a']) end def test_switch_with_default_of_true @app.reset @app.on_error do |ex| raise ex end @switch_value = nil @app.command [:foo] do |c| c.default_value true c.switch :switch c.action do |g,o,a| @switch_value = o[:switch] end end @app.run(['foo']) assert @switch_value == true,"Got: '#{@switch_value}', but expected true" @app.run(['foo','--no-switch']) assert @switch_value == false,"Got: '#{@switch_value}', but expected false" end def test_switch_with_default_true_and_not_negatable_causes_exception @app.reset @app.on_error do |ex| raise ex end @switch_value = nil assert_raises(RuntimeError) do @app.command [:foo] do |c| c.switch :switch, :default_value => true, :negatable => false end end end def test_two_flags_using_equals_with_a_default @app.reset @app.on_error do |ex| raise ex end @app.command [:foo] do |c| c.default_value "1" c.flag :i c.flag :s c.action do |g,o,a| assert_equal "5", o[:i],o.inspect assert_equal "a", o[:s],o.inspect end end @app.run(['foo', '-i5','-sa']) end def test_default_values_are_available_on_all_aliases @app.reset @app.on_error { |e| raise e } @app.default_value "global_default" @app.flag [ :f, :flag ] @global_options = {} @command_options = {} @app.command [:foo] do |c| c.default_value "command_default" c.flag [ :c,:commandflag] c.action do |g,o,a| @global_options = g @command_options = o end end @app.run(["foo"]) assert_equal "global_default", @global_options[:f] assert_equal "global_default", @global_options[:flag] assert_equal "global_default", @global_options["f"] assert_equal "global_default", @global_options["flag"] assert_equal "command_default", @command_options[:c] assert_equal "command_default", @command_options[:commandflag] assert_equal "command_default", @command_options["c"] assert_equal "command_default", @command_options["commandflag"] end def test_exits_zero_on_success @app.reset assert_equal 0,@app.run([]),@fake_stderr.to_s end def test_exits_nonzero_on_bad_command_line @app.reset @app.on_error { true } assert_equal 64,@app.run(['asdfasdfasdf']) end def test_exists_nonzero_on_raise_from_command @app.reset @app.on_error { true } @app.command(:foo) do |c| c.action do |g,o,a| raise "Problem" end end assert_equal 1,@app.run(['foo']) end def test_exits_nonzero_with_custom_exception @app.reset @app.on_error { true } @app.command(:foo) do |c| c.action do |g,o,a| raise GLI::CustomExit.new("Problem",45) end end assert_equal 45,@app.run(['foo']) end def test_exits_nonzero_with_exit_method @app.reset @app.on_error { true } @app.command(:foo) do |c| c.action do |g,o,a| @app.exit_now!("Problem",45) end end assert_equal 45,@app.run(['foo']) end def test_exits_nonzero_with_exit_method_by_default @app.reset @app.on_error { true } @app.command(:foo) do |c| c.action do |g,o,a| @app.exit_now!("Problem") end end assert_equal 1,@app.run(['foo']) end def test_help_now_exits_and_shows_help @app.reset @app.on_error { true } @app.command(:foo) do |c| c.action do |g,o,a| @app.help_now!("Problem") end end assert_equal 64,@app.run(['foo']),@fake_stderr.strings.join("\n") end def test_custom_exception_causes_error_to_be_printed_to_stderr @app.reset @app.on_error { true } error_message = "Something went wrong" @app.command(:foo) do |c| c.action do |g,o,a| raise error_message end end @app.run(['foo']) assert @fake_stderr.strings.include?("error: #{error_message}"),"STDERR was:\n" + @fake_stderr.to_s end def test_gli_debug_overrides_error_hiding ENV['GLI_DEBUG'] = 'true' @app.reset @app.on_error { true } @app.command(:foo) do |c| c.action do |g,o,a| @app.exit_now!("Problem",45) end end assert_raises(GLI::CustomExit) { @app.run(['foo']) } end def test_gli_help_does_not_raise_on_debug ENV['GLI_DEBUG'] = 'true' @app.reset @app.command(:multiply) do |c| c.action do |g,o,a| # Nothing end end assert_nothing_raised(GLI::CustomExit) { @app.run(['multiply', '--help']) } end class ConvertMe attr_reader :value def initialize(value) @value = value end end def test_that_we_can_add_new_casts_for_flags @app.reset @app.accept(ConvertMe) do |value| ConvertMe.new(value) end @app.flag :foo, :type => ConvertMe @foo = nil @baz = nil @app.command(:bar) do |c| c.flag :baz, :type => ConvertMe c.action do |g,o,a| @foo = g[:foo] @baz = o[:baz] end end assert_equal 0,@app.run(['--foo','blah','bar','--baz=crud']),@fake_stderr.to_s assert @foo.kind_of?(ConvertMe),"Expected a ConvertMe, but get a #{@foo.class}" assert_equal 'blah',@foo.value assert @baz.kind_of?(ConvertMe),"Expected a ConvertMe, but get a #{@foo.class}" assert_equal 'crud',@baz.value end def test_that_flags_can_be_used_multiple_times @app.reset @app.flag :flag, :multiple => true @app.command :foo do |c| c.action do |options, _, _| @flag = options[:flag] end end assert_equal 0,@app.run(%w(--flag 1 --flag=2 --flag 3 foo)),@fake_stderr.to_s assert_equal ['1','2','3'],@flag end def test_that_multiple_use_flags_are_empty_arrays_by_default @app.reset @app.flag :flag, :multiple => true @app.command :foo do |c| c.action do |options, _, _| @flag = options[:flag] end end assert_equal 0,@app.run(['foo']),@fake_stderr.to_s assert_equal [],@flag end def test_that_multiple_use_flags_can_take_other_defaults @app.reset @app.flag :flag, :multiple => true, :default_value => ['1'] @app.command :foo do |c| c.action do |options, _, _| @flag = options[:flag] end end assert_equal 0,@app.run(['foo']),@fake_stderr.to_s assert_equal ['1'],@flag end def test_that_we_mutate_ARGV_by_default @app.reset @app.flag :f @app.command :foo do |c| c.action do |*args| end end argv = %w(-f some_flag foo bar blah) @app.run(argv) assert_equal %w(bar blah),argv end def test_that_we_can_avoid_mutating_ARGV @app.reset @app.flag :f @app.command :foo do |c| c.action do |*args| end end @app.preserve_argv argv = %w(-f some_flag foo bar blah) @app.run(argv) assert_equal %w(-f some_flag foo bar blah),argv end private def do_test_flag_create(object) description = 'this is a description' long_desc = 'this is a very long description' object.desc description object.long_desc long_desc object.arg_name 'filename' object.default_value '~/.blah.rc' object.flag :f assert (object.flags[:f] ) assert_equal(description,object.flags[:f].description) assert_equal(long_desc,object.flags[:f].long_description) end def do_test_switch_create(object) do_test_switch_create_classic(object) do_test_switch_create_compact(object) end def some_descriptions lambda { @description = 'this is a description' @long_description = 'this is a very long description' } end def assert_switch_was_made(object,switch) lambda { assert object.switches[switch] assert_equal @description,object.switches[switch].description,"For switch #{switch}" assert_equal @long_description,object.switches[switch].long_description,"For switch #{switch}" assert(object.usage != nil) if object.respond_to? :usage } end def do_test_switch_create_classic(object) Given some_descriptions When { object.desc @description object.long_desc @long_description object.switch :f } Then assert_switch_was_made(object,:f) end def do_test_switch_create_compact(object) Given some_descriptions When { object.switch :g, :desc => @description, :long_desc => @long_description } Then assert_switch_was_made(object,:g) end def do_test_switch_create_twice(object) description = 'this is a description' object.desc description object.switch :f assert (object.switches[:f] ) assert_equal(description,object.switches[:f].description) object.switch :g assert (object.switches[:g]) assert_equal(nil,object.switches[:g].description) assert(object.usage != nil) if object.respond_to? :usage end end gli-2.14.0/LICENSE.txt0000644000175000017500000002613513017317740013132 0ustar micahmicah Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "[]" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright [yyyy] [name of copyright owner] Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. gli-2.14.0/gli.rdoc0000644000175000017500000000073313017317740012727 0ustar micahmicah== gli - create scaffolding for a GLI-powered application v2.12.1 === Global Options === -r|--root arg Root dir of project [Default Value] . This is the directory where the project''s directory will be made, so if you specify a project name ''foo'' and the root dir of ''.'', the directory ''./foo'' will be created' === --help Show this message === -n Dry run; dont change the disk === -v Be verbose === --version Display the program version === Commands gli-2.14.0/features/0000755000175000017500000000000013017317740013116 5ustar micahmicahgli-2.14.0/features/step_definitions/0000755000175000017500000000000013017317740016464 5ustar micahmicahgli-2.14.0/features/step_definitions/todo_steps.rb0000644000175000017500000000645013017317740021201 0ustar micahmicahGiven /^todo_legacy's bin directory is in my path/ do add_to_path(File.expand_path(File.join(File.dirname(__FILE__),'..','..','test','apps','todo_legacy','bin'))) end Given /^todo's bin directory is in my path/ do add_to_path(File.expand_path(File.join(File.dirname(__FILE__),'..','..','test','apps','todo','bin'))) end Given /^the todo app is coded to avoid sorted help commands$/ do ENV['TODO_SORT_HELP'] = 'manually' end Given /^the todo app is coded to avoid wrapping text$/ do ENV['TODO_WRAP_HELP_TEXT'] = 'one_line' end Given /^the todo app is coded to wrap text only for tty$/ do ENV['TODO_WRAP_HELP_TEXT'] = 'tty_only' end Given /^the todo app is coded to hide commands without description$/ do ENV['HIDE_COMMANDS_WITHOUT_DESC'] = 'true' end Given /^a clean home directory$/ do FileUtils.rm_rf File.join(ENV['HOME'],'gli_test_todo.rc') end Then /^the config file should contain a section for each command and subcommand$/ do config = File.open(File.join(ENV['HOME'],'gli_test_todo.rc')) do |file| YAML::load(file) end expect(config.keys).to include(:flag) expect(config[:flag]).to eq('foo') config[:flag].tap do |flag| if flag.respond_to?(:encoding) expect(flag.encoding.name).to eq('UTF-8') end end expect(config.keys).to include(:switch) expect(config[:switch]).to eq(true) expect(config.keys).to include(:otherswitch) expect(config[:otherswitch]).to eq(false) expect(config.keys).to include('commands') %w(chained chained2 create first list ls second).map(&:to_sym).each do |command_name| expect(config['commands'].keys).to include(command_name) end expect(config['commands'][:create].keys).to include('commands') expect(config['commands'][:create]['commands']).to include(:tasks) expect(config['commands'][:create]['commands']).to include(:contexts) expect(config['commands'][:list].keys).to include('commands') expect(config['commands'][:list]['commands']).to include(:tasks) expect(config['commands'][:list]['commands']).to include(:contexts) end Given /^a config file that specifies defaults for some commands with subcommands$/ do @config = { 'commands' => { :list => { 'commands' => { :tasks => { :flag => 'foobar', }, :contexts => { :otherflag => 'crud', }, } } } } File.open(File.join(ENV['HOME'],'gli_test_todo.rc'),'w') do |file| file.puts @config.to_yaml end end Then /^I should see the defaults for '(.*)' from the config file in the help$/ do |command_path| if command_path == 'list tasks' step %{the output should match /--flag.*default: foobar/} expect(unescape(all_output)).not_to match(/#{unescape("--otherflag.*default: crud")}/m) elsif command_path == 'list contexts' step %{the output should match /--otherflag.*default: crud/} expect(unescape(all_output)).not_to match(/#{unescape("--flag.*default: foobar")}/m) else raise "Don't know how to test for command path #{command_path}" end end Given /^the todo app is coded to use verbatim formatting$/ do ENV['TODO_WRAP_HELP_TEXT'] = 'verbatim' end Given(/^my terminal is (\d+) characters wide$/) do |terminal_width| ENV['COLUMNS'] = terminal_width.to_s end Given(/^my app is configured for "(.*?)" synopses$/) do |synopsis| ENV['SYNOPSES'] = synopsis end gli-2.14.0/features/step_definitions/gli_init_steps.rb0000644000175000017500000000040113017317740022020 0ustar micahmicahGiven /^GLI's libs are in my path$/ do ENV['RUBYLIB'] = GLI_LIB_PATH end Given /^I make sure todo's lib dir is in my lib path$/ do add_to_lib_path("./lib") end Given /^todo's libs are no longer in my load path$/ do remove_from_lib_path("./lib") end gli-2.14.0/features/step_definitions/gli_executable_steps.rb0000644000175000017500000000062013017317740023201 0ustar micahmicahGiven /^I have GLI installed$/ do add_to_lib_path(GLI_LIB_PATH) end Given /^my terminal size is "([^"]*)"$/ do |terminal_size| if terminal_size =~/^(\d+)x(\d+)$/ ENV['COLUMNS'] = $1 ENV['LINES'] = $2 else raise "Terminal size should be COLxLines, e.g. 80x24" end end Given /^the file "(.*?)" doesn't exist$/ do |filename| FileUtils.rm filename if File.exist?(filename) end gli-2.14.0/features/gli_init.feature0000644000175000017500000001757113017317740016304 0ustar micahmicahFeature: The scaffold GLI generates works As a developer who wants to make a GLI-powered command-line app When I generate a GLI-powered app Things work out of the box Background: Given I have GLI installed And GLI's libs are in my path And my terminal size is "80x24" Scenario: Scaffold generates and things look good When I run `gli init --rvmrc todo add complete list` Then the exit status should be 0 And the output should contain exactly: """ Creating dir ./todo/lib... Creating dir ./todo/bin... Creating dir ./todo/test... Created ./todo/bin/todo Created ./todo/README.rdoc Created ./todo/todo.rdoc Created ./todo/todo.gemspec Created ./todo/test/default_test.rb Created ./todo/test/test_helper.rb Created ./todo/Rakefile Created ./todo/Gemfile Created ./todo/features Created ./todo/lib/todo/version.rb Created ./todo/lib/todo.rb Created ./todo/.rvmrc """ And the following directories should exist: |todo | |todo/bin | |todo/test | |todo/lib | And the following files should exist: |todo/bin/todo | |todo/README.rdoc | |todo/todo.rdoc | |todo/todo.gemspec | |todo/test/default_test.rb | |todo/test/test_helper.rb | |todo/Rakefile | |todo/Gemfile | |todo/lib/todo/version.rb | |todo/lib/todo.rb | |todo/.rvmrc | When I cd to "todo" And I make sure todo's lib dir is in my lib path And I run `bin/todo` Then the output should contain: """ NAME todo - Describe your application here SYNOPSIS todo [global options] command [command options] [arguments...] VERSION 0.0.1 GLOBAL OPTIONS -f, --flagname=The name of the argument - Describe some flag here (default: the default) --help - Show this message -s, --[no-]switch - Describe some switch here --version - Display the program version COMMANDS add - Describe add here complete - Describe complete here help - Shows a list of commands or help for one command list - Describe list here """ And I run `bin/todo --help` Then the output should contain: """ NAME todo - Describe your application here SYNOPSIS todo [global options] command [command options] [arguments...] VERSION 0.0.1 GLOBAL OPTIONS -f, --flagname=The name of the argument - Describe some flag here (default: the default) --help - Show this message -s, --[no-]switch - Describe some switch here --version - Display the program version COMMANDS add - Describe add here complete - Describe complete here help - Shows a list of commands or help for one command list - Describe list here """ When I run `bin/todo help add` Then the output should contain: """ NAME add - Describe add here """ And the output should contain: """ SYNOPSIS todo [global options] add [command options] Describe arguments to add here """ And the output should contain: """ COMMAND OPTIONS -f arg - Describe a flag to add (default: default) -s - Describe a switch to add """ When I run `rake test` Then the output should contain: """ . """ And the output should contain: """ 1 tests, 1 assertions, 0 failures, 0 errors """ Given todo's libs are no longer in my load path When I run `rake features` Then the output should contain: """ 1 scenario (1 passed) """ And the output should contain: """ 2 steps (2 passed) """ @wip Scenario Outline: Scaffold generates and respects flags to create ext dir and avoid test dir When I run `` Then the exit status should be 0 And the output should contain exactly: """ Creating dir ./todo/lib... Creating dir ./todo/bin... Creating dir ./todo/ext... Created ./todo/bin/todo Created ./todo/README.rdoc Created ./todo/todo.rdoc Created ./todo/todo.gemspec Created ./todo/Rakefile Created ./todo/Gemfile Created ./todo/lib/todo/version.rb Created ./todo/lib/todo.rb """ And the following directories should exist: |todo | |todo/bin | |todo/ext | |todo/lib | And the following directories should not exist: |todo/test| And the following files should exist: |todo/bin/todo | |todo/README.rdoc | |todo/todo.rdoc | |todo/todo.gemspec | |todo/Rakefile | |todo/Gemfile | |todo/lib/todo/version.rb | |todo/lib/todo.rb | Examples: | command | | gli init -e --notest todo add complete list | | gli init todo add complete list -e --notest | Scenario: Running commands the normal way Given I successfully run `gli init todo add complete compute list` And I cd to "todo" And I make sure todo's lib dir is in my lib path When I successfully run `bin/todo add` Then the output should contain "add command ran" When I successfully run `bin/todo complete` Then the output should contain "complete command ran" When I run `bin/todo foobar` Then the stderr should contain "error: Unknown command 'foobar'" And the exit status should not be 0 Scenario: Running commands using short form Given I successfully run `gli init todo add complete compute list` And I cd to "todo" And I make sure todo's lib dir is in my lib path When I successfully run `bin/todo a` Then the output should contain "add command ran" When I successfully run `bin/todo l` Then the output should contain "list command ran" When I successfully run `bin/todo compl` Then the output should contain "complete command ran" Scenario: Ambiguous commands give helpful output Given I successfully run `gli init todo add complete compute list` And I cd to "todo" And I make sure todo's lib dir is in my lib path When I run `bin/todo comp` Then the stderr should contain "Ambiguous command 'comp'. It matches complete,compute" And the exit status should not be 0 Scenario: Running generated command without bundler gives a helpful error message Given I successfully run `gli init todo add complete compute list` And I cd to "todo" When I run `bin/todo comp` Then the exit status should not be 0 Then the stderr should contain "In development, you need to use `bundle exec bin/todo` to run your app" And the stderr should contain "At install-time, RubyGems will make sure lib, etc. are in the load path" And the stderr should contain "Feel free to remove this message from bin/todo now" Scenario: Running commands with a dash in the name Given I successfully run `gli init todo-app add complete compute list` And I cd to "todo-app" And I make sure todo's lib dir is in my lib path When I successfully run `bin/todo-app add` Then the output should contain "add command ran" When I successfully run `bin/todo-app complete` Then the output should contain "complete command ran" When I run `bin/todo-app foobar` Then the stderr should contain "error: Unknown command 'foobar'" And the exit status should not be 0 gli-2.14.0/features/todo_legacy.feature0000644000175000017500000000745413017317740016776 0ustar micahmicahFeature: The todo app is backwards compatible with legacy subcommand parsing As a user of GLI My apps with subcommands should support the old, legacy way, by default Background: Given I have GLI installed And GLI's libs are in my path And my terminal size is "80x24" And todo_legacy's bin directory is in my path Scenario: Help completion mode for subcommands When I successfully run `todo help -c list` Then the output should contain: """ contexts tasks """ Scenario: Help completion mode partial match for subcommands When I successfully run `todo help -c list con` Then the output should contain: """ contexts """ Scenario Outline: Getting Help for a top level command of todo # No idea why I have to do this again. Given todo_legacy's bin directory is in my path When I successfully run `todo ` Then the output should contain: """ NAME list - List things, such as tasks or contexts SYNOPSIS todo [global options] list [command options] [tasks] [--flag arg] [-x arg] todo [global options] list [command options] contexts [--otherflag arg] [-b] [-f|--foobar] DESCRIPTION List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases. COMMAND OPTIONS -l, --[no-]long - Show long form COMMANDS contexts - List contexts tasks - List tasks (default) """ Examples: | help_invocation | | help list | | list -h | | list --help | Scenario: Getting Help for a sub command of todo list When I successfully run `todo help list tasks` Then the output should contain: """ NAME tasks - List tasks SYNOPSIS todo [global options] list tasks [command options] todo [global options] list tasks [command options] open DESCRIPTION Lists all of your tasks that you have, in varying orders, and all that stuff. Yes, this is long, but I need a long description. COMMAND OPTIONS --flag=arg - (default: none) -x arg - blah blah crud x whatever (default: none) COMMANDS - list all tasks open - list open tasks """ Scenario: Getting Help for a sub command with no command options When I successfully run `todo help new` Then the output should contain: """ NAME create - Create a new task or context SYNOPSIS todo [global options] create todo [global options] create contexts [context_name] todo [global options] create tasks task_name[, task_name]* COMMANDS - Makes a new task contexts - Make a new context tasks - Make a new task """ And the output should not contain "COMMAND OPTIONS" Scenario: Running ls w/out subcommand shows help and an error When I run `todo ls` Then the exit status should not be 0 And the stderr should contain "error: Command 'ls' requires a subcommand" And the stdout should contain: """ NAME ls - LS things, such as tasks or contexts SYNOPSIS todo [global options] ls [command options] contexts [-b] [-f|--foobar] todo [global options] ls [command options] tasks [-x arg] DESCRIPTION List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases. COMMAND OPTIONS -l, --[no-]long - Show long form COMMANDS contexts - List contexts tasks - List tasks """ gli-2.14.0/features/todo.feature0000644000175000017500000004211213017317740015440 0ustar micahmicahFeature: The todo app has a nice user interface As a user of the todo application It should have a nice UI, since it's GLI-powered Background: Given I have GLI installed And GLI's libs are in my path And my terminal size is "80x24" And todo's bin directory is in my path Scenario: Error message for unknown command When I run `todo help unknown` Then the output should contain: """ error: Unknown command 'unknown'. Use 'todo help' for a list of commands. """ Scenario: Getting Help for todo in general When I successfully run `todo help` Then the exit status should be 0 Then the output should contain: """ NAME todo - Manages tasks A test program that has a sophisticated UI that can be used to exercise a lot of GLI's power SYNOPSIS todo [global options] command [command options] [arguments...] VERSION 0.0.1 GLOBAL OPTIONS --flag=arg - (default: none) --help - Show this message --[no-]otherswitch - --[no-]switch - --version - Display the program version COMMANDS chained - chained2, ch2 - create, new - Create a new task or context first - help - Shows a list of commands or help for one command initconfig - Initialize the config file using current global options list - List things, such as tasks or contexts ls - LS things, such as tasks or contexts make - second - third - """ Scenario: Version display When I successfully run `todo --version` Then the output should contain: """ todo version 0.0.1 """ Scenario: Help completion mode When I successfully run `todo help -c` Then the output should contain: """ _doc ch2 chained chained2 create first help initconfig list ls make new second third """ Scenario: Help completion mode for partial match When I successfully run `todo help -c ch` Then the output should contain: """ ch2 chained chained2 """ Scenario: Help completion mode for subcommands When I successfully run `todo help -c list` Then the output should contain: """ contexts tasks """ Scenario: Help completion mode partial match for subcommands When I successfully run `todo help -c list con` Then the output should contain: """ contexts """ Scenario: Getting Help with self-ordered commands Given the todo app is coded to avoid sorted help commands When I successfully run `todo help` Then the output should contain: """ NAME todo - Manages tasks A test program that has a sophisticated UI that can be used to exercise a lot of GLI's power SYNOPSIS todo [global options] command [command options] [arguments...] VERSION 0.0.1 GLOBAL OPTIONS --flag=arg - (default: none) --[no-]switch - --[no-]otherswitch - --version - Display the program version --help - Show this message COMMANDS help - Shows a list of commands or help for one command initconfig - Initialize the config file using current global options create, new - Create a new task or context list - List things, such as tasks or contexts ls - LS things, such as tasks or contexts make - third - first - second - chained - chained2, ch2 - """ Scenario: Getting Help with commands without description hidden Given the todo app is coded to hide commands without description When I successfully run `todo help` Then the output should contain: """ NAME todo - Manages tasks A test program that has a sophisticated UI that can be used to exercise a lot of GLI's power SYNOPSIS todo [global options] command [command options] [arguments...] VERSION 0.0.1 GLOBAL OPTIONS --flag=arg - (default: none) --help - Show this message --[no-]otherswitch - --[no-]switch - --version - Display the program version COMMANDS create, new - Create a new task or context help - Shows a list of commands or help for one command initconfig - Initialize the config file using current global options list - List things, such as tasks or contexts ls - LS things, such as tasks or contexts """ Scenario Outline: Getting Help for a top level command of todo # No idea why I have to do this again. Given todo's bin directory is in my path When I successfully run `todo ` Then the output should contain: """ NAME list - List things, such as tasks or contexts SYNOPSIS todo [global options] list [command options] [tasks] [--flag arg] [-x arg] [task][, [task]]* todo [global options] list [command options] contexts [--otherflag arg] [-b] [-f|--foobar] DESCRIPTION List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases. COMMAND OPTIONS -l, --[no-]long - Show long form --required_flag=arg - (required, default: none) --required_flag2=arg - (required, default: none) COMMANDS contexts - List contexts tasks - List tasks (default) """ Examples: | help_invocation | | help list | | list -h | | list --help | | --help list | Scenario: Getting Help for a top level command of todo with no command options When I successfully run `todo help chained` Then the output should contain: """ NAME chained - SYNOPSIS todo [global options] chained """ Scenario: Getting Help with no wrapping Given the todo app is coded to avoid wrapping text When I successfully run `todo help list` Then the output should contain: """ NAME list - List things, such as tasks or contexts SYNOPSIS todo [global options] list [command options] [tasks] [--flag arg] [-x arg] [task][, [task]]* todo [global options] list [command options] contexts [--otherflag arg] [-b] [-f|--foobar] DESCRIPTION List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases. COMMAND OPTIONS -l, --[no-]long - Show long form --required_flag=arg - (required, default: none) --required_flag2=arg - (required, default: none) COMMANDS contexts - List contexts tasks - List tasks (default) """ Scenario: Getting Help with verbatim formatting Given the todo app is coded to use verbatim formatting When I successfully run `todo help list` Then the output should contain: """ NAME list - List things, such as tasks or contexts SYNOPSIS todo [global options] list [command options] [tasks] [--flag arg] [-x arg] [task][, [task]]* todo [global options] list [command options] contexts [--otherflag arg] [-b] [-f|--foobar] DESCRIPTION List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases. COMMAND OPTIONS -l, --[no-]long - Show long form --required_flag=arg - (required, default: none) --required_flag2=arg - (required, default: none) COMMANDS contexts - List contexts tasks - List tasks (default) """ Scenario: Getting Help without wrapping Given the todo app is coded to wrap text only for tty When I successfully run `todo help list` Then the output should contain: """ NAME list - List things, such as tasks or contexts SYNOPSIS todo [global options] list [command options] [tasks] [--flag arg] [-x arg] [task][, [task]]* todo [global options] list [command options] contexts [--otherflag arg] [-b] [-f|--foobar] DESCRIPTION List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases. COMMAND OPTIONS -l, --[no-]long - Show long form --required_flag=arg - (required, default: none) --required_flag2=arg - (required, default: none) COMMANDS contexts - List contexts tasks - List tasks (default) """ Scenario: Getting Help for a sub command of todo list When I successfully run `todo help list tasks` Then the output should contain: """ NAME tasks - List tasks SYNOPSIS todo [global options] list tasks [command options] [task][, [task]]* todo [global options] list tasks [command options] open DESCRIPTION Lists all of your tasks that you have, in varying orders, and all that stuff. Yes, this is long, but I need a long description. COMMAND OPTIONS --flag=arg - (default: none) -x arg - blah blah crud x whatever (default: none) COMMANDS - list all tasks open - list open tasks """ Scenario: Getting Help for a sub command with no command options When I successfully run `todo help new` Then the output should contain: """ NAME create - Create a new task or context SYNOPSIS todo [global options] create todo [global options] create contexts [context_name] todo [global options] create relation_1-1 first second [name] todo [global options] create relation_1-n first second[, second]* [name] todo [global options] create relation_n-1 first[, first]* second [name] todo [global options] create tasks task_name[, task_name]* COMMANDS - Makes a new task contexts - Make a new context relation_1-1 - relation_1-n - relation_n-1 - tasks - Make a new task """ And the output should not contain "COMMAND OPTIONS" Scenario: Running list w/out subcommand performs list tasks by default When I successfully run `todo list --required_flag=blah --required_flag2=bleh boo yay` Then the output should contain "list tasks: boo,yay" Scenario: Running list w/out subcommand or any arguments performs list tasks by default When I successfully run `todo list --required_flag=blah --required_flag2=bleh` Then the output should contain "list tasks:" Scenario: Running chained commands works When I successfully run `todo chained foo bar` Then the output should contain: """ first: foo,bar second: foo,bar """ Scenario: Running chained commands works and is ordered When I successfully run `todo chained2 foo bar` Then the output should contain: """ second: foo,bar first: foo,bar """ Scenario: Running chained commands works and is ordered When I successfully run `todo ch2 foo bar` Then the output should contain: """ second: foo,bar first: foo,bar """ Scenario: Running ls w/out subcommand shows help and an error When I run `todo ls` Then the exit status should not be 0 And the stderr should contain "error: Command 'ls' requires a subcommand" And the stdout should contain: """ NAME ls - LS things, such as tasks or contexts SYNOPSIS todo [global options] ls [command options] contexts [-b] [-f|--foobar] todo [global options] ls [command options] tasks [-x arg] DESCRIPTION List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases. COMMAND OPTIONS -l, --[no-]long - Show long form COMMANDS contexts - List contexts tasks - List tasks """ Scenario: Access to the complex command-line options for nested subcommands Given I run `todo make -l MAKE task -l TASK bug -l BUG other args` Then the exit status should be 0 And the stdout should contain: """ new task bug other,args BUG BUG TASK TASK MAKE MAKE """ Scenario: Init Config makes a reasonable config file Given a clean home directory When I successfully run `todo --flag foo --switch --no-otherswitch initconfig` Then the config file should contain a section for each command and subcommand Scenario: Init Config makes a reasonable config file if one is there and we force it Given a clean home directory And I successfully run `todo --flag foo --switch --no-otherswitch initconfig` When I run `todo --flag foo --switch --no-otherswitch initconfig` Then the exit status should not be 0 When I run `todo --flag foo --switch --no-otherswitch initconfig --force` Then the exit status should be 0 Scenario: Configuration percolates to the app Given a clean home directory And a config file that specifies defaults for some commands with subcommands When I successfully run `todo help list tasks` Then I should see the defaults for 'list tasks' from the config file in the help Scenario: Do it again because aruba buffers all output Given a clean home directory And a config file that specifies defaults for some commands with subcommands When I successfully run `todo help list contexts` Then I should see the defaults for 'list contexts' from the config file in the help Scenario: A complex SYNOPSIS section gets summarized in terminal mode Given my terminal is 50 characters wide And my app is configured for "terminal" synopses When I run `todo ls` Then the exit status should not be 0 And the stderr should contain "error: Command 'ls' requires a subcommand" And the stdout should contain: """ NAME ls - LS things, such as tasks or contexts SYNOPSIS todo [global options] ls [command options] contexts [subcommand options] todo [global options] ls [command options] tasks [subcommand options] """ Scenario: We can always use a compact SYNOPSIS Given my terminal is 500 characters wide And my app is configured for "compact" synopses When I run `todo ls` Then the exit status should not be 0 And the stderr should contain "error: Command 'ls' requires a subcommand" And the stdout should contain: """ NAME ls - LS things, such as tasks or contexts SYNOPSIS todo [global options] ls [command options] contexts [subcommand options] todo [global options] ls [command options] tasks [subcommand options] """ Scenario: We get a clear error message when a required argument is missing Given a clean home directory When I run `todo list` Then the exit status should not be 0 And the stderr should contain "error: required_flag is required, required_flag2 is required" And the output should contain: """ NAME list - List things, such as tasks or contexts SYNOPSIS todo [global options] list [command options] [tasks] [subcommand options] [task][, [task]]* todo [global options] list [command options] contexts [subcommand options] DESCRIPTION List a whole lot of things that you might be keeping track of in your overall todo list. This is your go-to place or finding all of the things that you might have stored in your todo databases. COMMAND OPTIONS -l, --[no-]long - Show long form --required_flag=arg - (required, default: none) --required_flag2=arg - (required, default: none) COMMANDS contexts - List contexts tasks - List tasks (default) """ Scenario: Getting help on a root command with an arg_name outputs the argument description When I run `todo help first` And the stdout should contain: """ NAME first - SYNOPSIS todo [global options] first [argument] """ Scenario: Getting help on a root command with an arg outputs the argument description When I run `todo help second` And the stdout should contain: """ NAME second - SYNOPSIS todo [global options] second [argument] """ Scenario: Generate app documentation When I run `todo _doc` Then the exit status should be 0 And a file named "todo.rdoc" should exist And the file "todo.rdoc" should contain "Lists all of your contexts, which are places you might be" gli-2.14.0/features/support/0000755000175000017500000000000013017317740014632 5ustar micahmicahgli-2.14.0/features/support/env.rb0000644000175000017500000000277013017317740015755 0ustar micahmicahbegin require 'simplecov' SimpleCov.start rescue LoadError # Don't care end require 'aruba/cucumber' require 'fileutils' # Adds GLI's bin dir to our path ENV['PATH'] = "#{File.expand_path(File.dirname(__FILE__) + '/../../bin')}#{File::PATH_SEPARATOR}#{ENV['PATH']}" GLI_LIB_PATH = File.expand_path(File.join(File.dirname(__FILE__),'..','..','lib')) GLI_GEMSET = 'gli-testing' TMP_PATH = 'tmp/aruba' Before do # Not sure how else to get this dynamically @dirs = [TMP_PATH] @aruba_timeout_seconds = 5 @original_path = ENV['PATH'].split(File::PATH_SEPARATOR) @original_home = ENV['HOME'] new_home = "/tmp/fakehome" FileUtils.rm_rf new_home FileUtils.mkdir new_home ENV['HOME'] = new_home FileUtils.cp 'gli.rdoc','gli.rdoc.orig' end After do |scenario| ENV['RUBYLIB'] = '' todo_app_dir = File.join(TMP_PATH,'todo') if File.exist? todo_app_dir FileUtils.rm_rf(todo_app_dir) end ENV['PATH'] = @original_path.join(File::PATH_SEPARATOR) ENV['HOME'] = @original_home ENV['TODO_SORT_HELP'] = nil ENV['TODO_WRAP_HELP_TEXT'] = nil ENV['HIDE_COMMANDS_WITHOUT_DESC'] = nil FileUtils.mv 'gli.rdoc.orig','gli.rdoc' end def add_to_path(dir) ENV['PATH'] = "#{dir}#{File::PATH_SEPARATOR}#{ENV['PATH']}" end def add_to_lib_path(path) ENV["RUBYLIB"] = (String(ENV["RUBYLIB"]).split(File::PATH_SEPARATOR) + [path]).join(File::PATH_SEPARATOR) end def remove_from_lib_path(path) ENV["RUBYLIB"] = (String(ENV["RUBYLIB"]).split(File::PATH_SEPARATOR) - [path]).join(File::PATH_SEPARATOR) end gli-2.14.0/features/gli_executable.feature0000644000175000017500000000532113017317740017450 0ustar micahmicahFeature: The GLI executable works as intended As a developer who wants to make a GLI-powered command-line app When I use the app provided by GLI I get a reasonably working application Background: Given I have GLI installed And my terminal size is "80x24" Scenario Outline: Getting Help for GLI When I run `gli ` Then the exit status should be 0 And the output should contain: """ NAME gli - create scaffolding for a GLI-powered application SYNOPSIS gli [global options] command [command options] [arguments...] VERSION """ And the output should contain: """ GLOBAL OPTIONS --help - Show this message -n - Dry run; dont change the disk -r, --root=arg - Root dir of project (default: .) -v - Be verbose --version - Display the program version COMMANDS help - Shows a list of commands or help for one command init, scaffold - Create a new GLI-based project """ Examples: |command| | | |help | Scenario Outline: Getting help on scaffolding When I run `gli help ` Then the exit status should be 0 And the output should contain: """ NAME init - Create a new GLI-based project SYNOPSIS gli [global options] init [command options] project_name [command_name][, [command_name]]* DESCRIPTION This will create a scaffold command line project that uses GLI for command line processing. Specifically, this will create an executable ready to go, as well as a lib and test directory, all inside the directory named for your project COMMAND OPTIONS -e, --[no-]ext - Create an ext dir --[no-]force - Overwrite/ignore existing files and directories --notest - Do not create a test or features dir """ Examples: |command | |init | |scaffold | Scenario: GLI correctly identifies non-existent command When I run `gli foobar` Then the exit status should not be 0 And the stderr should contain "error: Unknown command 'foobar'" Scenario: GLI correctly identifies non-existent global flag When I run `gli -q help` Then the exit status should not be 0 And the stderr should contain "error: Unknown option -q" Scenario: GLI correctly identifies non-existent command flag When I run `gli init -q` Then the exit status should not be 0 And the stderr should contain "error: Unknown option -q" Scenario: The _doc command doesn't blow up Given the file "gli.rdoc" doesn't exist When I run `gli _doc` Then a file named "gli.rdoc" should exist gli-2.14.0/.ruby-gemset0000644000175000017500000000001013017317740013533 0ustar micahmicahgli-dev gli-2.14.0/bin/0000755000175000017500000000000013017317740012050 5ustar micahmicahgli-2.14.0/bin/gli0000755000175000017500000000345113017317740012554 0ustar micahmicah#!/usr/bin/env ruby require 'gli' require 'gli/commands/scaffold' include GLI::App program_desc 'create scaffolding for a GLI-powered application' version GLI::VERSION # Can't use these without changing the current behavior of gli # arguments :strict # subcommand_option_handling :normal switch :v, :desc => 'Be verbose' switch :n, :desc => 'Dry run; don''t change the disk' desc 'Root dir of project' long_desc < '.' desc 'Create a new GLI-based project' long_desc < 'Create an ext dir' c.switch :notest, :desc => 'Do not create a test or features dir', :negatable => false c.switch :force, :desc => 'Overwrite/ignore existing files and directories' c.switch :rvmrc, :desc => 'Create an .rvmrc based on your current RVM setup' c.action do |g,o,args| if args.length < 1 raise 'You must specify the name of your project' end GLI::Commands::Scaffold.create_scaffold(g[:r],!o[:notest],o[:e],args[0],args[1..-1],o[:force],g[:n],o[:rvmrc]) end end pre do |global,command,options,args| puts "Executing #{command.name}" if global[:v] true end post do |global,command,options,args| puts "Executed #{command.name}" if global[:v] end exit run(ARGV) gli-2.14.0/bin/test_all_rubies.sh0000755000175000017500000000053513017317740015572 0ustar micahmicahecho tests rvm 1.9.2@gli-dev,1.9.3@gli-dev,1.8.7@gli-dev,jruby@gli-dev,rbx@gli-dev,ree@gli-dev --yaml rake test | bin/report_on_rake_results rake clobber > /dev/null 2>&1 echo features rvm 1.9.2@gli-dev,1.9.3@gli-dev,1.8.7@gli-dev,jruby@gli-dev,rbx@gli-dev,ree@gli-dev --yaml rake features | bin/report_on_rake_results rake clobber > /dev/null 2>&1 gli-2.14.0/bin/report_on_rake_results0000755000175000017500000000042213017317740016566 0ustar micahmicah#!/usr/bin/env ruby require 'yaml' require 'rainbow' results = YAML::load(STDIN) success = results["successes"] || [] errors = results["errors"] || [] success.each { |ruby| puts ruby.color(:green) } errors.each { |ruby| puts ruby.color(:red) } exit -1 unless errors.empty? gli-2.14.0/README.rdoc0000644000175000017500000000737013017317740013115 0ustar micahmicah= Git-Like Interface Command Line Parser GLI is the best way to make a "command-suite" command-line application, e.g. one like git (for the best way to make a simpler command-line application, check out methadone[http://www.github.com/davetron5000/methadone]). GLI allows you to make a polished, easy-to-maintain command-line application without a lot of syntax, but without restricting you in any way from the power of +OptionParser+. * {Overview}[http://davetron5000.github.com/gli] * {Source on Github}[http://github.com/davetron5000/gli] * RDoc[http://davetron5000.github.com/gli/rdoc/index.html] {Build Status}[https://travis-ci.org/davetron5000/gli] == Use Install if you need to: gem install gli The simplest way to get started is to create a scaffold project gli init todo list add complete This will create a basic scaffold project in ./todo with: * executable in ./todo/bin/todo. This file demonstrates most of what you need to describe your command line interface. * an empty test in ./todo/test/default_test.rb that can bootstrap your tests * an empty feature in ./todo/features/todo.feature that can bootstrap testing your CLI via Aruba. * a gemspec shell * a README shell * Rakefile that can generate RDoc, package your Gem and run tests * A Gemfile suitable for use with Bundler to manage development-time dependencies Now, you are ready to go: > cd todo > bundle exec bin/todo help NAME todo - Describe your application here SYNOPSIS todo [global options] command [command options] [arguments...] VERSION 0.0.1 GLOBAL OPTIONS -f, --flagname=The name of the argument - Describe some flag here (default: the default) --help - Show this message -s, --[no-]switch - Describe some switch here COMMANDS add - Describe add here complete - Describe complete here help - Shows a list of commands or help for one command list - Describe list here > bundle exec bin/todo help list NAME list - Describe list here SYNOPSIS todo [global options] list [command options] Describe arguments to list here COMMAND OPTIONS -f arg - Describe a flag to list (default: default) -s - Describe a switch to list All you need to do is fill in the documentation and your code; the help system, command-line parsing and many other awesome features are all handled for you. Get a more detailed walkthrough on the {main site}[http://davetron5000.github.com/gli] == Supported Platforms Tests should be passing supported MRI Rubies (see +.travis.yml+ for specifics). Due to the vagaries of Travis, I can't keep the test suite running on unsupported Rubies, however GLI has been used on older ones, like: * 1.8.7 * 1.9.2 * 1.9.3 * Ruby Enterprise Edition 1.8.7 * JRuby == Documentation Extensive documentation is {available at the wiki}[https://github.com/davetron5000/gli/wiki]. API Documentation is available {here}[http://davetron5000.github.com/gli/rdoc/index.html]. Recommend starting with GLI::DSL or GLI::App. == Credits Author:: Dave Copeland (mailto:davetron5000 at g mail dot com) Copyright:: Copyright (c) 2010 by Dave Copeland License:: Distributes under the Apache License, see LICENSE.txt in the source distro == Links * [http://davetron5000.github.com/gli] - RubyDoc * [http://www.github.com/davetron5000/gli] - Source on GitHub * [http://www.github.com/davetron5000/gli/wiki] - Documentation Wiki * [http://www.github.com/davetron5000/gli/wiki/Changelog] - Changelog = gli CLI documentation :include:gli.rdoc gli-2.14.0/.travis.yml0000644000175000017500000000023313017317740013407 0ustar micahmicahnotifications: email: on_success: always script: 'bundle exec rake test features' rvm: - 2.1.8 - 2.2.4 - 2.3.0 branches: only: - 'gli-2' gli-2.14.0/Gemfile0000644000175000017500000000041713017317740012575 0ustar micahmicahsource 'https://rubygems.org' gemspec gem "rcov", ">= 0.9.8", :platforms => :mri_18 gem "simplecov", "~> 0.6.4", :platforms => :mri_19 gem "psych", :platforms => :mri_19 major,minor = RUBY_VERSION.split(/\./) if major.to_i >=2 && minor.to_i >= 2 gem "test-unit" end gli-2.14.0/gli.cheat0000644000175000017500000000506713017317740013071 0ustar micahmicahgli - create command-suite apps, a la git, using this awesome Ruby DSL ====================================================================== Setup and Usage --------------- Installation: $ gem install gli Show list of commands $ gli help [command] Show help for one command $ gli help init Create a new GLI-based project with the commands foo and bar $ gli init project_name foo bar Create a new GLI-based project with an ext directory $ gli init -e project_name foo bar Create a new GLI-based project without a test directory (bad!) $ gli init --notest project_name foo bar Create a new GLI-based project somewhere else than . $ gli -r /tmp init project_name foo bar Just see what GLI would create $ gli -n init -e project_name foo bar Using GLI's DSL --------------- Create a switch (option that takes no args) desc 'Dry-run; don't change the disk switch [:n,'dry-run'] # Both -n and --dry-run will work * --no-dry-run will set the switch to false # Access in code via global_options[:n] # or global_options[:'dry-run'] Create it on one line switch :n,'dry-run', :desc => 'Dry-run; don't change the disk Don't create a negatable version switch :n,'dry-run', :negatable => false, :desc => 'Dry-run; don't change the disk # --no-dry-run is not accepted Create a flag (option that takes an argument) desc 'Location of the config file' arg_name 'path_to_file' default_value '~/.glirc' flag [:c,:conf], :must_match => /^\..*rc$/ # Both -c and --conf work, if this flag is omitted # then the default of ~/.glirc is avaialble to the code # The argument must match the given regex # Access in code via global_options[:c] (or :conf) Create a flag in a more compact style flag :c,:conf, :desc => 'Location of the config file', :arg_name => 'path_to_file', :default_value => '~/.glirc' Create a command desc 'Get the list of open tickets' command [:tickets] do |c| c.desc 'Only assigned to me' c.switch [:m,:me] c.desc 'Show only tickets for one project' c.flag [:p,:project,'only-project'] c.action do |global_options,options,args| # global_options has the global options as a hash # options are the command specific ones (e.g. options[:p]) # args are the command line arguments that weren't parsed # raise an exception or exit_now! if things go wrong end end Set up stuff ahead of time pre do |global_options,command,options,args| return true if things_are_ok return false if we_should_abort_the_command end Use GLI exit run(ARGV) # run returns a suitable exit status gli-2.14.0/gli.gemspec0000644000175000017500000000257013017317740013424 0ustar micahmicah# Make sure we get the gli that's local require File.join([File.dirname(__FILE__),'lib','gli','version.rb']) spec = Gem::Specification.new do |s| s.name = 'gli' s.version = GLI::VERSION s.author = 'David Copeland' s.email = 'davidcopeland@naildrivin5.com' s.homepage = 'http://davetron5000.github.com/gli' s.platform = Gem::Platform::RUBY s.summary = 'Build command-suite CLI apps that are awesome.' s.description = 'Build command-suite CLI apps that are awesome. Bootstrap your app, add commands, options and documentation while maintaining a well-tested idiomatic command-line app' s.files = `git ls-files`.split("\n") s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") s.executables = 'gli' s.require_paths = ["lib"] s.has_rdoc = true s.extra_rdoc_files = ['README.rdoc', 'gli.rdoc'] s.rdoc_options << '--title' << 'Git Like Interface' << '--main' << 'README.rdoc' s.bindir = 'bin' s.rubyforge_project = 'gli' s.add_development_dependency('rake', '~> 0.9.2.2') s.add_development_dependency('rdoc') s.add_development_dependency('rainbow', '~> 1.1.1') s.add_development_dependency('clean_test') s.add_development_dependency('cucumber') s.add_development_dependency('gherkin') s.add_development_dependency('aruba', '0.5.1') s.add_development_dependency('sdoc') s.add_development_dependency('faker','1.0.0') end gli-2.14.0/ObjectModel.graffle0000644000175000017500000006617013017317740015031 0ustar micahmicah ActiveLayerIndex 0 ApplicationVersion com.omnigroup.OmniGraffle.MacAppStore 138.33 AutoAdjust BackgroundGraphic Bounds {{0, 0}, {576, 733}} Class SolidGraphic ID 2 Style shadow Draws NO stroke Draws NO CanvasOrigin {0, 0} ColumnAlign 1 ColumnSpacing 36 CreationDate 2012-03-23 19:22:12 +0000 Creator David Copeland DisplayScale 1 0/72 in = 1 0/72 in GraphDocumentVersion 8 GraphicsList Bounds {{20, 518}, {99, 61}} Class ShapedGraphic ID 66 Shape NoteShape Style Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 top most commands point to App as the parent} VerticalPad 0 Bounds {{114.5, 503.3269}, {45, 24}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color w 0 Font Helvetica Size 12 ID 65 Line ID 64 Position 0.31400206685066223 RotationType 0 Shape Rectangle Style shadow Draws NO stroke Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs24 \cf0 parent} Wrap NO Class LineGraphic Head ID 43 ID 64 OrthogonalBarAutomatic OrthogonalBarPoint {0, 0} OrthogonalBarPosition 57.505828857421875 Points {137, 568} {106.76401, 430.48834} Style stroke HeadArrow 0 LineType 2 Pattern 1 TailArrow Diamond Tail ID 63 Bounds {{92, 568}, {90, 36}} Class ShapedGraphic ID 63 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\b\fs24 \cf0 App} VerticalPad 0 Bounds {{208, 288}, {80, 47}} Class ShapedGraphic ID 62 Shape NoteShape Style Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\fs24 \cf0 nil if this is a global option} VerticalPad 0 Class LineGraphic Head ID 38 ID 61 Points {294.97458, 380.63699} {451.53046, 215.36313} Style stroke HeadArrow UMLInheritance LineType 1 TailArrow 0 Tail ID 55 Class LineGraphic Head ID 38 ID 60 Points {474.81839, 387.50204} {459.18158, 215.49792} Style stroke HeadArrow UMLInheritance LineType 1 TailArrow 0 Tail ID 58 Class TableGroup Graphics Bounds {{388, 388}, {175, 14}} Class ShapedGraphic FitText Vertical Flow Resize ID 58 Shape Rectangle Style fill GradientCenter {-0.29411799, -0.26470599} Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\b\fs24 \cf0 Switch} VerticalPad 0 TextPlacement 0 ID 57 Class TableGroup Graphics Bounds {{200.5, 381}, {175, 14}} Class ShapedGraphic FitText Vertical Flow Resize ID 55 Shape Rectangle Style fill GradientCenter {-0.29411799, -0.26470599} Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\b\fs24 \cf0 Flag} VerticalPad 0 TextPlacement 0 Bounds {{200.5, 395}, {175, 14}} Class ShapedGraphic FitText Vertical Flow Resize ID 56 Shape Rectangle Style fill GradientCenter {-0.29411799, -0.26470599} Text Align 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 \f0\fs24 \cf0 argument name} VerticalPad 0 TextPlacement 0 GridH 55 56 ID 54 Bounds {{258.604, 256.57898}, {127, 24}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color w 0 Font Helvetica Size 12 ID 53 Line ID 52 Position 0.36914369463920593 RotationType 0 Shape Rectangle Style shadow Draws NO stroke Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs24 \cf0 associated_command} Wrap NO Class LineGraphic Head ID 42 ID 52 Points {442.28226, 215.20296} {116.72269, 359.79709} Style stroke HeadArrow Diamond LineType 1 Pattern 1 TailArrow 0 Tail ID 38 Bounds {{11.5, 458.71945}, {45, 24}} Class ShapedGraphic FitText YES Flow Resize FontInfo Color w 0 Font Helvetica Size 12 ID 51 Line ID 49 Position 0.31400206685066223 RotationType 0 Shape Rectangle Style shadow Draws NO stroke Draws NO Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\pardirnatural\qc \f0\fs24 \cf0 parent} Wrap NO Class LineGraphic Head ID 43 ID 49 OrthogonalBarAutomatic OrthogonalBarPoint {0, 0} OrthogonalBarPosition -1 Points {62.860363, 430.30048} {34, 452} {74, 497} {92.555122, 430.48163} Style stroke HeadArrow 0 LineType 2 Pattern 1 TailArrow Diamond Tail ID 43 Class LineGraphic Head ID 35 ID 46 Points {106.2841, 359.6062} {290.28815, 124.3938} Style stroke HeadArrow UMLInheritance LineType 1 TailArrow 0 Tail ID 42 Class LineGraphic Head ID 10 ID 44 Points {100.5, 359.5} {100.5, 107} Style stroke HeadArrow UMLInheritance LineType 1 TailArrow 0 Tail ID 42 Bounds {{55.5, 71}, {90, 36}} Class ShapedGraphic ID 10 Magnets {0, 1} {0, -1} {1, 0} {-1, 0} Shape Rectangle Style Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\b\fs24 \cf0 DSL} VerticalPad 0 Class TableGroup Graphics Bounds {{13, 360}, {175, 14}} Class ShapedGraphic FitText Vertical Flow Resize ID 42 Shape Rectangle Style fill GradientCenter {-0.29411799, -0.26470599} Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\b\fs24 \cf0 Command} VerticalPad 0 TextPlacement 0 Bounds {{13, 374}, {175, 56}} Class ShapedGraphic FitText Vertical Flow Resize ID 43 Shape Rectangle Style fill GradientCenter {-0.29411799, -0.26470599} Text Align 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 \f0\fs24 \cf0 name\ description\ long_description\ aliases} VerticalPad 0 TextPlacement 0 GridH 42 43 ID 41 Class LineGraphic Head ID 35 ID 40 Points {447.65482, 186.72136} {354.61505, 124.27872} Style stroke HeadArrow UMLInheritance LineType 1 TailArrow 0 Tail ID 37 Class TableGroup Graphics Bounds {{371, 187}, {175, 14}} Class ShapedGraphic FitText Vertical Flow Resize ID 37 Shape Rectangle Style fill GradientCenter {-0.29411799, -0.26470599} Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\b\fs24 \cf0 CommandLineOption} VerticalPad 0 TextPlacement 0 Bounds {{371, 201}, {175, 14}} Class ShapedGraphic FitText Vertical Flow Resize ID 38 Shape Rectangle Style fill GradientCenter {-0.29411799, -0.26470599} Text Align 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 \f0\fs24 \cf0 default_value} VerticalPad 0 TextPlacement 0 GridH 37 38 ID 36 Class TableGroup Graphics Bounds {{225, 54}, {175, 14}} Class ShapedGraphic FitText Vertical Flow Resize ID 34 Shape Rectangle Style fill GradientCenter {-0.29411799, -0.26470599} Text Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720\qc \f0\b\fs24 \cf0 CommandLineToklen} VerticalPad 0 TextPlacement 0 Bounds {{225, 68}, {175, 56}} Class ShapedGraphic FitText Vertical Flow Resize ID 35 Shape Rectangle Style fill GradientCenter {-0.29411799, -0.26470599} Text Align 0 Text {\rtf1\ansi\ansicpg1252\cocoartf1138\cocoasubrtf320 {\fonttbl\f0\fswiss\fcharset0 Helvetica;} {\colortbl;\red255\green255\blue255;} \pard\tx560\tx1120\tx1680\tx2240\tx2800\tx3360\tx3920\tx4480\tx5040\tx5600\tx6160\tx6720 \f0\fs24 \cf0 name\ description\ long_description\ aliases} VerticalPad 0 TextPlacement 0 GridH 34 35 ID 33 GridInfo GuidesLocked NO GuidesVisible YES HPages 1 ImageCounter 1 KeepToScale Layers Lock NO Name Layer 1 Print YES View YES LayoutInfo Animate NO circoMinDist 18 circoSeparation 0.0 layoutEngine dot neatoSeparation 0.0 twopiSeparation 0.0 LinksVisible NO MagnetsVisible NO MasterSheets ModificationDate 2012-03-23 21:24:30 +0000 Modifier David Copeland NotesVisible NO Orientation 2 OriginVisible NO PageBreaks YES PrintInfo NSBottomMargin float 41 NSHorizonalPagination int 0 NSLeftMargin float 18 NSPaperSize coded BAtzdHJlYW10eXBlZIHoA4QBQISEhAdOU1ZhbHVlAISECE5TT2JqZWN0AIWEASqEhAx7X05TU2l6ZT1mZn2WgWQCgRgDhg== NSPrintReverseOrientation int 0 NSRightMargin float 18 NSTopMargin float 18 PrintOnePage ReadOnly NO RowAlign 1 RowSpacing 36 SheetTitle Canvas 1 SmartAlignmentGuidesActive YES SmartDistanceGuidesActive YES UniqueID 1 UseEntirePage VPages 1 WindowInfo CurrentSheet 0 ExpandedCanvases name Canvas 1 Frame {{218, 0}, {710, 878}} ListView OutlineWidth 142 RightSidebar ShowRuler Sidebar SidebarWidth 120 VisibleRegion {{0, 0}, {575, 723}} Zoom 1 ZoomValues Canvas 1 1 1 saveQuickLookFiles YES gli-2.14.0/Rakefile0000644000175000017500000000646413017317740012757 0ustar micahmicahrequire 'sdoc' require 'bundler' require 'rake/clean' require 'rake/testtask' require 'rdoc/task' require 'cucumber' require 'cucumber/rake/task' include Rake::DSL CLEAN << "log" CLOBBER << FileList['**/*.rbc'] task :rdoc => [:build_rdoc, :hack_css] Rake::RDocTask.new(:build_rdoc) do |rd| rd.main = "README.rdoc" rd.rdoc_files = FileList["lib/**/*.rb","README.rdoc"] - FileList["lib/gli/commands/help_modules/*.rb"] - ["lib/gli/commands/help.rb", "lib/gli/commands/scaffold.rb", "lib/gli/support/*.rb", "lib/gli/app_support.rb", "lib/gli/option_parser_factory.rb", "lib/gli/gli_option_parser.rb", "lib/gli/command_support.rb",] rd.title = 'GLI - Git Like Interface for your command-line apps' rd.options << '-f' << 'sdoc' rd.template = 'direct' end FONT_FIX = { "0.82em" => "16px", "0.833em" => "16px", "0.85em" => "16px", "1.15em" => "20px", "1.1em" => "20px", "1.2em" => "20px", "1.4em" => "24px", "1.5em" => "24px", "1.6em" => "32px", "1em" => "16px", "2.1em" => "38px", } task :hack_css do maincss = File.open('html/css/main.css').readlines File.open('html/css/main.css','w') do |file| file.puts '@import url(http://fonts.googleapis.com/css?family=Karla:400,700,400italic,700italic|Alegreya);' maincss.each do |line| if line.strip == 'font-family: "Helvetica Neue", Arial, sans-serif;' file.puts 'font-family: Karla, "Helvetica Neue", Arial, sans-serif;' elsif line.strip == 'font-family: monospace;' file.puts 'font-family: Monaco, monospace;' elsif line =~ /^pre\s*$/ file.puts "pre { font-family: Monaco, monospace; margin-bottom: 1em; } pre.original" elsif line =~ /^\s*font-size:\s*(.*)\s*;/ if FONT_FIX[$1] file.puts "font-size: #{FONT_FIX[$1]};" else file.puts line.chomp end else file.puts line.chomp end end end end Bundler::GemHelper.install_tasks desc 'run unit tests' Rake::TestTask.new do |t| t.libs << "test" t.test_files = FileList['test/init_simplecov.rb','test/tc_*.rb'] end CUKE_RESULTS = 'results.html' CLEAN << CUKE_RESULTS Cucumber::Rake::Task.new(:features) do |t| opts = "features --format html -o #{CUKE_RESULTS} --format progress -x" opts += " --tags #{ENV['TAGS']}" if ENV['TAGS'] t.cucumber_opts = opts t.fork = false end Cucumber::Rake::Task.new('features:wip') do |t| tag_opts = ' --tags ~@pending' tag_opts = ' --tags @wip' t.cucumber_opts = "features --format html -o #{CUKE_RESULTS} --format pretty -x -s#{tag_opts}" t.fork = false end begin require 'rcov/rcovtask' task :clobber_coverage do rm_rf "coverage" end desc 'Measures test coverage' task :coverage => :rcov do puts "coverage/index.html contains what you need" end Rcov::RcovTask.new do |t| t.libs << 'lib' t.test_files = FileList['test/tc_*.rb'] end rescue LoadError begin require 'simplecov' rescue LoadError $stderr.puts "neither rcov nor simplecov are installed; you won't be able to check code coverage" end end desc 'Publish rdoc on github pages and push to github' task :publish_rdoc => [:rdoc,:publish] task :default => [:test,:features]