boson 0.2.2 → 0.2.3

Sign up to get free protection for your applications and to get access to all the features.
data/lib/boson/options.rb CHANGED
@@ -4,7 +4,7 @@ module Boson
4
4
  # === Creating Your Own Option Type
5
5
  # Defining your own option type simply requires one method (create_@type) to parse the option value and create
6
6
  # the desired object. To create an option type :date, you could create the following create_date method:
7
- # # Drop this in your ~/.irbrc after require 'boson'
7
+ # # Drop this in ~/.boson/commands/date_option.rb
8
8
  # module Boson::Options::Date
9
9
  # def create_date(value)
10
10
  # # value should be mm/dd
@@ -13,6 +13,10 @@ module Boson
13
13
  # end
14
14
  # Boson::OptionParser.send :include, Boson::Options::Date
15
15
  #
16
+ # Modify your config to load this new library by default:
17
+ # :defaults:
18
+ # - date_option
19
+ #
16
20
  # In a FileLibrary, we could then use this new option:
17
21
  # module Calendar
18
22
  # #@options :day=>:date
@@ -76,15 +80,8 @@ module Boson
76
80
  end
77
81
 
78
82
  def create_hash(value)
79
- splitter = current_attributes[:split] || ','
80
83
  (keys = current_attributes[:keys]) && keys = keys.sort_by {|e| e.to_s }
