boson-more 0.1.0

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 (67) hide show
  1. data/.gemspec +22 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.md +97 -0
  4. data/Rakefile +35 -0
  5. data/deps.rip +1 -0
  6. data/lib/boson/alias.rb +75 -0
  7. data/lib/boson/argument_inspector.rb +90 -0
  8. data/lib/boson/commands/core.rb +67 -0
  9. data/lib/boson/commands/view_core.rb +19 -0
  10. data/lib/boson/commands/web_core.rb +153 -0
  11. data/lib/boson/comment_inspector.rb +100 -0
  12. data/lib/boson/console.rb +40 -0
  13. data/lib/boson/console_runner.rb +60 -0
  14. data/lib/boson/index.rb +48 -0
  15. data/lib/boson/libraries/file_library.rb +144 -0
  16. data/lib/boson/libraries/gem_library.rb +30 -0
  17. data/lib/boson/libraries/local_file_library.rb +30 -0
  18. data/lib/boson/libraries/module_library.rb +37 -0
  19. data/lib/boson/libraries/require_library.rb +23 -0
  20. data/lib/boson/libraries.rb +183 -0
  21. data/lib/boson/more/version.rb +5 -0
  22. data/lib/boson/more.rb +18 -0
  23. data/lib/boson/more_commands.rb +14 -0
  24. data/lib/boson/more_inspector.rb +42 -0
  25. data/lib/boson/more_manager.rb +34 -0
  26. data/lib/boson/more_method_inspector.rb +74 -0
  27. data/lib/boson/more_option_parser.rb +28 -0
  28. data/lib/boson/more_scientist.rb +68 -0
  29. data/lib/boson/more_util.rb +30 -0
  30. data/lib/boson/namespace.rb +31 -0
  31. data/lib/boson/namespacer.rb +117 -0
  32. data/lib/boson/pipe.rb +156 -0
  33. data/lib/boson/pipe_runner.rb +44 -0
  34. data/lib/boson/pipes.rb +75 -0
  35. data/lib/boson/repo.rb +96 -0
  36. data/lib/boson/repo_index.rb +135 -0
  37. data/lib/boson/runner_options.rb +88 -0
  38. data/lib/boson/save.rb +198 -0
  39. data/lib/boson/science.rb +273 -0
  40. data/lib/boson/view.rb +98 -0
  41. data/lib/boson/viewable.rb +48 -0
  42. data/test/alias_test.rb +55 -0
  43. data/test/argument_inspector_test.rb +40 -0
  44. data/test/command_test.rb +22 -0
  45. data/test/commands_test.rb +53 -0
  46. data/test/comment_inspector_test.rb +126 -0
  47. data/test/console_runner_test.rb +58 -0
  48. data/test/deps.rip +4 -0
  49. data/test/file_library_test.rb +41 -0
  50. data/test/gem_library_test.rb +40 -0
  51. data/test/libraries_test.rb +55 -0
  52. data/test/loader_test.rb +38 -0
  53. data/test/module_library_test.rb +30 -0
  54. data/test/more_manager_test.rb +29 -0
  55. data/test/more_method_inspector_test.rb +42 -0
  56. data/test/more_scientist_test.rb +10 -0
  57. data/test/namespacer_test.rb +61 -0
  58. data/test/pipes_test.rb +65 -0
  59. data/test/repo_index_test.rb +123 -0
  60. data/test/repo_test.rb +23 -0
  61. data/test/runner_options_test.rb +29 -0
  62. data/test/save_test.rb +86 -0
  63. data/test/science_test.rb +58 -0
  64. data/test/scientist_test.rb +195 -0
  65. data/test/test_helper.rb +165 -0
  66. data/test/web_test.rb +22 -0
  67. metadata +169 -0
