boson 0.2.0 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -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