bosonson 0.304.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. data/CHANGELOG.rdoc +108 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.rdoc +181 -0
  4. data/bin/bss +6 -0
  5. data/bosonson.gemspec +24 -0
  6. data/deps.rip +2 -0
  7. data/lib/boson.rb +96 -0
  8. data/lib/boson/command.rb +196 -0
  9. data/lib/boson/commands.rb +7 -0
  10. data/lib/boson/commands/core.rb +77 -0
  11. data/lib/boson/commands/web_core.rb +153 -0
  12. data/lib/boson/index.rb +48 -0
  13. data/lib/boson/inspector.rb +120 -0
  14. data/lib/boson/inspectors/argument_inspector.rb +97 -0
  15. data/lib/boson/inspectors/comment_inspector.rb +100 -0
  16. data/lib/boson/inspectors/method_inspector.rb +98 -0
  17. data/lib/boson/libraries/file_library.rb +144 -0
  18. data/lib/boson/libraries/gem_library.rb +30 -0
  19. data/lib/boson/libraries/local_file_library.rb +30 -0
  20. data/lib/boson/libraries/module_library.rb +37 -0
  21. data/lib/boson/libraries/require_library.rb +23 -0
  22. data/lib/boson/library.rb +179 -0
  23. data/lib/boson/loader.rb +118 -0
  24. data/lib/boson/manager.rb +169 -0
  25. data/lib/boson/namespace.rb +31 -0
  26. data/lib/boson/option_command.rb +222 -0
  27. data/lib/boson/option_parser.rb +475 -0
  28. data/lib/boson/options.rb +146 -0
  29. data/lib/boson/pipe.rb +147 -0
  30. data/lib/boson/pipes.rb +75 -0
  31. data/lib/boson/repo.rb +107 -0
  32. data/lib/boson/repo_index.rb +124 -0
  33. data/lib/boson/runner.rb +81 -0
  34. data/lib/boson/runners/bin_runner.rb +208 -0
  35. data/lib/boson/runners/console_runner.rb +58 -0
  36. data/lib/boson/scientist.rb +182 -0
  37. data/lib/boson/util.rb +129 -0
  38. data/lib/boson/version.rb +3 -0
  39. data/lib/boson/view.rb +95 -0
  40. data/test/argument_inspector_test.rb +62 -0
  41. data/test/bin_runner_test.rb +223 -0
  42. data/test/command_test.rb +22 -0
  43. data/test/commands_test.rb +22 -0
  44. data/test/comment_inspector_test.rb +126 -0
  45. data/test/deps.rip +4 -0
  46. data/test/file_library_test.rb +42 -0
  47. data/test/loader_test.rb +235 -0
  48. data/test/manager_test.rb +114 -0
  49. data/test/method_inspector_test.rb +90 -0
  50. data/test/option_parser_test.rb +367 -0
  51. data/test/options_test.rb +189 -0
  52. data/test/pipes_test.rb +65 -0
  53. data/test/repo_index_test.rb +122 -0
  54. data/test/repo_test.rb +23 -0
  55. data/test/runner_test.rb +40 -0
  56. data/test/scientist_test.rb +341 -0
  57. data/test/test_helper.rb +130 -0
  58. data/test/util_test.rb +56 -0
  59. data/vendor/bundle/gems/bacon-bits-0.1.0/deps.rip +1 -0
  60. data/vendor/bundle/gems/hirb-0.6.0/test/deps.rip +4 -0
  61. metadata +217 -0
