bosonson 0.304.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.
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,124 @@
1
+ require 'digest/md5'
2
+ module Boson
3
+ # This class provides an index for commands and libraries of a given a Repo.
4
+ # When this index updates, it detects library files whose md5 hash have changed and reindexes them.
5
+ # The index is stored with Marshal at config/index.marshal (relative to a Repo's root directory).
6
+ # Since the index is marshaled, putting lambdas/procs in it will break it.If an index gets corrupted,
7
+ # simply delete it and next time Boson needs it, the index will be recreated.
8
+
9
+ class RepoIndex
10
+ attr_reader :libraries, :commands, :repo
11
+ def initialize(repo)
12
+ @repo = repo
13
+ end
14
+
15
+ # Updates the index.
16
+ def update(options={})
17
+ libraries_to_update = !exists? ? repo.all_libraries : options[:libraries] || changed_libraries
18
+ read_and_transfer(libraries_to_update)
19
+ if options[:verbose]
20
+ puts !exists? ? "Generating index for all #{libraries_to_update.size} libraries. Patience ... is a bitch" :
21
+ (libraries_to_update.empty? ? "No libraries indexed" :
22
+ "Indexing the following libraries: #{libraries_to_update.join(', ')}")
23
+ end
24
+ Manager.failed_libraries = []
25
+ unless libraries_to_update.empty?
26
+ Manager.load(libraries_to_update, options.merge(:index=>true))
27
+ unless Manager.failed_libraries.empty?
28
+ $stderr.puts("Error: These libraries failed to load while indexing: #{Manager.failed_libraries.join(', ')}")
29
+ end
30
+ end
31
+ write(Manager.failed_libraries)
32
+ end
33
+
34
+ # Reads and initializes index.
35
+ def read
36
+ return if @read
37
+ @libraries, @commands, @lib_hashes = exists? ?
38
+ File.open( marshal_file, 'rb' ){|f| Marshal.load( f.read ) } : [[], [], {}]
39
+ delete_stale_libraries_and_commands
40
+ set_command_namespaces
41
+ @read = true
42
+ end
43
+
44
+ # Writes/saves current index to config/index.marshal.
45
+ def write(failed_libraries=[])
46
+ latest = latest_hashes
47
+ failed_libraries.each {|e| latest.delete(e) }
48
+ save_marshal_index Marshal.dump([Boson.libraries, Boson.commands, latest])
49
+ end
50
+
51
+ #:stopdoc:
52
+ def read_and_transfer(ignored_libraries=[])
53
+ read
54
+ existing_libraries = (Boson.libraries.map {|e| e.name} + ignored_libraries).uniq
55
+ libraries_to_add = @libraries.select {|e| !existing_libraries.include?(e.name)}
56
+ Boson.libraries += libraries_to_add
57
+ # depends on saved commands being correctly associated with saved libraries
58
+ Boson.commands += libraries_to_add.map {|e| e.command_objects(e.commands, @commands) }.flatten
59
+ end
60
+
61
+ def exists?
62
+ File.exists? marshal_file
63
+ end
64
+
65
+ def save_marshal_index(marshal_string)
66
+ File.open(marshal_file, 'wb') {|f| f.write marshal_string }
67
+ end
68
+
69
+ def delete_stale_libraries_and_commands
70
+ cached_libraries = @lib_hashes.keys
71
+ libs_to_delete = @libraries.select {|e| !cached_libraries.include?(e.name) && e.is_a?(FileLibrary) }
72
+ names_to_delete = libs_to_delete.map {|e| e.name }
73
+ libs_to_delete.each {|e| @libraries.delete(e) }
74
+ @commands.delete_if {|e| names_to_delete.include? e.lib }
75
+ end
76
+
77
+ # set namespaces for commands
78
+ def set_command_namespaces
79
+ lib_commands = @commands.inject({}) {|t,e| (t[e.lib] ||= []) << e; t }
80
+ namespace_libs = @libraries.select {|e| e.namespace(e.indexed_namespace) }
81
+ namespace_libs.each {|lib|
82
+ (lib_commands[lib.name] || []).each {|e| e.namespace = lib.namespace }
83
+ }
84
+ end
85
+
86
+ def namespaces
87
+ nsps = @libraries.map {|e| e.namespace }.compact
88
+ nsps.delete(false)
89
+ nsps
90
+ end
91
+
92
+ def all_main_methods
93
+ @commands.reject {|e| e.namespace }.map {|e| [e.name, e.alias]}.flatten.compact + namespaces
94
+ end
95
+
96
+ def marshal_file
97
+ File.join(repo.config_dir, 'index.marshal')
98
+ end
99
+
100
+ def find_library(command, object=false)
101
+ read
102
+ namespace_command = command.split(NAMESPACE)[0]
103
+ if (lib = @libraries.find {|e| e.namespace == namespace_command })
104
+ object ? lib : lib.name
105
+ elsif (cmd = Command.find(command, @commands))
106
+ object ? @libraries.find {|e| e.name == cmd.lib} : cmd.lib
107
+ end
108
+ end
109
+
110
+ def changed_libraries
111
+ read
112
+ latest_hashes.select {|lib, hash| @lib_hashes[lib] != hash}.map {|e| e[0]}
113
+ end
114
+
115
+ def latest_hashes
116
+ repo.all_libraries.inject({}) {|h, e|
117
+ lib_file = FileLibrary.library_file(e, repo.dir)
118
+ h[e] = Digest::MD5.hexdigest(File.read(lib_file)) if File.exists?(lib_file)
119
+ h
120
+ }
121
+ end
122
+ #:startdoc:
123
+ end
124
+ end
@@ -0,0 +1,81 @@
1
+ module Boson
2
+ # Base class for runners.
3
+ class Runner
4
+ class<<self
5
+ attr_accessor :debug
6
+
7
+ # Enables view, adds local load path and loads default_libraries
8
+ def init
9
+ View.enable
10
+ add_load_path
11
+ Manager.load default_libraries, load_options
12
+ end
13
+
14
+ # Libraries that come with Boson
15
+ def default_libraries
16
+ Boson.repos.map {|e| e.config[:defaults] || [] }.flatten + [Boson::Commands::Core, Boson::Commands::WebCore]
17
+ end
18
+
19
+ # Libraries detected in repositories
20
+ def detected_libraries
21
+ Boson.repos.map {|e| e.detected_libraries }.flatten.uniq
22
+ end
23
+
24
+ # Libraries specified in config files and detected_libraries
25
+ def all_libraries
26
+ Boson.repos.map {|e| e.all_libraries }.flatten.uniq
27
+ end
28
+
29
+ # Returns true if commands are being executed from a non-ruby shell i.e. bash. Returns false if
30
+ # in a ruby shell i.e. irb.
31
+ def in_shell?
32
+ !!@in_shell
33
+ end
34
+
35
+ # Returns true if in commandline with verbose flag or if set explicitly. Useful in plugins.
36
+ def verbose?
37
+ @verbose.nil? ? Boson.const_defined?(:BinRunner) && BinRunner.options[:verbose] : @verbose
38
+ end
39
+
40
+ #:stopdoc:
41
+ def verbose=(val)
42
+ @verbose = val
43
+ end
44
+
45
+ def in_shell=(val)
46
+ @in_shell = val
47
+ end
48
+
49
+ def add_load_path
50
+ Boson.repos.each {|repo|
51
+ if repo.config[:add_load_path] || File.exists?(File.join(repo.dir, 'lib'))
52
+ $: << File.join(repo.dir, 'lib') unless $:.include? File.expand_path(File.join(repo.dir, 'lib'))
53
+ end
54
+ }
55
+ end
56
+
57
+ def load_options
58
+ {:verbose=>@options[:verbose]}
59
+ end
60
+
61
+ def autoload_command(cmd, opts={:verbose=>verbose?})
62
+ Index.read
63
+ (lib = Index.find_library(cmd)) && Manager.load(lib, opts)
64
+ lib
65
+ end
66
+
67
+ def define_autoloader
68
+ class << ::Boson.main_object
69
+ def method_missing(method, *args, &block)
70
+ if Runner.autoload_command(method.to_s)
71
+ send(method, *args, &block) if respond_to?(method)
72
+ else
73
+ super
74
+ end
75
+ end
76
+ end
77
+ end
78
+ #:startdoc:
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,208 @@
1
+ module Boson
2
+ # This class handles the boson executable (boson command execution from the commandline). Any changes
3
+ # to your commands are immediately available from the commandline except for changes to the main config file.
4
+ # For those changes to take effect you need to explicitly load and index the libraries with --index.
5
+ # See RepoIndex to understand how Boson can immediately detect the latest commands.
6
+ #
7
+ # Usage for the boson shell command looks like this:
8
+ # boson [GLOBAL OPTIONS] [COMMAND] [ARGS] [COMMAND OPTIONS]
9
+ #
10
+ # The boson executable comes with these global options:
11
+ # [:help] Gives a basic help of global options. When a command is given the help shifts to a command's help.
12
+ # [:verbose] Using this along with :help option shows more help. Also gives verbosity to other actions i.e. loading.
13
+ # [:execute] Like ruby -e, this executes a string of ruby code. However, this has the advantage that all
14
+ # commands are available as normal methods, automatically loading as needed. This is a good
15
+ # way to call commands that take non-string arguments.
16
+ # [:console] This drops Boson into irb after having loaded default commands and any explict libraries with
17
+ # :load option. This is a good way to start irb with only certain libraries loaded.
18
+ # [:load] Explicitly loads a list of libraries separated by commas. Most useful when used with :console option.
19
+ # Can also be used to explicitly load libraries that aren't being detected automatically.
20
+ # [:index] Updates index for given libraries allowing you to use them. This is useful if Boson's autodetection of
21
+ # changed libraries isn't picking up your changes. Since this option has a :bool_default attribute, arguments
22
+ # passed to this option need to be passed with '=' i.e. '--index=my_lib'.
23
+ # [:render] Toggles the auto-rendering done for commands that don't have views. Doesn't affect commands that already have views.
24
+ # Default is false. Also see Auto Rendering section below.
25
+ # [:pager_toggle] Toggles Hirb's pager in case you'd like to pipe to another command.
26
+ # [:backtrace] Prints full backtrace on error. Default is false.
27
+ #
28
+ # ==== Auto Rendering
29
+ # Commands that don't have views (defined via render_options) have their return value auto-rendered as a view as follows:
30
+ # * nil,false and true aren't rendered
31
+ # * arrays are rendered with Hirb's tables
32
+ # * non-arrays are printed with inspect()
33
+ # * Any of these cases can be toggled to render/not render with the global option :render
34
+ # To turn off auto-rendering by default, add a :no_auto_render: true entry to the main config.
35
+ class BinRunner < Runner
36
+ GLOBAL_OPTIONS = {
37
+ :verbose=>{:type=>:boolean, :desc=>"Verbose description of loading libraries, errors or help"},
38
+ :version=>{:type=>:boolean, :desc=>"Prints the current version"},
39
+ :index=>{:type=>:array, :desc=>"Libraries to index. Libraries must be passed with '='.",
40
+ :bool_default=>nil, :values=>all_libraries, :regexp=>true, :enum=>false},
41
+ :execute=>{:type=>:string, :desc=>"Executes given arguments as a one line script"},
42
+ :console=>{:type=>:boolean, :desc=>"Drops into irb with default and explicit libraries loaded"},
43
+ :help=>{:type=>:boolean, :desc=>"Displays this help message or a command's help if given a command"},
44
+ :load=>{:type=>:array, :values=>all_libraries, :regexp=>true, :enum=>false,
45
+ :desc=>"A comma delimited array of libraries to load"},
46
+ :unload=>{:type=>:string, :desc=>"Acts as a regular expression to unload default libraries"},
47
+ :render=>{:type=>:boolean, :desc=>"Renders a Hirb view from result of command without options"},
48
+ :pager_toggle=>{:type=>:boolean, :desc=>"Toggles Hirb's pager"},
49
+ :option_commands=>{:type=>:boolean, :desc=>"Toggles on all commands to be defined as option commands" },
50
+ :ruby_debug=>{:type=>:boolean, :desc=>"Sets $DEBUG", :alias=>'D'},
51
+ :debug=>{:type=>:boolean, :desc=>"Prints debug info for boson"},
52
+ :load_path=>{:type=>:string, :desc=>"Add to front of $LOAD_PATH", :alias=>'I'},
53
+ :backtrace=>{:type=>:boolean, :desc=>'Prints full backtrace'}
54
+ } #:nodoc:
55
+
56
+ PIPE = '+'
57
+
58
+ class <<self
59
+ attr_accessor :command
60
+
61
+ # Starts, processes and ends a commandline request.
62
+ def start(args=ARGV)
63
+ @command, @options, @args = parse_args(args)
64
+ return puts("boson #{Boson::VERSION}") if @options[:version]
65
+ return print_usage if args.empty? || (@command.nil? && !@options[:console] && !@options[:execute])
66
+ $:.unshift(*options[:load_path].split(":")) if options[:load_path]
67
+ Runner.debug = true if @options[:debug]
68
+ return ConsoleRunner.bin_start(@options[:console], @options[:load]) if @options[:console]
69
+ $DEBUG = true if options[:ruby_debug]
70
+ init
71
+
72
+ if @options[:help]
73
+ autoload_command @command
74
+ Boson.invoke(:usage, @command, :verbose=>@options[:verbose])
75
+ elsif @options[:execute]
76
+ define_autoloader
77
+ Boson.main_object.instance_eval @options[:execute]
78
+ else
79
+ execute_command
80
+ end
81
+ rescue NoMethodError
82
+ abort_with no_method_error_message
83
+ rescue
84
+ abort_with default_error_message
85
+ end
86
+
87
+ def no_method_error_message #:nodoc:
88
+ @command = @command.to_s
89
+ if $!.backtrace.grep(/`(invoke|full_invoke)'$/).empty? ||
90
+ !$!.message[/undefined method `(\w+\.)?#{@command.split(NAMESPACE)[-1]}'/]
91
+ default_error_message
92
+ else
93
+ @command.to_s[/\w+/] &&
94
+ (!(Index.read && Index.find_command(@command[/\w+/])) || @command.include?(NAMESPACE)) ?
95
+ "Error: Command '#{@command}' not found" : default_error_message
96
+ end
97
+ end
98
+
99
+ # Loads libraries and handles non-critical options
100
+ def init
101
+ Runner.in_shell = true
102
+ Command.all_option_commands = true if @options[:option_commands]
103
+ super
104
+
105
+ if @options.key?(:index)
106
+ Index.update(:verbose=>true, :libraries=>@options[:index])
107
+ @index_updated = true
108
+ elsif !@options[:help] && @command && Boson.can_invoke?(@command)
109
+ Index.update(:verbose=>@options[:verbose])
110
+ @index_updated = true
111
+ end
112
+ Manager.load @options[:load], load_options if @options[:load]
113
+ View.toggle_pager if @options[:pager_toggle]
114
+ end
115
+
116
+ # Hash of global options passed in from commandline
117
+ def options
118
+ @options ||= {}
119
+ end
120
+
121
+ # Commands to executed, in order given by user
122
+ def commands
123
+ @commands ||= @all_args.map {|e| e[0]}
124
+ end
125
+
126
+ #:stopdoc:
127
+ def abort_with(message)
128
+ message += "\nOriginal error: #{$!}\n #{$!.backtrace.join("\n ")}" if options[:verbose] || options[:backtrace]
129
+ abort message
130
+ end
131
+
132
+ def default_error_message
133
+ "Error: #{$!.message}"
134
+ end
135
+
136
+ def autoload_command(cmd)
137
+ if !Boson.can_invoke?(cmd, false)
138
+ unless @index_updated
139
+ Index.update(:verbose=>@options[:verbose])
140
+ @index_updated = true
141
+ end
142
+ super(cmd, load_options)
143
+ end
144
+ end
145
+
146
+ def default_libraries
147
+ libs = super + Boson.repos.map {|e| e.config[:bin_defaults] || [] }.flatten + Dir.glob('Bosonfile')
148
+ @options[:unload] ? libs.select {|e| e !~ /#{@options[:unload]}/} : libs
149
+ end
150
+
151
+ def execute_command
152
+ output = @all_args.inject(nil) {|acc, (cmd,*args)|
153
+ begin
154
+ @command = cmd # for external errors
155
+ autoload_command cmd
156
+ args = translate_args(args, acc)
157
+ Boson.full_invoke(cmd, args)
158
+ rescue ArgumentError
159
+ if $!.class == OptionCommand::CommandArgumentError || ($!.message[/wrong number of arguments/] &&
160
+ (cmd_obj = Command.find(cmd)) && cmd_obj.arg_size != args.size)
161
+ abort_with "'#{cmd}' was called incorrectly.\n" + Command.usage(cmd)
162
+ else
163
+ raise
164
+ end
165
+ end
166
+ }
167
+ render_output output
168
+ end
169
+
170
+ def translate_args(args, piped)
171
+ args.unshift piped if piped
172
+ args
173
+ end
174
+
175
+ def parse_args(args)
176
+ @all_args = Util.split_array_by(args, PIPE)
177
+ args = @all_args[0]
178
+ @option_parser = OptionParser.new(GLOBAL_OPTIONS)
179
+ options = @option_parser.parse(args.dup, :opts_before_args=>true)
180
+ new_args = @option_parser.non_opts
181
+ @all_args[0] = new_args
182
+ [new_args[0], options, new_args[1..-1]]
183
+ end
184
+
185
+ def render_output(output)
186
+ if (!Scientist.rendered && !View.silent_object?(output)) ^ @options[:render] ^
187
+ Boson.repo.config[:no_auto_render]
188
+ opts = output.is_a?(String) ? {:method=>'puts'} :
189
+ {:inspect=>!output.is_a?(Array) || (Scientist.global_options || {})[:render] }
190
+ View.render output, opts
191
+ end
192
+ end
193
+
194
+ def print_usage
195
+ puts "boson [GLOBAL OPTIONS] [COMMAND] [ARGS] [COMMAND OPTIONS]\n\n"
196
+ puts "GLOBAL OPTIONS"
197
+ View.enable
198
+ @option_parser.print_usage_table
199
+ if @options[:verbose]
200
+ Manager.load [Boson::Commands::Core]
201
+ puts "\n\nDEFAULT COMMANDS"
202
+ Boson.invoke :commands, :fields=>["name", "usage", "description"], :description=>false
203
+ end
204
+ end
205
+ #:startdoc:
206
+ end
207
+ end
208
+ end
@@ -0,0 +1,58 @@
1
+ module Boson
2
+ # Runner used when starting irb. To use in irb, drop this in your ~/.irbrc:
3
+ # require 'boson'
4
+ # Boson.start
5
+ class ConsoleRunner < Runner
6
+ class <<self
7
+ # Starts Boson by loading configured libraries. If no default libraries are specified in the config,
8
+ # it will load up all detected libraries. Options:
9
+ # [:libraries] Array of libraries to load.
10
+ # [:verbose] Boolean to be verbose about libraries loading. Default is true.
11
+ # [:no_defaults] Boolean or :all which turns off loading default libraries. If set to true,
12
+ # effects loading user's console default libraries. If set to :all, effects
13
+ # all libraries including boson's. Default is false.
14
+ # [:autoload_libraries] Boolean which makes any command execution easier. It redefines
15
+ # method_missing on Boson.main_object so that commands with unloaded
16
+ # libraries are automatically loaded. Default is false.
17
+ def start(options={})
18
+ @options = {:verbose=>true}.merge options
19
+ init unless @initialized
20
+ Manager.load(@options[:libraries], load_options) if @options[:libraries]
21
+ end
22
+
23
+ # Loads libraries and then starts irb (or the configured console) from the commandline.
24
+ def bin_start(repl, libraries)
25
+ start :no_defaults=>true, :libraries=>libraries
26
+ repl = Boson.repo.config[:console] if Boson.repo.config[:console]
27
+ repl = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb' unless repl.is_a?(String)
28
+ unless repl.index('/') == 0 || (repl = Util.which(repl))
29
+ abort "Console not found. Please specify full path in config[:console]."
30
+ else
31
+ load_repl(repl)
32
+ end
33
+ end
34
+
35
+ def load_repl(repl) #:nodoc:
36
+ ARGV.replace ['-f']
37
+ $progname = $0
38
+ alias $0 $progname
39
+ Kernel.load $0 = repl
40
+ end
41
+
42
+ def init #:nodoc:
43
+ super
44
+ define_autoloader if @options[:autoload_libraries]
45
+ @initialized = true
46
+ end
47
+
48
+ def default_libraries #:nodoc:
49
+ return [] if @options[:no_defaults] == :all
50
+ return super if @options[:no_defaults]
51
+ defaults = super + Boson.repos.map {|e| e.config[:console_defaults] }.flatten
52
+ defaults += detected_libraries if defaults.empty?
53
+ defaults.uniq
54
+ end
55
+ end
56
+ end
57
+ end
58
+