81
- if !value.include?(':') && current_attributes[:default_keys]
82
- value = current_attributes[:default_keys].to_s + ":#{value}"
83
- end
84
- # Creates array pairs, grouping array of keys with a value
85
- aoa = Hash[*value.split(/(?::)([^#{Regexp.quote(splitter)}]+)#{Regexp.quote(splitter)}?/)].to_a
86
- aoa.each_with_index {|(k,v),i| aoa[i][0] = keys.join(splitter) if k == '*' } if keys
87
- hash = aoa.inject({}) {|t,(k,v)| k.split(splitter).each {|e| t[e] = v }; t }
84
+ hash = parse_hash(value, keys)
88
85
  if keys
89
86
  hash = hash.inject({}) {|h,(k,v)|
90
87
  h[auto_alias_value(keys, k)] = v; h
@@ -94,6 +91,18 @@ module Boson
94
91
  hash
95
92
  end
96
93
 
94
+ def parse_hash(value, keys)
95
+ splitter = current_attributes[:split] || ','
96
+ if !value.include?(':') && current_attributes[:default_keys]
97
+ value = current_attributes[:default_keys].to_s + ":#{value}"
98
+ end
99
+
100
+ # Creates array pairs, grouping array of keys with a value
101
+ aoa = Hash[*value.split(/(?::)([^#{Regexp.quote(splitter)}]+)#{Regexp.quote(splitter)}?/)].to_a
102
+ aoa.each_with_index {|(k,v),i| aoa[i][0] = keys.join(splitter) if k == '*' } if keys
103
+ aoa.inject({}) {|t,(k,v)| k.split(splitter).each {|e| t[e] = v }; t }
104
+ end
105
+
97
106
  # Validation methods
98
107
  def validate_string(value)
99
108
  raise OptionParser::Error, "cannot pass '#{value}' as an argument to option '#{@current_option}'" if valid?(value)
data/lib/boson/pipe.rb CHANGED
@@ -1,8 +1,10 @@
1
1
  module Boson
2
- # This module passes a command's return value through methods/commands specified as pipe options. Pipe options
2
+ # This module passes an original command's return value through methods/commands specified as pipe options. Pipe options
3
3
  # are processed in this order:
4
- # * A :query option searches an array of objects or hashes using Pipe.search_object.
5
- # * A :sort option sorts an array of objects or hashes using Pipe.sort_object.
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.
6
8
  # * All user-defined pipe options (:pipe_options key in Repo.config) are processed in random order.
7
9
  #
8
10
  # Some points:
@@ -10,35 +12,27 @@ module Boson
10
12
  # command loaded when used. The easiest way to do this is by adding the pipe command's library to :defaults in main config.
11
13
  # * By default, pipe commands do not modify the value their given. This means you can activate multiple pipes using
12
14
  # a method's original return value.
13
- # * If you want a pipe command to modify the value its given, set its pipe option's :filter attribute to true.
14
15
  # * A pipe command expects a command's return value as its first argument. If the pipe option takes an argument, it's passed
15
16
  # on as a second argument.
16
17
  # * When piping occurs in relation to rendering depends on the Hirb view. With the default Hirb view, piping occurs
17
18
  # occurs in the middle of the rendering, after Hirb has converted the return value into an array of hashes.
18
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.
19
22
  #
20
- # === Default Pipes: Search and Sort
21
- # The default pipe options, :query, :sort and :reverse_sort, are quite useful for searching and sorting arrays:
22
- # Some examples using default commands:
23
- # # Searches commands in the full_name field for 'lib' and sorts results by that field.
24
- # bash> boson commands -q=f:lib -s=f # or commands --query=full_name:lib --sort=full_name
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.
25
34
  #
26
- # # Multiple fields can be searched if separated by a ','. This searches the full_name and desc fields.
27
- # bash> boson commands -q=f,d:web # or commands --query=full_name,desc:web
28
- #
29
- # # All fields can be queried using a '*'.
30
- # # Searches all library fields and then reverse sorts on name field
31
- # bash> boson libraries -q=*:core -s=n -R # or libraries --query=*:core --sort=name --reverse_sort
32
- #
33
- # # Multiple searches can be joined together by ','
34
- # # Searches for libraries that have the name matching core or a library_type matching gem
35
- # bash> boson libraries -q=n:core,l:gem # or libraries --query=name:core,library_type:gem
36
- #
37
- # In these examples, we queried commands and examples with an explicit --query. However, -q or --query isn't necessary
38
- # for these commands because they already default to it when not present. This behavior comes from the default_option
39
- # attribute a command can have.
40
- #
41
- # === User-defined Pipes
35
+ # === User Pipes Example
42
36
  # Let's say you want to have two commands, browser and copy, you want to make available as pipe options:
43
37
  # # Opens url in browser. This command already ships with Boson.
44
38
  # def browser(url)
@@ -63,93 +57,89 @@ module Boson
63
57
  #
64
58
  # Some examples of these options using commands from {my libraries}[http://github.com/cldwalker/irbfiles]:
65
59
  # # Creates a gist and then opens url in browser and copies it.
66
- # bash> cat some_file | boson gist -bC # or cat some_file | boson gist --browser --copy
60
+ # $ cat some_file | boson gist -bC # or cat some_file | boson gist --browser --copy
67
61
  #
68
62
  # # Generates rdoc in current directory and then opens it in browser
69
63
  # irb>> rdoc '-b' # or rdoc '--browser'
70
64
  module Pipe
71
65
  extend self
72
66
 
73
- # Main method which processes all pipe commands, both default and user-defined ones.
74
- def process(object, options)
75
- if object.is_a?(Array)
76
- object = search_object(object, options[:query]) if options[:query]
77
- object = sort_object(object, options[:sort], options[:reverse_sort]) if options[:sort]
78
- end
79
- process_user_pipes(object, options)
67
+ # Process pipes for Scientist
68
+ def scientist_process(object, global_opt, env={})
69
+ @env = env
70
+ [:query, :sort, :reverse_sort].each {|e| global_opt.delete(e) } unless object.is_a?(Array)
71
+ process_pipes(object, global_opt)
80
72
  end
81
73
 
82
- # Searches an array of objects or hashes using the :query option.
83
- # This option is a hash of fields mapped to their search terms. Searches are OR-ed.
84
- def search_object(object, query_hash)
85
- if object[0].is_a?(Hash)
86
- TableCallbacks.search_callback(object, :query=>query_hash)
87
- else
88
- query_hash.map {|field,query| object.select {|e| e.send(field).to_s =~ /#{query}/i } }.flatten.uniq
89
- end
90
- rescue NoMethodError
91
- $stderr.puts "Query failed with nonexistant method '#{$!.message[/`(.*)'/,1]}'"
74
+ # Main method which processes all pipe commands, both default and user-defined ones.
75
+ def process_pipes(obj, options)
76
+ internal_pipes(options).each {|pipe|
77
+ obj = Pipes.send("#{pipe}_pipe", obj, options[pipe]) if options[pipe]
78
+ }
79
+ process_user_pipes(obj, options)
92
80
  end
93
81
 
94
- # Sorts an array of objects or hashes using a sort field. Sort is reversed with reverse_sort set to true.
95
- def sort_object(object, sort, reverse_sort=false)
96
- if object[0].is_a?(Hash)
97
- TableCallbacks.sort_callback(object, :sort=>sort, :reverse_sort=>reverse_sort)
98
- else
99
- sort_lambda = object.all? {|e| e.send(sort).respond_to?(:<=>) } ? lambda {|e| e.send(sort) || ''} :
100
- lambda {|e| e.send(sort).to_s }
101
- object = object.sort_by &sort_lambda
102
- object = object.reverse if reverse_sort
103
- object
104
- end
105
- rescue NoMethodError, ArgumentError
106
- $stderr.puts "Sort failed with nonexistant method '#{sort}'"
82
+ # A hash that defines user pipes in the same way as the :pipe_options key in Repo.config.
83
+ # This method should be called when a pipe's library is loading.
84
+ def add_pipes(hash)
85
+ pipe_options.merge! setup_pipes(hash)
107
86
  end
108
87
 
109
88
  #:stopdoc:
89
+ def internal_pipes(global_opt)
90
+ internals = [:query, :sort, :reverse_sort, :pipes]
91
+ internals.delete(:pipes) if pipes_to_process(global_opt).any? {|e| pipe(e)[:solo] }
92
+ internals
93
+ end
94
+
110
95
  def pipe_options
111
- @pipe_options ||= Boson.repo.config[:pipe_options] || {}
96
+ @pipe_options ||= setup_pipes(Boson.repo.config[:pipe_options] || {})
97
+ end
98
+
99
+ def setup_pipes(hash)
100
+ hash.each {|k,v| v[:pipe] ||= k }
112
101
  end
113
102
 
114
- def process_user_pipes(result, options)
115
- (options.keys & pipe_options.keys).each {|e|
116
- command = pipe_options[e][:pipe] ||= e
117
- pipe_result = pipe_options[e][:type] == :boolean ? Boson.invoke(command, result) :
118
- Boson.invoke(command, result, options[e])
119
- result = pipe_result if pipe_options[e][:filter]
103
+ def pipe(key)
104
+ pipe_options[key] || {}
105
+ end
106
+
107
+ # global_opt can come from Hirb callback or Scientist
108
+ def process_user_pipes(result, global_opt)
109
+ pipes_to_process(global_opt).each {|e|
110
+ args = [pipe(e)[:pipe], result]
111
+ args << global_opt[e] unless pipe(e)[:type] == :boolean
112
+ args << get_env(e, global_opt) if pipe(e)[:env]
113
+ pipe_result = Boson.invoke(*args)
114
+ result = pipe_result if pipe(e)[:filter]
120
115
  }
121
116
  result
122
117
  end
118
+
119
+ def get_env(key, global_opt)
120
+ { :global_options=>global_opt.merge(:delete_callbacks=>[:z_boson_pipes]),
121
+ :config=>(@env[:config].dup[key] || {}),
122
+ :args=>@env[:args],
123
+ :options=>@env[:options] || {}
124
+ }
125
+ end
126
+
127
+ def any_no_render_pipes?(global_opt)
128
+ !(pipes = pipes_to_process(global_opt)).empty? &&
129
+ pipes.any? {|e| pipe(e)[:no_render] }
130
+ end
131
+
132
+ def pipes_to_process(global_opt)
133
+ pipes = (global_opt.keys & pipe_options.keys)
134
+ (solo_pipe = pipes.find {|e| pipe(e)[:solo] }) ? [solo_pipe] : pipes
135
+ end
123
136
  #:startdoc:
124
137
 
125
138
  # Callbacks used by Hirb::Helpers::Table to search,sort and run custom pipe commands on arrays of hashes.
126
139
  module TableCallbacks
127
- extend self
128
- # Case-insensitive searches an array of hashes using the option :query. Numerical string keys
129
- # in :query are converted to actual numbers to interface with Hirb. See Pipe.search_object for more
130
- # about :query.
131
- def search_callback(obj, options)
132
- !options[:query] ? obj : begin
133
- options[:query].map {|field,query|
134
- field = field.to_i if field.to_s[/^\d+$/]
135
- obj.select {|e| e[field].to_s =~ /#{query}/i }
136
- }.flatten.uniq
137
- end
138
- end
139
-
140
- # Sorts an array of hashes using :sort option and reverses the sort with :reverse_sort option.
141
- def sort_callback(obj, options)
142
- return obj unless options[:sort]
143
- sort = options[:sort].to_s[/^\d+$/] ? options[:sort].to_i : options[:sort]
144
- sort_lambda = (obj.all? {|e| e[sort].respond_to?(:<=>) } ? lambda {|e| e[sort] } : lambda {|e| e[sort].to_s })
145
- obj = obj.sort_by &sort_lambda
146
- obj = obj.reverse if options[:reverse_sort]
147
- obj
148
- end
149
-
150
- # Processes user-defined pipes in random order.
151
- def z_user_pipes_callback(obj, options)
152
- Pipe.process_user_pipes(obj, options)
140
+ # Processes boson's pipes
141
+ def z_boson_pipes_callback(obj, options)
142
+ Pipe.process_pipes(obj, options)
153
143
  end
154
144
  end
155
145
  end
@@ -0,0 +1,67 @@
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
+ sort = sort.to_i if sort.to_s[/^\d+$/]
47
+ sort_lambda = (object.all? {|e| e[sort].respond_to?(:<=>) } ? lambda {|e| e[sort] } : lambda {|e| e[sort].to_s })
48
+ else
49
+ sort_lambda = object.all? {|e| e.send(sort).respond_to?(:<=>) } ? lambda {|e| e.send(sort) || ''} :
50
+ lambda {|e| e.send(sort).to_s }
51
+ end
52
+ object.sort_by &sort_lambda
53
+ rescue NoMethodError, ArgumentError
54
+ $stderr.puts "Sort failed with nonexistant method '#{sort}'"
55
+ end
56
+
57
+ # Reverse an object
58
+ def reverse_sort_pipe(object, extra=nil)
59
+ object.reverse
60
+ end
61
+
62
+ # Pipes output of multiple commands recursively, given initial object
63
+ def pipes_pipe(obj, arr)
64
+ arr.inject(obj) {|acc,e| Boson.full_invoke(e, [acc]) }
65
+ end
66
+ end
67
+ end
data/lib/boson/repo.rb CHANGED
@@ -70,10 +70,12 @@ module Boson
70
70
  # depend on commands from other libraries. Default is false.
71
71
  # [:ignore_directories] Array of directories to ignore when detecting local repositories for Boson.local_repo.
72
72
  # [:no_auto_render] When set, turns off commandline auto-rendering of a command's output. Default is false.
73
+ # [:option_underscore_search] When set, OptionParser option values (with :values or :keys) are auto aliased with underscore searching.
74
+ # Default is true. See Util.underscore_search.
73
75
  def config(reload=false)
74
76
  if reload || @config.nil?
75
77
  begin
76
- @config = {:libraries=>{}, :command_aliases=>{}, :console_defaults=>[]}
78
+ @config = {:libraries=>{}, :command_aliases=>{}, :console_defaults=>[], :option_underscore_search=>true}
77
79
  @config.merge!(YAML::load_file(config_file(true))) if File.exists?(config_file)
78
80
  rescue ArgumentError
79
81
  message = $!.message !~ /syntax error on line (\d+)/ ? "Error"+$!.message :
@@ -84,6 +86,16 @@ module Boson
84
86
  @config
85
87
  end
86
88
 
89
+ # Updates main config file by passing config into a block to be modified and then saved
90
+ def update_config
91
+ yield(config)
92
+ write_config_file
93
+ end
94
+
95
+ def write_config_file #:nodoc:
96
+ File.open(config_file, 'w') {|f| f.write config.to_yaml }
97
+ end
98
+
87
99
  def detected_libraries #:nodoc:
88
100
  Dir[File.join(commands_dir, '**/*.rb')].map {|e| e.gsub(/^#{commands_dir}\/|\.rb$/, '') }
89
101
  end
@@ -65,10 +65,10 @@ module Boson
65
65
  else
66
66
  execute_command
67
67
  end
68
- rescue Exception
68
+ rescue
69
69
  is_invalid_command = lambda {|command| !Boson.can_invoke?(command[/\w+/]) ||
70
70
  (Boson.can_invoke?(command[/\w+/]) && command.include?('.') && $!.is_a?(NoMethodError)) }
71
- print_error_message @command && is_invalid_command.call(@command) ?
71
+ print_error_message @command.to_s[/\w+/] && is_invalid_command.call(@command) ?
72
72
  "Error: Command '#{@command}' not found" : "Error: #{$!.message}"
73
73
  end
74
74
 
@@ -115,10 +115,11 @@ module Boson
115
115
  begin
116
116
  output = Boson.full_invoke(@command, @args)
117
117
  rescue ArgumentError
118
- # for the rare case it's raised outside of boson
119
- raise unless $!.backtrace.first.include?('boson/')
118
+ raise unless $!.message[/wrong number of arguments/] &&
119
+ # Throw out errors that aren't external or from option_command
120
+ ($!.backtrace.first[/boson/].nil? || $!.backtrace.first[/boson\/option_command.rb/])
120
121
  print_error_message "'#{@command}' was called incorrectly."
121
- Boson.invoke(:usage, @command)
122
+ Boson.invoke(:usage, @command, :one_line=>true)
122
123
  return
123
124
  end
124
125
  render_output output
@@ -88,12 +88,12 @@ module Boson
88
88
 
89
89
  def translate_and_render(obj, command, args, &block)
90
90
  @global_options, @command, original_args = {}, command, args.dup
91
- args = translate_args(obj, args)
91
+ @args = translate_args(obj, args)
92
92
  return run_help_option if @global_options[:help]
93
- run_pretend_option(args)
94
- render_or_raw call_original_command(args, &block) unless @global_options[:pretend]
93
+ run_pretend_option(@args)
94
+ render_or_raw call_original_command(@args, &block) unless @global_options[:pretend]
95
95
  rescue OptionCommand::CommandArgumentError
96
- run_pretend_option(args ||= [])
96
+ run_pretend_option(@args ||= [])
97
97
  return if !@global_options[:pretend] && run_verbose_help(option_command, original_args)
98
98
  raise unless @global_options[:pretend]
99
99
  rescue OptionParser::Error, Error
@@ -104,17 +104,17 @@ module Boson
104
104
 
105
105
  def translate_args(obj, args)
106
106
  option_command.modify_args(args)
107
- @global_options, parsed_options, args = option_command.parse(args)
107
+ @global_options, @current_options, args = option_command.parse(args)
108
108
  return if @global_options[:help]
109
109
 
110
110
  (@global_options[:delete_options] || []).map {|e|
111
111
  @global_options.keys.map {|k| k.to_s }.grep(/^#{e}/)
112
112
  }.flatten.each {|e| @global_options.delete(e.to_sym) }
113
113
 
114
- if parsed_options
114
+ if @current_options
115
115
  option_command.add_default_args(args, obj)
116
116
  return args if @no_option_commands.include?(@command)
117
- args << parsed_options
117
+ args << @current_options
118
118
  option_command.check_argument_size(args)
119
119
  end
120
120
  args
@@ -131,7 +131,9 @@ module Boson
131
131
  end
132
132
 
133
133
  def run_help_option
134
- Boson.invoke(:usage, @command.name, :verbose=>@global_options[:verbose])
134
+ opts = @global_options[:verbose] ? ['--verbose'] : []
135
+ opts << "--render_options=#{@global_options[:usage_options]}" if @global_options[:usage_options]
136
+ Boson.invoke :usage, @command.name + " " + opts.join(' ')
135
137
  end
136
138
 
137
139
  def run_pretend_option(args)
@@ -143,18 +145,19 @@ module Boson
143
145
 
144
146
  def render_or_raw(result)
145
147
  if (@rendered = render?)
146
- result = Pipe.process(result, @global_options) if @global_options.key?(:class) ||
147
- @global_options.key?(:method)
148
+ if @global_options.key?(:class) || @global_options.key?(:method)
149
+ result = Pipe.scientist_process(result, @global_options, :config=>@command.config, :args=>@args, :options=>@current_options)
150
+ end
148
151
  View.render(result, OptionCommand.delete_non_render_options(@global_options.dup), false)
149
152
  else
150
- Pipe.process(result, @global_options)
153
+ Pipe.scientist_process(result, @global_options, :config=>@command.config, :args=>@args, :options=>@current_options)
151
154
  end
152
155
  rescue StandardError
153
156
  raise Error, $!.message, $!.backtrace
154
157
  end
155
158
 
156
159
  def render?
157
- !!@command.render_options ^ @global_options[:render]
160
+ (!!@command.render_options ^ @global_options[:render]) && !Pipe.any_no_render_pipes?(@global_options)
158
161
  end
159
162
  #:startdoc:
160
163
  end