@@ -0,0 +1,146 @@
1
+ module Boson
2
+ # This module contains the methods used to define the default option types.
3
+ #
4
+ # === Creating Your Own Option Type
5
+ # Defining your own option type simply requires one method (create_@type) to parse the option value and create
6
+ # the desired object. To create an option type :date, you could create the following create_date method:
7
+ # # Drop this in ~/.boson/commands/date_option.rb
8
+ # module Boson::Options::Date
9
+ # def create_date(value)
10
+ # # value should be mm/dd
11
+ # Date.parse(value + "/#{Date.today.year}")
12
+ # end
13
+ # end
14
+ # Boson::OptionParser.send :include, Boson::Options::Date
15
+ #
16
+ # Modify your config to load this new library by default:
17
+ # :defaults:
18
+ # - date_option
19
+ #
20
+ # In a FileLibrary, we could then use this new option:
21
+ # module Calendar
22
+ # #@options :day=>:date
23
+ # def appointments(options={})
24
+ # # ...
25
+ # end
26
+ # end
27
+ # # >> appointments '-d 10/10' -> {:day=>#<Date: 4910229/2,0,2299161> }
28
+ # As you can see, a date object is created from the :date option's value and passed into appointments().
29
+ #
30
+ # Some additional tips on the create_* method:
31
+ # * The argument passed to the method is the option value from the user.
32
+ # * To access the current option name use @current_option.
33
+ # * To access the hash of attributes the current option has use OptionParser.current_attributes. See
34
+ # OptionParser.new for more about option attributes.
35
+ #
36
+ # There are two optional methods per option type: validate_@type and usage_for_@type i.e. validate_date and usage_for_date.
37
+ # Like create_@type, validate_@type takes the option's value. If the value validation fails, raise an
38
+ # OptionParser::Error with a proper message. All user-defined option types automatically validate for an option value's existence.
39
+ # The usage_for_* method takes an option's name (i.e. --day) and returns a usage string to be wrapped in '[ ]'. If no usage is defined
40
+ # the default would look like '[--day=:date]'. Consider using the OptionParser.default_usage helper method for your usage.
41
+ module Options
42
+ #:stopdoc:
43
+ # Parse/create methods
44
+ def create_string(value)
45
+ if (values = current_attributes[:values]) && (values = values.sort_by {|e| e.to_s})
46
+ value = auto_alias_value(values, value)
47
+ validate_enum_values(values, value)
48
+ end
49
+ value
50
+ end
51
+
52
+ def create_boolean(value)
53
+ if (!@opt_types.key?(dasherize(@current_option)) && @current_option =~ /^no-(\w+)$/)
54
+ opt = (opt = original_no_opt($1)) ? undasherize(opt) : $1
55
+ (@current_option.replace(opt) && false)
56
+ else
57
+ true
58
+ end
59
+ end
60
+
61
+ def create_numeric(value)
62
+ value.index('.') ? value.to_f : value.to_i
63
+ end
64
+
65
+ def create_array(value)
66
+ splitter = current_attributes[:split] || ','
67
+ array = value.split(splitter)
68
+ if (values = current_attributes[:values]) && (values = values.sort_by {|e| e.to_s })
69
+ if current_attributes[:regexp]
70
+ array = array.map {|e|
71
+ (new_values = values.grep(/#{e}/)).empty? ? e : new_values
72
+ }.compact.flatten.uniq
73
+ else
74
+ array.each {|e| array.delete(e) && array += values if e == '*'}
75
+ array.map! {|e| auto_alias_value(values, e) }
76
+ end
77
+ validate_enum_values(values, array)
78
+ end
79
+ array
80
+ end
81
+
82
+ def create_hash(value)
83
+ (keys = current_attributes[:keys]) && keys = keys.sort_by {|e| e.to_s }
84
+ hash = parse_hash(value, keys)
85
+ if keys
86
+ hash = hash.inject({}) {|h,(k,v)|
87
+ h[auto_alias_value(keys, k)] = v; h
88
+ }
89
+ validate_enum_values(keys, hash.keys)
90
+ end
91
+ hash
92
+ end
93
+
94
+ def parse_hash(value, keys)
95
+ splitter = current_attributes[:split] || ','
96
+ if !value.include?(':') && current_attributes[:default_keys]
97
+ value = current_attributes[:default_keys].to_s + ":#{value}"
98
+ end
99
+
100
+ # Creates array pairs, grouping array of keys with a value
101
+ aoa = Hash[*value.split(/(?::)([^#{Regexp.quote(splitter)}]+)#{Regexp.quote(splitter)}?/)].to_a
102
+ aoa.each_with_index {|(k,v),i| aoa[i][0] = keys.join(splitter) if k == '*' } if keys
103
+ aoa.inject({}) {|t,(k,v)| k.split(splitter).each {|e| t[e] = v }; t }
104
+ end
105
+
106
+ # Validation methods
107
+ def validate_string(value)
108
+ raise OptionParser::Error, "cannot pass '#{value}' as an argument to option '#{@current_option}'" if valid?(value)
109
+ end
110
+
111
+ def validate_numeric(value)
112
+ unless value =~ OptionParser::NUMERIC and $& == value
113
+ raise OptionParser::Error, "expected numeric value for option '#{@current_option}'; got #{value.inspect}"
114
+ end
115
+ end
116
+
117
+ def validate_hash(value)
118
+ if !value.include?(':') && !current_attributes[:default_keys]
119
+ raise(OptionParser::Error, "invalid key:value pair for option '#{@current_option}'")
120
+ end
121
+ end
122
+
123
+ # Usage methods
124
+ def usage_for_boolean(opt)
125
+ opt
126
+ end
127
+
128
+ def usage_for_string(opt)
129
+ default_usage(opt, undasherize(opt).upcase)
130
+ end
131
+
132
+ def usage_for_numeric(opt)
133
+ default_usage opt, "N"
134
+ end
135
+
136
+ def usage_for_array(opt)
137
+ default_usage opt, "A,B,C"
138
+ end
139
+
140
+ def usage_for_hash(opt)
141
+ default_usage opt, "A:B,C:D"
142
+ end
143
+ #:startdoc:
144
+ end
145
+ end
146
+ Boson::OptionParser.send :include, Boson::Options
@@ -0,0 +1,147 @@
1
+ module Boson
2
+ # This module passes an original command's return value through methods/commands specified as pipe options. Pipe options
3
+ # are processed in this order:
4
+ # * A :query option searches an array of objects or hashes using Pipes.query_pipe.
5
+ # * A :sort option sorts an array of objects or hashes using Pipes.sort_pipe.
6
+ # * A :reverse_sort pipe option reverses an array.
7
+ # * A :pipes option takes an array of commands that modify the return value using Pipes.pipes_pipe.
8
+ # * All user-defined pipe options (:pipe_options key in Repo.config) are processed in random order.
9
+ #
10
+ # Some points:
11
+ # * User-defined pipes call a command (the option's name by default). It's the user's responsibility to have this
12
+ # command loaded when used. The easiest way to do this is by adding the pipe command's library to :defaults in main config.
13
+ # * By default, pipe commands do not modify the value their given. This means you can activate multiple pipes using
14
+ # a method's original return value.
15
+ # * A pipe command expects a command's return value as its first argument. If the pipe option takes an argument, it's passed
16
+ # on as a second argument.
17
+ # * When piping occurs in relation to rendering depends on the Hirb view. With the default Hirb view, piping occurs
18
+ # occurs in the middle of the rendering, after Hirb has converted the return value into an array of hashes.
19
+ # If using a custom Hirb view, piping occurs before rendering.
20
+ # * What the pipe command should expect as a return value depends on the type of command. If it's a command rendered with hirb's
21
+ # tables, the return value is a an array of hashes. For everything else, it's the method's original return value.
22
+ #
23
+ # === User Pipes
24
+ # User pipes have the following attributes which alter their behavior:
25
+ # [*:pipe*] Pipe command the pipe executes when called. Default is the pipe's name.
26
+ # [*:env*] Boolean which enables passing an additional hash to the pipe command. This hash contains information from the first
27
+ # command's input with the following keys: :args (command's arguments), :options (command's options),
28
+ # :global_options (command's global options) and :config (a command's configuration hash). Default is false.
29
+ # [*:filter*] Boolean which has the pipe command modify the original command's output with the value it returns. Default is false.
30
+ # [*:no_render*] Boolean to turn off auto-rendering of the original command's final output. Only applicable to :filter enabled
31
+ # pipes. Default is false.
32
+ # [*:solo*] Boolean to indicate this pipe can't run with other user pipes or pipes from :pipes option.
33
+ # If a user calls multiple solo pipes, only the first one detected is called.
34
+ #
35
+ # === User Pipes Example
36
+ # Let's say you want to have two commands, browser and copy, you want to make available as pipe options:
37
+ # # Opens url in browser. This command already ships with Boson.
38
+ # def browser(url)
39
+ # system('open', url)
40
+ # end
41
+ #
42
+ # # Copy to clipboard
43
+ # def copy(str)
44
+ # IO.popen('pbcopy', 'w+') {|clipboard| clipboard.write(str)}
45
+ # end
46
+ #
47
+ # To configure them, drop the following config in ~/.boson/config/boson.yml:
48
+ # :pipe_options:
49
+ # :browser:
50
+ # :type: :boolean
51
+ # :desc: Open in browser
52
+ # :copy:
53
+ # :type: :boolean
54
+ # :desc: Copy to clipboard
55
+ #
56
+ # Now for any command that returns a url string, these pipe options can be turned on to execute the url.
57
+ #
58
+ # Some examples of these options using commands from {my libraries}[http://github.com/cldwalker/irbfiles]:
59
+ # # Creates a gist and then opens url in browser and copies it.
60
+ # $ cat some_file | boson gist -bC # or cat some_file | boson gist --browser --copy
61
+ #
62
+ # # Generates rdoc in current directory and then opens it in browser
63
+ # irb>> rdoc '-b' # or rdoc '--browser'
64
+ module Pipe
65
+ extend self
66
+
67
+ # Process pipes for Scientist
68
+ def scientist_process(object, global_opt, env={})
69
+ @env = env
70
+ [:query, :sort, :reverse_sort].each {|e| global_opt.delete(e) } unless object.is_a?(Array)
71
+ process_pipes(object, global_opt)
72
+ end
73
+
74
+ # Main method which processes all pipe commands, both default and user-defined ones.
75
+ def process_pipes(obj, options)
76
+ internal_pipes(options).each {|pipe|
77
+ obj = Pipes.send("#{pipe}_pipe", obj, options[pipe]) if options[pipe]
78
+ }
79
+ process_user_pipes(obj, options)
80
+ end
81
+
82
+ # A hash that defines user pipes in the same way as the :pipe_options key in Repo.config.
83
+ # This method should be called when a pipe's library is loading.
84
+ def add_pipes(hash)
85
+ pipe_options.merge! setup_pipes(hash)
86
+ end
87
+
88
+ #:stopdoc:
89
+ def internal_pipes(global_opt)
90
+ internals = [:query, :sort, :reverse_sort, :pipes]
91
+ internals.delete(:pipes) if pipes_to_process(global_opt).any? {|e| pipe(e)[:solo] }
92
+ internals
93
+ end
94
+
95
+ def pipe_options
96
+ @pipe_options ||= setup_pipes(Boson.repo.config[:pipe_options] || {})
97
+ end
98
+
99
+ def setup_pipes(hash)
100
+ hash.each {|k,v| v[:pipe] ||= k }
101
+ end
102
+
103
+ def pipe(key)
104
+ pipe_options[key] || {}
105
+ end
106
+
107
+ # global_opt can come from Hirb callback or Scientist
108
+ def process_user_pipes(result, global_opt)
109
+ pipes_to_process(global_opt).each {|e|
110
+ args = [pipe(e)[:pipe], result]
111
+ args << global_opt[e] unless pipe(e)[:type] == :boolean
112
+ args << get_env(e, global_opt) if pipe(e)[:env]
113
+ pipe_result = Boson.invoke(*args)
114
+ result = pipe_result if pipe(e)[:filter]
115
+ }
116
+ result
117
+ end
118
+
119
+ def get_env(key, global_opt)
120
+ { :global_options=>global_opt.merge(:delete_callbacks=>[:z_boson_pipes]),
121
+ :config=>(@env[:config].dup[key] || {}),
122
+ :args=>@env[:args],
123
+ :options=>@env[:options] || {}
124
+ }
125
+ end
126
+
127
+ def any_no_render_pipes?(global_opt)
128
+ !(pipes = pipes_to_process(global_opt)).empty? &&
129
+ pipes.any? {|e| pipe(e)[:no_render] }
130
+ end
131
+
132
+ def pipes_to_process(global_opt)
133
+ pipes = (global_opt.keys & pipe_options.keys)
134
+ (solo_pipe = pipes.find {|e| pipe(e)[:solo] }) ? [solo_pipe] : pipes
135
+ end
136
+ #:startdoc:
137
+
138
+ # Callbacks used by Hirb::Helpers::Table to search,sort and run custom pipe commands on arrays of hashes.
139
+ module TableCallbacks
140
+ # Processes boson's pipes
141
+ def z_boson_pipes_callback(obj, options)
142
+ Pipe.process_pipes(obj, options)
143
+ end
144
+ end
145
+ end
146
+ end
147
+ Hirb::Helpers::Table.send :include, Boson::Pipe::TableCallbacks
@@ -0,0 +1,75 @@
1
+ module Boson
2
+ # === Default Pipes: Search and Sort
3
+ # The default pipe options, :query, :sort and :reverse_sort, are quite useful for searching and sorting arrays:
4
+ # Some examples using default commands:
5
+ # # Searches commands in the full_name field for 'lib' and sorts results by that field.
6
+ # $ boson commands -q=f:lib -s=f # or commands --query=full_name:lib --sort=full_name
7
+ #
8
+ # # Multiple fields can be searched if separated by a ','. This searches the full_name and desc fields.
9
+ # $ boson commands -q=f,d:web # or commands --query=full_name,desc:web
10
+ #
11
+ # # All fields can be queried using a '*'.
12
+ # # Searches all library fields and then reverse sorts on name field
13
+ # $ boson libraries -q=*:core -s=n -R # or libraries --query=*:core --sort=name --reverse_sort
14
+ #
15
+ # # Multiple searches can be joined together by ','
16
+ # # Searches for libraries that have the name matching core or a library_type matching gem
17
+ # $ boson libraries -q=n:core,l:gem # or libraries --query=name:core,library_type:gem
18
+ #
19
+ # In these examples, we queried commands and examples with an explicit --query. However, -q or --query isn't necessary
20
+ # for these commands because they already default to it when not present. This behavior comes from the default_option
21
+ # attribute a command can have.
22
+ module Pipes
23
+ extend self
24
+
25
+ # Case-insensitive search an array of objects or hashes for the :query option.
26
+ # This option is a hash of fields mapped to their search terms. Searches are OR-ed.
27
+ # When searching hashes, numerical string keys in query_hash are converted to actual numbers to
28
+ # interface with Hirb.
29
+ def query_pipe(object, query_hash)
30
+ if object[0].is_a?(Hash)
31
+ query_hash.map {|field,query|
32
+ field = field.to_i if field.to_s[/^\d+$/]
33
+ object.select {|e| e[field].to_s =~ /#{query}/i }
34
+ }.flatten.uniq
35
+ else
36
+ query_hash.map {|field,query| object.select {|e| e.send(field).to_s =~ /#{query}/i } }.flatten.uniq
37
+ end
38
+ rescue NoMethodError
39
+ $stderr.puts "Query failed with nonexistant method '#{$!.message[/`(.*)'/,1]}'"
40
+ end
41
+
42
+ # Sorts an array of objects or hashes using a sort field. Sort is reversed with reverse_sort set to true.
43
+ def sort_pipe(object, sort)
44
+ sort_lambda = lambda {}
45
+ if object[0].is_a?(Hash)
46
+ if sort.to_s[/^\d+$/]
47
+ sort = sort.to_i
48
+ elsif object[0].keys.all? {|e| e.is_a?(Symbol) }
49
+ sort = sort.to_sym
50
+ end
51
+ sort_lambda = untouched_sort?(object.map {|e| e[sort] }) ? lambda {|e| e[sort] } : lambda {|e| e[sort].to_s }
52
+ else
53
+ sort_lambda = untouched_sort?(object.map {|e| e.send(sort) }) ? lambda {|e| e.send(sort) || ''} :
54
+ lambda {|e| e.send(sort).to_s }
55
+ end
56
+ object.sort_by &sort_lambda
57
+ rescue NoMethodError, ArgumentError
58
+ $stderr.puts "Sort failed with nonexistant method '#{sort}'"
59
+ end
60
+
61
+ def untouched_sort?(values) #:nodoc:
62
+ values.all? {|e| e.respond_to?(:<=>) } && values.map {|e| e.class }.uniq.size == 1
63
+ end
64
+
65
+ # Reverse an object
66
+ def reverse_sort_pipe(object, extra=nil)
67
+ object.reverse
68
+ end
69
+
70
+ # Pipes output of multiple commands recursively, given initial object
71
+ def pipes_pipe(obj, arr)
72
+ arr.inject(obj) {|acc,e| Boson.full_invoke(e, [acc]) }
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,107 @@
1
+ %w{yaml fileutils}.each {|e| require e }
2
+ module Boson
3
+ # A class for repositories. A repository has a root directory with required subdirectories config/ and
4
+ # commands/ and optional subdirectory lib/. Each repository has a primary config file at config/boson.yml.
5
+ class Repo
6
+ def self.commands_dir(dir) #:nodoc:
7
+ File.join(dir, 'commands')
8
+ end
9
+
10
+ attr_accessor :dir, :config
11
+ # Creates a repository given a root directory.
12
+ def initialize(dir)
13
+ @dir = dir
14
+ end
15
+
16
+ # Points to the config/ subdirectory and is automatically created when called. Used for config files.
17
+ def config_dir
18
+ @config_dir ||= FileUtils.mkdir_p(config_dir_path) && config_dir_path
19
+ end
20
+
21
+ def config_dir_path
22
+ "#{dir}/config"
23
+ end
24
+
25
+ # Path name of main config file. If passed true, parent directory of file is created.
26
+ def config_file(create_dir=false)
27
+ File.join((create_dir ? config_dir : config_dir_path), 'boson.yml')
28
+ end
29
+
30
+ # Points to the commands/ subdirectory and is automatically created when called. Used for command libraries.
31
+ def commands_dir
32
+ @commands_dir ||= (cdir = self.class.commands_dir(@dir)) && FileUtils.mkdir_p(cdir) && cdir
33
+ end
34
+
35
+ # A hash read from the YAML config file at config/boson.yml.
36
+ # {See here}[http://github.com/cldwalker/irbfiles/blob/master/boson/config/boson.yml] for an example config file.
37
+ # Top level config keys, library attributes and config attributes need to be symbols.
38
+ # ==== Config keys for all repositories:
39
+ # [:libraries] Hash of libraries mapping their name to attribute hashes. See Library.new for configurable attributes.
40
+ # Example:
41
+ # :libraries=>{'completion'=>{:namespace=>true}}
42
+ # [:command_aliases] Hash of commands names and their aliases. Since this is global it will be read by _all_ libraries.
43
+ # This is useful for quickly creating aliases without having to worry about placing them under
44
+ # the correct library config. For non-global aliasing, aliases should be placed under the :command_aliases
45
+ # key of a library entry in :libraries.
46
+ # Example:
47
+ # :command_aliases=>{'libraries'=>'lib', 'commands'=>'com'}
48
+ # [:defaults] Array of libraries to load at start up for commandline and irb. This is useful for extending boson i.e. adding your
49
+ # own option types since these are loaded before any other libraries. Default is no libraries.
50
+ # [:console_defaults] Array of libraries to load at start up when used in irb. Default is to load all library files and libraries
51
+ # defined in the config.
52
+ # [:bin_defaults] Array of libraries to load at start up when used from the commandline. Default is no libraries.
53
+ # [:add_load_path] Boolean specifying whether to add a load path pointing to the lib subdirectory/. This is useful in sharing
54
+ # classes between libraries without resorting to packaging them as gems. Defaults to false if the lib
55
+ # subdirectory doesn't exist in the boson directory.
56
+ #
57
+ # ==== Config keys specific to the main repo config ~/.boson/config/boson.yml
58
+ # [:pipe_options] Hash of options available to all option commands for piping (see Pipe). A pipe option has the
59
+ # {normal option attributes}[link:classes/Boson/OptionParser.html#M000081] and these:
60
+ # * :pipe: Specifies the command to call when piping. Defaults to the pipe's option name.
61
+ # * :filter: Boolean which indicates that the pipe command will modify its input with what it returns.
62
+ # Default is false.
63
+ # [:render_options] Hash of render options available to all option commands to be passed to a Hirb view (see View). Since
64
+ # this merges with default render options, it's possible to override default render options.
65
+ # [:error_method_conflicts] Boolean specifying library loading behavior when its methods conflicts with existing methods in
66
+ # the global namespace. When set to false, Boson automatically puts the library in its own namespace.
67
+ # When set to true, the library fails to load explicitly. Default is false.
68
+ # [:console] Console to load when using --console from commandline. Default is irb.
69
+ # [:auto_namespace] Boolean which automatically namespaces all user-defined libraries. Be aware this can break libraries which
70
+ # depend on commands from other libraries. Default is false.
71
+ # [:ignore_directories] Array of directories to ignore when detecting local repositories for Boson.local_repo.
72
+ # [:no_auto_render] When set, turns off commandline auto-rendering of a command's output. Default is false.
73
+ # [:option_underscore_search] When set, OptionParser option values (with :values or :keys) are auto aliased with underscore searching.
74
+ # Default is true. See Util.underscore_search.
75
+ def config(reload=false)
76
+ if reload || @config.nil?
77
+ begin
78
+ @config = {:libraries=>{}, :command_aliases=>{}, :console_defaults=>[], :option_underscore_search=>true}
79
+ @config.merge!(YAML::load_file(config_file(true))) if File.exists?(config_file)
80
+ rescue ArgumentError
81
+ message = $!.message !~ /syntax error on line (\d+)/ ? "Error"+$!.message :
82
+ "Error: Syntax error in line #{$1} of config file '#{config_file}'"
83
+ Kernel.abort message
84
+ end
85
+ end
86
+ @config
87
+ end
88
+
89
+ # Updates main config file by passing config into a block to be modified and then saved
90
+ def update_config
91
+ yield(config)
92
+ write_config_file
93
+ end
94
+
95
+ def write_config_file #:nodoc:
96
+ File.open(config_file, 'w') {|f| f.write config.to_yaml }
97
+ end
98
+
99
+ def detected_libraries #:nodoc:
100
+ Dir[File.join(commands_dir, '**/*.rb')].map {|e| e.gsub(/^#{commands_dir}\/|\.rb$/, '') }
101
+ end
102
+
103
+ def all_libraries #:nodoc:
104
+ (detected_libraries + config[:libraries].keys).uniq
105
+ end
106
+ end
107
+ end