boson 0.2.0 → 0.2.1

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.
@@ -0,0 +1,30 @@
1
+ # This class loads any local file and is most commonly used to load a local
2
+ # Bosonfile. Since this file doesn't exist inside a normal Repo, it is not indexed with any repo.
3
+ # Since file-based libraries need to be associated with a repository, Boson associates it
4
+ # with a local repository if it exists or defaults to Boson.repo. See Boson::FileLibrary
5
+ # for more info about this library.
6
+ #
7
+ # Example:
8
+ # >> load_library 'Bosonfile'
9
+ # => true
10
+ class Boson::LocalFileLibrary < Boson::FileLibrary
11
+ handles {|source|
12
+ @repo = (File.exists?(source.to_s) ? (Boson.local_repo || Boson.repo) : nil)
13
+ !!@repo
14
+ }
15
+
16
+ #:stopdoc:
17
+ def set_name(name)
18
+ @lib_file = File.expand_path(name.to_s)
19
+ File.basename(@lib_file).downcase
20
+ end
21
+
22
+ def base_module
23
+ Boson::Commands
24
+ end
25
+
26
+ def library_file
27
+ @lib_file
28
+ end
29
+ #:startdoc:
30
+ end
@@ -24,7 +24,7 @@ module Boson
24
24
  def set_name(name)
25
25
  @module = name
26
26
  underscore_lib = name.to_s[/^Boson::Commands/] ? name.to_s.split('::')[-1] : name.to_s
27
- super Util.underscore(underscore_lib)
27
+ Util.underscore(underscore_lib)
28
28
  end
29
29
 
30
30
  def initialize_library_module
data/lib/boson/library.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  module Boson
2
2
  # A library is a group of commands (Command objects) usually grouped together by a module.
3
3
  # Libraries are loaded from different sources depending on the library subclass. Default library
4
- # subclasses are FileLibrary, GemLibrary, RequireLibrary and ModuleLibrary.
4
+ # subclasses are FileLibrary, GemLibrary, RequireLibrary, ModuleLibrary and LocalFileLibrary.
5
5
  # See Loader for callbacks a library's module can have.
6
6
  #
7
7
  # == Naming a Library Module
@@ -15,12 +15,15 @@ module Boson
15
15
  # over the top level module and the top level module has to be prefixed with '::' _everywhere_.
16
16
  #
17
17
  # == Configuration
18
- # To configure a library, pass a hash of {library attributes}[link:classes/Boson/Library.html#M000077] under
19
- # {the :libraries key}[link:classes/Boson/Repo.html#M000070] of a config file. When not using FileLibrary,
20
- # you can give most commands the functionality FileLibrary naturally gives its commands by configuring
21
- # the :commands key. Here's a config example of a GemLibrary that does that:
22
- # :libraries:
23
- # httparty:
18
+ # Libraries and their commands can be configured in different ways in this order:
19
+ # * If library is a FileLibrary, commands be configured with a config method attribute (see Inspector).
20
+ # * If a library has a module, you can set library + command attributes via the config() callback (see Loader).
21
+ # * All libraries can be configured by passing a hash of {library attributes}[link:classes/Boson/Library.html#M000077] under
22
+ # {the :libraries key}[link:classes/Boson/Repo.html#M000070] to the main config file ~/.boson/config/boson.yml.
23
+ # For most libraries this may be the only way to configure a library's commands.
24
+ # An example of a GemLibrary config:
25
+ # :libraries:
26
+ # httparty:
24
27
  # :class_commands:
25
28
  # delete: HTTParty.delete
26
29
  # :commands:
@@ -28,6 +31,9 @@ module Boson
28
31
  # :alias: d
29
32
  # :description: Http delete a given url
30
33
  #
34
+ # When installing a third-party library, use the config file as a way to override default library and command attributes
35
+ # without modifying the library.
36
+ #
31
37
  # === Creating Your Own Library
32
38
  # To create your own subclass you need to define what sources the subclass can handle with handles().
33
39
  # If handles() returns true then the subclass is chosen to load. See Loader to see what instance methods
@@ -81,7 +87,7 @@ module Boson
81
87
  def initialize(hash)
82
88
  repo = set_repo
83
89
  @repo_dir = repo.dir
84
- @name = set_name hash.delete(:name)
90
+ @name = set_name(hash.delete(:name)) or raise ArgumentError, "Library missing required key :name"
85
91
  @loaded = false
