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,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
+