rubycom 0.2.5 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +8 -8
- data/README.md +4 -0
- data/Rakefile +18 -9
- data/lib/rubycom.rb +119 -331
- data/lib/rubycom/arguments.rb +102 -0
- data/lib/rubycom/commands.rb +63 -0
- data/lib/rubycom/documentation.rb +204 -0
- data/lib/rubycom/version.rb +1 -1
- data/test/rubycom/arguments_test.rb +148 -0
- data/test/rubycom/commands_test.rb +51 -0
- data/test/rubycom/documentation_test.rb +186 -0
- data/test/rubycom/rubycom_test.rb +527 -0
- data/test/rubycom/util_test_composite.rb +1 -0
- metadata +13 -4
- data/test/rubycom/test_rubycom.rb +0 -762
    
        checksums.yaml
    CHANGED
    
    | @@ -1,15 +1,15 @@ | |
| 1 1 | 
             
            ---
         | 
| 2 2 | 
             
            !binary "U0hBMQ==":
         | 
| 3 3 | 
             
              metadata.gz: !binary |-
         | 
| 4 | 
            -
                 | 
| 4 | 
            +
                NDQwZWY1MzUyMTk1MGQ1OTc2YTEzYzNhOWRlNTZjZTkzMjU3MzliNw==
         | 
| 5 5 | 
             
              data.tar.gz: !binary |-
         | 
| 6 | 
            -
                 | 
| 6 | 
            +
                OWVkOTI4MjY3ODhlODM1M2FhMTAxZTc2YjQxYTVkN2JmZDg2ZGE1Yg==
         | 
| 7 7 | 
             
            !binary "U0hBNTEy":
         | 
| 8 8 | 
             
              metadata.gz: !binary |-
         | 
| 9 | 
            -
                 | 
| 10 | 
            -
                 | 
| 11 | 
            -
                 | 
| 9 | 
            +
                Nzc5NWUyZTczZWMzZjUxM2UwMmEyNGQxYTZlZTEyYmI1MmViYmUwZDE0NmRm
         | 
| 10 | 
            +
                ZTYzM2Y0N2Q1MjhkOThjZTdiN2Q5OWIyODEyMmVkMjdiMjQ3OTc0ZWQ2MTcx
         | 
| 11 | 
            +
                NzUwZTAzMzZjOWMwN2FlZjk1NjUzMTFlM2EzZTdkZTk3NjI5ZGQ=
         | 
| 12 12 | 
             
              data.tar.gz: !binary |-
         | 
| 13 | 
            -
                 | 
| 14 | 
            -
                 | 
| 15 | 
            -
                 | 
| 13 | 
            +
                YzVlZTBiODM0MzU0MmQzNTg1YWI1NDg0MTE0ZGRhOWMyMDA2M2Y5NTIxZTY1
         | 
| 14 | 
            +
                NTFmM2Y3NDM5MDU5ZmRhYmI2Y2FlMDc1YjdiNzA3OWIzYWVlNGYxNTRhMjU3
         | 
| 15 | 
            +
                MzZjYzNjMmQ2NzUwZmIxNDM2MzcxODU4OGUzMWYyZGNmOGVjNGY=
         | 
    
        data/README.md
    CHANGED
    
    | @@ -20,6 +20,8 @@ by simply including Rubycom at the bottom. | |
| 20 20 | 
             
            * Method parameters become required CLI arguments. Optional (defaulted) parameters become CLI options.
         | 
| 21 21 | 
             
            * Command consoles can be built up by including other modules before including Rubycom.
         | 
| 22 22 | 
             
            * Included modules become commands, their public singleton methods become sub-commands.
         | 
| 23 | 
            +
            * Built in tab completion support for all commands.
         | 
| 24 | 
            +
                * Users may call `./path/to/my_command.rb register_completions` then `source ~/.bash_profile` to register completions.
         | 
| 23 25 |  | 
| 24 26 | 
             
            Usage
         | 
| 25 27 | 
             
            ---------------
         | 