86
92
  @commands_hash = {}
87
93
  @commands = []
@@ -117,7 +123,7 @@ module Boson
117
123
  end
118
124
 
119
125
  def set_name(name)
120
- name.to_s or raise ArgumentError, "New library missing required key :name"
126
+ name.to_s
121
127
  end
122
128
 
123
129
  def set_config(config, force=false)
data/lib/boson/loader.rb CHANGED
@@ -2,9 +2,9 @@ module Boson
2
2
  # Raised if a library has a method which conflicts with existing methods in Boson.main_object.
3
3
  class MethodConflictError < LoaderError; end
4
4
 
5
- # This module is mixed into Library to give it load() and reload() functionality.
6
- # When creating your own Library subclass, you should override load_source_and_set_module and
7
- # reload_source_and_set_module . You can override other methods in this module as needed.
5
+ # This module is mixed into Library to give it load() functionality.
6
+ # When creating your own Library subclass, you should override load_source_and_set_module
7
+ # You can override other methods in this module as needed.
8
8
  #
9
9
  # === Module Callbacks
10
10
  # For libraries that have a module i.e. FileLibrary and GemLibrary, the following class methods
@@ -47,21 +47,6 @@ module Boson
47
47
  !!@module
48
48
  end
49
49
 
50
- # Reloads a library from its source and adds new commands. Only implemented
51
- # for FileLibrary for now.
52
- def reload
53
- original_commands = @commands
54
- reload_source_and_set_module
55
- detect_additions { load_module_commands } if @new_module
56
- @new_commands = @commands - original_commands
57
- true
58
- end
59
-
60
- # Same as load_source_and_set_module except it reloads.
61
- def reload_source_and_set_module
62
- raise LoaderError, "Reload not implemented"
63
- end
64
-
65
50
  #:stopdoc:
66
51
  def module_callbacks
67
52
  set_config(@module.config) if @module.respond_to?(:config)
data/lib/boson/manager.rb CHANGED
@@ -4,7 +4,7 @@ module Boson
4
4
  # Raised when a library's append_features returns false.
5
5
  class AppendFeaturesFalseError < StandardError; end
6
6
 
7
- # Handles loading and reloading of libraries and commands.
7
+ # Handles loading of libraries and commands.
8
8
  class Manager
9
9
  class <<self
10
10
  attr_accessor :failed_libraries
@@ -23,28 +23,6 @@ module Boson
23
23
  }.all?
24
24
  end
25
25
 
26
- # Reloads a library or an array of libraries with the following options:
27
- # * :verbose: Boolean to print reload status. Default is false.
28
- def reload(source, options={})
29
- if (lib = Boson.library(source))
30
- if lib.loaded
31
- command_size = Boson.commands.size
32
- @options = options
33
- if (result = rescue_load_action(lib.name, :reload) { lib.reload })
34
- after_reload(lib)
35
- puts "Reloaded library #{source}: Added #{Boson.commands.size - command_size} commands" if options[:verbose]
36
- end
37
- result
38
- else
39
- puts "Library hasn't been loaded yet. Loading library #{source}..." if options[:verbose]
40
- load(source, options)
41
- end
42
- else
43
- puts "Library #{source} doesn't exist." if options[:verbose]
44
- false
45
- end
46
- end
47
-
48
26
  #:stopdoc:
49
27
  def failed_libraries
50
28
  @failed_libraries ||= []
@@ -123,11 +101,6 @@ module Boson
123
101
  true
124
102
  end
125
103
 
126
- def after_reload(lib)
127
- Boson.commands.delete_if {|e| e.lib == lib.name } if lib.new_module
128
- create_commands(lib, lib.new_commands)
129
- end
130
-
131
104
  def before_create_commands(lib)
132
105
  lib.is_a?(FileLibrary) && lib.module && Inspector.add_method_data_to_library(lib)
133
106
  end
@@ -136,17 +109,17 @@ module Boson
136
109
  before_create_commands(lib)
137
110
  commands.each {|e| Boson.commands << Command.create(e, lib)}
138
111
  create_command_aliases(lib, commands) if commands.size > 0 && !lib.no_alias_creation
139
- create_option_commands(lib, commands)
112
+ redefine_commands(lib, commands)
140
113
  end
141
114
 
142
- def create_option_commands(lib, commands)
115
+ def redefine_commands(lib, commands)
143
116
  option_commands = lib.command_objects(commands).select {|e| e.option_command? }
