bahuvrihi-tap 0.10.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (86) hide show
  1. data/History +69 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README +119 -0
  4. data/bin/tap +114 -0
  5. data/cmd/console.rb +42 -0
  6. data/cmd/destroy.rb +16 -0
  7. data/cmd/generate.rb +16 -0
  8. data/cmd/run.rb +126 -0
  9. data/doc/Class Reference +362 -0
  10. data/doc/Command Reference +153 -0
  11. data/doc/Tutorial +237 -0
  12. data/lib/tap.rb +32 -0
  13. data/lib/tap/app.rb +720 -0
  14. data/lib/tap/constants.rb +8 -0
  15. data/lib/tap/env.rb +640 -0
  16. data/lib/tap/file_task.rb +547 -0
  17. data/lib/tap/generator/base.rb +109 -0
  18. data/lib/tap/generator/destroy.rb +37 -0
  19. data/lib/tap/generator/generate.rb +61 -0
  20. data/lib/tap/generator/generators/command/command_generator.rb +21 -0
  21. data/lib/tap/generator/generators/command/templates/command.erb +32 -0
  22. data/lib/tap/generator/generators/config/config_generator.rb +26 -0
  23. data/lib/tap/generator/generators/config/templates/doc.erb +12 -0
  24. data/lib/tap/generator/generators/config/templates/nodoc.erb +8 -0
  25. data/lib/tap/generator/generators/file_task/file_task_generator.rb +27 -0
  26. data/lib/tap/generator/generators/file_task/templates/file.txt +11 -0
  27. data/lib/tap/generator/generators/file_task/templates/result.yml +6 -0
  28. data/lib/tap/generator/generators/file_task/templates/task.erb +33 -0
  29. data/lib/tap/generator/generators/file_task/templates/test.erb +29 -0
  30. data/lib/tap/generator/generators/root/root_generator.rb +55 -0
  31. data/lib/tap/generator/generators/root/templates/Rakefile +86 -0
  32. data/lib/tap/generator/generators/root/templates/gemspec +27 -0
  33. data/lib/tap/generator/generators/root/templates/tapfile +8 -0
  34. data/lib/tap/generator/generators/root/templates/test/tap_test_helper.rb +3 -0
  35. data/lib/tap/generator/generators/root/templates/test/tap_test_suite.rb +5 -0
  36. data/lib/tap/generator/generators/root/templates/test/tapfile_test.rb +15 -0
  37. data/lib/tap/generator/generators/task/task_generator.rb +27 -0
  38. data/lib/tap/generator/generators/task/templates/task.erb +14 -0
  39. data/lib/tap/generator/generators/task/templates/test.erb +21 -0
  40. data/lib/tap/generator/manifest.rb +14 -0
  41. data/lib/tap/patches/rake/rake_test_loader.rb +8 -0
  42. data/lib/tap/patches/rake/testtask.rb +55 -0
  43. data/lib/tap/patches/ruby19/backtrace_filter.rb +51 -0
  44. data/lib/tap/patches/ruby19/parsedate.rb +16 -0
  45. data/lib/tap/root.rb +581 -0
  46. data/lib/tap/support/aggregator.rb +55 -0
  47. data/lib/tap/support/assignments.rb +172 -0
  48. data/lib/tap/support/audit.rb +418 -0
  49. data/lib/tap/support/batchable.rb +47 -0
  50. data/lib/tap/support/batchable_class.rb +107 -0
  51. data/lib/tap/support/class_configuration.rb +194 -0
  52. data/lib/tap/support/command_line.rb +98 -0
  53. data/lib/tap/support/comment.rb +270 -0
  54. data/lib/tap/support/configurable.rb +114 -0
  55. data/lib/tap/support/configurable_class.rb +296 -0
  56. data/lib/tap/support/configuration.rb +122 -0
  57. data/lib/tap/support/constant.rb +70 -0
  58. data/lib/tap/support/constant_utils.rb +127 -0
  59. data/lib/tap/support/declarations.rb +111 -0
  60. data/lib/tap/support/executable.rb +111 -0
  61. data/lib/tap/support/executable_queue.rb +82 -0
  62. data/lib/tap/support/framework.rb +71 -0
  63. data/lib/tap/support/framework_class.rb +199 -0
  64. data/lib/tap/support/instance_configuration.rb +147 -0
  65. data/lib/tap/support/lazydoc.rb +428 -0
  66. data/lib/tap/support/manifest.rb +89 -0
  67. data/lib/tap/support/run_error.rb +39 -0
  68. data/lib/tap/support/shell_utils.rb +71 -0
  69. data/lib/tap/support/summary.rb +30 -0
  70. data/lib/tap/support/tdoc.rb +404 -0
  71. data/lib/tap/support/tdoc/tdoc_html_generator.rb +38 -0
  72. data/lib/tap/support/tdoc/tdoc_html_template.rb +42 -0
  73. data/lib/tap/support/templater.rb +180 -0
  74. data/lib/tap/support/validation.rb +410 -0
  75. data/lib/tap/support/versions.rb +97 -0
  76. data/lib/tap/task.rb +259 -0
  77. data/lib/tap/tasks/dump.rb +56 -0
  78. data/lib/tap/tasks/rake.rb +93 -0
  79. data/lib/tap/test.rb +37 -0
  80. data/lib/tap/test/env_vars.rb +29 -0
  81. data/lib/tap/test/file_methods.rb +377 -0
  82. data/lib/tap/test/script_methods.rb +144 -0
  83. data/lib/tap/test/subset_methods.rb +420 -0
  84. data/lib/tap/test/tap_methods.rb +237 -0
  85. data/lib/tap/workflow.rb +187 -0
  86. metadata +145 -0
