scout-gear 1.1.1 → 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,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
@@ -0,0 +1,26 @@
1
+ module SOPT
2
+
3
+ def self.setup(str)
4
+ parts = str.split(/\n\n+/)
5
+
6
+ summary = parts.shift unless parts.first =~ /^\s*\$-/
7
+ synopsys = parts.shift if parts.first =~ /^\s*\$/
8
+
9
+ description = []
10
+ while parts.first and parts.first !~ /^\s*-/
11
+ description << parts.shift
12
+ end
13
+ description = description * "\n\n"
14
+
15
+ options = parts.collect{|part| part.split("\n").select{|l| l=~ /^\s*-/ } }.flatten.compact * "\n"
16
+
17
+ synopsys.sub!(/^\$\s+/,'') if synopsys
18
+
19
+ SOPT.summary = summary.strip if summary
20
+ SOPT.synopsys = synopsys.strip if synopsys
21
+ SOPT.description = description.strip if description
22
+ SOPT.parse options if options
23
+
24
+ SOPT.consume
25
+ end
26
+ end
@@ -0,0 +1,5 @@
1
+ require_relative 'simple_opt/accessor'
2
+ require_relative 'simple_opt/parse'
3
+ require_relative 'simple_opt/doc'
4
+ require_relative 'simple_opt/get'
5
+ require_relative 'simple_opt/setup'
data/lib/scout/tmpfile.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require 'fileutils'
2
+ require_relative 'misc'
2
3
 
3
4
  module TmpFile
4
5
  def self.user_tmp(subdir = nil)
@@ -83,13 +84,8 @@ module TmpFile
83
84
 
84
85
  def self.in_dir(*args)
85
86
  with_dir(*args) do |dir|
86
- old_pwd = FileUtils.pwd
87
- begin
88
- FileUtils.mkdir_p dir unless File.exist?(dir)
89
- FileUtils.cd dir
87
+ Misc.in_dir dir do
90
88
  yield dir
91
- ensure
92
- FileUtils.cd old_pwd
93
89
  end
94
90
  end
95
91
  end
data/lib/scout-gear.rb CHANGED
@@ -1,3 +1,5 @@
1
1
  require_relative 'scout/indiferent_hash'
2
2
  require_relative 'scout/tmpfile'
3
3
  require_relative 'scout/log'
4
+ require_relative 'scout/path'
5
+ require_relative 'scout/simple_opt'
data/scout-gear.gemspec CHANGED
@@ -2,16 +2,16 @@
2
2
  # DO NOT EDIT THIS FILE DIRECTLY
3
3
  # Instead, edit Juwelier::Tasks in Rakefile, and run 'rake gemspec'
4
4
  # -*- encoding: utf-8 -*-
5
- # stub: scout-gear 1.1.1 ruby lib
5
+ # stub: scout-gear 2.0.0 ruby lib
6
6
 
7
7
  Gem::Specification.new do |s|
8
8
  s.name = "scout-gear".freeze
9
- s.version = "1.1.1"
9
+ s.version = "2.0.0"
10
10
 
11
11
  s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version=
12
12
  s.require_paths = ["lib".freeze]
13
13
  s.authors = ["Miguel Vazquez".freeze]
14
- s.date = "2023-03-24"
14
+ s.date = "2023-03-30"
15
15
  s.description = "Temporary files, logs, etc.".freeze
16
16
  s.email = "mikisvaz@gmail.com".freeze
17
17
  s.executables = ["scout".freeze]
@@ -21,6 +21,7 @@ Gem::Specification.new do |s|
21
21
  ]