144
117
  accepted, rejected = option_commands.partition {|e| e.args(lib) || e.arg_size }
145
118
  if @options[:verbose] && rejected.size > 0
146
119
  puts "Following commands cannot have options until their arguments are configured: " +
147
120
  rejected.map {|e| e.name}.join(', ')
148
121
  end
149
- accepted.each {|cmd| Scientist.create_option_command(lib.namespace_object, cmd) }
122
+ accepted.each {|cmd| Scientist.redefine_command(lib.namespace_object, cmd) }
150
123
  end
151
124
 
152
125
  def create_command_aliases(lib, commands)
@@ -0,0 +1,204 @@
1
+ require 'shellwords'
2
+ module Boson
3
+ # A class used by Scientist to wrap around Command objects. It's main purpose is to parse
4
+ # a command's global options (basic options, render options, pipe options) and local options.
5
+ # As the names imply, global options are available to all commands while local options are specific to a command.
6
+ # When passing options to commands, global ones _must_ be passed first, then local ones.
7
+ # For more about pipe and render options see Pipe and View respectively.
8
+ #
9
+ # === Basic Global Options
10
+ # Any command with options comes with basic global options. For example '-hv' on an option command
11
+ # prints a help summarizing global and local options. Another basic global option is --pretend. This
12
+ # option displays what global options have been parsed and the actual arguments to be passed to a
13
+ # command if executed. For example:
14
+ #
15
+ # # Define this command in a library
16
+ # options :level=>:numeric, :verbose=>:boolean
17
+ # def foo(*args)
18
+ # args
19
+ # end
20
+ #
21
+ # irb>> foo 'testin -p -l=1'
22
+ # Arguments: ["testin", {:level=>1}]
23
+ # Global options: {:pretend=>true}
24
+ #
25
+ # If a global option conflicts with a local option, the local option takes precedence. You can get around
26
+ # this by passing a --global option which takes a string of options without their dashes. For example:
27
+ # foo '-p --fields=f1,f2 -l=1'
28
+ # # is the same as
29
+ # foo ' -g "p fields=f1,f2" -l=1 '
30
+ #
31
+ # === Toggling Views With the Basic Global Option --render
32
+ # One of the more important global options is --render. This option toggles the rendering of a command's
33
+ # output done with View and Hirb[http://github.com/cldwalker/hirb].
34
+ #
35
+ # Here's a simple example of toggling Hirb's table view:
36
+ # # Defined in a library file:
37
+ # #@options {}
38
+ # def list(options={})
39
+ # [1,2,3]
40
+ # end
41
+ #
42
+ # Using it in irb:
43
+ # >> list
44
+ # => [1,2,3]
45
+ # >> list '-r' # or list --render
46
+ # +-------+
47
+ # | value |
48
+ # +-------+
49
+ # | 1 |
50
+ # | 2 |
51
+ # | 3 |
52
+ # +-------+
53
+ # 3 rows in set
54
+ # => true
55
+ class OptionCommand
56
+ BASIC_OPTIONS = {
57
+ :help=>{:type=>:boolean, :desc=>"Display a command's help"},
58
+ :render=>{:type=>:boolean, :desc=>"Toggle a command's default rendering behavior"},
59
+ :verbose=>{:type=>:boolean, :desc=>"Increase verbosity for help, errors, etc."},
60
+ :global=>{:type=>:string, :desc=>"Pass a string of global options without the dashes"},
61
+ :pretend=>{:type=>:boolean, :desc=>"Display what a command would execute without executing it"},
62
+ } #:nodoc:
63
+
64
+ RENDER_OPTIONS = {
65
+ :fields=>{:type=>:array, :desc=>"Displays fields in the order given"},
66
+ :class=>{:type=>:string, :desc=>"Hirb helper class which renders"},
67
+ :max_width=>{:type=>:numeric, :desc=>"Max width of a table"},
68
+ :vertical=>{:type=>:boolean, :desc=>"Display a vertical table"},
69
+ } #:nodoc:
70
+
71
+ PIPE_OPTIONS = {
72
+ :sort=>{:type=>:string, :desc=>"Sort by given field"},
73
+ :reverse_sort=>{:type=>:boolean, :desc=>"Reverse a given sort"},
74
+ :query=>{:type=>:hash, :desc=>"Queries fields given field:search pairs"},
75
+ } #:nodoc:
76
+
77
+ class <<self
78
+ #:stopdoc:
79
+ def default_option_parser
80
+ @default_option_parser ||= OptionParser.new default_pipe_options.
81
+ merge(default_render_options.merge(BASIC_OPTIONS))
82
+ end
83
+
84
+ def default_pipe_options
85
+ @default_pipe_options ||= PIPE_OPTIONS.merge Pipe.pipe_options
86
+ end
87
+
88
+ def default_render_options
89
+ @default_render_options ||= RENDER_OPTIONS.merge Boson.repo.config[:render_options] || {}
90
+ end
91
+
92
+ def delete_non_render_options(opt)
93
+ opt.delete_if {|k,v| BASIC_OPTIONS.keys.include?(k) }
94
+ end
95
+ #:startdoc:
96
+ end
97
+
98
+ attr_accessor :command
99
+ def initialize(cmd)
100
+ @command = cmd
101
+ end
102
+
103
+ # Parses arguments and returns global options, local options and leftover arguments.
104
+ def parse(args)
105
+ if args.size == 1 && args[0].is_a?(String)
106
+ global_opt, parsed_options, args = parse_options Shellwords.shellwords(args[0])
107
+ # last string argument interpreted as args + options
108
+ elsif args.size > 1 && args[-1].is_a?(String)
109
+ temp_args = Boson.const_defined?(:BinRunner) ? args : Shellwords.shellwords(args.pop)
110
+ global_opt, parsed_options, new_args = parse_options temp_args
111
+ args += new_args
112
+ # add default options
113
+ elsif @command.options.to_s.empty? || (!@command.has_splat_args? &&
114
+ args.size <= (@command.arg_size - 1).abs) || (@command.has_splat_args? && !args[-1].is_a?(Hash))
115
+ global_opt, parsed_options = parse_options([])[0,2]
116
+ # merge default options with given hash of options
117
+ elsif (@command.has_splat_args? || (args.size == @command.arg_size)) && args[-1].is_a?(Hash)
118
+ global_opt, parsed_options = parse_options([])[0,2]
119
+ parsed_options.merge!(args.pop)
120
+ end
121
+ [global_opt || {}, parsed_options, args]
122
+ end
123
+
124
+ #:stopdoc:
125
+ def parse_options(args)
126
+ parsed_options = @command.option_parser.parse(args, :delete_invalid_opts=>true)
127
+ global_options = option_parser.parse @command.option_parser.leading_non_opts
128
+ new_args = option_parser.non_opts.dup + @command.option_parser.trailing_non_opts
129
+ if global_options[:global]
130
+ global_opts = Shellwords.shellwords(global_options[:global]).map {|str|
131
+ ((str[/^(.*?)=/,1] || str).length > 1 ? "--" : "-") + str }
132
+ global_options.merge! option_parser.parse(global_opts)
133
+ end
134
+ [global_options, parsed_options, new_args]
135
+ end
136
+
137
+ def option_parser
138
+ @option_parser ||= @command.render_options ? OptionParser.new(all_global_options) :
139
+ self.class.default_option_parser
140
+ end
141
+
142
+ def all_global_options
143
+ @command.render_options.each {|k,v|
144
+ if !v.is_a?(Hash) && !v.is_a?(Symbol)
145
+ @command.render_options[k] = {:default=>v}
146
+ end
147
+ }
148
+ render_opts = Util.recursive_hash_merge(@command.render_options, Util.deep_copy(self.class.default_render_options))
149
+ merged_opts = Util.recursive_hash_merge Util.deep_copy(self.class.default_pipe_options), render_opts
150
+ opts = Util.recursive_hash_merge merged_opts, Util.deep_copy(BASIC_OPTIONS)
151
+ set_global_option_defaults opts
152
+ end
153
+
154
+ def set_global_option_defaults(opts)
155
+ if !opts[:fields].key?(:values)
156
+ if opts[:fields][:default]
157
+ opts[:fields][:values] = opts[:fields][:default]
158
+ else
159
+ if opts[:change_fields] && (changed = opts[:change_fields][:default])
160
+ opts[:fields][:values] = changed.is_a?(Array) ? changed : changed.values
161
+ end
162
+ opts[:fields][:values] ||= opts[:headers][:default].keys if opts[:headers] && opts[:headers][:default]
163
+ end
164
+ opts[:fields][:enum] = false if opts[:fields][:values] && !opts[:fields].key?(:enum)
165
+ end
166
+ if opts[:fields][:values]
167
+ opts[:sort][:values] ||= opts[:fields][:values]
168
+ opts[:query][:keys] ||= opts[:fields][:values]
169
+ opts[:query][:default_keys] ||= "*"
170
+ end
171
+ opts
172
+ end
173
+
174
+ def prepend_default_option(args)
175
+ if @command.default_option && @command.arg_size <= 1 && !@command.has_splat_args? && args[0].to_s[/./] != '-'
176
+ args[0] = "--#{@command.default_option}=#{args[0]}" unless args.join.empty? || args[0].is_a?(Hash)
177
+ end
178
+ end
179
+
180
+ def check_argument_size(args)
181
+ if args.size != @command.arg_size && !@command.has_splat_args?
182
+ command_size, args_size = args.size > @command.arg_size ? [@command.arg_size, args.size] :
183
+ [@command.arg_size - 1, args.size - 1]
184
+ raise ArgumentError, "wrong number of arguments (#{args_size} for #{command_size})"
185
+ end
186
+ end
187
+
188
+ def add_default_args(args, obj)
189
+ if @command.args && args.size < @command.args.size - 1
190
+ # leave off last arg since its an option
191
+ @command.args.slice(0..-2).each_with_index {|arr,i|
192
+ next if args.size >= i + 1 # only fill in once args run out
193
+ break if arr.size != 2 # a default arg value must exist
194
+ begin
195
+ args[i] = @command.file_parsed_args? ? obj.instance_eval(arr[1]) : arr[1]
196
+ rescue Exception
197
+ raise Scientist::Error, "Unable to set default argument at position #{i+1}.\nReason: #{$!.message}"
198
+ end
199
+ }
200
+ end
201
+ end
202
+ #:startdoc:
203
+ end
204
+ end
@@ -35,7 +35,7 @@ module Boson
35
35
  # String, Integer, Float, Array, Hash, FalseClass, TrueClass.