@@ -0,0 +1,122 @@
1
+ module Tap
2
+ module Support
3
+ class Configuration
4
+ class << self
5
+ SHORT_REGEXP = /^-[A-z]$/
6
+
7
+ # Turns the input string into a short-format option. Raises
8
+ # an error if the option does not match SHORT_REGEXP.
9
+ #
10
+ # Configuration.shortify("-o") # => '-o'
11
+ # Configuration.shortify(:o) # => '-o'
12
+ #
13
+ def shortify(str)
14
+ str = str.to_s
15
+ str = "-#{str}" unless str[0] == ?-
16
+ raise "invalid short option: #{str}" unless str =~ SHORT_REGEXP
17
+ str
18
+ end
19
+
20
+ LONG_REGEXP = /^--(\[no-\])?([A-z][\w-]*)$/
21
+
22
+ # Turns the input string into a long-format option. Raises
23
+ # an error if the option does not match LONG_REGEXP.
24
+ #
25
+ # Configuration.longify("--opt") # => '--opt'
26
+ # Configuration.longify(:opt) # => '--opt'
27
+ # Configuration.longify(:opt, true) # => '--[no-]opt'
28
+ # Configuration.longify(:opt_ion) # => '--opt-ion'
29
+ # Configuration.longify(:opt_ion, false, false) # => '--opt_ion'
30
+ #
31
+ def longify(str, switch_notation=false, hyphenize=true)
32
+ str = str.to_s
33
+ str = "--#{str}" unless str.index("--")
34
+ str.gsub!(/_/, '-') if hyphenize
35
+
36
+ raise "invalid long option: #{str}" unless str =~ LONG_REGEXP
37
+
38
+ if switch_notation && $1.nil?
39
+ str = "--[no-]#{$2}"
40
+ end
41
+
42
+ str
43
+ end
44
+ end
45
+
46
+ attr_reader :name
47
+ attr_reader :reader
48
+ attr_reader :writer
49
+ attr_reader :duplicable
50
+ attr_reader :attributes
51
+
52
+ def initialize(name, default=nil, options={})
53
+ @name = name
54
+ self.default = default
55
+
56
+ self.reader = options.delete(:reader) || name
57
+ self.writer = options.delete(:writer) || "#{name}="
58
+ @attributes = options
59
+ end
60
+
61
+ # Sets the default value for self and determines if the
62
+ # default is duplicable (ie not nil, true, false, Symbol,
63
+ # Numeric, and responds_to?(:dup)).
64
+ def default=(value)
65
+ @duplicable = case value
66
+ when nil, true, false, Symbol, Numeric then false
67
+ else value.respond_to?(:dup)
68
+ end
69
+
70
+ @default = value.freeze
71
+ end
72
+
73
+ # Returns the default value, or a duplicate of the default
74
+ # value if specified and the default value is duplicable.
75
+ def default(duplicate=true)
76
+ duplicate && duplicable ? @default.dup : @default
77
+ end
78
+
79
+ # Sets the reader for self. The reader is symbolized.
80
+ def reader=(value)
81
+ @reader = value.to_sym
82
+ end
83
+
84
+ # Sets the writer for self. The writer is symbolized.
85
+ def writer=(value)
86
+ @writer = value.to_sym
87
+ end
88
+
89
+ def arg_name
90
+ attributes[:arg_name] || name.to_s.upcase
91
+ end
92
+
93
+ def arg_type
94
+ attributes[:arg_type] || :mandatory
95
+ end
96
+
97
+ def long(switch_notation=false, hyphenize=true)
98
+ Configuration.longify(attributes[:long] || name.to_s, switch_notation, hyphenize)
99
+ end
100
+
101
+ def short
102
+ attributes[:short] ? Configuration.shortify(attributes[:short]) : nil
103
+ end
104
+
105
+ def desc
106
+ attributes[:desc]
107
+ end
108
+
109
+ # True if another is a kind of Configuration with the same name,
110
+ # default value, reader and writer; other attributes are NOT
111
+ # taken into account.
112
+ def ==(another)
113
+ another.kind_of?(Configuration) &&
114
+ self.name == another.name &&
115
+ self.reader == another.reader &&
116
+ self.writer == another.writer &&
117
+ self.default(false) == another.default(false)
118
+ end
119
+
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,70 @@
1
+ require 'tap/support/constant_utils'
2
+ class String # :nodoc:
3
+ include Tap::Support::ConstantUtils
4
+ end
5
+
6
+ module Tap
7
+ module Support
8
+ class Constant
9
+
10
+ # The camelized name for self.
11
+ attr_reader :name
12
+
13
+ # The path to load to initialize the constant name.
14
+ attr_reader :require_path
15
+
16
+ def initialize(name, require_path=nil)
17
+ @name = name
18
+ @require_path = require_path
19
+ end
20
+
21
+ # Returns the underscored name.
22
+ def path
23
+ @path ||= name.underscore
24
+ end
25
+
26
+ # Returns the basename of path.
27
+ def basename
28
+ @basename ||= File.basename(path)
29
+ end
30
+
31
+ # Returns the path, minus the basename of path.
32
+ def dirname
33
+ @dirname ||= (dirname = File.dirname(path)) == "." ? "" : dirname
34
+ end
35
+
36
+ # Returns the name of the constant, minus nesting.
37
+ def const_name
38
+ @const_name ||= (name =~ /.*::(.*)$/ ? $1 : name)
39
+ end
40
+
41
+ # Returns an array of the nesting constants of name.
42
+ def nesting
43
+ @nesting ||= (name =~ /(.*)::.*$/ ? $1 : '')
44
+ end
45
+
46
+ # Returns the number of constants in nesting.
47
+ def nesting_depth
48
+ @nesting_depth ||= nesting.split(/::/).length
49
+ end
50
+
51
+ # Returns the document for require_path, if set, or nil otherwise.
52
+ def document
53
+ require_path ? Support::Lazydoc[require_path] : nil
54
+ end
55
+
56
+ def ==(another)
57
+ another.kind_of?(Constant) &&
58
+ another.name == self.name &&
59
+ another.require_path == self.require_path
60
+ end
61
+
62
+ def constantize
63
+ name.try_constantize do |const_name|
64
+ require require_path
65
+ name.constantize
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,127 @@
1
+ module Tap
2
+ module Support
3
+
4
+ # ConstantUtils provides methods for transforming strings into constants.
5
+ # Several methods are directly taken from or based heavily on the
6
+ # ActiveSupport {Inflections}[http://api.rubyonrails.org/classes/ActiveSupport/CoreExtensions/String/Inflections.html]
7
+ # module and should not cause conflicts if ActiveSupport is loaded
8
+ # alongside Tap.
9
+ #
10
+ # ActiveSupport is distributed with an MIT-LICENSE:
11
+ #
12
+ # Copyright (c) 2004-2008 David Heinemeier Hansson
13
+ #
14
+ # Permission is hereby granted, free of charge, to any person obtaining a copy of this software and
15
+ # associated documentation files (the "Software"), to deal in the Software without restriction,
16
+ # including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense,
17
+ # and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so,
18
+ # subject to the following conditions:
19
+ #
20
+ # The above copyright notice and this permission notice shall be included in all copies or substantial
21
+ # portions of the Software.
22
+ #
23
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
24
+ # LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
25
+ # NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
26
+ # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
27
+ # SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
28
+ #
29
+ module ConstantUtils
30
+
31
+ # camelize converts self to UpperCamelCase. If the argument to
32
+ # camelize is set to :lower then camelize produces lowerCamelCase.
33
+ # camelize will also convert '/' to '::' which is useful for
34
+ # converting paths to namespaces.
35
+ def camelize(first_letter = :upper)
36
+ case first_letter
37
+ when :upper then self.to_s.gsub(/\/(.?)/) { "::" + $1.upcase }.gsub(/(^|_)(.)/) { $2.upcase }
38
+ when :lower then self.first + camelize[1..-1]
39
+ end
40
+ end
41
+
42
+ # The reverse of camelize. Makes an underscored, lowercase form
43
+ # from self. underscore will also change '::' to '/' to convert
44
+ # namespaces to paths.
45
+ def underscore
46
+ self.gsub(/::/, '/').
47
+ gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
48
+ gsub(/([a-z\d])([A-Z])/,'\1_\2').
49
+ tr("-", "_").
50
+ downcase
51
+ end
52
+
53
+ # constantize tries to find a declared constant with the name specified
54
+ # by self. It raises a NameError when the name is not in CamelCase
55
+ # or is not initialized.
56
+ def constantize
57
+ case RUBY_VERSION
58
+ when /^1.9/
59
+
60
+ # a check is necessary to maintain the 1.8 behavior
61
+ # in 1.9, where ancestor constants may be returned
62
+ # by a direct evaluation
63
+ const_name.split("::").inject(Object) do |current, const|
64
+ const = const.to_sym
65
+
66
+ current.const_get(const).tap do |c|
67
+ unless current.const_defined?(const, false)
68
+ raise NameError.new("uninitialized constant #{const_name}")
69
+ end
70
+ end
71
+ end
72
+
73
+ else
74
+ Object.module_eval("::#{const_name}", __FILE__, __LINE__)
75
+ end
76
+ end
77
+
78
+ # Tries to constantize self; if a NameError is raised, try_constantize
79
+ # passes control to the block. Control is only passed if the NameError
80
+ # is for one of the constants in self.
81
+ def try_constantize
82
+ begin
83
+ constantize
84
+ rescue(NameError)
85
+ error_name = $!.name.to_s
86
+ missing_const = const_name.split(/::/).inject(Object) do |current, const|
87
+ if current.const_defined?(const)
88
+ current.const_get(const)
89
+ else
90
+ break(const)
91
+ end
92
+ end
93
+
94
+ # check that the error_name is the first missing constant
95
+ raise $! unless missing_const == error_name
96
+ yield(const_name)
97
+ end
98
+ end
99
+
100
+ def constants_split
101
+ camel_cased_word = camelize
102
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ camel_cased_word
103
+ raise NameError, "#{camel_cased_word.inspect} is not a valid constant name!"
104
+ end
105
+
106
+ constants = $1.split(/::/)
107
+ current = Object
108
+ while !constants.empty?
109
+ break unless current.const_defined?(constants[0])
110
+ current = current.const_get(constants.shift)
111
+ end
112
+
113
+ [current, constants]
114
+ end
115
+
116
+ protected
117
+
118
+ def const_name
119
+ unless /\A(?:::)?([A-Z]\w*(?:::[A-Z]\w*)*)\z/ =~ self
120
+ raise NameError, "#{inspect} is not a valid constant name!"
121
+ end
122
+ $1
123
+ end
124
+
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,111 @@
1
+ module Tap
2
+ module Support
3
+ module Declarations
4
+ def self.set_declaration_base(base)
5
+ # TODO -- warn if base is Object -- conflict with Rake
6
+ declaration_base = base.to_s
7
+ declaration_base = "" if ["Object", "Tap"].include?(declaration_base)
8
+
9
+ base.instance_variable_set(:@tap_declaration_base, declaration_base.underscore)
10
+ end
11
+
12
+ def self.included(base)
13
+ set_declaration_base(base)
14
+ end
15
+
16
+ def self.extended(base)
17
+ set_declaration_base(base)
18
+ end
19
+
20
+ def tasc(name, configs={}, options={}, &block)
21
+ Tap::Task.subclass(nest(name), configs, options, &block)
22
+ end
23
+
24
+ def task(name, configs={}, options={}, &block)
25
+ options[:arity] = arity(block)
26
+ tasc(name, configs, options, &task_block(block)).new
27
+ end
28
+
29
+ def file_tasc(name, configs={}, options={}, &block)
30
+ Tap::FileTask.subclass(nest(name), configs, options, &block)
31
+ end
32
+
33
+ def file_task(name, configs={}, options={}, &block)
34
+ options[:arity] = arity(block)
35
+ file_tasc(nest(name), configs, options, &task_block(block)).new
36
+ end
37
+
38
+ def worcflow(name, configs={}, options={}, &block)
39
+ Tap::Workflow.subclass(nest(name), configs, options, &block)
40
+ end
41
+
42
+ def workflow(name, configs={}, options={}, &block)
43
+ options[:arity] = arity(block)
44
+ worcflow(name, configs, options, &task_block(block)).new
45
+ end
46
+
47
+ protected
48
+
49
+ def config(key, value=nil, options={}, &block)
50
+ caller.each_with_index do |line, index|
51
+ case line
52
+ when /^(([A-z]:)?[^:]+):(\d+)/
53
+ options[:desc] = Support::Lazydoc.register($1, $3.to_i - 1)
54
+ break
55
+ end
56
+ end if options[:desc] == nil
57
+
58
+ [:config, key, value, options, block]
59
+ end
60
+
61
+ def config_attr(key, value=nil, options={}, &block)
62
+ caller.each_with_index do |line, index|
63
+ case line
64
+ when /^(([A-z]:)?[^:]+):(\d+)/
65
+ options[:desc] = Support::Lazydoc.register($1, $3.to_i - 1)
66
+ break
67
+ end
68
+ end if options[:desc] == nil
69
+
70
+ [:config_attr, key, value, options, block]
71
+ end
72
+
73
+ def c
74
+ Support::Validation
75
+ end
76
+
77
+ private
78
+
79
+ def nest(name)
80
+ # use self if self is a Module or Class,
81
+ # or self.class if self is an instance.
82
+ File.join((self.kind_of?(Module) ? self : self.class).instance_variable_get(:@tap_declaration_base), name.to_s)
83
+ end
84
+
85
+ def arity(block)
86
+ arity = block.arity
87
+
88
+ case
89
+ when arity > 0 then arity -= 1
90
+ when arity < 0 then arity += 1
91
+ end
92
+
93
+ arity
94
+ end
95
+
96
+ def task_block(block)
97
+ lambda do |*inputs|
98
+ inputs.unshift(self)
99
+
100
+ arity = block.arity
101
+ n = inputs.length
102
+ unless n == arity || (arity < 0 && (-1-n) <= arity)
103
+ raise ArgumentError.new("wrong number of arguments (#{n} for #{arity})")
104
+ end
105
+
106
+ block.call(*inputs)
107
+ end
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,111 @@
1
+ require 'tap/support/audit'
2
+
3
+ module Tap
4
+ module Support
5
+ # Executable wraps methods to make them executable by App. Methods are
6
+ # wrapped by extending the object that receives them; the easiest way
7
+ # to make an object executable is to use Object#_method.
8
+ module Executable
9
+
10
+ # The method called when an Executable is executed via _execute
11
+ attr_reader :_method_name
12
+
13
+ # Indicates whether or not to execute in multithread mode.
14
+ attr_accessor :multithread
15
+
16
+ # Stores the on complete block.
17
+ attr_reader :on_complete_block
18
+
19
+ public
20
+
21
+ # Extends obj with Executable and sets up all required variables. The
22
+ # specified method will be called on _execute.
23
+ def self.initialize(obj, method_name, multithread=false, &on_complete_block)
24
+ obj.extend Executable
25
+ obj.instance_variable_set(:@_method_name, method_name)
26
+ obj.instance_variable_set(:@multithread, multithread)
27
+ obj.instance_variable_set(:@on_complete_block, on_complete_block)
28
+ obj
29
+ end
30
+
31
+ # Sets a block to receive the results of _execute. Raises an error
32
+ # if an on_complete block is already set. Override an existing
33
+ # on_complete block by specifying override = true.
34
+ #
35
+ # Note the block recieves an audited result and not
36
+ # the result itself (see Audit for more information).
37
+ def on_complete(override=false, &block) # :yields: _result
38
+ unless on_complete_block == nil || override
39
+ raise "on_complete_block already set: #{self}"
40
+ end
41
+ @on_complete_block = block
42
+ end
43
+
44
+ # Auditing method call. Executes _method_name for self, but audits
45
+ # the result. Sends the audited result to the on_complete_block if set.
46
+ #
47
+ # Audits are initialized in the follwing manner:
48
+ # no inputs:: create a new, empty Audit. The first value of the audit
49
+ # will be the result of call
50
+ # one input:: forks the input if it is an audit, otherwise initializes
51
+ # a new audit using the input
52
+ # multiple inputs:: merges the inputs into a new Audit.
53
+ #
54
+ def _execute(*inputs)
55
+ audit = case inputs.length
56
+ when 0 then Audit.new
57
+ when 1
58
+ audit = inputs.first
59
+ if audit.kind_of?(Audit)
60
+ inputs = [audit._current]
61
+ audit._fork
62
+ else
63
+ Audit.new(audit)
64
+ end
65
+ else
66
+ sources = []
67
+ inputs.collect! do |input|
68
+ if input.kind_of?(Audit)
69
+ sources << input._fork
70
+ input._current
71
+ else
72
+ sources << nil
73
+ input
74
+ end
75
+ end
76
+ Audit.new(inputs, sources)
77
+ end
78
+
79
+ audit._record(self, send(_method_name, *inputs))
80
+ on_complete_block.call(audit) if on_complete_block
81
+
82
+ audit
83
+ end
84
+ end
85
+ end
86
+ end
87
+
88
+ # Tap extends Object with <tt>_method</tt> to generate executable methods
89
+ # that can be enqued by Tap::App and incorporated into workflows.
90
+ #
91
+ # array = []
92
+ # push_to_array = array._method(:push)
93
+ #
94
+ # task = Tap::Task.new
95
+ # task.app.sequence(task, push_to_array)
96
+ #
97
+ # task.enq(1).enq(2,3)
98
+ # task.app.run
99
+ #
100
+ # array # => [[1],[2,3]]
101
+ #
102
+ class Object
103
+
104
+ # Initializes a Tap::Support::Executable using the Method returned by
105
+ # Object#method(method_name), setting multithread and the on_complete
106
+ # block as specified. Returns nil if Object#method returns nil.
107
+ def _method(method_name, multithread=false, &on_complete_block) # :yields: _result
108
+ return nil unless m = method(method_name)
109
+ Tap::Support::Executable.initialize(m, :call, multithread, &on_complete_block)
110
+ end
111
+ end