snippr 0.3.0 → 0.13.1
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/.gitignore +13 -0
- data/.rspec +2 -0
- data/.travis.yml +8 -0
- data/Gemfile +4 -0
- data/README.md +143 -0
- data/Rakefile +5 -4
- data/lib/snippr.rb +14 -2
- data/lib/snippr/i18n.rb +9 -10
- data/lib/snippr/links.rb +59 -0
- data/lib/snippr/meta_data.rb +33 -0
- data/lib/snippr/normalizer.rb +18 -0
- data/lib/snippr/normalizer/camelizer.rb +18 -0
- data/lib/snippr/normalizer/de_rester.rb +25 -0
- data/lib/snippr/path.rb +27 -6
- data/lib/snippr/processor.rb +18 -0
- data/lib/snippr/processor/dynamics.rb +31 -0
- data/lib/snippr/processor/functions.rb +50 -0
- data/lib/snippr/processor/links.rb +20 -0
- data/lib/snippr/processor/wikilinks.rb +20 -0
- data/lib/snippr/snip.rb +57 -0
- data/lib/snippr/snippr.rb +41 -45
- data/lib/snippr/view_helper.rb +71 -0
- data/snippr.gemspec +30 -0
- data/spec/fixtures/a/path/aSnippet.snip +1 -0
- data/spec/fixtures/a/path/aSnippetWithParam.snip +1 -0
- data/spec/fixtures/controller/action/aSnippet.snip +1 -0
- data/spec/fixtures/empty.snip +3 -0
- data/spec/fixtures/i18n/list_de.snip +0 -0
- data/spec/fixtures/meta/broken.snip +5 -0
- data/spec/fixtures/meta/withContent.snip +5 -0
- data/spec/fixtures/meta/withContentNoNewline.snip +4 -0
- data/spec/fixtures/meta/withNoContent.snip +4 -0
- data/spec/fixtures/withUnderscore/andUnderscore/aSnippet.snip +1 -0
- data/spec/fixtures/withViewHelperMethod.snip +1 -0
- data/spec/snippr/i18n_spec.rb +30 -0
- data/spec/snippr/links_spec.rb +137 -0
- data/spec/snippr/normalizer/camelizer_spec.rb +13 -0
- data/spec/snippr/normalizer/de_rester_spec.rb +24 -0
- data/spec/snippr/normalizer_spec.rb +40 -0
- data/spec/snippr/path_spec.rb +87 -0
- data/spec/snippr/processor/dynamics_spec.rb +49 -0
- data/spec/snippr/processor/functions_spec.rb +72 -0
- data/spec/snippr/processor/links_spec.rb +12 -0
- data/spec/snippr/processor/wikilinks_spec.rb +12 -0
- data/spec/snippr/processor_spec.rb +35 -0
- data/spec/snippr/snip_spec.rb +179 -0
- data/spec/snippr/snippr_spec.rb +60 -55
- data/spec/snippr/view_helper_spec.rb +221 -0
- data/spec/spec_helper.rb +17 -3
- metadata +178 -87
- data/README.rdoc +0 -77
- data/lib/snippr/core_ext.rb +0 -12
- data/lib/snippr/helper.rb +0 -23
- data/lib/snippr/link.rb +0 -26
- data/spec/fixtures/tariff/einheit.snip +0 -1
- data/spec/fixtures/wiki.snip +0 -1
- data/spec/snippr/core_ext_spec.rb +0 -11
@@ -0,0 +1 @@
|
|
1
|
+
an underscored snippet with param {param}
|
@@ -0,0 +1 @@
|
|
1
|
+
with helper *{view.helper_method("TEST")}*
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Snippr::I18n do
|
4
|
+
|
5
|
+
describe "enabled" do
|
6
|
+
|
7
|
+
it "should store the enabled state" do
|
8
|
+
subject.enabled = nil
|
9
|
+
subject.enabled?.should == false
|
10
|
+
subject.enabled = true
|
11
|
+
subject.enabled?.should == true
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
describe "locale" do
|
17
|
+
|
18
|
+
it "should return an empty string when I18n is not enabled" do
|
19
|
+
subject.enabled = false
|
20
|
+
subject.locale.should == ''
|
21
|
+
end
|
22
|
+
|
23
|
+
it "should return the locale string when I18n is enabled" do
|
24
|
+
subject.enabled = true
|
25
|
+
subject.locale.should == "_#{I18n.locale}"
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
@@ -0,0 +1,137 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Snippr::Links do
|
4
|
+
|
5
|
+
describe "adjust_urls_except" do
|
6
|
+
|
7
|
+
it "should default to [/^#/, /^[a-z]+:/i]" do
|
8
|
+
subject.adjust_urls_except = nil
|
9
|
+
subject.adjust_urls_except.should == [/^#/, /^[a-z]+:/i]
|
10
|
+
end
|
11
|
+
|
12
|
+
it "should store exceptions" do
|
13
|
+
subject.adjust_urls_except = [/^cms.*/]
|
14
|
+
subject.adjust_urls_except.should == [/^cms.*/]
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
describe ".adjust_link" do
|
20
|
+
|
21
|
+
it "should return an a without href unchanged" do
|
22
|
+
subject.adjust_link('<a name="headline"/>').should == '<a name="headline"/>'
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should adjust the href to the adjustd_url" do
|
26
|
+
subject.expects(:adjust_url).with('url').returns('adjustd_url')
|
27
|
+
subject.adjust_link('<a onclick="return true;" href="url" class="link">test</a>').should == '<a onclick="return true;" href="adjustd_url" class="link">test</a>'
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should add an onclick when href starts with popup:" do
|
31
|
+
subject.expects(:adjust_url).with('popup_url').returns('adjustd_url')
|
32
|
+
subject.adjust_link('<a href="popup:popup_url" class="link">test</a>').should == '<a href="adjustd_url" class="link" onclick="if (typeof popup == \'undefined\') { return true; } else { popup(this); return false; }">test</a>'
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should replace an existing onclick when href starts with popup:" do
|
36
|
+
subject.expects(:adjust_url).with('url').returns('adjustd_url')
|
37
|
+
subject.adjust_link('<a onclick="return true;" href="popup://url" class="link">test</a>').should == '<a onclick="if (typeof popup == \'undefined\') { return true; } else { popup(this); return false; }" href="adjustd_url" class="link">test</a>'
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
41
|
+
|
42
|
+
describe ".adjust_url" do
|
43
|
+
|
44
|
+
before do
|
45
|
+
subject.stubs(:relative_url_root).returns('/root/')
|
46
|
+
end
|
47
|
+
|
48
|
+
it "should leave absolute links (http) as is" do
|
49
|
+
subject.adjust_url('http://www.blaulabs.de').should == 'http://www.blaulabs.de'
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should leave absolute links (https) as is" do
|
53
|
+
subject.adjust_url('https://www.blaulabs.de').should == 'https://www.blaulabs.de'
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should leave special links (mailto) as is" do
|
57
|
+
subject.adjust_url('mailto:info@blau.de').should == 'mailto:info@blau.de'
|
58
|
+
end
|
59
|
+
|
60
|
+
it "should convert relative links to server absolute links" do
|
61
|
+
subject.adjust_url('relative.html').should == '/root/relative.html'
|
62
|
+
end
|
63
|
+
|
64
|
+
it "should convert app absolute links to server absolute links" do
|
65
|
+
subject.adjust_url('/absolute.html').should == '/root/absolute.html'
|
66
|
+
end
|
67
|
+
|
68
|
+
it "should leave internal links (#) as is" do
|
69
|
+
subject.adjust_url('#aname').should == '#aname'
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should convert links excepted in next test to server absolute links" do
|
73
|
+
subject.adjust_url('/cms/excepted.html').should == '/root/cms/excepted.html'
|
74
|
+
end
|
75
|
+
|
76
|
+
it "should leave server absolute links as is" do
|
77
|
+
subject.adjust_url('/root/bla/absolute.html').should == '/root/bla/absolute.html'
|
78
|
+
end
|
79
|
+
|
80
|
+
it "should leave excepted links as is" do
|
81
|
+
subject.adjust_urls_except << /^\/cms\//
|
82
|
+
subject.adjust_url('/cms/excepted.html').should == '/cms/excepted.html'
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
describe ".relative_url_root" do
|
88
|
+
|
89
|
+
it "should return / without rails" do
|
90
|
+
subject.relative_url_root.should == '/'
|
91
|
+
end
|
92
|
+
|
93
|
+
context "with rails" do
|
94
|
+
|
95
|
+
before do
|
96
|
+
module ::ActionController; class Base; end; end
|
97
|
+
end
|
98
|
+
|
99
|
+
after do
|
100
|
+
Object.send :remove_const, :ActionController
|
101
|
+
end
|
102
|
+
|
103
|
+
it "should return / with relative_url_root set to nil" do
|
104
|
+
ActionController::Base.stubs(:config).returns(stub :relative_url_root => nil)
|
105
|
+
subject.relative_url_root.should == '/'
|
106
|
+
end
|
107
|
+
|
108
|
+
it "should return / with relative_url_root set to empty string" do
|
109
|
+
ActionController::Base.stubs(:config).returns(stub :relative_url_root => '')
|
110
|
+
subject.relative_url_root.should == '/'
|
111
|
+
end
|
112
|
+
|
113
|
+
it "should return / with relative_url_root set to /" do
|
114
|
+
ActionController::Base.stubs(:config).returns(stub :relative_url_root => '/')
|
115
|
+
subject.relative_url_root.should == '/'
|
116
|
+
end
|
117
|
+
|
118
|
+
it "should return /root/ with relative_url_root set to root" do
|
119
|
+
ActionController::Base.stubs(:config).returns(stub :relative_url_root => 'root')
|
120
|
+
subject.relative_url_root.should == '/root/'
|
121
|
+
end
|
122
|
+
|
123
|
+
it "should return /root/ with relative_url_root set to /root" do
|
124
|
+
ActionController::Base.stubs(:config).returns(stub :relative_url_root => '/root')
|
125
|
+
subject.relative_url_root.should == '/root/'
|
126
|
+
end
|
127
|
+
|
128
|
+
it "should return /root/ with relative_url_root set to /root/" do
|
129
|
+
ActionController::Base.stubs(:config).returns(stub :relative_url_root => '/root/')
|
130
|
+
subject.relative_url_root.should == '/root/'
|
131
|
+
end
|
132
|
+
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Snippr::Normalizer::Camelizer do
|
4
|
+
|
5
|
+
it "should leave a string as is" do
|
6
|
+
subject.normalize("blaHui_ja").should == "blaHui_ja"
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should lower camelize a symbol" do
|
10
|
+
subject.normalize(:symbol_with_separators).should == "symbolWithSeparators"
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Snippr::Normalizer::DeRester do
|
4
|
+
|
5
|
+
{
|
6
|
+
"show" => "show",
|
7
|
+
"destroy" => "show",
|
8
|
+
"new" => "new",
|
9
|
+
"create" => "new",
|
10
|
+
"edit" => "edit",
|
11
|
+
"update" => "edit",
|
12
|
+
:show => "show",
|
13
|
+
:destroy => "show",
|
14
|
+
:new => "new",
|
15
|
+
:create => "new",
|
16
|
+
:edit => "edit",
|
17
|
+
:update => "edit"
|
18
|
+
}.each do |replace, with|
|
19
|
+
it "should replace #{replace.inspect} in a path with #{with.inspect}" do
|
20
|
+
subject.normalize(replace).should == with
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Snippr::Normalizer do
|
4
|
+
|
5
|
+
describe ".normalizers" do
|
6
|
+
|
7
|
+
it "should be an array" do
|
8
|
+
subject.normalizers.should be_an(Array)
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should have a set of default normalizers" do
|
12
|
+
normalizers = subject.normalizers
|
13
|
+
normalizers.size.should == 1
|
14
|
+
normalizers[0].should be_a(Snippr::Normalizer::Camelizer)
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
describe ".normalize" do
|
20
|
+
|
21
|
+
before do
|
22
|
+
subject.normalizers << Snippr::Normalizer::DeRester.new # add a second normalizer to ensure chain behaviour
|
23
|
+
end
|
24
|
+
|
25
|
+
after do
|
26
|
+
subject.normalizers.pop # remove second normalizer
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should call normalize on all normalizers, passing the path element between them and returning the last result" do
|
30
|
+
seq = sequence "normalizers"
|
31
|
+
subject.normalizers.each_with_index do |normalizer, i|
|
32
|
+
normalizer.should respond_to(:normalize)
|
33
|
+
normalizer.expects(:normalize).with(i.to_s).returns((i + 1).to_s).in_sequence(seq)
|
34
|
+
end
|
35
|
+
subject.normalize('0').should == subject.normalizers.size.to_s
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
39
|
+
|
40
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Snippr::Path do
|
4
|
+
|
5
|
+
describe "path" do
|
6
|
+
|
7
|
+
it "should store the path" do
|
8
|
+
subject.path = nil
|
9
|
+
subject.path.should == ''
|
10
|
+
subject.path = 'path'
|
11
|
+
subject.path.should == 'path'
|
12
|
+
end
|
13
|
+
|
14
|
+
# TODO test JVM path? [thomas, 2010-08-26]
|
15
|
+
|
16
|
+
end
|
17
|
+
|
18
|
+
describe ".normalize_name" do
|
19
|
+
|
20
|
+
it "should call Snippr::Normalizer.normalize with all names and return normalized result" do
|
21
|
+
seq = sequence "normalizers"
|
22
|
+
Snippr::Normalizer.expects(:normalize).with("a").in_sequence(seq).returns("AA")
|
23
|
+
Snippr::Normalizer.expects(:normalize).with(:b).in_sequence(seq).returns("BB")
|
24
|
+
subject.normalize_name("a", :b).should == "AA/BB"
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
describe ".path_from_name" do
|
30
|
+
|
31
|
+
before do
|
32
|
+
subject.path = 'path'
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should join path and name (with extension)" do
|
36
|
+
subject.path_from_name('name', 'snip').should == 'path/name.snip'
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should join path and name (without extension)" do
|
40
|
+
subject.path_from_name('file').should == 'path/file'
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
|
45
|
+
describe ".list" do
|
46
|
+
|
47
|
+
context "without I18n" do
|
48
|
+
|
49
|
+
before do
|
50
|
+
Snippr::I18n.enabled = false
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should return a list of all snippr names" do
|
54
|
+
subject.list(:topup).should == [:some_error, :success]
|
55
|
+
end
|
56
|
+
|
57
|
+
it "should return an empty array for non existant dirs" do
|
58
|
+
subject.list(:doesnotexist).should == []
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
context "with I18n" do
|
64
|
+
|
65
|
+
before do
|
66
|
+
Snippr::I18n.enabled = true
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should return a list of all snippr names of the current locale (de)" do
|
70
|
+
I18n.locale = :de
|
71
|
+
subject.list(:i18n).should == [:list, :shop]
|
72
|
+
end
|
73
|
+
|
74
|
+
it "should return a list of all snippr names of the current locale (en)" do
|
75
|
+
I18n.locale = :en
|
76
|
+
subject.list(:i18n).should == [:shop]
|
77
|
+
end
|
78
|
+
|
79
|
+
it "should return an empty array for non existant dirs" do
|
80
|
+
subject.list(:doesnotexist).should == []
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Snippr::Processor::Dynamics do
|
4
|
+
|
5
|
+
class Klass
|
6
|
+
def method; "METHOD"; end
|
7
|
+
def method2(param); "METHOD WITH #{param}"; end
|
8
|
+
def method3(param1, param2); "METHOD WITH #{param1} AND #{param2}"; end
|
9
|
+
end
|
10
|
+
|
11
|
+
it "should replace placeholders with dynamic values" do
|
12
|
+
today = Date.today
|
13
|
+
subject.process('Your topup of {topup_amount} at {date_today} was successful.', {
|
14
|
+
:topup_amount => "15,00 €",
|
15
|
+
:date_today => today
|
16
|
+
}).should == "Your topup of 15,00 € at #{today} was successful."
|
17
|
+
end
|
18
|
+
|
19
|
+
it "parses multi-line parameters" do
|
20
|
+
tpl = "An instance {var.method2(\"PARAM\t\nETER\")}"
|
21
|
+
subject.process(tpl, :var => Klass.new).should == "An instance METHOD WITH PARAMETER"
|
22
|
+
end
|
23
|
+
|
24
|
+
it "Does not kill all whitespace" do
|
25
|
+
tpl = "An instance {var.method2(\"PART1\t\n SPACE PART2\")}"
|
26
|
+
subject.process(tpl, :var => Klass.new).should == "An instance METHOD WITH PART1 SPACE PART2"
|
27
|
+
end
|
28
|
+
|
29
|
+
it "should allow calling methods on placeholders" do
|
30
|
+
tpl = "An instance {var.method()}"
|
31
|
+
subject.process(tpl, :var => Klass.new).should == "An instance METHOD"
|
32
|
+
end
|
33
|
+
|
34
|
+
it "should allow calling methods with parameters on placeholders" do
|
35
|
+
tpl = 'An instance {var.method2("PARAMETER")}'
|
36
|
+
subject.process(tpl, :var => Klass.new).should == "An instance METHOD WITH PARAMETER"
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should allow calling methods with multiple parameters on placeholders" do
|
40
|
+
tpl = 'An instance {var.method3("PARAMETER1","PARAMETER2")}'
|
41
|
+
subject.process(tpl, :var => Klass.new).should == "An instance METHOD WITH PARAMETER1 AND PARAMETER2"
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should keep the {snip} if calling a method but the method is not defined" do
|
45
|
+
tpl = "An instance {var.method_not_exist()}"
|
46
|
+
subject.process(tpl, :var => Klass.new).should == tpl
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Snippr::Processor::Functions do
|
4
|
+
|
5
|
+
describe "#cmd_snip" do
|
6
|
+
|
7
|
+
it "should include snips inside of snips" do
|
8
|
+
subject.process('Include a {snip:home} inside a snip').should == "Include a <!-- starting snippr: home -->\n<p>Home</p>\n<!-- closing snippr: home --> inside a snip"
|
9
|
+
end
|
10
|
+
|
11
|
+
|
12
|
+
it "should pass parameters to the include" do
|
13
|
+
subject.process('Include a {snip:topup/success} inside a snip', {
|
14
|
+
:topup_amount => '10',
|
15
|
+
:date_today => '123'
|
16
|
+
}).should == "Include a <!-- starting snippr: topup/success -->\n<p>You're topup of 10 at 123 was successful.</p>\n<!-- closing snippr: topup/success --> inside a snip"
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should allow additional parameters to be passed to the included snippet" do
|
20
|
+
subject.process('Include a {snip:topup/success,topup_amount=99} inside a snip', {
|
21
|
+
:date_today => '123'
|
22
|
+
}).should == "Include a <!-- starting snippr: topup/success -->\n<p>You're topup of 99 at 123 was successful.</p>\n<!-- closing snippr: topup/success --> inside a snip"
|
23
|
+
end
|
24
|
+
|
25
|
+
it "should allow additional parameters of the snip call to override parent options" do
|
26
|
+
subject.process('Include a {snip:topup/success,topup_amount=99} inside a snip', {
|
27
|
+
:date_today => '123',
|
28
|
+
:topup_amount => '1'
|
29
|
+
}).should == "Include a <!-- starting snippr: topup/success -->\n<p>You're topup of 99 at 123 was successful.</p>\n<!-- closing snippr: topup/success --> inside a snip"
|
30
|
+
end
|
31
|
+
|
32
|
+
context "for home/show/blauappOverviewBoxMobile (regression test)" do
|
33
|
+
|
34
|
+
before do
|
35
|
+
Snippr::Normalizer.normalizers << Snippr::Normalizer::DeRester.new # add a second normalizer to ensure chain behaviour
|
36
|
+
Snippr::I18n.enabled = true
|
37
|
+
I18n.locale = :de
|
38
|
+
end
|
39
|
+
|
40
|
+
after do
|
41
|
+
Snippr::Normalizer.normalizers.pop # remove second normalizer
|
42
|
+
end
|
43
|
+
|
44
|
+
it "should work" do
|
45
|
+
subject.process("{snip:home/show/blauappOverviewBoxMobile}").should == "<!-- missing snippr: home/show/blauappOverviewBoxMobile_de -->"
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
describe "#hashify" do
|
53
|
+
|
54
|
+
it "should process a single argument with no value as default" do
|
55
|
+
subject.send(:hashify, "test").should == { :default => "test" }
|
56
|
+
end
|
57
|
+
|
58
|
+
it "should process a single argument with key and value" do
|
59
|
+
subject.send(:hashify, "key=value").should == { :key => "value" }
|
60
|
+
end
|
61
|
+
|
62
|
+
it "should process multiple arguments delimited by comma" do
|
63
|
+
subject.send(:hashify, "key=value,key2=value2").should == { :key => "value", :key2 => "value2" }
|
64
|
+
end
|
65
|
+
|
66
|
+
it "should process a combination of all arguments" do
|
67
|
+
subject.send(:hashify, "default,key=value,key2=value2").should == { :default => 'default', :key => "value", :key2 => "value2" }
|
68
|
+
end
|
69
|
+
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|