data/lib/boson/pipe.rb ADDED
@@ -0,0 +1,156 @@
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
+ # == Repo Config
36
+ # This class adds the following key to the main repo config:
37
+ #
38
+ # [:pipe_options] Hash of options available to all option commands for piping (see Pipe). A pipe option has the
39
+ # {normal option attributes}[link:classes/Boson/OptionParser.html#M000081] and these:
40
+ # * :pipe: Specifies the command to call when piping. Defaults to the pipe's option name.
41
+ # * :filter: Boolean which indicates that the pipe command will modify its input with what it returns.
42
+ # Default is false.
43
+ #
44
+ # === User Pipes Example
45
+ # Let's say you want to have two commands, browser and copy, you want to make available as pipe options:
46
+ # # Opens url in browser. This command already ships with Boson.
47
+ # def browser(url)
48
+ # system('open', url)
49
+ # end
50
+ #
51
+ # # Copy to clipboard
52
+ # def copy(str)
53
+ # IO.popen('pbcopy', 'w+') {|clipboard| clipboard.write(str)}
54
+ # end
55
+ #
56
+ # To configure them, drop the following config in ~/.boson/config/boson.yml:
57
+ # :pipe_options:
58
+ # :browser:
59
+ # :type: :boolean
60
+ # :desc: Open in browser
61
+ # :copy:
62
+ # :type: :boolean
63
+ # :desc: Copy to clipboard
64
+ #
65
+ # Now for any command that returns a url string, these pipe options can be turned on to execute the url.
66
+ #
67
+ # Some examples of these options using commands from {my libraries}[http://github.com/cldwalker/irbfiles]:
68
+ # # Creates a gist and then opens url in browser and copies it.
69
+ # $ cat some_file | boson gist -bC # or cat some_file | boson gist --browser --copy
70
+ #
71
+ # # Generates rdoc in current directory and then opens it in browser
72
+ # irb>> rdoc '-b' # or rdoc '--browser'
73
+ module Pipe
74
+ extend self
75
+
76
+ # Process pipes for Scientist
77
+ def scientist_process(object, global_opt, env={})
78
+ @env = env
79
+ [:query, :sort, :reverse_sort].each {|e| global_opt.delete(e) } unless object.is_a?(Array)
80
+ process_pipes(object, global_opt)
81
+ end
82
+
83
+ # Main method which processes all pipe commands, both default and user-defined ones.
84
+ def process_pipes(obj, options)
85
+ internal_pipes(options).each {|pipe|
86
+ obj = Pipes.send("#{pipe}_pipe", obj, options[pipe]) if options[pipe]
87
+ }
88
+ process_user_pipes(obj, options)
89
+ end
90
+
91
+ # A hash that defines user pipes in the same way as the :pipe_options key in Repo.config.
92
+ # This method should be called when a pipe's library is loading.
93
+ def add_pipes(hash)
94
+ pipe_options.merge! setup_pipes(hash)
95
+ end
96
+
97
+ #:stopdoc:
98
+ def internal_pipes(global_opt)
99
+ internals = [:query, :sort, :reverse_sort, :pipes]
100
+ internals.delete(:pipes) if pipes_to_process(global_opt).any? {|e| pipe(e)[:solo] }
101
+ internals
102
+ end
103
+
104
+ def pipe_options
105
+ @pipe_options ||= setup_pipes(Boson.repo.config[:pipe_options] || {})
106
+ end
107
+
108
+ def setup_pipes(hash)
109
+ hash.each {|k,v| v[:pipe] ||= k }
110
+ end
111
+
112
+ def pipe(key)
113
+ pipe_options[key] || {}
114
+ end
115
+
116
+ # global_opt can come from Hirb callback or Scientist
117
+ def process_user_pipes(result, global_opt)
118
+ pipes_to_process(global_opt).each {|e|
119
+ args = [pipe(e)[:pipe], result]
120
+ args << global_opt[e] unless pipe(e)[:type] == :boolean
121
+ args << get_env(e, global_opt) if pipe(e)[:env]
122
+ pipe_result = Boson.invoke(*args)
123
+ result = pipe_result if pipe(e)[:filter]
124
+ }
125
+ result
126
+ end
127
+
128
+ def get_env(key, global_opt)
129
+ { :global_options=>global_opt.merge(:delete_callbacks=>[:z_boson_pipes]),
130
+ :config=>(@env[:config].dup[key] || {}),
131
+ :args=>@env[:args],
132
+ :options=>@env[:options] || {}
133
+ }
134
+ end
135
+
136
+ def any_no_render_pipes?(global_opt)
137
+ !(pipes = pipes_to_process(global_opt)).empty? &&
138
+ pipes.any? {|e| pipe(e)[:no_render] }
139
+ end
140
+
141
+ def pipes_to_process(global_opt)
142
+ pipes = (global_opt.keys & pipe_options.keys)
143
+ (solo_pipe = pipes.find {|e| pipe(e)[:solo] }) ? [solo_pipe] : pipes
144
+ end
145
+ #:startdoc:
146
+
147
+ # Callbacks used by Hirb::Helpers::Table to search,sort and run custom pipe commands on arrays of hashes.
148
+ module TableCallbacks
149
+ # Processes boson's pipes
150
+ def z_boson_pipes_callback(obj, options)
151
+ Pipe.process_pipes(obj, options)
152
+ end
153
+ end
154
+ end
155
+ end
156
+ Hirb::Helpers::Table.send :include, Boson::Pipe::TableCallbacks
@@ -0,0 +1,44 @@
1
+ module Boson
2
+ module PipeRunner
3
+ PIPE = '+'
4
+
5
+ # Splits array into array of arrays with given element
6
+ def self.split_array_by(arr, divider)
7
+ arr.inject([[]]) {|results, element|
8
+ (divider == element) ? (results << []) : (results.last << element)
9
+ results
10
+ }
11
+ end
12
+
13
+ def parse_args(args)
14
+ @all_args = PipeRunner.split_array_by(args, PIPE)
15
+ args = @all_args[0]
16
+ super(args).tap do |result|
17
+ @all_args[0] = ([result[0]] + Array(result[2])).compact
18
+ end
19
+ end
20
+
21
+ def execute_command(command, args)
22
+ @all_args.inject(nil) do |acc, (cmd,*args)|
23
+ args = translate_args(args, acc)
24
+ super(cmd, args)
25
+ end
26
+ end
27
+
28
+ def translate_args(args, piped)
29
+ args.unshift piped if piped
30
+ args
31
+ end
32
+
33
+ # Commands to executed, in order given by user
34
+ def commands
35
+ @commands ||= @all_args.map {|e| e[0]}
36
+ end
37
+ end
38
+
39
+ if defined? BinRunner
40
+ class BinRunner < BareRunner
41
+ class << self; include PipeRunner; end
42
+ end
43
+ end
44
+ end
@@ -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
data/lib/boson/repo.rb ADDED
@@ -0,0 +1,96 @@
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
+ # [:bin_defaults] Array of libraries to load at start up when used from the commandline. Default is no libraries.
51
+ # [:add_load_path] Boolean specifying whether to add a load path pointing to the lib subdirectory/. This is useful in sharing
52
+ # classes between libraries without resorting to packaging them as gems. Defaults to false if the lib
53
+ # subdirectory doesn't exist in the boson directory.
54
+ #
55
+ # ==== Config keys specific to the main repo config ~/.boson/config/boson.yml
56
+ # [:error_method_conflicts] Boolean specifying library loading behavior when its methods conflicts with existing methods in
57
+ # the global namespace. When set to false, Boson automatically puts the library in its own namespace.
58
+ # When set to true, the library fails to load explicitly. Default is false.
59
+ # [:auto_namespace] Boolean which automatically namespaces all user-defined libraries. Be aware this can break libraries which
60
+ # depend on commands from other libraries. Default is false.
61
+ # [:ignore_directories] Array of directories to ignore when detecting local repositories for Boson.local_repo.
62
+ # [:option_underscore_search] When set, OptionParser option values (with :values or :keys) are auto aliased with underscore searching.
63
+ # Default is true. See Util.underscore_search.
64
+ def config(reload=false)
65
+ if reload || @config.nil?
66
+ begin
67
+ @config = Boson::CONFIG.dup
68
+ @config.merge!(YAML::load_file(config_file(true))) if File.exists?(config_file)
69
+ rescue ArgumentError
70
+ message = $!.message !~ /syntax error on line (\d+)/ ? "Error"+$!.message :
71
+ "Error: Syntax error in line #{$1} of config file '#{config_file}'"
72
+ Kernel.abort message
73
+ end
74
+ end
75
+ @config
76
+ end
77
+
78
+ # Updates main config file by passing config into a block to be modified and then saved
79
+ def update_config
80
+ yield(config)
81
+ write_config_file
82
+ end
83
+
84
+ def write_config_file #:nodoc:
85
+ File.open(config_file, 'w') {|f| f.write config.to_yaml }
86
+ end
87
+
88
+ def detected_libraries #:nodoc:
89
+ Dir[File.join(commands_dir, '**/*.rb')].map {|e| e.gsub(/^#{commands_dir}\/|\.rb$/, '') }
90
+ end
91
+
92
+ def all_libraries #:nodoc:
93
+ (detected_libraries + config[:libraries].keys).uniq
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,135 @@
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.instance.failed_libraries = []
25
+ unless libraries_to_update.empty?
26
+ Manager.load(libraries_to_update, options.merge(:index=>true))
27
+ unless Manager.instance.failed_libraries.empty?
28
+ $stderr.puts("Error: These libraries failed to load while indexing: #{Manager.instance.failed_libraries.join(', ')}")
29
+ end
30
+ end
31
+ write(Manager.instance.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') do |f|
39
+ f.flock(File::LOCK_EX)
40
+ Marshal.load(f)
41
+ end : [[], [], {}]
42
+ delete_stale_libraries_and_commands
43
+ set_command_namespaces
44
+ @read = true
45
+ end
46
+
47
+ # Writes/saves current index to config/index.marshal.
48
+ def write(failed_libraries=[])
49
+ latest = latest_hashes
50
+ failed_libraries.each {|e| latest.delete(e) }
51
+ save_marshal_index Marshal.dump([Boson.libraries, Boson.commands, latest])
52
+ end
53
+
54
+ #:stopdoc:
55
+ def read_and_transfer(ignored_libraries=[])
56
+ read
57
+ existing_libraries = (Boson.libraries.map {|e| e.name} + ignored_libraries).uniq
58
+ libraries_to_add = @libraries.select {|e| !existing_libraries.include?(e.name)}
59
+ Boson.libraries += libraries_to_add
60
+ # depends on saved commands being correctly associated with saved libraries
61
+ Boson.commands += libraries_to_add.map {|e| e.command_objects(e.commands, @commands) }.flatten
62
+ end
63
+
64
+ def exists?
65
+ File.exists? marshal_file
66
+ end
67
+
68
+ def save_marshal_index(marshal_string)
69
+ binmode = defined?(File::BINARY) ? File::BINARY : 0
70
+ rdwr_access = File::RDWR | File::CREAT | binmode
71
+ # To protect the file truncing with a lock we cannot use the 'wb' options.
72
+ # The w option truncates the file before calling the File.open block
73
+ File.open(marshal_file, rdwr_access) do |f|
74
+ f.flock(File::LOCK_EX)
75
+ f.truncate 0
76
+ f.write(marshal_string)
77
+ end
78
+ end
79
+
80
+ def delete_stale_libraries_and_commands
81
+ cached_libraries = @lib_hashes.keys
82
+ libs_to_delete = @libraries.select {|e| !cached_libraries.include?(e.name) && e.is_a?(FileLibrary) }
83
+ names_to_delete = libs_to_delete.map {|e| e.name }
84
+ libs_to_delete.each {|e| @libraries.delete(e) }
85
+ @commands.delete_if {|e| names_to_delete.include? e.lib }
86
+ end
87
+
88
+ # set namespaces for commands
89
+ def set_command_namespaces
90
+ lib_commands = @commands.inject({}) {|t,e| (t[e.lib] ||= []) << e; t }
91
+ namespace_libs = @libraries.select {|e| e.namespace(e.indexed_namespace) }
92
+ namespace_libs.each {|lib|
93
+ (lib_commands[lib.name] || []).each {|e| e.namespace = lib.namespace }
94
+ }
95
+ end
96
+
97
+ def namespaces
98
+ nsps = @libraries.map {|e| e.namespace }.compact
99
+ nsps.delete(false)
100
+ nsps
101
+ end
102
+
103
+ def all_main_methods
104
+ @commands.reject {|e| e.namespace }.map {|e| [e.name, e.alias]}.flatten.compact + namespaces
105
+ end
106
+
107
+ def marshal_file
108
+ File.join(repo.config_dir, 'index.marshal')
109
+ end
110
+
111
+ def find_library(command, object=false)
112
+ read
113
+ namespace_command = command.split(NAMESPACE)[0]
114
+ if (lib = @libraries.find {|e| e.namespace == namespace_command })
115
+ object ? lib : lib.name
116
+ elsif (cmd = Command.find(command, @commands))
117
+ object ? @libraries.find {|e| e.name == cmd.lib} : cmd.lib
118
+ end
119
+ end
120
+
121
+ def changed_libraries
122
+ read
123
+ latest_hashes.select {|lib, hash| @lib_hashes[lib] != hash}.map {|e| e[0]}
124
+ end
125
+
126
+ def latest_hashes
127
+ repo.all_libraries.inject({}) {|h, e|
128
+ lib_file = FileLibrary.library_file(e, repo.dir)
129
+ h[e] = Digest::MD5.hexdigest(File.read(lib_file)) if File.exists?(lib_file)
130
+ h
131
+ }
132
+ end
133
+ #:startdoc:
134
+ end
135
+ end
@@ -0,0 +1,88 @@
1
+ require 'boson/save'
2
+
3
+ module Boson
4
+ module RunnerOptions
5
+ # [:help] Gives a basic help of global options. When a command is given the help shifts to a command's help.
6
+ # [:verbose] Using this along with :help option shows more help. Also gives verbosity to other actions i.e. loading.
7
+ # [:backtrace] Prints full backtrace on error. Default is false.
8
+ # [:index] Updates index for given libraries allowing you to use them. This is useful if Boson's autodetection of
9
+ # changed libraries isn't picking up your changes. Since this option has a :bool_default attribute, arguments
10
+ # passed to this option need to be passed with '=' i.e. '--index=my_lib'.
11
+ # [:load] Explicitly loads a list of libraries separated by commas. Most useful when used with :console option.
12
+ # Can also be used to explicitly load libraries that aren't being detected automatically.
13
+ # [:pager_toggle] Toggles Hirb's pager in case you'd like to pipe to another command.
14
+ def init
15
+ super
16
+ Boson.verbose = true if options[:verbose]
17
+
18
+ if @options.key?(:index)
19
+ Index.update(:verbose=>true, :libraries=>@options[:index])
20
+ @index_updated = true
21
+ elsif !@options[:help] && @command && Boson.can_invoke?(@command)
22
+ Index.update(:verbose=>@options[:verbose])
23
+ @index_updated = true
24
+ end
25
+
26
+ Manager.load @options[:load], load_options if @options[:load]
27
+ View.toggle_pager if @options[:pager_toggle]
28
+ end
29
+
30
+ def default_libraries
31
+ libs = super
32
+ @options[:unload] ? libs.select {|e| e !~ /#{@options[:unload]}/} : libs
33
+ end
34
+
35
+ def update_index
36
+ unless @index_updated
37
+ super
38
+ @index_updated = true
39
+ end
40
+ end
41
+
42
+ def verbose
43
+ @options[:verbose]
44
+ end
45
+
46
+ def abort_with(message)
47
+ if verbose || options[:backtrace]
48
+ message += "\nOriginal error: #{$!}\n #{$!.backtrace.join("\n ")}"
49
+ end
50
+ super(message)
51
+ end
52
+
53
+ def print_usage
54
+ super
55
+ if @options[:verbose]
56
+ Manager.load [Boson::Commands::Core]
57
+ puts "\n\nDEFAULT COMMANDS"
58
+ Boson.invoke :commands, :fields=>["name", "usage", "description"], :description=>false
59
+ end
60
+ end
61
+
62
+ def execute_option_or_command(options, command, args)
63
+ if options[:help]
64
+ autoload_command command
65
+ Boson.invoke(:usage, command, verbose: verbose)
66
+ else
67
+ super
68
+ end
69
+ end
70
+ end
71
+
72
+ if defined? BinRunner
73
+ class BinRunner < BareRunner
74
+ GLOBAL_OPTIONS.update({
75
+ :backtrace=>{:type=>:boolean, :desc=>'Prints full backtrace'},
76
+ :verbose=>{:type=>:boolean, :desc=>"Verbose description of loading libraries, errors or help"},
77
+ :index=>{
78
+ :type=>:array, :desc=>"Libraries to index. Libraries must be passed with '='.",
79
+ :bool_default=>nil, :values=>all_libraries, :regexp=>true, :enum=>false},
80
+ :pager_toggle=>{:type=>:boolean, :desc=>"Toggles Hirb's pager"},
81
+ :unload=>{:type=>:string, :desc=>"Acts as a regular expression to unload default libraries"},
82
+ :load=>{:type=>:array, :values=>all_libraries, :regexp=>true, :enum=>false,
83
+ :desc=>"A comma delimited array of libraries to load"}
84
+ })
85
+ extend RunnerOptions
86
+ end
87
+ end
88
+ end