| @@ -131,11 +133,13 @@ If a set of functions needs to be accessible from the terminal, just `include Ru | |
| 131 133 | 
             
            * Usage documentation is pulled from method comments.
         | 
| 132 134 | 
             
            * Method parameters become required CLI arguments.
         | 
| 133 135 | 
             
            * Optional (defaulted) parameters become CLI options.
         | 
| 136 | 
            +
            * Tab completion support if the user has registered it for the file.
         | 
| 134 137 |  | 
| 135 138 | 
             
            The result is a function library which can be consumed easily from other classes/modules and which is accessible from the command line.
         | 
| 136 139 |  | 
| 137 140 | 
             
            Coming Soon
         | 
| 138 141 | 
             
            ---------------
         | 
| 142 | 
            +
            * Support for piping.
         | 
| 139 143 | 
             
            * Build a job yaml by running each command in sequence with a special option --job_add <path_to_yaml>[:step_number]
         | 
| 140 144 | 
             
            * Edit job files from the command line using special options.
         | 
| 141 145 | 
             
                * --job_update <path_to_yaml>[:step_number]
         | 
    
        data/Rakefile
    CHANGED
    
    | @@ -1,4 +1,5 @@ | |
| 1 1 | 
             
            require 'yard'
         | 
| 2 | 
            +
            require 'rake/testtask'
         | 
| 2 3 |  | 
| 3 4 | 
             
            task :default => [:package]
         | 
| 4 5 |  | 
| @@ -9,19 +10,19 @@ task :clean do | |
| 9 10 | 
             
              FileUtils.rm(Dir.glob('./rubycom-*.gem'))
         | 
| 10 11 | 
             
            end
         | 
| 11 12 |  | 
| 12 | 
            -
            task : | 
| 13 | 
            -
               | 
| 14 | 
            -
             | 
| 15 | 
            -
             | 
| 16 | 
            -
             | 
| 17 | 
            -
             | 
| 18 | 
            -
             | 
| 19 | 
            -
               | 
| 13 | 
            +
            task :bundle do
         | 
| 14 | 
            +
              system("bundle install")
         | 
| 15 | 
            +
            end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
            Rake::TestTask.new do |t|
         | 
| 18 | 
            +
              t.libs << "test"
         | 
| 19 | 
            +
              t.test_files = FileList['test/*/*_test.rb']
         | 
| 20 | 
            +
              t.verbose = true
         | 
| 20 21 | 
             
            end
         | 
| 21 22 |  | 
| 22 23 | 
             
            YARD::Rake::YardocTask.new
         | 
| 23 24 |  | 
| 24 | 
            -
            task :package => [:clean, :test, :yard] do
         | 
| 25 | 
            +
            task :package => [:clean, :bundle, :test, :yard] do
         | 
| 25 26 | 
             
              gem_specs = Dir.glob("**/*.gemspec")
         | 
| 26 27 | 
             
              gem_specs.each { |gem_spec|
         | 
| 27 28 | 
             
                system("gem build #{gem_spec}")
         | 
| @@ -34,7 +35,15 @@ task :install => :package do | |
| 34 35 | 
             
              system("gem install #{File.expand_path(File.dirname(__FILE__))}/rubycom-#{Rubycom::VERSION}.gem")
         | 
| 35 36 | 
             
            end
         | 
| 36 37 |  | 
| 38 | 
            +
            task :upgrade => :package do
         | 
| 39 | 
            +
              system("gem uninstall rubycom -a")
         | 
| 40 | 
            +
              load "#{File.expand_path(File.dirname(__FILE__))}/lib/rubycom/version.rb"
         | 
| 41 | 
            +
              system("gem install #{File.expand_path(File.dirname(__FILE__))}/rubycom-#{Rubycom::VERSION}.gem")
         | 
| 42 | 
            +
            end
         | 
| 43 | 
            +
             | 
| 37 44 | 
             
            task :version_set, [:version] do |t, args|
         | 
| 45 | 
            +
              raise "Must provide a version.\n If you called 'rake version_set 1.2.3', try 'rake version_set[1.2.3]'" if args[:version].nil? || args[:version].empty?
         | 
| 46 | 
            +
             | 
| 38 47 | 
             
              version_file = <<-END.gsub(/^ {4}/, '')
         | 
| 39 48 | 
             
                module Rubycom
         | 
| 40 49 | 
             
                  VERSION = "#{args[:version]}"
         | 
    
        data/lib/rubycom.rb
    CHANGED
    
    | @@ -1,7 +1,9 @@ | |
| 1 | 
            -
            require "#{File. | 
| 1 | 
            +
            require "#{File.dirname(__FILE__)}/rubycom/arguments.rb"
         | 
| 2 | 
            +
            require "#{File.dirname(__FILE__)}/rubycom/commands.rb"
         | 
| 3 | 
            +
            require "#{File.dirname(__FILE__)}/rubycom/documentation.rb"
         | 
| 4 | 
            +
            require "#{File.dirname(__FILE__)}/rubycom/version.rb"
         | 
| 5 | 
            +
             | 
| 2 6 | 
             
            require 'yaml'
         | 
| 3 | 
            -
            require 'find'
         | 
| 4 | 
            -
            require 'method_source'
         | 
| 5 7 |  | 
| 6 8 | 
             
            # Upon inclusion in another Module, Rubycom will attempt to call a method in the including module by parsing
         | 
| 7 9 | 
             
            # ARGV for a method name and a list of arguments.
         | 
| @@ -35,59 +37,78 @@ module Rubycom | |
| 35 37 | 
             
                  command = args[0] || nil
         | 
| 36 38 | 
             
                  arguments = args[1..-1] || []
         | 
| 37 39 |  | 
| 38 | 
            -
                   | 
| 39 | 
            -
                     | 
| 40 | 
            -
             | 
| 41 | 
            -
             | 
| 42 | 
            -
                      puts  | 
| 43 | 
            -
             | 
| 44 | 
            -
             | 
| 45 | 
            -
                       | 
| 46 | 
            -
             | 
| 47 | 
            -
             | 
| 48 | 
            -
             | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
                       | 
| 52 | 
            -
             | 
| 53 | 
            -
             | 
| 54 | 
            -
             | 
| 55 | 
            -
                       | 
| 56 | 
            -
                         | 
| 57 | 
            -
                         | 
| 58 | 
            -
             | 
| 59 | 
            -
             | 
| 60 | 
            -
             | 
| 61 | 
            -
             | 
| 62 | 
            -
             | 
| 63 | 
            -
                          puts "#{[step,context,cmd].join(' ')}"
         | 
| 64 | 
            -
                        }
         | 
| 40 | 
            +
                  case command
         | 
| 41 | 
            +
                    when 'register_completions'
         | 
| 42 | 
            +
                      puts self.register_completions(base)
         | 
| 43 | 
            +
                    when 'tab_complete'
         | 
| 44 | 
            +
                      puts self.tab_complete(base, args)
         | 
| 45 | 
            +
                    when 'help'
         | 
| 46 | 
            +
                      help_topic = arguments[0]
         | 
| 47 | 
            +
                      if help_topic.nil?
         | 
| 48 | 
            +
                        usage = Documentation.get_usage(base)
         | 
| 49 | 
            +
                        default_usage = Documentation.get_default_commands_usage
         | 
| 50 | 
            +
                        puts usage
         | 
| 51 | 
            +
                        puts default_usage
         | 
| 52 | 
            +
                        return usage+"\n"+default_usage
         | 
| 53 | 
            +
                      elsif help_topic == 'job'
         | 
| 54 | 
            +
                        usage = Documentation.get_job_usage(base)
         | 
| 55 | 
            +
                        puts usage
         | 
| 56 | 
            +
                        return usage
         | 
| 57 | 
            +
                      elsif help_topic == 'register_completions'
         | 
| 58 | 
            +
                        usage = Documentation.get_register_completions_usage(base)
         | 
| 59 | 
            +
                        puts usage
         | 
| 60 | 
            +
                        return usage
         | 
| 61 | 
            +
                      elsif help_topic == 'tab_complete'
         | 
| 62 | 
            +
                        usage = Documentation.get_tab_complete_usage(base)
         | 
| 63 | 
            +
                        puts usage
         | 
| 64 | 
            +
                        return usage
         | 
| 65 65 | 
             
                      else
         | 
| 66 | 
            -
                         | 
| 67 | 
            -
                         | 
| 68 | 
            -
             | 
| 69 | 
            -
                          context = step_hash.select{|key| key!="cmd"}.map{|key,val| "[#{key}: #{val}]"}.join(' ')
         | 
| 70 | 
            -
                          env = job_hash['env'] || {}
         | 
| 71 | 
            -
                          env.map { |key, val| step_hash['cmd'].gsub!("env[#{key}]", "#{((val.class == String)&&(val.match(/\w+/))) ? "\"#{val}\"" : val}") }
         | 
| 72 | 
            -
                          cmd = "[cmd: #{step_hash['cmd']}]"
         | 
| 73 | 
            -
                          puts "#{[step,context,cmd].join(' ')}"
         | 
| 74 | 
            -
                          system(step_hash['cmd'])
         | 
| 75 | 
            -
                        }
         | 
| 66 | 
            +
                        cmd_usage = Documentation.get_command_usage(base, help_topic, arguments[1..-1])
         | 
| 67 | 
            +
                        puts cmd_usage
         | 
| 68 | 
            +
                        return cmd_usage
         | 
| 76 69 | 
             
                      end
         | 
| 77 | 
            -
                     | 
| 78 | 
            -
                       | 
| 79 | 
            -
             | 
| 80 | 
            -
             | 
| 81 | 
            -
             | 
| 82 | 
            -
             | 
| 83 | 
            -
             | 
| 84 | 
            -
             | 
| 85 | 
            -
             | 
| 70 | 
            +
                    when 'job'
         | 
| 71 | 
            +
                      begin
         | 
| 72 | 
            +
                        raise CLIError, 'No job specified' if arguments[0].nil? || arguments[0].empty?
         | 
| 73 | 
            +
                        job_hash = YAML.load_file(arguments[0])
         | 
| 74 | 
            +
                        job_hash = {} if job_hash.nil?
         | 
| 75 | 
            +
                        STDOUT.sync = true
         | 
| 76 | 
            +
                        if arguments.delete('-test') || arguments.delete('--test')
         | 
| 77 | 
            +
                          puts "[Test Job #{arguments[0]}]"
         | 
| 78 | 
            +
                          job_hash['steps'].each { |step, step_hash|
         | 
| 79 | 
            +
                            step = "[Step: #{step}/#{job_hash['steps'].length}]"
         | 
| 80 | 
            +
                            context = step_hash.select { |key| key!="cmd" }.map { |key, val| "[#{key}: #{val}]" }.join(' ')
         | 
| 81 | 
            +
                            env = job_hash['env'] || {}
         | 
| 82 | 
            +
                            env.each { |key, val| step_hash['cmd'].gsub!("env[#{key}]", "#{((val.class == String)&&(val.match(/\w+/))) ? "\"#{val}\"" : val}") }
         | 
| 83 | 
            +
                            cmd = "[cmd: #{step_hash['cmd']}]"
         | 
| 84 | 
            +
                            puts "#{[step, context, cmd].join(' ')}"
         | 
| 85 | 
            +
                          }
         | 
| 86 | 
            +
                        else
         | 
| 87 | 
            +
                          puts "[Job #{arguments[0]}]"
         | 
| 88 | 
            +
                          job_hash['steps'].each { |step, step_hash|
         | 
| 89 | 
            +
                            step = "[Step: #{step}/#{job_hash['steps'].length}]"
         | 
| 90 | 
            +
                            context = step_hash.select { |key| key!="cmd" }.map { |key, val| "[#{key}: #{val}]" }.join(' ')
         | 
| 91 | 
            +
                            env = job_hash['env'] || {}
         | 
| 92 | 
            +
                            env.each { |key, val| step_hash['cmd'].gsub!("env[#{key}]", "#{((val.class == String)&&(val.match(/\w+/))) ? "\"#{val}\"" : val}") }
         | 
| 93 | 
            +
                            cmd = "[cmd: #{step_hash['cmd']}]"
         | 
| 94 | 
            +
                            puts "#{[step, context, cmd].join(' ')}"
         | 
| 95 | 
            +
                            system(step_hash['cmd'])
         | 
| 96 | 
            +
                          }
         | 
| 97 | 
            +
                        end
         | 
| 98 | 
            +
                      rescue CLIError => e
         | 
| 99 | 
            +
                        $stderr.puts e
         | 
| 100 | 
            +
                      end
         | 
| 101 | 
            +
                    else
         | 
| 102 | 
            +
                      output = self.run_command(base, command, arguments)
         | 
| 103 | 
            +
                      std_output = nil
         | 
| 104 | 
            +
                      std_output = output.to_yaml unless [String, NilClass, TrueClass, FalseClass, Fixnum, Float, Symbol].include?(output.class)
         | 
| 105 | 
            +
                      puts std_output || output
         | 
| 106 | 
            +
                      return output
         | 
| 86 107 | 
             
                  end
         | 
| 87 108 |  | 
| 88 109 | 
             
                rescue CLIError => e
         | 
| 89 110 | 
             
                  $stderr.puts e
         | 
| 90 | 
            -
                  $stderr.puts  | 
| 111 | 
            +
                  $stderr.puts Documentation.get_summary(base)
         | 
| 91 112 | 
             
                end
         | 
| 92 113 | 
             
              end
         | 
| 93 114 |  | 
| @@ -100,314 +121,81 @@ module Rubycom | |
| 100 121 | 
             
                arguments = [] if arguments.nil?
         | 
| 101 122 | 
             
                raise CLIError, 'No command specified.' if command.nil? || command.length == 0
         | 
| 102 123 | 
             
                begin
         | 
| 103 | 
            -
                  raise CLIError, "Invalid Command: #{command}" unless  | 
| 124 | 
            +
                  raise CLIError, "Invalid Command: #{command}" unless Commands.get_top_level_commands(base).include? command.to_sym
         | 
| 104 125 | 
             
                  if base.included_modules.map { |mod| mod.name.to_sym }.include?(command.to_sym)
         | 
| 105 126 | 
             
                    self.run_command(eval(command), arguments[0], arguments[1..-1])
         | 
| 106 127 | 
             
                  else
         | 
| 107 128 | 
             
                    method = base.public_method(command.to_sym)
         | 
| 108 129 | 
             
                    raise CLIError, "No public method found for symbol: #{command.to_sym}" if method.nil?
         | 
| 109 | 
            -
                    param_defs =  | 
| 110 | 
            -
                    args =  | 
| 130 | 
            +
                    param_defs = Arguments.get_param_definitions(method)
         | 
| 131 | 
            +
                    args = Arguments.parse_arguments(param_defs, arguments)
         | 
| 111 132 | 
             
                    flatten = false
         | 
| 112 | 
            -
                    params = method.parameters.map { |arr| flatten = true if arr[0]==:rest; args[arr[1]]}
         | 
| 133 | 
            +
                    params = method.parameters.map { |arr| flatten = true if arr[0]==:rest; args[arr[1]] }
         | 
| 113 134 | 
             
                    if flatten
         | 
| 114 135 | 
             
                      rest_arr = params.delete_at(-1)
         | 
| 115 | 
            -
                      rest_arr.each{|arg| params << arg}
         | 
| 136 | 
            +
                      rest_arr.each { |arg| params << arg }
         | 
| 116 137 | 
             
                    end
         | 
| 117 138 | 
             
                    (arguments.nil? || arguments.empty?) ? method.call : method.call(*params)
         | 
| 118 139 | 
             
                  end
         | 
| 119 140 | 
             
                rescue CLIError => e
         | 
| 120 141 | 
             
                  $stderr.puts e
         | 
| 121 | 
            -
                  $stderr.puts  | 
| 122 | 
            -
                end
         | 
| 123 | 
            -
              end
         | 
| 124 | 
            -
             | 
| 125 | 
            -
              # Parses the given arguments and matches them to the given parameters
         | 
| 126 | 
            -
              #
         | 
| 127 | 
            -
              # @param [Hash] parameters a Hash representing the parameters to match.
         | 
| 128 | 
            -
              #         Entries should match :param_name => { type: :req||:opt||:rest,
         | 
| 129 | 
            -
              #                                               def:(source_definition),
         | 
| 130 | 
            -
              #                                               default:(default_value || :nil_rubycom_required_param)
         | 
| 131 | 
            -
              #                                              }
         | 
| 132 | 
            -
              # @param [Array] arguments an Array of Strings representing the arguments to be parsed
         | 
| 133 | 
            -
              # @return [Hash] a Hash mapping the defined parameters to their matching argument values
         | 
| 134 | 
            -
              def self.parse_arguments(parameters={}, arguments=[])
         | 
| 135 | 
            -
                raise CLIError, 'parameters may not be nil' if parameters.nil?
         | 
| 136 | 
            -
                raise CLIError, 'arguments may not be nil' if arguments.nil?
         | 
| 137 | 
            -
                sorted_args = arguments.map { |arg|
         | 
| 138 | 
            -
                  Rubycom.parse_arg(arg)
         | 
| 139 | 
            -
                }.group_by { |hsh|
         | 
| 140 | 
            -
                  hsh.keys.first
         | 
| 141 | 
            -
                }.map { |key, arr|
         | 
| 142 | 
            -
                  (key == :rubycom_non_opt_arg) ? Hash[key, arr.map { |hsh| hsh.values }.flatten(1)] : Hash[key, arr.map { |hsh| hsh.values.first }.reduce(&:merge)]
         | 
| 143 | 
            -
                }.reduce(&:merge) || {}
         | 
| 144 | 
            -
             | 
| 145 | 
            -
                sorted_arg_count = sorted_args.map{|key,val| val}.flatten(1).length
         | 
| 146 | 
            -
                types = parameters.values.group_by { |hsh| hsh[:type] }.map { |type, defs_arr| Hash[type, defs_arr.length] }.reduce(&:merge) || {}
         | 
| 147 | 
            -
                raise CLIError, "Wrong number of arguments. Expected at least #{types[:req]}, received #{sorted_arg_count}" if sorted_arg_count < (types[:req]||0)
         | 
| 148 | 
            -
                raise CLIError, "Wrong number of arguments. Expected at most #{(types[:req]||0) + (types[:opt]||0)}, received #{sorted_arg_count}" if types[:rest].nil? && (sorted_arg_count > ((types[:req]||0) + (types[:opt]||0)))
         | 
| 149 | 
            -
             | 
| 150 | 
            -
                parameters.map { |param_sym, def_hash|
         | 
| 151 | 
            -
                  if def_hash[:type] == :req
         | 
| 152 | 
            -
                    raise CLIError, "No argument available for #{param_sym}" if sorted_args[:rubycom_non_opt_arg].nil? || sorted_args[:rubycom_non_opt_arg].length == 0
         | 
| 153 | 
            -
                    Hash[param_sym, sorted_args[:rubycom_non_opt_arg].shift]
         | 
| 154 | 
            -
                  elsif def_hash[:type] == :opt
         | 
| 155 | 
            -
                    if sorted_args[param_sym].nil?
         | 
| 156 | 
            -
                      arg = (sorted_args[:rubycom_non_opt_arg].nil? || sorted_args[:rubycom_non_opt_arg].empty?) ? parameters[param_sym][:default] : sorted_args[:rubycom_non_opt_arg].shift
         | 
| 157 | 
            -
                    else
         | 
| 158 | 
            -
                      arg = sorted_args[param_sym]
         | 
| 159 | 
            -
                    end
         | 
| 160 | 
            -
                    Hash[param_sym, arg]
         | 
| 161 | 
            -
                  elsif def_hash[:type] == :rest
         | 
| 162 | 
            -
                    ret = Hash[param_sym, ((!sorted_args[param_sym].nil?) ? sorted_args[param_sym] : sorted_args[:rubycom_non_opt_arg])]
         | 
| 163 | 
            -
                    sorted_args[:rubycom_non_opt_arg] = []
         | 
| 164 | 
            -
                    ret
         | 
| 165 | 
            -
                  end
         | 
| 166 | 
            -
                }.reduce(&:merge)
         | 
| 167 | 
            -
              end
         | 
| 168 | 
            -
             | 
| 169 | 
            -
              # Uses YAML.load to parse the given String
         | 
| 170 | 
            -
              #
         | 
| 171 | 
            -
              # @param [String] arg a String representing the argument to be parsed
         | 
| 172 | 
            -
              # @return [Object] the result of parsing the given arg with YAML.load
         | 
| 173 | 
            -
              def self.parse_arg(arg)
         | 
| 174 | 
            -
                return Hash[:rubycom_non_opt_arg, nil] if arg.nil?
         | 
| 175 | 
            -
                if arg.is_a?(String) && ((arg.match(/^[-]{3,}\w+/) != nil) || ((arg.match(/^[-]{1,}\w+/) == nil) && (arg.match(/^\w+=/) != nil)))
         | 
| 176 | 
            -
                  raise CLIError, "Improper option specification, options must start with one or two dashes. Received: #{arg}"
         | 
| 177 | 
            -
                elsif arg.is_a?(String) && arg.match(/^(-|--)\w+[=|\s]{1}/) != nil
         | 
| 178 | 
            -
                  k, v = arg.partition(/^(-|--)\w+[=|\s]{1}/).select { |part|
         | 
| 179 | 
            -
                    !part.empty?
         | 
| 180 | 
            -
                  }.each_with_index.map { |part, index|
         | 
| 181 | 
            -
                    index == 0 ? part.chomp('=').gsub(/^--/, '').gsub(/^-/, '').strip.to_sym : (YAML.load(part) rescue "#{part}")
         | 
| 182 | 
            -
                  }
         | 
| 183 | 
            -
                  Hash[k, v]
         | 
| 184 | 
            -
                else
         | 
| 185 | 
            -
                  begin
         | 
| 186 | 
            -
                    parsed_arg = YAML.load("#{arg}")
         | 
| 187 | 
            -
                  rescue Exception
         | 
| 188 | 
            -
                    parsed_arg = "#{arg}"
         | 
| 189 | 
            -
                  end
         | 
| 190 | 
            -
                  Hash[:rubycom_non_opt_arg, parsed_arg]
         | 
| 142 | 
            +
                  $stderr.puts Documentation.get_command_usage(base, command, arguments)
         | 
| 191 143 | 
             
                end
         | 
| 192 144 | 
             
              end
         | 
| 193 145 |  | 
| 194 | 
            -
              #  | 
| 146 | 
            +
              # Inserts a tab completion into the current user's .bash_profile with a command entry to register the function for
         | 
| 147 | 
            +
              # the current running ruby file
         | 
| 195 148 | 
             
              #
         | 
| 196 149 | 
             
              # @param [Module] base the module which invoked 'include Rubycom'
         | 
| 197 | 
            -
              # @return [String]  | 
| 198 | 
            -
              def self. | 
| 199 | 
            -
                 | 
| 200 | 
            -
             | 
| 201 | 
            -
                } | 
| 202 | 
            -
             | 
| 203 | 
            -
             | 
| 204 | 
            -
             | 
| 205 | 
            -
             | 
| 206 | 
            -
             | 
| 207 | 
            -
             | 
| 208 | 
            -
              # @return [String] a spaced separator String for use in a command/description list
         | 
| 209 | 
            -
              def self.get_separator(sym, spacer_length=0)
         | 
| 210 | 
            -
                [].unshift(" " * (spacer_length - sym.to_s.length)).join << "  -  "
         | 
| 211 | 
            -
              end
         | 
| 150 | 
            +
              # @return [String] a message indicating the result of the command
         | 
| 151 | 
            +
              def self.register_completions(base)
         | 
| 152 | 
            +
                completion_function = <<-END.gsub(/^ {4}/, '')
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                _#{base}_complete() {
         | 
| 155 | 
            +
                  COMPREPLY=()
         | 
| 156 | 
            +
                  local completions="$(ruby #{File.absolute_path($0)} tab_complete ${COMP_WORDS[*]} 2>/dev/null)"
         | 
| 157 | 
            +
                  COMPREPLY=( $(compgen -W "$completions") )
         | 
| 158 | 
            +
                }
         | 
| 159 | 
            +
                complete -o bashdefault -o default -o nospace -F _#{base}_complete #{$0.split('/').last}
         | 
| 160 | 
            +
                END
         | 
| 212 161 |  | 
| 213 | 
            -
             | 
| 214 | 
            -
             | 
| 215 | 
            -
             | 
| 216 | 
            -
              # @param [String] command_name the command to retrieve usage for
         | 
| 217 | 
            -
              # @return [String] a summary of the given command_name
         | 
| 218 | 
            -
              def self.get_command_summary(base, command_name, separator = '  -  ')
         | 
| 219 | 
            -
                raise CLIError, "Can not get usage for #{command_name} with base: #{base||"nil"}" if base.nil? || !base.respond_to?(:included_modules)
         | 
| 220 | 
            -
                return 'No command specified.' if command_name.nil? || command_name.length == 0
         | 
| 221 | 
            -
                if base.included_modules.map { |mod| mod.name.to_sym }.include?(command_name.to_sym)
         | 
| 222 | 
            -
                  begin
         | 
| 223 | 
            -
                  mod_const = Kernel.const_get(command_name.to_sym)
         | 
| 224 | 
            -
                  desc = File.read(mod_const.public_method(mod_const.singleton_methods().first).source_location.first).split(//).reduce(""){|str,c|
         | 
| 225 | 
            -
                    unless str.gsub("\n",'').gsub(/\s+/,'').include?("module#{mod_const}")
         | 
| 226 | 
            -
                      str << c
         | 
| 227 | 
            -
                      end
         | 
| 228 | 
            -
                      str
         | 
| 229 | 
            -
                  }.split("\n").select{|line| line.strip.match(/^#/)}.map{|line| line.strip.gsub(/^#+/,'')}.join("\n")
         | 
| 230 | 
            -
                  rescue
         | 
| 231 | 
            -
                    desc = ""
         | 
| 232 | 
            -
                  end
         | 
| 162 | 
            +
                already_registered = File.readlines("#{Dir.home}/.bash_profile").map { |line| line.include?("_#{base}_complete()") }.reduce(:|) rescue false
         | 
| 163 | 
            +
                if already_registered
         | 
| 164 | 
            +
                  "Completion function for #{base} already registered."
         | 
| 233 165 | 
             
                else
         | 
| 234 | 
            -
                   | 
| 235 | 
            -
             | 
| 166 | 
            +
                  File.open("#{Dir.home}/.bash_profile", 'a+') { |file|
         | 
| 167 | 
            +
                    file.write(completion_function)
         | 
| 168 | 
            +
                  }
         | 
| 169 | 
            +
                  "Registration complete, run 'source #{Dir.home}/.bash_profile' to enable auto-completion."
         | 
| 236 170 | 
             
                end
         | 
| 237 | 
            -
                (desc.nil?||desc=='nil'||desc.length==0) ? "#{command_name}\n" : self.get_formatted_summary(command_name, desc, separator)
         | 
| 238 | 
            -
              end
         | 
| 239 | 
            -
             | 
| 240 | 
            -
              # Arranges the given command_name and command_description with the separator in a standard format
         | 
| 241 | 
            -
              #
         | 
| 242 | 
            -
              # @param [String] command_name the command format
         | 
| 243 | 
            -
              # @param [String] command_description the description for the given command
         | 
| 244 | 
            -
              # @param [String] separator optional separator to use
         | 
| 245 | 
            -
              def self.get_formatted_summary(command_name, command_description, separator = '  -  ')
         | 
| 246 | 
            -
                width = 95
         | 
| 247 | 
            -
                prefix = command_name.to_s.split(//).map { " " }.join + separator.split(//).map { " " }.join
         | 
| 248 | 
            -
                line_width = width - prefix.length
         | 
| 249 | 
            -
                description_msg = command_description.gsub(/(.{1,#{line_width}})(?: +|$)\n?|(.{#{line_width}})/, "#{prefix}"+'\1\2'+"\n")
         | 
| 250 | 
            -
                "#{command_name}#{separator}#{description_msg.lstrip}"
         | 
| 251 171 | 
             
              end
         | 
| 252 172 |  | 
| 253 | 
            -
              #  | 
| 173 | 
            +
              # Discovers a list of possible matches to the given arguments
         | 
| 174 | 
            +
              # Intended for use with bash tab completion
         | 
| 254 175 | 
             
              #
         | 
| 255 176 | 
             
              # @param [Module] base the module which invoked 'include Rubycom'
         | 
| 256 | 
            -
              # @ | 
| 257 | 
            -
               | 
| 258 | 
            -
             | 
| 259 | 
            -
                 | 
| 260 | 
            -
                " | 
| 261 | 
            -
             | 
| 262 | 
            -
             | 
| 263 | 
            -
             | 
| 264 | 
            -
             | 
| 265 | 
            -
             | 
| 266 | 
            -
             | 
| 267 | 
            -
             | 
| 268 | 
            -
              # @return [String] the detailed usage description for the given command_name
         | 
| 269 | 
            -
              def self.get_command_usage(base, command_name, args=[])
         | 
| 270 | 
            -
                raise CLIError, "Can not get usage for #{command_name} with base: #{base||"nil"}" if base.nil? || !base.respond_to?(:included_modules)
         | 
| 271 | 
            -
                return 'No command specified.' if command_name.nil? || command_name.length == 0
         | 
| 272 | 
            -
                if base.included_modules.map { |mod| mod.name.to_sym }.include?(command_name.to_sym)
         | 
| 273 | 
            -
                  if args.empty?
         | 
| 274 | 
            -
                    self.get_usage(eval(command_name.to_s))
         | 
| 275 | 
            -
                  else
         | 
| 276 | 
            -
                    self.get_command_usage(eval(command_name.to_s), args[0], args[1..-1])
         | 
| 277 | 
            -
                  end
         | 
| 278 | 
            -
                else
         | 
| 279 | 
            -
                  raise CLIError, "Invalid command for #{base}, #{command_name}" unless base.public_methods.include?(command_name.to_sym)
         | 
| 280 | 
            -
                  m = base.public_method(command_name.to_sym)
         | 
| 281 | 
            -
                  method_doc = self.get_doc(m) || {}
         | 
| 282 | 
            -
             | 
| 283 | 
            -
                  msg = "Usage: #{m.name} #{self.get_param_usage(m)}\n"
         | 
| 284 | 
            -
                  msg << "#{"Parameters:"}\n" unless m.parameters.empty?
         | 
| 285 | 
            -
                  msg << "#{method_doc[:param].join("\n    ")}\n" unless method_doc[:param].nil?
         | 
| 286 | 
            -
                  msg << "#{"Returns:"}\n"  unless method_doc[:return].nil?
         | 
| 287 | 
            -
                  msg << "#{method_doc[:return].join("\n    ")}\n" unless method_doc[:return].nil?
         | 
| 288 | 
            -
                  msg
         | 
| 289 | 
            -
                end
         | 
| 290 | 
            -
              end
         | 
| 291 | 
            -
             | 
| 292 | 
            -
              # Discovers the given Method's parameters and uses them to build a formatted usage string
         | 
| 293 | 
            -
              #
         | 
| 294 | 
            -
              # @param [Method] method the Method object to generate usage for
         | 
| 295 | 
            -
              def self.get_param_usage(method)
         | 
| 296 | 
            -
                return "" if method.parameters.nil? || method.parameters.empty?
         | 
| 297 | 
            -
                method.parameters.map { |type, param| {type => param}
         | 
| 298 | 
            -
                }.group_by { |entry| entry.keys.first
         | 
| 299 | 
            -
                }.map { |key, val| Hash[key, val.map { |param| param.values.first }]
         | 
| 300 | 
            -
                }.reduce(&:merge).map { |type, arr|
         | 
| 301 | 
            -
                  if type == :req
         | 
| 302 | 
            -
                    Hash[type, arr.map { |param| " <#{param.to_s}>" }.reduce(:+)]
         | 
| 303 | 
            -
                  elsif type == :opt
         | 
| 304 | 
            -
                    Hash[type, "[#{arr.map { |param| "-#{param}=val" }.join("|")}]"]
         | 
| 305 | 
            -
                  else
         | 
| 306 | 
            -
                    Hash[type, "[&#{arr.join(',')}]"]
         | 
| 177 | 
            +
              # @param [Array] arguments a String Array representing the arguments to be matched
         | 
| 178 | 
            +
              # @return [Array] a String Array including the possible matches for the given arguments
         | 
| 179 | 
            +
              def self.tab_complete(base, arguments)
         | 
| 180 | 
            +
                arguments = [] if arguments.nil?
         | 
| 181 | 
            +
                args = (arguments.include?("tab_complete")) ? arguments[2..-1] : arguments
         | 
| 182 | 
            +
                matches = ['']
         | 
| 183 | 
            +
                if args.nil? || args.empty?
         | 
| 184 | 
            +
                  matches = Rubycom::Commands.get_top_level_commands(base).map { |sym| sym.to_s }
         | 
| 185 | 
            +
                elsif args.length == 1
         | 
| 186 | 
            +
                  matches = Rubycom::Commands.get_top_level_commands(base).map { |sym| sym.to_s }.select { |word| !word.match(/^#{args[0]}/).nil? }
         | 
| 187 | 
            +
                  if matches.size == 1 && matches[0] == args[0]
         | 
| 188 | 
            +
                    matches = self.tab_complete(Kernel.const_get(args[0].to_sym), args[1..-1])
         | 
| 307 189 | 
             
                  end
         | 
| 308 | 
            -
                 | 
| 309 | 
            -
             | 
| 310 | 
            -
             | 
| 311 | 
            -
             | 
| 312 | 
            -
             | 
| 313 | 
            -
              # for each parameter defined by the given method.
         | 
| 314 | 
            -
              #
         | 
| 315 | 
            -
              # @param [Method] method the Method who's parameter hash should be built
         | 
| 316 | 
            -
              # @return [Hash] a Hash representing the given Method's parameters
         | 
| 317 | 
            -
              def self.get_param_definitions(method)
         | 
| 318 | 
            -
                raise CLIError, 'method must be an instance of the Method class' unless method.class == Method
         | 
| 319 | 
            -
                method.parameters.map { |param|
         | 
| 320 | 
            -
                  param[1].to_s
         | 
| 321 | 
            -
                }.map { |param_name|
         | 
| 322 | 
            -
                  {param_name.to_sym => method.source.split("\n").select { |line| line.include?(param_name) }.first}
         | 
| 323 | 
            -
                }.map { |param_hash|
         | 
| 324 | 
            -
                  param_def = param_hash.flatten[1].gsub(/(def\s+self\.#{method.name.to_s}|def\s+#{method.name.to_s})/, '').lstrip.chomp.chomp(')').reverse.chomp('(').reverse.split(',').map { |param_n| param_n.lstrip }.select { |candidate| candidate.include?(param_hash.flatten[0].to_s) }.first
         | 
| 325 | 
            -
                  Hash[
         | 
| 326 | 
            -
                      param_hash.flatten[0],
         | 
| 327 | 
            -
                      Hash[
         | 
| 328 | 
            -
                          :def, param_def,
         | 
| 329 | 
            -
                          :type, method.parameters.select { |arr| arr[1] == param_hash.flatten[0] }.flatten[0],
         | 
| 330 | 
            -
                          :default, (param_def.include?('=') ? YAML.load(param_def.split('=')[1..-1].join('=')) : :nil_rubycom_required_param)
         | 
| 331 | 
            -
                      ]
         | 
| 332 | 
            -
                  ]
         | 
| 333 | 
            -
                }.reduce(&:merge) || {}
         | 
| 334 | 
            -
              end
         | 
| 335 | 
            -
             | 
| 336 | 
            -
              # Retrieves the given method's documentation from it's source code
         | 
| 337 | 
            -
              #
         | 
| 338 | 
            -
              # @param [Method] method the Method who's documentation should be retrieved
         | 
| 339 | 
            -
              # @return [Hash] a Hash representing the given Method's documentation, documentation parsed as follows:
         | 
| 340 | 
            -
              #                :desc = the first general method comment lines,
         | 
| 341 | 
            -
              #                :word = each @word comment (i.e.- a line starting with @param will be saved as :param => ["line"])
         | 
| 342 | 
            -
              def self.get_doc(method)
         | 
| 343 | 
            -
                method.comment.split("\n").map { |line|
         | 
| 344 | 
            -
                  line.gsub(/#\s*/, '') }.group_by { |doc|
         | 
| 345 | 
            -
                  if doc.match(/^@\w+/).nil?
         | 
| 346 | 
            -
                    :desc
         | 
| 347 | 
            -
                  else
         | 
| 348 | 
            -
                    doc.match(/^@\w+/).to_s.gsub('@', '').to_sym
         | 
| 190 | 
            +
                elsif args.length > 1
         | 
| 191 | 
            +
                  begin
         | 
| 192 | 
            +
                    matches = self.tab_complete(Kernel.const_get(args[0].to_sym), args[1..-1])
         | 
| 193 | 
            +
                  rescue Exception
         | 
| 194 | 
            +
                    matches = ['']
         | 
| 349 195 | 
             
                  end
         | 
| 350 | 
            -
                 | 
| 351 | 
            -
             | 
| 352 | 
            -
                 | 
| 353 | 
            -
              end
         | 
| 354 | 
            -
             | 
| 355 | 
            -
              # Looks up the commands which will be available on the given base Module and returns the longest command name
         | 
| 356 | 
            -
              # Used in arranging the command list format
         | 
| 357 | 
            -
              #
         | 
| 358 | 
            -
              # @param [Module] base the base Module to look up
         | 
| 359 | 
            -
              # @return [String] the longest command name which will show in a list of commands for the given base Module
         | 
| 360 | 
            -
              def self.get_longest_command_name(base)
         | 
| 361 | 
            -
                return '' if base.nil?
         | 
| 362 | 
            -
                self.get_commands(base, false).map { |_, mod_hash|
         | 
| 363 | 
            -
                  mod_hash[:commands] + mod_hash[:inclusions].flatten }.flatten.max_by(&:size) or ''
         | 
| 364 | 
            -
              end
         | 
| 365 | 
            -
             | 
| 366 | 
            -
              # Retrieves the singleton methods in the given base and included Modules
         | 
| 367 | 
            -
              #
         | 
| 368 | 
            -
              # @param [Module] base the module which invoked 'include Rubycom'
         | 
| 369 | 
            -
              # @param [Boolean] all if true recursively search for included modules' commands, if false return only top level commands
         | 
| 370 | 
            -
              # @return [Hash] a Hash of Symbols representing the command methods in the given base and it's included modules (if all=true)
         | 
| 371 | 
            -
              def self.get_commands(base, all=true)
         | 
| 372 | 
            -
                return {} if base.nil? || !base.respond_to?(:singleton_methods) || !base.respond_to?(:included_modules)
         | 
| 373 | 
            -
                {
         | 
| 374 | 
            -
                    base.name.to_sym => {
         | 
| 375 | 
            -
                        commands: base.singleton_methods(true).select { |sym| ![:included, :extended].include?(sym) },
         | 
| 376 | 
            -
                        inclusions: base.included_modules.select { |mod|
         | 
| 377 | 
            -
                          ![:Rubycom].include?(mod.name.to_sym)
         | 
| 378 | 
            -
                        }.map { |mod|
         | 
| 379 | 
            -
                          all ? self.get_commands(mod) : mod.name.to_sym
         | 
| 380 | 
            -
                        }
         | 
| 381 | 
            -
                    }
         | 
| 382 | 
            -
                }
         | 
| 383 | 
            -
              end
         | 
| 384 | 
            -
             | 
| 385 | 
            -
              # Discovers the commands specified in the given base without considering the commands contained in sub-modules
         | 
| 386 | 
            -
              #
         | 
| 387 | 
            -
              # @param [Module] base the base Module to search
         | 
| 388 | 
            -
              # @return [Array] a list of command name symbols which are defined in the given Module
         | 
| 389 | 
            -
              def self.get_top_level_commands(base)
         | 
| 390 | 
            -
                return {} if base.nil? || !base.respond_to?(:singleton_methods) || !base.respond_to?(:included_modules)
         | 
| 391 | 
            -
                excluded_commands = [:included, :extended]
         | 
| 392 | 
            -
                excluded_modules = [:Rubycom]
         | 
| 393 | 
            -
                base.singleton_methods(true).select { |sym| !excluded_commands.include?(sym) } +
         | 
| 394 | 
            -
                    base.included_modules.select { |mod| !excluded_modules.include?(mod.name.to_sym) }.map { |mod| mod.name.to_sym }.flatten
         | 
| 395 | 
            -
              end
         | 
| 396 | 
            -
             | 
| 397 | 
            -
              # Discovers the commands specified in the given base and included Modules
         | 
| 398 | 
            -
              #
         | 
| 399 | 
            -
              # @param [Module] base the base Module to search
         | 
| 400 | 
            -
              # @return [Hash] a set of command name symbols mapped to containing Modules
         | 
| 401 | 
            -
              def self.index_commands(base)
         | 
| 402 | 
            -
                excluded_commands = [:included, :extended]
         | 
| 403 | 
            -
                excluded_modules = [:Rubycom]
         | 
| 404 | 
            -
                Hash[base.singleton_methods(true).select { |sym| !excluded_commands.include?(sym) }.map { |sym|
         | 
| 405 | 
            -
                  [sym, base]
         | 
| 406 | 
            -
                }].merge(
         | 
| 407 | 
            -
                    base.included_modules.select { |mod| !excluded_modules.include?(mod.name.to_sym) }.map { |mod|
         | 
| 408 | 
            -
                      self.index_commands(mod)
         | 
| 409 | 
            -
                    }.reduce(&:merge) || {}
         | 
| 410 | 
            -
                )
         | 
| 196 | 
            +
                end unless base.nil?
         | 
| 197 | 
            +
                matches = [''] if matches.nil? || matches.include?(args[0])
         | 
| 198 | 
            +
                matches
         | 
| 411 199 | 
             
              end
         | 
| 412 200 |  | 
| 413 201 | 
             
            end
         |