22
22
  s.files = [
23
23
  ".document",
24
+ ".gitmodules",
24
25
  ".vimproject",
25
26
  "LICENSE.txt",
26
27
  "README.rdoc",
@@ -40,16 +41,31 @@ Gem::Specification.new do |s|
40
41
  "lib/scout/log/progress/report.rb",
41
42
  "lib/scout/log/progress/util.rb",
42
43
  "lib/scout/meta_extension.rb",
44
+ "lib/scout/misc.rb",
45
+ "lib/scout/misc/format.rb",
43
46
  "lib/scout/path.rb",
44
47
  "lib/scout/path/find.rb",
48
+ "lib/scout/path/util.rb",
49
+ "lib/scout/simple_opt.rb",
50
+ "lib/scout/simple_opt/accessor.rb",
51
+ "lib/scout/simple_opt/doc.rb",
52
+ "lib/scout/simple_opt/get.rb",
53
+ "lib/scout/simple_opt/parse.rb",
54
+ "lib/scout/simple_opt/setup.rb",
45
55
  "lib/scout/tmpfile.rb",
46
56
  "scout-gear.gemspec",
57
+ "test/scout/indiferent_hash/test_case_insensitive.rb",
47
58
  "test/scout/indiferent_hash/test_options.rb",
48
59
  "test/scout/log/test_progress.rb",
49
60
  "test/scout/path/test_find.rb",
61
+ "test/scout/path/test_util.rb",
62
+ "test/scout/simple_opt/test_get.rb",
63
+ "test/scout/simple_opt/test_parse.rb",
64
+ "test/scout/simple_opt/test_setup.rb",
50
65
  "test/scout/test_indiferent_hash.rb",
51
66
  "test/scout/test_log.rb",
52
67
  "test/scout/test_meta_extension.rb",
68
+ "test/scout/test_misc.rb",
53
69
  "test/scout/test_path.rb",
54
70
  "test/scout/test_tmpfile.rb",
55
71
  "test/test_helper.rb"
@@ -0,0 +1,16 @@
1
+ require File.expand_path(__FILE__).sub(%r(/test/.*), '/test/test_helper.rb')
2
+ require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')
3
+
4
+ class TestCaseInsensitiveHash < Test::Unit::TestCase
5
+ def test_case_insensitive_hash
6
+ a = {:a => 1, "b" => 2}
7
+ a.extend CaseInsensitiveHash
8
+
9
+ assert_equal 1, a[:a]
10
+ assert_equal 1, a["A"]
11
+ assert_equal 1, a[:A]
12
+ assert_equal 2, a["B"]
13
+ assert_equal 2, a[:b]
14
+ end
15
+ end
16
+
@@ -1,6 +1,8 @@
1
1
  require File.expand_path(__FILE__).sub(%r(/test/.*), '/test/test_helper.rb')
2
2
  require 'scout/path'
3
+ require 'scout/misc'
3
4
  require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')
5
+
4
6
  class TestPathFind < Test::Unit::TestCase
5
7
  def test_parts
6
8
  path = Path.setup("share/data/some_file", 'scout')
@@ -8,7 +10,7 @@ class TestPathFind < Test::Unit::TestCase
8
10
  assert_equal "data/some_file", path._subpath
9
11
 
10
12
  path = Path.setup("data", 'scout')
11
- assert_equal nil, path._toplevel
13
+ assert_equal "", path._toplevel
12
14
  assert_equal "data", path._subpath
13
15
  end
14
16
 
@@ -30,5 +32,44 @@ class TestPathFind < Test::Unit::TestCase
30
32
  assert_equal File.join(tmpdir,"share/data/some_file"), path.find(:current)
31
33
  end
32
34
  end
35
+
36
+ def test_current_find
37
+ path = Path.setup("share/data/some_file", 'scout')
38
+ TmpFile.in_dir do |tmpdir|
39
+ FileUtils.mkdir_p(File.dirname(File.join(tmpdir, path)))
40
+ File.write(File.join(tmpdir, path), 'string')
41
+ assert_equal File.join(tmpdir,"share/data/some_file"), path.find
42
+ assert_equal :current, path.find.where
43
+ assert_equal "share/data/some_file", path.find.original
44
+ end
45
+ end
46
+
47
+ def test_current_find_all
48
+ path = Path.setup("share/data/some_file", 'scout')
49
+ TmpFile.with_dir do |tmpdir|
50
+ Path.setup tmpdir
51
+
52
+ FileUtils.mkdir_p(tmpdir.lib)
53
+ FileUtils.mkdir_p(tmpdir.share.data)
54
+ File.write(tmpdir.share.data.some_file, 'string')
55
+
56
+ FileUtils.mkdir_p(tmpdir.subdir.share.data)
57
+ File.write(tmpdir.subdir.share.data.some_file, 'string')
58
+
59
+ path.libdir = tmpdir
60
+ Misc.in_dir tmpdir.subdir do
61
+ assert_equal 2, path.find_all.length
62
+ end
63
+ end
64
+ end
65
+
66
+ def test_located?
67
+
68
+ p = Path.setup("/tmp/foo/bar")
69
+ assert p.located?
70
+ assert_equal p, p.find
71
+
72
+ end
73
+
33
74
  end
34
75
 
@@ -0,0 +1,22 @@
1
+ require File.expand_path(__FILE__).sub(%r(/test/.*), '/test/test_helper.rb')
2
+ require 'scout/path'
3
+ require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')
4
+
5
+ class TestPathUtil < Test::Unit::TestCase
6
+ def test_dirname
7
+ p = Path.setup("/usr/share/scout/data")
8
+
9
+ assert_equal "/usr/share/scout", p.dirname
10
+ end
11
+
12
+ def test_glob
13
+ TmpFile.in_dir :erase => false do |tmpdir|
14
+ Path.setup tmpdir
15
+ File.write(tmpdir.foo, 'foo')
16
+ File.write(tmpdir.bar, 'bar')
17
+ assert_equal 2, tmpdir.glob.length
18
+ assert_equal %w(foo bar).sort, tmpdir.glob.collect{|p| p.basename }.sort
19
+ end
20
+ end
21
+ end
22
+
@@ -0,0 +1,11 @@
1
+ require File.expand_path(__FILE__).sub(%r(/test/.*), '/test/test_helper.rb')
2
+ require 'scout/simple_opt'
3
+ require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')
4
+
5
+ class TestSOPTParse < Test::Unit::TestCase
6
+ def test_consume
7
+ SOPT.parse("-f--first* first arg:-f--fun")
8
+ args = "-f myfile --fun".split(" ")
9
+ assert_equal "myfile", SOPT.consume(args)[:first]
10
+ end
11
+ end
@@ -0,0 +1,10 @@
1
+ require File.expand_path(__FILE__).sub(%r(/test/.*), '/test/test_helper.rb')
2
+ require 'scout/path'
3
+ require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')
4
+
5
+ class TestSOPTParse < Test::Unit::TestCase
6
+ def test_parse
7
+ SOPT.parse("-f--first* first arg:-f--fun")
8
+ assert_equal "fun", SOPT.shortcuts["fu"]
9
+ end
10
+ end
@@ -0,0 +1,77 @@
1
+ require File.expand_path(__FILE__).sub(%r(/test/.*), '/test/test_helper.rb')
2
+ require 'scout/simple_opt'
3
+ require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')
4
+
5
+ class TestSOPTSetup < Test::Unit::TestCase
6
+ def setup
7
+ SOPT.inputs = nil
8
+ SOPT.input_types = nil
9
+ SOPT.input_descriptions = nil
10
+ SOPT.input_shortcuts = nil
11
+ end
12
+
13
+ def test_setup
14
+ SOPT.setup <<-EOF
15
+ Test application
16
+
17
+ $ test cmd -arg 1
18
+
19
+ It does some imaginary stuff
20
+
21
+ -a--arg* Argument
22
+ -a2--arg2* Argument
23
+
24
+ EOF
25
+
26
+ assert_equal "test cmd -arg 1", SOPT.synopsys
27
+ assert SOPT.inputs.include? "arg"
28
+ assert SOPT.inputs.include? "arg2"
29
+ end
30
+
31
+ def test_setup_alt
32
+ SOPT.setup <<-EOF
33
+ Test application
34
+
35
+ It does some imaginary stuff
36
+
37
+ -a--arg* Argument
38
+ -a2--arg2* Argument
39
+
40
+ EOF
41
+
42
+ assert SOPT.inputs.include? "arg"
43
+ assert SOPT.inputs.include? "arg2"
44
+ end
45
+
46
+ def test_setup_alt2
47
+ SOPT.setup <<-EOF
48
+ Test application
49
+
50
+ -a--arg* Argument
51
+ -a2--arg2* Argument
52
+
53
+ EOF
54
+
55
+ assert SOPT.inputs.include? "arg"
56
+ assert SOPT.inputs.include? "arg2"
57
+ end
58
+
59
+ def test_setup_alt3
60
+ SOPT.setup <<-EOF
61
+ Pulls the values from a tsv colum
62
+
63
+ $ rbbt tsv values [options] <filename.tsv|->
64
+
65
+ Use - to read from STDIN
66
+
67
+ -tch--tokyocabinet File is a tokyocabinet hash database
68
+ -tcb--tokyocabinet_bd File is a tokyocabinet B database
69
+ -h--help Print this help
70
+ EOF
71
+
72
+ assert SOPT.inputs.include? "tokyocabinet"
73
+ end
74
+
75
+ end
76
+
77
+
@@ -5,10 +5,10 @@ class TestMetaExtension < Test::Unit::TestCase
5
5
  module ExtensionClass
6
6
  extend MetaExtension
7
7
 
8
- extension_attr :code
8
+ extension_attr :code, :code2
9
9
  end
10
10
 
11
- def test_setup
11
+ def test_setup_annotate
12
12
  str = "String"
13
13
  ExtensionClass.setup(str, :code)
14
14
  assert ExtensionClass === str
@@ -18,5 +18,19 @@ class TestMetaExtension < Test::Unit::TestCase
18
18
  str.annotate(str2)
19
19
  assert_equal :code, str2.code
20
20
  end
21
+
22
+ def test_setup_alternatives
23
+ str = "String"
24
+
25
+ ExtensionClass.setup(str, :code2 => :code)
26
+ assert_equal :code, str.code2
27
+
28
+ ExtensionClass.setup(str, code2: :code)
29
+ assert_equal :code, str.code2
30
+
31
+ ExtensionClass.setup(str, "code2" => :code)
32
+ assert_equal :code, str.code2
33
+
34
+ end
21
35
  end
22
36
 
@@ -0,0 +1,13 @@
1
+ require File.expand_path(__FILE__).sub(%r(/test/.*), '/test/test_helper.rb')
2
+ require File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')
3
+
4
+ class TestMisc < Test::Unit::TestCase
5
+ def test_in_dir
6
+ TmpFile.with_file do |tmpdir|
7
+ Misc.in_dir tmpdir do
8
+ assert_equal tmpdir, FileUtils.pwd
9
+ end
10
+ end
11
+ end
12
+ end
13
+
@@ -32,5 +32,21 @@ class TestPath < Test::Unit::TestCase
32
32
  assert_equal 'scout', path.pkgdir
33
33
  assert path.libdir.end_with?("scout-gear")
34
34
  end
35
+
36
+ def test_lib_dir
37
+ TmpFile.with_file do |tmpdir|
38
+ Path.setup tmpdir
39
+ FileUtils.mkdir_p tmpdir.lib
40
+ File.write tmpdir.lib.file, <<-EOR
41
+ require '#{File.expand_path(__FILE__).sub(%r(.*/test/), '').sub(/test_(.*)\.rb/,'\1')}'
42
+ a = "1"
43
+ Path.setup(a)
44
+ print a.libdir
45
+ EOR
46
+ Misc.in_dir tmpdir.tmp do
47
+ assert_equal tmpdir, `ruby #{tmpdir.lib.file}`
48
+ end
49
+ end
50
+ end
35
51
  end
36
52