36
36
  # * Users can define their own option types which create objects for _any_ Ruby class. See Options.
37
37
  # * Each option type can have attributes to enable more features (see OptionParser.new).
38
- # * When options are parsed by OptionParser.parse, an IndifferentAccessHash hash is returned.
38
+ # * When options are parsed by parse(), an IndifferentAccessHash hash is returned.
39
39
  # * Options are also called switches, parameters, flags etc.
40
40
  #
41
41
  # Default option types:
@@ -79,6 +79,17 @@ module Boson
79
79
 
80
80
  attr_reader :leading_non_opts, :trailing_non_opts, :opt_aliases
81
81
 
82
+ # Given options to pass to OptionParser.new, this method parses ARGV and returns a hash of
83
+ # parsed options. This is useful for scripts outside of Boson.
84
+ def self.parse(options, args=ARGV)
85
+ (@opt_parser ||= new(options)).parse(args)
86
+ end
87
+
88
+ # Usage string summarizing options defined in parse
89
+ def self.usage
90
+ @opt_parser.to_s
91
+ end
92
+
82
93
  # Array of arguments left after defined options have been parsed out by parse.
83
94
  def non_opts
84
95
  leading_non_opts + trailing_non_opts
@@ -134,7 +145,7 @@ module Boson
134
145
  # [*:split*] For :array and :hash options. A string or regular expression on which an array value splits
135
146
  # to produce an array of values. Default is ','.
136
147
  # [*:keys*] :hash option only. An array of values a hash option's keys can have. Keys can be aliased just like :values.
137
- # [:default_keys] :hash option only. Default keys to assume when only a value is given. Multiple keys can be joined
148
+ # [*:default_keys*] :hash option only. Default keys to assume when only a value is given. Multiple keys can be joined
138
149
  # by the :split character. Defaults to first key of :keys if :keys given.
139
150
  def initialize(opts)
140
151
  @defaults = {}
data/lib/boson/options.rb CHANGED
@@ -7,7 +7,7 @@ module Boson
7
7
  # # Drop this in your ~/.irbrc after require 'boson'
8
8
  # module Boson::Options::Date
9
9
  # def create_date(value)
10
- # # value_shift should be mm/dd
10
+ # # value should be mm/dd
11
11
  # Date.parse(value + "/#{Date.today.year}")
12
12
  # end
13
13
  # end