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,179 @@
1
+ module Boson
2
+ # A library is a group of commands (Command objects) usually grouped together by a module.
3
+ # Libraries are loaded from different sources depending on the library subclass. Default library
4
+ # subclasses are FileLibrary, GemLibrary, RequireLibrary, ModuleLibrary and LocalFileLibrary.
5
+ # See Loader for callbacks a library's module can have.
6
+ #
7
+ # == Naming a Library Module
8
+ # Although you can name a library module almost anything, here's the fine print:
9
+ # * A module can have any name if it's the only module in a library.
10
+ # * If there are multiple modules in a file library, the module's name must be a camelized version
11
+ # of the file's basename i.e. ~/.boson/commands/ruby_core.rb -> RubyCore.
12
+ # * Although modules are evaluated under the Boson::Commands namespace, Boson will warn you about creating
13
+ # modules whose name is the same as a top level class/module. The warning is to encourage users to stay
14
+ # away from error-prone libraries. Once you introduce such a module, _all_ libraries assume the nested module
15
+ # over the top level module and the top level module has to be prefixed with '::' _everywhere_.
16
+ #
17
+ # == Configuration
18
+ # Libraries and their commands can be configured in different ways in this order:
19
+ # * If library is a FileLibrary, commands be configured with a config method attribute (see Inspector).
20
+ # * If a library has a module, you can set library + command attributes via the config() callback (see Loader).
21
+ # * All libraries can be configured by passing a hash of {library attributes}[link:classes/Boson/Library.html#M000077] under
22
+ # {the :libraries key}[link:classes/Boson/Repo.html#M000070] to the main config file ~/.boson/config/boson.yml.
23
+ # For most libraries this may be the only way to configure a library's commands.
24
+ # An example of a GemLibrary config:
25
+ # :libraries:
26
+ # httparty:
27
+ # :class_commands:
28
+ # delete: HTTParty.delete
29
+ # :commands:
30
+ # delete:
31
+ # :alias: d
32
+ # :desc: Http delete a given url
33
+ #
34
+ # When installing a third-party library, use the config file as a way to override default library and command attributes
35
+ # without modifying the library.
36
+ #
37
+ # === Creating Your Own Library
38
+ # To create your own subclass you need to define what sources the subclass can handle with handles().
39
+ # If handles() returns true then the subclass is chosen to load. See Loader to see what instance methods
40
+ # to override for a subclass.
41
+ class Library
42
+ include Loader
43
+ class <<self
44
+ #:stopdoc:
45
+ attr_accessor :handle_blocks
46
+ def handles(&block)
47
+ (Library.handle_blocks ||= []) << [self,block]
48
+ end
49
+ #:startdoc:
50
+ end
51
+
52
+ # Public attributes for use outside of Boson.
53
+ ATTRIBUTES = [:gems, :dependencies, :commands, :loaded, :module, :name, :namespace, :indexed_namespace]
54
+ attr_reader *(ATTRIBUTES + [:commands_hash, :library_file, :object_namespace])
55
+ # Private attribute for use within Boson.
56
+ attr_reader :no_alias_creation, :new_module, :new_commands, :class_commands, :lib_file, :repo_dir
57
+ # Optional namespace name for a library. When enabled defaults to a library's name.
58
+ attr_writer :namespace
59
+
60
+ # Creates a library object with a hash of attributes which must include a :name attribute.
61
+ # Each hash pair maps directly to an instance variable and value. Defaults for attributes
62
+ # are read from config[:libraries][@library_name][@attribute]. When loading libraries, attributes
63
+ # can also be set via a library module's config() method (see Loader).
64
+ #
65
+ # Attributes that can be configured:
66
+ # [*:dependencies*] An array of libraries that this library depends on. A library won't load
67
+ # unless its dependencies are loaded first.
68
+ # [*:commands*] A hash or array of commands that belong to this library. A hash configures command attributes
69
+ # for the given commands with command names pointing to their configs. See Command.new for a
70
+ # command's configurable attributes. If an array, the commands are set for the given library,
71
+ # overidding default command detection. Example:
72
+ # :commands=>{'commands'=>{:desc=>'Lists commands', :alias=>'com'}}
73
+ # [*:class_commands*] A hash of commands to create. A hash key-pair can map command names to any string of ruby code
74
+ # that ends with a method call. Or a key-pair can map a class to an array of its class methods
75
+ # to create commands of the same name. Example:
76
+ # :class_commands=>{'spy'=>'Bond.spy', 'create'=>'Alias.manager.create',
77
+ # 'Boson::Util'=>['detect', 'any_const_get']}
78
+ # [*:force*] Boolean which forces a library to ignore when a library's methods are overriding existing ones.
79
+ # Use with caution. Default is false.
80
+ # [*:object_methods*] Boolean which detects any Object/Kernel methods created when loading a library and automatically
81
+ # adds them to a library's commands. Default is true.
82
+ # [*:namespace*] Boolean or string which namespaces a library. When true, the library is automatically namespaced
83
+ # to the library's name. When a string, the library is namespaced to the string. Default is nil.
84
+ # To control the namespacing of all libraries see Boson::Repo.config.
85
+ # [*:no_alias_creation*] Boolean which doesn't create aliases for a library. Useful for libraries that configure command
86
+ # aliases outside of Boson's control. Default is false.
87
+ def initialize(hash)
88
+ repo = set_repo
89
+ @repo_dir = repo.dir
90
+ @name = set_name(hash.delete(:name)) or raise ArgumentError, "Library missing required key :name"
91
+ @loaded = false
92
+ @commands_hash = {}
93
+ @commands = []
94
+ set_config (repo.config[:libraries][@name] || {}).merge(hash), true
95
+ set_command_aliases(repo.config[:command_aliases])
96
+ end
97
+
98
+ # A concise symbol version of a library type i.e. FileLibrary -> :file.
99
+ def library_type
100
+ str = self.class.to_s[/::(\w+)Library$/, 1] || 'library'
101
+ str.downcase.to_sym
102
+ end
103
+
104
+ def namespace(orig=@namespace)
105
+ @namespace = [String,FalseClass].include?(orig.class) ? orig : begin
106
+ if (@namespace == true || (Boson.repo.config[:auto_namespace] && !@index))
107
+ @namespace = clean_name
108
+ else
109
+ @namespace = false
110
+ end
111
+ end
112
+ end
113
+
114
+ # The object a library uses for executing its commands.
115
+ def namespace_object
116
+ @namespace_object ||= namespace ? Boson.invoke(namespace) : Boson.main_object
117
+ end
118
+
119
+ #:stopdoc:
120
+ # handles names under directories
121
+ def clean_name
122
+ @name[/\w+$/]
123
+ end
124
+
125
+ def local?
126
+ is_a?(LocalFileLibrary) || (Boson.local_repo && Boson.local_repo.dir == @repo_dir)
127
+ end
128
+
129
+ def set_name(name)
130
+ name.to_s
131
+ end
132
+
133
+ def set_config(config, force=false)
134
+ if (commands = config.delete(:commands))
135
+ if commands.is_a?(Array)
136
+ @commands += commands
137
+ @pre_defined_commands = true
138
+ elsif commands.is_a?(Hash)
139
+ @commands += commands.keys
140
+ @commands_hash = Util.recursive_hash_merge commands, @commands_hash
141
+ end
142
+ end
143
+ set_command_aliases config.delete(:command_aliases) if config[:command_aliases]
144
+ set_attributes config, force
145
+ end
146
+
147
+ def set_command_aliases(command_aliases)
148
+ (command_aliases || {}).each do |cmd, cmd_alias|
149
+ @commands_hash[cmd] ||= {}
150
+ @commands_hash[cmd][:alias] ||= cmd_alias
151
+ end
152
+ end
153
+
154
+ def set_repo
155
+ Boson.repo
156
+ end
157
+
158
+ def set_attributes(hash, force=false)
159
+ hash.each {|k,v| instance_variable_set("@#{k}", v) if instance_variable_get("@#{k}").nil? || force }
160
+ end
161
+
162
+ def command_objects(names=self.commands, command_array=Boson.commands)
163
+ command_array.select {|e| names.include?(e.name) && e.lib == self.name }
164
+ end
165
+
166
+ def command_object(name)
167
+ command_objects([name])[0]
168
+ end
169
+
170
+ def marshal_dump
171
+ [@name, @commands, @gems, @module.to_s, @repo_dir, @indexed_namespace]
172
+ end
173
+
174
+ def marshal_load(ary)
175
+ @name, @commands, @gems, @module, @repo_dir, @indexed_namespace = ary
176
+ end
177
+ #:startdoc:
178
+ end
179
+ end
@@ -0,0 +1,118 @@
1
+ module Boson
2
+ # Raised if a library has a method which conflicts with existing methods in Boson.main_object.
3
+ class MethodConflictError < LoaderError; end
4
+
5
+ # This module is mixed into Library to give it load() functionality.
6
+ # When creating your own Library subclass, you should override load_source_and_set_module
7
+ # You can override other methods in this module as needed.
8
+ #
9
+ # === Module Callbacks
10
+ # For libraries that have a module i.e. FileLibrary and GemLibrary, the following class methods
11
+ # are invoked in the order below when loading a library:
12
+ #
13
+ # [*:config*] This method returns a library's hash of attributes as explained by Library.new. This is useful
14
+ # for distributing libraries with a default configuration. The library attributes specified here
15
+ # are overridden by ones a user has in their config file except for the :commands attribute, which
16
+ # is recursively merged together.
17
+ # [*:append_features*] In addition to its normal behavior, this method's return value determines if a
18
+ # library is loaded in the current environment. This is useful for libraries that you
19
+ # want loaded by default but not in some environments i.e. different ruby versions or
20
+ # in irb but not in script/console. Remember to use super when returning true.
21
+ # [*:included*] In addition to its normal behavior, this method should be used to require external libraries.
22
+ # Although requiring dependencies could be done anywhere in a module, putting dependencies here
23
+ # are encouraged. By not having dependencies hardcoded in a module, it's possible to analyze
24
+ # and view a library's commands without having to install and load its dependencies.
25
+ # If creating commands here, note that conflicts with existing commands won't be detected.
26
+ # [*:after_included*] This method is called after included() to initialize functionality. This is useful for
27
+ # libraries that are primarily executing ruby code i.e. defining ruby extensions or
28
+ # setting irb features. This method isn't called when indexing a library.
29
+ module Loader
30
+ # Loads a library and its dependencies and returns true if library loads correctly.
31
+ def load
32
+ @gems ||= []
33
+ load_source_and_set_module
34
+ module_callbacks if @module
35
+ yield if block_given?
36
+ (@module || @class_commands) ? detect_additions { load_module_commands } : @namespace = false
37
+ set_library_commands
38
+ @indexed_namespace = (@namespace == false) ? nil : @namespace if @index
39
+ loaded_correctly? && (@loaded = true)
40
+ end
41
+
42
+ # Load the source and set instance variables necessary to make a library valid i.e. @module.
43
+ def load_source_and_set_module; end
44
+
45
+ # Boolean which indicates if library loaded correctly.
46
+ def loaded_correctly?
47
+ !!@module
48
+ end
49
+
50
+ #:stopdoc:
51
+ def module_callbacks
52
+ set_config(@module.config) if @module.respond_to?(:config)
53
+ if @module.respond_to?(:append_features)
54
+ raise AppendFeaturesFalseError unless @module.append_features(Module.new)
55
+ end
56
+ end
57
+
58
+ def load_module_commands
59
+ initialize_library_module
60
+ rescue MethodConflictError=>e
61
+ if Boson.repo.config[:error_method_conflicts] || namespace
62
+ raise MethodConflictError, e.message
63
+ else
64
+ @namespace = clean_name
65
+ @method_conflict = true
66
+ $stderr.puts "#{e.message}. Attempting load into the namespace #{@namespace}..."
67
+ initialize_library_module
68
+ end
69
+ end
70
+
71
+ def detect_additions(options={}, &block)
72
+ options[:object_methods] = @object_methods if !@object_methods.nil?
73
+ detected = Util.detect(options, &block)
74
+ @gems += detected[:gems] if detected[:gems]
75
+ @commands += detected[:methods].map {|e| e.to_s }
76
+ detected
77
+ end
78
+
79
+ def initialize_library_module
80
+ @module = @module ? Util.constantize(@module) : Util.create_module(Boson::Commands, clean_name)
81
+ raise(LoaderError, "No module for library #{@name}") unless @module
82
+ if (conflict = Util.top_level_class_conflict(Boson::Commands, @module.to_s))
83
+ warn "Library module '#{@module}' may conflict with top level class/module '#{conflict}' references in"+
84
+ " your libraries. Rename your module to avoid this warning."
85
+ end
86
+
87
+ Manager.create_class_aliases(@module, @class_commands) unless @class_commands.nil? ||
88
+ @class_commands.empty? || @method_conflict
89
+ check_for_method_conflicts unless @force
90
+ @namespace = clean_name if @object_namespace
91
+ namespace ? Namespace.create(namespace, self) : include_in_universe
92
+ end
93
+
94
+ def include_in_universe(lib_module=@module)
95
+ Boson::Universe.send :include, lib_module
96
+ @module.after_included if lib_module.respond_to?(:after_included) && !@index
97
+ Boson::Universe.send :extend_object, Boson.main_object
98
+ end
99
+
100
+ def check_for_method_conflicts
101
+ conflicts = namespace ? (Boson.can_invoke?(namespace) ? [namespace] : []) :
102
+ (@module.instance_methods + @module.private_instance_methods) & (Boson.main_object.methods +
103
+ Boson.main_object.private_methods)
104
+ unless conflicts.empty?
105
+ raise MethodConflictError,"The following commands conflict with existing commands: #{conflicts.join(', ')}"
106
+ end
107
+ end
108
+
109
+ def set_library_commands
110
+ aliases = @commands_hash.select {|k,v| @commands.include?(k) }.map {|k,v| v[:alias]}.compact
111
+ @commands -= aliases
112
+ @commands.delete(namespace) if namespace
113
+ @commands += Boson.invoke(namespace).boson_commands if namespace && !@pre_defined_commands
114
+ @commands.uniq!
115
+ end
116
+ #:startdoc:
117
+ end
118
+ end
@@ -0,0 +1,169 @@
1
+ module Boson
2
+ # Base class for library loading errors. Raised mostly in Boson::Loader and rescued by Boson::Manager.
3
+ class LoaderError < StandardError; end
4
+ # Raised when a library's append_features returns false.
5
+ class AppendFeaturesFalseError < StandardError; end
6
+
7
+ # Handles loading of libraries and commands.
8
+ class Manager
9
+ class <<self
10
+ attr_accessor :failed_libraries
11
+
12
+ # Loads a library or an array of libraries with options. Manager loads the first library subclass
13
+ # to meet a library subclass' criteria in this order: ModuleLibrary, FileLibrary, GemLibrary, RequireLibrary.
14
+ # ==== Examples:
15
+ # Manager.load 'my_commands' -> Loads a FileLibrary object from ~/.boson/commands/my_commands.rb
16
+ # Manager.load 'method_lister' -> Loads a GemLibrary object which requires the method_lister gem
17
+ # Any options that aren't listed here are passed as library attributes to the libraries (see Library.new)
18
+ # ==== Options:
19
+ # [:verbose] Boolean to print each library's loaded status along with more verbose errors. Default is false.
20
+ def load(libraries, options={})
21
+ Array(libraries).map {|e|
22
+ (@library = load_once(e, options)) ? after_load : false
23
+ }.all?
24
+ end
25
+
26
+ #:stopdoc:
27
+ def failed_libraries
28
+ @failed_libraries ||= []
29
+ end
30
+
31
+ def add_library(lib)
32
+ Boson.libraries.delete(Boson.library(lib.name))
33
+ Boson.libraries << lib
34
+ end
35
+
36
+ def loaded?(lib_name)
37
+ ((lib = Boson.library(lib_name)) && lib.loaded) ? true : false
38
+ end
39
+
40
+ def rescue_load_action(library, load_method)
41
+ yield
42
+ rescue AppendFeaturesFalseError
43
+ warn "DEBUG: Library #{library} didn't load due to append_features" if Runner.debug
44
+ rescue LoaderError=>e
45
+ FileLibrary.reset_file_cache(library.to_s)
46
+ failed_libraries << library
47
+ $stderr.puts "Unable to #{load_method} library #{library}. Reason: #{e.message}"
48
+ rescue StandardError, SyntaxError, LoadError =>e
49
+ FileLibrary.reset_file_cache(library.to_s)
50
+ failed_libraries << library
51
+ message = "Unable to #{load_method} library #{library}. Reason: #{$!}"
52
+ if Runner.debug
53
+ message += "\n" + e.backtrace.map {|e| " " + e }.join("\n")
54
+ elsif @options[:verbose]
55
+ message += "\n" + e.backtrace.slice(0,3).map {|e| " " + e }.join("\n")
56
+ end
57
+ $stderr.puts message
58
+ ensure
59
+ Inspector.disable if Inspector.enabled
60
+ end
61
+
62
+ def load_once(source, options={})
63
+ @options = options
64
+ rescue_load_action(source, :load) do
65
+ lib = loader_create(source)
66
+ if loaded?(lib.name)
67
+ $stderr.puts "Library #{lib.name} already exists." if options[:verbose] && !options[:dependency]
68
+ false
69
+ else
70
+ if lib.load { load_dependencies(lib, options) }
71
+ lib
72
+ else
73
+ $stderr.puts "Library #{lib.name} did not load successfully." if !options[:dependency]
74
+ $stderr.puts " "+lib.inspect if Runner.debug
75
+ false
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def lib_dependencies
82
+ @lib_dependencies ||= {}
83
+ end
84
+
85
+ def load_dependencies(lib, options={})
86
+ lib_dependencies[lib] = Array(lib.dependencies).map do |e|
87
+ next if loaded?(e)
88
+ load_once(e, options.merge(:dependency=>true)) ||
89
+ raise(LoaderError, "Can't load dependency #{e}")
90
+ end.compact
91
+ end
92
+
93
+ def loader_create(source)
94
+ lib_class = Library.handle_blocks.find {|k,v| v.call(source) } or raise(LoaderError, "Library #{source} not found.")
95
+ lib_class[0].new(@options.merge(:name=>source))
96
+ end
97
+
98
+ def after_load
99
+ create_commands(@library)
100
+ add_library(@library)
101
+ puts "Loaded library #{@library.name}" if @options[:verbose]
102
+ (lib_dependencies[@library] || []).each do |e|
103
+ create_commands(e)
104
+ add_library(e)
105
+ puts "Loaded library dependency #{e.name}" if @options[:verbose]
106
+ end
107
+ true
108
+ end
109
+
110
+ def before_create_commands(lib)
111
+ lib.is_a?(FileLibrary) && lib.module && Inspector.add_method_data_to_library(lib)
112
+ end
113
+
114
+ def create_commands(lib, commands=lib.commands)
115
+ before_create_commands(lib)
116
+ commands.each {|e| Boson.commands << Command.create(e, lib)}
117
+ create_command_aliases(lib, commands) if commands.size > 0 && !lib.no_alias_creation
118
+ redefine_commands(lib, commands)
119
+ end
120
+
121
+ def redefine_commands(lib, commands)
122
+ option_commands = lib.command_objects(commands).select {|e| e.option_command? }
123
+ accepted, rejected = option_commands.partition {|e| e.args(lib) || e.arg_size }
124
+ if @options[:verbose] && rejected.size > 0
125
+ puts "Following commands cannot have options until their arguments are configured: " +
126
+ rejected.map {|e| e.name}.join(', ')
127
+ end
128
+ accepted.each {|cmd| Scientist.redefine_command(lib.namespace_object, cmd) }
129
+ end
130
+
131
+ def create_command_aliases(lib, commands)
132
+ lib.module ? prep_and_create_instance_aliases(commands, lib.module) : check_for_uncreated_aliases(lib, commands)
133
+ end
134
+
135
+ def prep_and_create_instance_aliases(commands, lib_module)
136
+ aliases_hash = {}
137
+ select_commands = Boson.commands.select {|e| commands.include?(e.name)}
138
+ select_commands.each do |e|
139
+ if e.alias
140
+ aliases_hash[lib_module.to_s] ||= {}
141
+ aliases_hash[lib_module.to_s][e.name] = e.alias
142
+ end
143
+ end
144
+ create_instance_aliases(aliases_hash)
145
+ end
146
+
147
+ def create_instance_aliases(aliases_hash)
148
+ Alias.manager.create_aliases(:instance_method, aliases_hash)
149
+ end
150
+
151
+ def create_class_aliases(mod, class_commands)
152
+ class_commands.dup.each {|k,v|
153
+ if v.is_a?(Array)
154
+ class_commands.delete(k).each {|e| class_commands[e] = "#{k}.#{e}"}
155
+ end
156
+ }
157
+ Alias.manager.create_aliases(:any_to_instance_method, mod.to_s=>class_commands.invert)
158
+ end
159
+
160
+ def check_for_uncreated_aliases(lib, commands)
161
+ return if lib.is_a?(GemLibrary)
162
+ if (found_commands = Boson.commands.select {|e| commands.include?(e.name)}) && found_commands.find {|e| e.alias }
163
+ $stderr.puts "No aliases created for library #{lib.name} because it has no module"
164
+ end
165
+ end
166
+ #:startdoc:
167
+ end
168
+ end
169
+ end