scout-gear 1.2.0 → 2.0.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.
@@ -0,0 +1,227 @@
1
+ module Misc
2
+ COLOR_LIST = %w(#BC80BD #CCEBC5 #FFED6F #8DD3C7 #FFFFB3 #BEBADA #FB8072 #80B1D3 #FDB462 #B3DE69 #FCCDE5 #D9D9D9)
3
+
4
+ def self.colors_for(list)
5
+ unused = COLOR_LIST.dup
6
+
7
+ used = {}
8
+ colors = list.collect do |elem|
9
+ if used.include? elem
10
+ used[elem]
11
+ else
12
+ color = unused.shift
13
+ used[elem]=color
14
+ color
15
+ end
16
+ end
17
+
18
+ [colors, used]
19
+ end
20
+
21
+ def self.format_seconds(time, extended = false)
22
+ seconds = time.to_i
23
+ str = [seconds/3600, seconds/60 % 60, seconds % 60].map{|t| "%02i" % t }.join(':')
24
+ str << ".%02i" % ((time - seconds) * 100) if extended
25
+ str
26
+ end
27
+
28
+ def self.format_paragraph(text, size = 80, indent = 0, offset = 0)
29
+ i = 0
30
+ size = size + offset + indent
31
+ re = /((?:\n\s*\n\s*)|(?:\n\s*(?=\*)))/
32
+ text.split(re).collect do |paragraph|
33
+ i += 1
34
+ str = if i % 2 == 1
35
+ words = paragraph.gsub(/\s+/, "\s").split(" ")
36
+ lines = []
37
+ line = " "*offset
38
+ word = words.shift
39
+ while word
40
+ word = word[0..size-indent-offset-4] + '...' if word.length >= size - indent - offset
41
+ while word and Log.uncolor(line).length + Log.uncolor(word).length <= size - indent
42
+ line << word << " "
43
+ word = words.shift
44
+ end
45
+ offset = 0
46
+ lines << ((" " * indent) << line[0..-2])
47
+ line = ""
48
+ end
49
+ (lines * "\n")
50
+ else
51
+ paragraph
52
+ end
53
+ offset = 0
54
+ str
55
+ end*""
56
+ end
57
+
58
+ def self.format_definition_list_item(dt, dd, size = 80, indent = 20, color = :yellow)
59
+ dd = "" if dd.nil?
60
+ dt = Log.color color, dt if color
61
+ dt = dt.to_s unless dd.empty?
62
+ len = Log.uncolor(dt).length
63
+
64
+ if indent < 0
65
+ text = format_paragraph(dd, size, indent.abs-1, 0)
66
+ text = dt << "\n" << text
67
+ else
68
+ offset = len - indent
69
+ offset = 0 if offset < 0
70
+ text = format_paragraph(dd, size, indent.abs+1, offset)
71
+ text[0..len-1] = dt
72
+ end
73
+ text
74
+ end
75
+
76
+ def self.format_definition_list(defs, size = 80, indent = 20, color = :yellow, sep = "\n\n")
77
+ entries = []
78
+ defs.each do |dt,dd|
79
+ text = format_definition_list_item(dt,dd,size,indent,color)
80
+ entries << text
81
+ end
82
+ entries * sep
83
+ end
84
+
85
+ def self.camel_case(string)
86
+ return string if string !~ /_/ && string =~ /[A-Z]+.*/
87
+ string.split(/_|(\d+)/).map{|e|
88
+ (e =~ /^[A-Z]{2,}$/ ? e : e.capitalize)
89
+ }.join
90
+ end
91
+
92
+ def self.camel_case_lower(string)
93
+ string.split('_').inject([]){ |buffer,e|
94
+ buffer.push(buffer.empty? ? e.downcase : (e =~ /^[A-Z]{2,}$/ ? e : e.capitalize))
95
+ }.join
96
+ end
97
+
98
+ def self.snake_case(string)
99
+ return nil if string.nil?
100
+ string = string.to_s if Symbol === string
101
+ string.
102
+ gsub(/([A-Z]{2,})([A-Z][a-z])/,'\1_\2').
103
+ gsub(/([a-z])([A-Z])/,'\1_\2').
104
+ gsub(/\s/,'_').gsub(/[^\w_]/, '').
105
+ split("_").collect{|p| p.match(/[A-Z]{2,}/) ? p : p.downcase } * "_"
106
+ end
107
+
108
+ # source: https://gist.github.com/ekdevdes/2450285
109
+ # author: Ethan Kramer (https://github.com/ekdevdes)
110
+ def self.humanize(value, options = {})
111
+ if options.empty?
112
+ options[:format] = :sentence
113
+ end
114
+
115
+ values = value.to_s.split('_')
116
+ values.each_index do |index|
117
+ # lower case each item in array
118
+ # Miguel Vazquez edit: Except for acronyms
119
+ values[index].downcase! unless values[index].match(/[a-zA-Z][A-Z]/)
120
+ end
121
+ if options[:format] == :allcaps
122
+ values.each do |value|
123
+ value.capitalize!
124
+ end
125
+
126
+ if options.empty?
127
+ options[:seperator] = " "
128
+ end
129
+
130
+ return values.join " "
131
+ end
132
+
133
+ if options[:format] == :class
134
+ values.each do |value|
135
+ value.capitalize!
136
+ end
137
+
138
+ return values.join ""
139
+ end
140
+
141
+ if options[:format] == :sentence
142
+ values[0].capitalize! unless values[0].match(/[a-zA-Z][A-Z]/)
143
+
144
+ return values.join " "
145
+ end
146
+
147
+ if options[:format] == :nocaps
148
+ return values.join " "
149
+ end
150
+ end
151
+
152
+ def self.fixascii(string)
153
+ if string.respond_to?(:encode)
154
+ self.fixutf8(string).encode("ASCII-8BIT")
155
+ else
156
+ string
157
+ end
158
+ end
159
+
160
+ def self.to_utf8(string)
161
+ string.encode("UTF-16BE", :invalid => :replace, :undef => :replace, :replace => "?").encode('UTF-8')
162
+ end
163
+
164
+ def self.fixutf8(string)
165
+ return nil if string.nil?
166
+ return string if string.respond_to?(:encoding) && string.encoding.to_s == "UTF-8" && (string.respond_to?(:valid_encoding?) && string.valid_encoding?) ||
167
+ (string.respond_to?(:valid_encoding) && string.valid_encoding)
168
+
169
+ if string.respond_to?(:encode)
170
+ string.encode('UTF-8', 'binary', invalid: :replace, undef: :replace, replace: '')
171
+ else
172
+ require 'iconv'
173
+ @@ic ||= Iconv.new('UTF-8//IGNORE', 'UTF-8')
174
+ @@ic.iconv(string)
175
+ end
176
+ end
177
+
178
+ def self.humanize_list(list)
179
+ return "" if list.empty?
180
+ if list.length == 1
181
+ list.first
182
+ else
183
+ list[0..-2].collect{|e| e.to_s} * ", " << " and " << list[-1].to_s
184
+ end
185
+ end
186
+
187
+ def self.parse_sql_values(txt)
188
+ io = StringIO.new txt.strip
189
+
190
+ values = []
191
+ fields = []
192
+ current = nil
193
+ quoted = false
194
+ while c = io.getc
195
+ if quoted
196
+ if c == "'"
197
+ quoted = false
198
+ else
199
+ current << c
200
+ end
201
+ else
202
+ case c
203
+ when "("
204
+ current = ""
205
+ when ")"
206
+ fields << current
207
+ values << fields
208
+ fields = []
209
+ current = nil
210
+ when ','
211
+ if not current.nil?
212
+ fields << current
213
+ current = ""
214
+ end
215
+ when "'"
216
+ quoted = true
217
+ when ";"
218
+ break
219
+ else
220
+ current << c
221
+ end
222
+ end
223
+ end
224
+ values
225
+ end
226
+
227
+ end
data/lib/scout/misc.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require_relative 'misc/format'
1
2
  module Misc
2
3
  def self.in_dir(dir)
3
4
  old_pwd = FileUtils.pwd
@@ -1,25 +1,27 @@
1
+ require_relative '../indiferent_hash'
1
2
  module Path
3
+
2
4
  def _parts
3
5
  @_parts ||= self.split("/")
4
6
  end
5
7
 
6
8
  def _subpath
7
- @subpath ||= _parts.length > 1 ? _parts[1..-1] * "/" : _parts[0]
9
+ @subpath ||= _parts.length > 1 ? _parts[1..-1] * "/" : _parts[0] || ""
8
10
  end
9
11
 
10
12
  def _toplevel
11
- @toplevel ||= _parts.length > 1 ? _parts[0] : nil
13
+ @toplevel ||= _parts.length > 1 ? _parts[0] : ""
12
14
  end
13
15
 
14
16
  def self.follow(path, map)
15
- map.sub('{PKGDIR}', path.pkgdir || Path.default_pkgdir).
16
- sub('{RESOURCE}', path.to_s).
17
+ map.sub('{PKGDIR}', path.pkgdir.respond_to?(:subdir) ? path.pkgdir.pkgdir : path.pkgdir || Path.default_pkgdir).
18
+ sub('{RESOURCE}', path.pkgdir.to_s).
17
19
  sub('{PWD}', FileUtils.pwd).
18
20
  sub('{TOPLEVEL}', path._toplevel).
19
21
  sub('{SUBPATH}', path._subpath).
20
22
  sub('{BASENAME}', File.basename(path)).
21
23
  sub('{PATH}', path).
22
- sub('{LIBDIR}', path.libdir || Path.caller_lib_dir).
24
+ sub('{LIBDIR}', path.libdir || (path.pkgdir.respond_to?(:libdir) && path.pkgdir.libdir) || Path.caller_lib_dir).
23
25
  sub('{REMOVE}/', '').
24
26
  sub('{REMOVE}', '').gsub(/\/+/,'/')
25
27
  end
@@ -48,11 +50,16 @@ module Path
48
50
  @@search_order ||= (path_maps.keys & map_search) + (path_maps.keys - map_search)
49
51
  end
50
52
 
53
+ def self.add_path(name, map)
54
+ @@path_maps[name] = map
55
+ @@search_order = nil
56
+ end
57
+
51
58
  SLASH = "/"[0]
52
59
  DOT = "."[0]
53
60
  def located?
54
61
  # OPEN RESOURCE
55
- self.slice(0,1) == SLASH || (self.char(0,1) == DOT && self.char(1,2) == SLASH) # || (resource != Rbbt && (Open.remote?(self) || Open.ssh?(self)))
62
+ self.slice(0,1) == SLASH || (self.slice(0,1) == DOT && self.slice(1,2) == SLASH) # || (resource != Rbbt && (Open.remote?(self) || Open.ssh?(self)))
56
63
  end
57
64
 
58
65
  def annotate_found_where(found, where)
@@ -85,13 +92,13 @@ module Path
85
92
 
86
93
  def find(where = nil)
87
94
  return self if located?
95
+ return find_all if where == 'all' || where == :all
88
96
  return follow(where) if where
89
97
 
90
-
91
98
  Path.search_order.each do |map_name|
92
99
  found = follow(map_name, false)
93
100
 
94
- return annotate_found_where(found, map_name) if File.exist?(found) || File.directory?(real_path)
101
+ return annotate_found_where(found, map_name) if File.exist?(found) || File.directory?(found)
95
102
  end
96
103
 
97
104
  return follow(:default)
@@ -110,5 +117,4 @@ module Path
110
117
  .collect{|where| find(where) }
111
118
  .select{|file| file.exist? }.uniq
112
119
  end
113
-
114
120
  end
@@ -31,12 +31,11 @@ module Path
31
31
  end
32
32
 
33
33
  def glob_all(pattern = nil, caller_lib = nil, search_paths = nil)
34
- search_paths ||= Path.search_paths
34
+ search_paths ||= Path.path_maps
35
35
  search_paths = search_paths.dup
36
36
 
37
- location_paths = {}
38
37
  search_paths.keys.collect do |where|
39
- found = find(where, Path.caller_lib_dir, search_paths)
38
+ found = find(where)
40
39
  paths = pattern ? Dir.glob(File.join(found, pattern)) : Dir.glob(found)
41
40
 
42
41
  paths = paths.collect{|p| self.annotate p }
@@ -46,7 +45,7 @@ module Path
46
45
  p.where = where
47
46
  end if found.original and pattern
48
47
 
49
- location_paths[where] = paths
50
- end
48
+ paths
49
+ end.flatten.uniq
51
50
  end
52
51
  end
data/lib/scout/path.rb CHANGED
@@ -4,16 +4,17 @@ require_relative 'path/util'
4
4
 
5
5
  module Path
6
6
  extend MetaExtension
7
- extension_attr :pkgdir, :libdir
7
+ extension_attr :pkgdir, :libdir, :path_maps
8
8
 
9
9
  def self.caller_lib_dir(file = nil, relative_to = ['lib', 'bin'])
10
10
 
11
11
  if file.nil?
12
12
  caller_dup = caller.dup
13
13
  while file = caller_dup.shift
14
- break unless file =~ /scout\/(?:resource\.rb|workflow\.rb)/ or
15
- file =~ /scout\/path\.rb/ or
16
- file =~ /scout\/persist.rb/
14
+ break unless file =~ /(?:scout|rbbt)\/(?:resource\.rb|workflow\.rb)/ or
15
+ file =~ /(?:scout|rbbt)\/(?:.*\/)?path\.rb/ or
16
+ file =~ /(?:scout|rbbt)\/(?:.*\/)?path\/(?:find|refactor|util)\.rb/ or
17
+ file =~ /(?:scout|rbbt)\/persist.rb/
17
18
  end
18
19
  file = file.sub(/\.rb[^\w].*/,'.rb')
19
20
  end
@@ -34,15 +35,20 @@ module Path
34
35
  end
35
36
 
36
37
  def self.default_pkgdir
37
- @@default_pkgdir = 'scout'
38
+ @@default_pkgdir ||= 'scout'
38
39
  end
40
+
41
+ def self.default_pkgdir=(pkgdir)
42
+ @@default_pkgdir = pkgdir
43
+ end
44
+
39
45
 
40
46
  def pkgdir
41
47
  @pkgdir ||= Path.default_pkgdir
42
48
  end
43
49
 
44
50
  def libdir
45
- @libdir ||= Path.caller_lib_dir
51
+ @libdir
46
52
  end
47
53
 
48
54
  def join(subpath, prevpath = nil)
@@ -50,7 +56,7 @@ module Path
50
56
  prevpath = prevpath.to_s if Symbol === prevpath
51
57
 
52
58
  subpath = File.join(prevpath.to_s, subpath) if prevpath
53
- new = File.join(self, subpath)
59
+ new = self.empty? ? subpath : File.join(self, subpath)
54
60
  self.annotate(new)
55
61
  new
56
62
  end
@@ -0,0 +1,54 @@
1
+ module SOPT
2
+ class << self
3
+ attr_accessor :inputs, :input_shortcuts, :input_types, :input_descriptions, :input_defaults
4
+ end
5
+
6
+ def self.all
7
+ @all ||= {}
8
+ end
9
+
10
+ def self.shortcuts
11
+ @shortcuts ||= {}
12
+ end
13
+
14
+ def self.inputs
15
+ @inputs ||= []
16
+ end
17
+
18
+ def self.input_shortcuts
19
+ @input_shortcuts ||= {}
20
+ end
21
+
22
+ def self.input_types
23
+ @input_types ||= {}
24
+ end
25
+
26
+ def self.input_descriptions
27
+ @input_descriptions ||= {}
28
+ end
29
+
30
+ def self.input_defaults
31
+ @input_defaults ||= {}
32
+ end
33
+
34
+ def self.reset
35
+ @shortcuts = {}
36
+ @all = {}
37
+ end
38
+
39
+ def self.delete_inputs(inputs)
40
+ inputs.each do |input|
41
+ input = input.to_s
42
+ self.shortcuts.delete self.input_shortcuts.delete(input)
43
+ self.inputs.delete input
44
+ self.input_types.delete input
45
+ self.input_defaults.delete input
46
+ self.input_descriptions.delete input
47
+ end
48
+ end
49
+
50
+ def self.usage
51
+ puts SOPT.doc
52
+ exit 0
53
+ end
54
+ end
@@ -0,0 +1,120 @@
1
+ require_relative '../log'
2
+ module SOPT
3
+
4
+ class << self
5
+ attr_accessor :command, :summary, :synopsys, :description
6
+ end
7
+
8
+ def self.command
9
+ @command ||= File.basename($0)
10
+ end
11
+
12
+ def self.summary
13
+ @summary ||= ""
14
+ end
15
+
16
+ def self.synopsys
17
+ @synopsys ||= begin
18
+ "#{command} " <<
19
+ inputs.collect{|name|
20
+ "[" << input_format(name, input_types[name] || :string, input_defaults[name], input_shortcuts[name]).sub(/:$/,'') << "]"
21
+ } * " "
22
+ end
23
+ end
24
+
25
+ def self.description
26
+ @description ||= "Missing"
27
+ end
28
+
29
+ def self.input_format(name, type = nil, default = nil, short = nil)
30
+ input_str = (short.nil? or short.empty?) ? "--#{name}" : "-#{short},--#{name}"
31
+ input_str = Log.color(:blue, input_str)
32
+ extra = case type
33
+ when nil
34
+ ""
35
+ when :boolean
36
+ "[=false]"
37
+ when :tsv, :text
38
+ "=<file|->"
39
+ when :array
40
+ "=<list|file|->"
41
+ else
42
+ "=<#{ type }>"
43
+ end
44
+ #extra << " (default: #{Array === default ? (default.length > 3 ? default[0..2]*", " + ', ...' : default*", " ): default})" if default != nil
45
+ extra << " (default: #{Misc.fingerprint(default)})" if default != nil
46
+ input_str << Log.color(:green, extra)
47
+ end
48
+
49
+ def self.input_doc(inputs, input_types = nil, input_descriptions = nil, input_defaults = nil, input_shortcuts = nil)
50
+ type = description = default = nil
51
+ shortcut = ""
52
+ seen = []
53
+ inputs.collect do |name|
54
+ next if seen.include? name
55
+ seen << name
56
+
57
+ type = input_types[name] unless input_types.nil?
58
+ description = input_descriptions[name] unless input_descriptions.nil?
59
+ default = input_defaults[name] unless input_defaults.nil?
60
+
61
+ name = name.to_s
62
+
63
+ case input_shortcuts
64
+ when nil, FalseClass
65
+ shortcut = nil
66
+ when Hash
67
+ shortcut = input_shortcuts[name]
68
+ when TrueClass
69
+ shortcut = fix_shortcut(name[0], name)
70
+ end
71
+
72
+ type = :string if type.nil?
73
+ register(shortcut, name, type, description) unless self.inputs.include? name
74
+
75
+ name = SOPT.input_format(name, type.to_sym, default, shortcut)
76
+ description
77
+ Misc.format_definition_list_item(name, description, 80, 31, nil)
78
+ end * "\n"
79
+ end
80
+
81
+ def self.doc
82
+ doc = <<-EOF
83
+ #{Log.color :magenta}#{command}(1) -- #{summary}
84
+ #{"=" * (command.length + summary.length + 7)}#{Log.color :reset}
85
+
86
+ #{ Log.color :magenta, "## SYNOPSYS"}
87
+
88
+ #{Log.color :blue, synopsys}
89
+
90
+ #{ Log.color :magenta, "## DESCRIPTION"}
91
+
92
+ #{Misc.format_paragraph description}
93
+
94
+ #{ Log.color :magenta, "## OPTIONS"}
95
+
96
+ #{input_doc(inputs, input_types, input_descriptions, input_defaults, input_shortcuts)}
97
+ EOF
98
+ end
99
+
100
+ def self.doc
101
+ doc = <<-EOF
102
+ #{Log.color :magenta}#{command}(1) -- #{summary}
103
+ #{"=" * (command.length + summary.length + 7)}#{Log.color :reset}
104
+
105
+ EOF
106
+
107
+ if synopsys and not synopsys.empty?
108
+ doc << Log.color(:magenta, "## SYNOPSYS") << "\n\n"
109
+ doc << Log.color(:blue, synopsys) << "\n\n"
110
+ end
111
+
112
+ if description and not description.empty?
113
+ doc << Log.color(:magenta, "## DESCRIPTION") << "\n\n"
114
+ doc << Misc.format_paragraph(description) << "\n\n"
115
+ end
116
+
117
+ doc << Log.color(:magenta, "## OPTIONS") << "\n\n"
118
+ doc << input_doc(inputs, input_types, input_descriptions, input_defaults, input_shortcuts)
119
+ end
120
+ end
@@ -0,0 +1,57 @@
1
+ module SOPT
2
+ GOT_OPTIONS= IndiferentHash.setup({})
3
+ def self.current_options=(options)
4
+ @@current_options = options
5
+ end
6
+ def self.consume(args = ARGV)
7
+ i = 0
8
+ @@current_options ||= {}
9
+ while i < args.length do
10
+ current = args[i]
11
+ break if current == "--"
12
+ if m = current.match(/--?(.+?)(?:=(.+))?$/)
13
+ key = $1
14
+ value = $2
15
+
16
+ input = inputs.include?(key)? key : shortcuts[key]
17
+
18
+ if input.nil?
19
+ i += 1
20
+ next
21
+ else
22
+ args.delete_at i
23
+ end
24
+ else
25
+ i += 1
26
+ next
27
+ end
28
+
29
+ if input_types[input] == :string
30
+ value = args.delete_at(i) if value.nil?
31
+ @@current_options[input] = value
32
+ else
33
+ if value.nil? and %w(F false FALSE no).include?(args[i])
34
+ Log.warn "Boolean values are best specified as #{current}=[true|false], not #{ current } [true|false]. Token '#{args[i]}' following '#{current}' automatically assigned as value"
35
+ value = args.delete_at(i)
36
+ end
37
+ @@current_options[input] = %w(F false FALSE no).include?(value)? false : true
38
+ end
39
+ end
40
+
41
+ IndiferentHash.setup @@current_options
42
+ GOT_OPTIONS.merge! @@current_options
43
+
44
+ @@current_options
45
+ end
46
+
47
+ def self.get(opt_str)
48
+ SOPT.parse(opt_str)
49
+ SOPT.consume(ARGV)
50
+ end
51
+
52
+ def self.require(options, *parameters)
53
+ parameters.flatten.each do |parameter|
54
+ raise ParameterException, "Parameter '#{ Log.color :blue, parameter }' not given" if options[parameter].nil?
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,66 @@
1
+ module SOPT
2
+ def self.fix_shortcut(short, long)
3
+ return short unless short and shortcuts.include?(short)
4
+
5
+ current = shortcuts.select{|s,l| l == long}.collect{|s,l| s }.first
6
+ return current if current
7
+
8
+ chars = long.chars.to_a
9
+ current = [chars.shift]
10
+ short = current * ""
11
+
12
+ if (shortcuts.include?(short) and not shortcuts[short] == long)
13
+ if long.index "-" or long.index "_"
14
+ parts = long.split(/[_-]/)
15
+ acc = parts.collect{|s| s[0] } * ""
16
+ return acc unless shortcuts.include? acc
17
+ elsif m = long.match(/(\d+)/)
18
+ n = m[0]
19
+ acc = long[0] + n
20
+ return acc unless shortcuts.include? acc
21
+ end
22
+ end
23
+
24
+ while shortcuts.include?(short) && shortcuts[short] != long
25
+ next_letter = chars.shift
26
+ next_letter = chars.shift while %w(. - _).include?(next_letter)
27
+ return nil if next_letter.nil?
28
+ current << next_letter
29
+ short = current * ""
30
+ end
31
+
32
+ return nil if shortcuts.include? short
33
+
34
+ short
35
+ end
36
+
37
+ def self.register(short, long, asterisk, description)
38
+ short = fix_shortcut(short, long)
39
+ shortcuts[short] = long if short
40
+ inputs << long
41
+ input_shortcuts[long] = short
42
+ input_descriptions[long] = description
43
+ input_types[long] = asterisk ? :string : :boolean
44
+ end
45
+
46
+ def self.parse(opt_str)
47
+ info = {}
48
+
49
+ inputs = []
50
+ if opt_str.include? "\n"
51
+ re = /\n+/
52
+ else
53
+ re = /:/
54
+ end
55
+ opt_str.split(re).each do |entry|
56
+ entry.strip!
57
+ next if entry.empty?
58
+ names, _sep, description = entry.partition /\s+/
59
+ short, long, asterisk = names.match(/\s*(?:-(.+))?(?:--(.+?))([*])?$/).values_at 1,2,3
60
+
61
+ inputs << long
62
+ register short, long, asterisk, description
63
+ end
64
+ inputs
65
+ end
66
+ end