fhlow 1.91.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.
- data/bin/fhlow +186 -0
- data/lib/module_cmdparse/cmdparse.rb +480 -0
- data/lib/module_cmdparse/cmdparse/wrappers/optparse.rb +65 -0
- data/lib/module_config/config.rb +67 -0
- data/lib/module_config/configexception.rb +18 -0
- data/lib/module_config/configitems/complex.rb +71 -0
- data/lib/module_config/configitems/complexpackage.rb +67 -0
- data/lib/module_config/configitems/complexunit.rb +76 -0
- data/lib/module_config/configitems/simple.rb +56 -0
- data/lib/module_config/configitems/simplearchitecture.rb +52 -0
- data/lib/module_config/configitems/simplepackage.rb +47 -0
- data/lib/module_config/configitems/simpleunit.rb +53 -0
- data/lib/module_config/configs.test +24 -0
- data/lib/module_config/item.rb +34 -0
- data/lib/module_config/itemfactory.rb +55 -0
- data/lib/module_config/section.rb +69 -0
- data/lib/module_config/test.flw +20 -0
- data/lib/module_config/test.rb +85 -0
- data/lib/module_config/tmp +3 -0
- data/lib/module_config/unittests/config_1.flw +14 -0
- data/lib/module_config/unittests/config_1_fixed.flw +14 -0
- data/lib/module_config/unittests/config_2.flw +15 -0
- data/lib/module_config/unittests/config_3a.flw +16 -0
- data/lib/module_config/unittests/config_3b.flw +15 -0
- data/lib/module_config/unittests/config_test.rb +579 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configexception_rb.html +647 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-complex_rb.html +700 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-complexpackage_rb.html +694 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-complexunit_rb.html +704 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-simple_rb.html +685 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-simplepackage_rb.html +676 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-configitems-simpleunit_rb.html +682 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-item_rb.html +663 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-itemfactory_rb.html +687 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-myconfig_rb.html +687 -0
- data/lib/module_config/unittests/coverage/-home-simon-tmp-fhlow_v2-flw-core-lib-module_config-section_rb.html +692 -0
- data/lib/module_config/unittests/coverage/index.html +689 -0
- data/lib/module_fhlow/fhlowexception.rb +55 -0
- data/lib/module_fhlow/leaf.rb +197 -0
- data/lib/module_fhlow/leaf.rb~ +202 -0
- data/lib/module_fhlow/leaffactory.rb +97 -0
- data/lib/module_fhlow/leafs/Package.rb +55 -0
- data/lib/module_fhlow/leafs/Unit.rb +152 -0
- data/lib/module_fhlow/log.rb +100 -0
- data/lib/module_fhlow/node.rb +206 -0
- data/lib/module_fhlow/pen.rb +101 -0
- data/lib/module_fhlow/plugin.rb +54 -0
- data/lib/module_fhlow/pluginpool.rb +81 -0
- data/lib/module_fhlow/rootnode.rb +98 -0
- data/lib/module_fhlow/test.rb +15 -0
- data/lib/module_term/ansicolor.rb +102 -0
- data/tests/testsuite.rb +20 -0
- metadata +106 -0
@@ -0,0 +1,65 @@
|
|
1
|
+
#
|
2
|
+
#--
|
3
|
+
#
|
4
|
+
# $Id: optparse.rb 329 2005-08-14 15:39:05Z thomas $
|
5
|
+
#
|
6
|
+
# cmdparse: advanced command line parser supporting commands
|
7
|
+
# Copyright (C) 2004 Thomas Leitner
|
8
|
+
#
|
9
|
+
# This program is free software; you can redistribute it and/or modify it under the terms of the GNU
|
10
|
+
# General Public License as published by the Free Software Foundation; either version 2 of the
|
11
|
+
# License, or (at your option) any later version.
|
12
|
+
#
|
13
|
+
# This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
|
14
|
+
# even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
15
|
+
# General Public License for more details.
|
16
|
+
#
|
17
|
+
# You should have received a copy of the GNU General Public License along with this program; if not,
|
18
|
+
# write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
19
|
+
#
|
20
|
+
#++
|
21
|
+
#
|
22
|
+
#
|
23
|
+
|
24
|
+
require 'optparse'
|
25
|
+
|
26
|
+
# Some extension to the standard option parser class
|
27
|
+
class OptionParser
|
28
|
+
|
29
|
+
if const_defined?( 'Officious' )
|
30
|
+
Officious.delete( 'version' )
|
31
|
+
Officious.delete( 'help' )
|
32
|
+
else
|
33
|
+
DefaultList.long.delete( 'version' )
|
34
|
+
DefaultList.long.delete( 'help' )
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
module CmdParse
|
40
|
+
|
41
|
+
# Parser wrapper for OptionParser (included in Ruby Standard Library).
|
42
|
+
class OptionParserWrapper < ParserWrapper
|
43
|
+
|
44
|
+
# Initializes the wrapper with a default OptionParser instance or the +parser+ parameter and
|
45
|
+
# yields this instance.
|
46
|
+
def initialize( parser = OptionParser.new, &block )
|
47
|
+
@instance = parser
|
48
|
+
self.instance( &block )
|
49
|
+
end
|
50
|
+
|
51
|
+
def order( args )
|
52
|
+
@instance.order( args )
|
53
|
+
end
|
54
|
+
|
55
|
+
def permute( args )
|
56
|
+
@instance.permute( args )
|
57
|
+
end
|
58
|
+
|
59
|
+
def summarize
|
60
|
+
@instance.summarize
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'section'
|
2
|
+
require 'configexception'
|
3
|
+
|
4
|
+
module Config
|
5
|
+
|
6
|
+
class Config
|
7
|
+
|
8
|
+
@@factoriesloaded = false
|
9
|
+
|
10
|
+
def initialize(_filename)
|
11
|
+
unless @@factoriesloaded
|
12
|
+
ItemFactory.load(File.join(File.dirname(__FILE__), "configitems"))
|
13
|
+
@@factoriesloaded = true
|
14
|
+
end
|
15
|
+
|
16
|
+
@filename = _filename
|
17
|
+
@sections = []
|
18
|
+
file = IO.read(_filename)
|
19
|
+
|
20
|
+
file.gsub!(/#.*$/, "") # clear all the comments
|
21
|
+
|
22
|
+
while !file.strip.empty?
|
23
|
+
file = file.sub(Section.getRegex) do |match|
|
24
|
+
begin
|
25
|
+
@sections.push(Section.new(match))
|
26
|
+
"" # delete the proceded string (mached string will be sub'ed with the return value of this block)
|
27
|
+
rescue ConfigException => e
|
28
|
+
raise ConfigException.new(self.class), " File: #{_filename}\n#{e.message}"
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def each(&block)
|
35
|
+
@sections.each(&block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def [](_section)
|
39
|
+
ifnotfound = lambda { raise Fhlow::FhlowException.new(@filename), "No such section. <#{_section}>" }
|
40
|
+
|
41
|
+
@sections.detect(ifnotfound) { |section| section.name == _section }
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
# merges _conf with self
|
46
|
+
def merge(_conf)
|
47
|
+
begin
|
48
|
+
_conf.each do |arg_section|
|
49
|
+
if s = @sections.detect { |section| section.name == arg_section.name }
|
50
|
+
s.merge(arg_section)
|
51
|
+
else
|
52
|
+
@sections.push(arg_section)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
rescue ConfigException => e
|
56
|
+
raise ConfigException.new(self.class), " File: #{_filename}\n#{e.message}"
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
# prints the configuration
|
61
|
+
def print()
|
62
|
+
puts "File: #{@filename}"
|
63
|
+
@sections.each { |s| s.print() }
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module Config
|
2
|
+
|
3
|
+
class ConfigException < StandardError
|
4
|
+
attr_reader :caller
|
5
|
+
|
6
|
+
def initialize(_caller, *_args)
|
7
|
+
super(_args)
|
8
|
+
@caller = _caller
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class ConfigWrongFormatException < ConfigException
|
13
|
+
end
|
14
|
+
|
15
|
+
class ConfigMergeException < ConfigException
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
require 'item'
|
2
|
+
require 'itemfactory'
|
3
|
+
require 'configexception'
|
4
|
+
|
5
|
+
module Config
|
6
|
+
|
7
|
+
class ComplexFactory < ItemFactory
|
8
|
+
|
9
|
+
|
10
|
+
def ComplexFactory.match(_str)
|
11
|
+
_str.sub!(Complex.getRegex, '')
|
12
|
+
return $&
|
13
|
+
end
|
14
|
+
|
15
|
+
def ComplexFactory.create(*_args)
|
16
|
+
Complex.new(*_args)
|
17
|
+
end
|
18
|
+
|
19
|
+
def ComplexFactory.priority
|
20
|
+
11
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
class Complex < Item
|
26
|
+
@@regex = /(\w+)\s*=(\s*|[\n]*)\{((\s*.+[;][\s\n]*)*(\s*.+[;]*[\s\n]*))\}/
|
27
|
+
@@subitemregex = /\A.+\Z/
|
28
|
+
|
29
|
+
attr_reader :name
|
30
|
+
attr_reader :value
|
31
|
+
|
32
|
+
def initialize(_str)
|
33
|
+
|
34
|
+
|
35
|
+
if _str =~ @@regex
|
36
|
+
@name = $1
|
37
|
+
@value = $3.strip.split(';').each do |item|
|
38
|
+
item.strip!
|
39
|
+
raise ConfigWrongFormatException.new(self.class), "Configuration subitem <#{item}> of item <#{name}> has wrong format." unless item =~ @@subitemregex
|
40
|
+
end
|
41
|
+
else
|
42
|
+
raise ConfigWrongFormatException.new(self.class), "Configuration item <#{_str}> has wrong format."
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
def merge(_complexitem)
|
47
|
+
raise ConfigMergeException.new(self.class), "Merge: Configuration items do not fit! <#{_complexitem}> is not of type #{self.class.to_s}." unless _complexitem.instance_of?(self.class)
|
48
|
+
raise ConfigMergeException.new(self.class), "Merge: Configuration items do not have the same name! <#{_complexitem.name}> vs. <#{@name}>" unless _complexitem.name == @name
|
49
|
+
|
50
|
+
@value = _complexitem.value | @value
|
51
|
+
end
|
52
|
+
|
53
|
+
def each(&block)
|
54
|
+
@value.each(&block)
|
55
|
+
end
|
56
|
+
|
57
|
+
def print
|
58
|
+
puts "<Complex>"
|
59
|
+
puts " #{@name} = {"
|
60
|
+
@value.each { |v| puts " #{v};" }
|
61
|
+
puts " }"
|
62
|
+
end
|
63
|
+
|
64
|
+
def Complex.getRegex()
|
65
|
+
@@regex
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
require 'item'
|
2
|
+
require 'itemfactory'
|
3
|
+
require 'configexception'
|
4
|
+
|
5
|
+
module Config
|
6
|
+
|
7
|
+
class ComplexPackageFactory < ItemFactory
|
8
|
+
|
9
|
+
def ComplexPackageFactory.match(_str)
|
10
|
+
_str.sub!(ComplexPackage.getRegex, '')
|
11
|
+
return $&
|
12
|
+
end
|
13
|
+
|
14
|
+
def ComplexPackageFactory.create(*_args)
|
15
|
+
ComplexPackage.new(*_args)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class ComplexPackage < Item
|
21
|
+
@@regex = /(\w*Package\w*)\s*=\s*\{((\s*[^\(\)]*[;]\s*)*(\s*[^\(\)\{]*[;]?\s*)?)\s*\}/
|
22
|
+
@@subitemregex = /\A.+\Z/
|
23
|
+
|
24
|
+
attr_reader :name
|
25
|
+
attr_reader :value
|
26
|
+
|
27
|
+
def initialize(_str)
|
28
|
+
if _str =~ @@regex
|
29
|
+
@name = $1
|
30
|
+
@value = Array.new
|
31
|
+
$2.strip.split(';').each do |item|
|
32
|
+
item.strip!
|
33
|
+
raise ConfigWrongFormatException.new(self.class), "Configuration subitem \n -----\n #{item} \n -----\n of item <#{name}> has wrong format." unless item =~ @@subitemregex
|
34
|
+
@value.push(item.split(/,\s/))
|
35
|
+
end
|
36
|
+
else
|
37
|
+
raise ConfigWrongFormatException.new(self.class), "Configuration item <#{_str}> has wrong format."
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def merge(_complexitem)
|
42
|
+
raise ConfigMergeException.new(self.class), "Merge: Configuration items do not fit! <#{_complexitem}> is not of type #{self.class.to_s}." unless _complexitem.instance_of?(self.class)
|
43
|
+
raise ConfigMergeException.new(self.class), "Merge: Configuration items do not have the same name! <#{_complexitem.name}> vs. <#{@name}>" unless _complexitem.name == @name
|
44
|
+
|
45
|
+
@value = _complexitem.value | @value
|
46
|
+
end
|
47
|
+
|
48
|
+
def each(&block)
|
49
|
+
@value.each(&block)
|
50
|
+
end
|
51
|
+
|
52
|
+
def print
|
53
|
+
puts "<ComplexPackage>"
|
54
|
+
puts " #{@name} = {"
|
55
|
+
@value.each { |v| puts " #{v};" }
|
56
|
+
puts " }"
|
57
|
+
end
|
58
|
+
|
59
|
+
def ComplexPackage.getRegex()
|
60
|
+
@@regex
|
61
|
+
end
|
62
|
+
|
63
|
+
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
67
|
+
|
@@ -0,0 +1,76 @@
|
|
1
|
+
require 'item'
|
2
|
+
require 'itemfactory'
|
3
|
+
require 'configexception'
|
4
|
+
|
5
|
+
module Config
|
6
|
+
|
7
|
+
class ComplexUnitFactory < ItemFactory
|
8
|
+
|
9
|
+
def ComplexUnitFactory.match(_str)
|
10
|
+
_str.sub!(ComplexUnit.getRegex, '')
|
11
|
+
return $&
|
12
|
+
end
|
13
|
+
|
14
|
+
def ComplexUnitFactory.create(*_args)
|
15
|
+
ComplexUnit.new(*_args)
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
|
20
|
+
class ComplexUnit < Item
|
21
|
+
@@regex = /(\w*Unit\w*)\s*=\s*\{((\s*.*[;]\s*)*(\s*.*\(.*\)[;]*\s*)?)\s*\}/
|
22
|
+
@@subitemregex = /\A(([\w\/]+,\s*)*[\w\/]+)\s*\((.+)\)\Z/
|
23
|
+
|
24
|
+
attr_reader :name
|
25
|
+
attr_reader :value
|
26
|
+
|
27
|
+
def initialize(_str)
|
28
|
+
if _str =~ @@regex
|
29
|
+
@name = $1
|
30
|
+
@value = Array.new
|
31
|
+
|
32
|
+
$2.strip.split(';').each do |item|
|
33
|
+
|
34
|
+
item.strip!
|
35
|
+
if item =~ @@subitemregex
|
36
|
+
item = Hash.new
|
37
|
+
item["deppath"] = $1
|
38
|
+
item["archs"] = $3.split(/,\s*/)
|
39
|
+
item["deppath"] = item["deppath"].split(/,\s*/)
|
40
|
+
@value.push(item)
|
41
|
+
else
|
42
|
+
raise ConfigWrongFormatException.new(self.class), "Configuration subitem <#{item}> of item <#{name}> has wrong format."
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
else
|
47
|
+
raise ConfigWrongFormatException.new(self.class), "Configuration item <#{_str}> has wrong format."
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def merge(_complexitem)
|
52
|
+
raise ConfigMergeException.new(self.class), "Merge: Configuration items do not fit! <#{_complexitem}> is not of type #{self.class.to_s}." unless _complexitem.instance_of?(self.class)
|
53
|
+
raise ConfigMergeException.new(self.class), "Merge: Configuration items do not have the same name! <#{_complexitem.name}> vs. <#{@name}>" unless _complexitem.name == @name
|
54
|
+
|
55
|
+
@value = _complexitem.value | @value
|
56
|
+
end
|
57
|
+
|
58
|
+
def each(&block)
|
59
|
+
@value.each(&block)
|
60
|
+
end
|
61
|
+
|
62
|
+
def print
|
63
|
+
puts "<ComplexUnit>"
|
64
|
+
puts " #{@name} = {"
|
65
|
+
@value.each { |v| puts " #{v["deppath"].join(", ")}(#{v["archs"].join(", ")});" }
|
66
|
+
puts " }"
|
67
|
+
end
|
68
|
+
|
69
|
+
def ComplexUnit.getRegex()
|
70
|
+
@@regex
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
76
|
+
|
@@ -0,0 +1,56 @@
|
|
1
|
+
require 'item'
|
2
|
+
require 'itemfactory'
|
3
|
+
require 'configexception'
|
4
|
+
|
5
|
+
module Config
|
6
|
+
|
7
|
+
class SimpleFactory < ItemFactory
|
8
|
+
|
9
|
+
def SimpleFactory.match(_str)
|
10
|
+
_str.sub!(Simple.getRegex(), '')
|
11
|
+
return $&
|
12
|
+
end
|
13
|
+
|
14
|
+
def SimpleFactory.create(*_args)
|
15
|
+
Simple.new(*_args)
|
16
|
+
end
|
17
|
+
|
18
|
+
def SimpleFactory.priority
|
19
|
+
10
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
|
24
|
+
class Simple < Item
|
25
|
+
@@regex = /^\s*(\w+)\s*=\s*([\w,\/.;\t ]+)[\t ]*$/
|
26
|
+
|
27
|
+
attr_reader :name
|
28
|
+
attr_reader :value
|
29
|
+
|
30
|
+
def initialize(_str)
|
31
|
+
|
32
|
+
if _str =~ @@regex
|
33
|
+
@name = $1
|
34
|
+
@value = $2
|
35
|
+
else
|
36
|
+
raise ConfigWrongFormatException.new(self.class), "Configuration item <#{_str}> has wrong format."
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def merge(_item)
|
41
|
+
raise ConfigMergeException.new(self.class), "Merge: Configuration items do not fit! <#{_item}> is not of type #{self.class.to_s}." unless _item.instance_of?(self.class)
|
42
|
+
raise ConfigMergeException.new(self.class), "Merge: Configuration items do not have the same name! <#{_item.name}> vs. <#{@name}>" unless _item.name == @name
|
43
|
+
|
44
|
+
@value = _item.value
|
45
|
+
end
|
46
|
+
|
47
|
+
|
48
|
+
def Simple.getRegex()
|
49
|
+
@@regex
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
end
|
56
|
+
|
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'item'
|
2
|
+
require 'itemfactory'
|
3
|
+
require 'configexception'
|
4
|
+
|
5
|
+
module Config
|
6
|
+
|
7
|
+
class SimpleArchitectureFactory < ItemFactory
|
8
|
+
def SimpleArchitectureFactory.match(_str)
|
9
|
+
_str.sub!(SimpleArchitecture.getRegex, '')
|
10
|
+
return $&
|
11
|
+
end
|
12
|
+
|
13
|
+
def SimpleArchitectureFactory.create(*_args)
|
14
|
+
SimpleArchitecture.new(*_args)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
|
19
|
+
class SimpleArchitecture < Item
|
20
|
+
@@regex = /^\s*(\w*Architecture\w*)\s*=\s*((\s*\w+\s*[,])*\s*\w+)*\s*$/
|
21
|
+
|
22
|
+
attr_reader :name
|
23
|
+
attr_reader :value
|
24
|
+
|
25
|
+
def initialize(_str)
|
26
|
+
if _str =~ @@regex
|
27
|
+
@name = $1
|
28
|
+
@value = Array.new
|
29
|
+
@value = $2.split(/,\s*/) if $2
|
30
|
+
else
|
31
|
+
raise ConfigWrongFormatException.new(self.class), "Configuration item <#{_str}> has wrong format."
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def merge(_item)
|
36
|
+
raise ConfigMergeException.new(self.class), "Merge: Configuration items do not fit! <#{_item}> is not of type #{self.class.to_s}." unless _item.instance_of?(self.class)
|
37
|
+
raise ConfigMergeException.new(self.class), "Merge: Configuration items do not have the same name! <#{_item.name}> vs. <#{@name}>" unless _item.name == @name
|
38
|
+
|
39
|
+
@value = _item.value
|
40
|
+
end
|
41
|
+
|
42
|
+
def print
|
43
|
+
puts "<#{self.class.to_s}> -> ".ljust(40)+" #{@name} = #{@value.join(", ")}"
|
44
|
+
end
|
45
|
+
|
46
|
+
def SimpleArchitecture.getRegex()
|
47
|
+
@@regex
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
end
|