caramelize 0.1.2 → 1.1.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.
- checksums.yaml +7 -0
- data/.gitignore +21 -53
- data/.travis.yml +5 -0
- data/CODE_OF_CONDUCT.md +76 -0
- data/Gemfile +3 -3
- data/LICENSE.md +1 -1
- data/README.md +48 -46
- data/Rakefile +8 -7
- data/bin/caramelize +73 -10
- data/caramelize.gemspec +28 -24
- data/lib/caramelize.rb +14 -0
- data/lib/caramelize/caramel.rb +48 -44
- data/lib/caramelize/content_transferer.rb +126 -69
- data/lib/caramelize/database_connector.rb +3 -6
- data/lib/caramelize/filter_processor.rb +27 -0
- data/lib/caramelize/filters/remove_table_tab_line_endings.rb +15 -0
- data/lib/caramelize/filters/swap_wiki_links.rb +26 -0
- data/lib/caramelize/filters/wikka_to_markdown.rb +67 -0
- data/lib/caramelize/health_check.rb +85 -0
- data/lib/caramelize/input_wiki/redmine_wiki.rb +120 -0
- data/lib/caramelize/input_wiki/wiki.rb +59 -0
- data/lib/caramelize/input_wiki/wikkawiki.rb +69 -0
- data/lib/caramelize/output_wiki/gollum.rb +80 -0
- data/lib/caramelize/page.rb +38 -14
- data/lib/caramelize/services/page_builder.rb +20 -0
- data/lib/caramelize/version.rb +1 -1
- data/spec/fixtures/markup/swap-links-input.textile +57 -0
- data/spec/fixtures/markup/swap-links-output.textile +57 -0
- data/spec/fixtures/markup/table-tab-line-endings-input.textile +145 -0
- data/spec/fixtures/markup/table-tab-line-endings-output.textile +145 -0
- data/spec/lib/caramelize/content_transferer_spec.rb +9 -0
- data/spec/lib/caramelize/filter_processor_spec.rb +34 -0
- data/spec/lib/caramelize/filters/remove_table_tab_line_endings_spec.rb +49 -0
- data/spec/lib/caramelize/filters/swap_wiki_links_spec.rb +49 -0
- data/spec/lib/caramelize/filters/wikka_to_markdown_spec.rb +198 -0
- data/spec/lib/caramelize/input_wiki/wiki_spec.rb +57 -0
- data/spec/lib/caramelize/output_wiki/gollum_spec.rb +113 -0
- data/spec/lib/caramelize/page_spec.rb +67 -0
- data/spec/lib/caramelize/services/page_builder.rb +29 -0
- data/spec/spec_helper.rb +8 -0
- metadata +165 -54
- data/lib/caramelize/author.rb +0 -8
- data/lib/caramelize/cli.rb +0 -80
- data/lib/caramelize/cli/create_command.rb +0 -52
- data/lib/caramelize/cli/run_command.rb +0 -33
- data/lib/caramelize/ext.rb +0 -17
- data/lib/caramelize/gollum_output.rb +0 -56
- data/lib/caramelize/wiki/redmine_wiki.rb +0 -60
- data/lib/caramelize/wiki/trac_converter.rb +0 -82
- data/lib/caramelize/wiki/wiki.rb +0 -41
- data/lib/caramelize/wiki/wikka_converter.rb +0 -38
- data/lib/caramelize/wiki/wikkawiki.rb +0 -55
- data/test/helper.rb +0 -18
- data/test/test_caramelize.rb +0 -7
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Caramelize::FilterProcessor do
|
4
|
+
let(:filters) { [] }
|
5
|
+
let(:input_wiki) { double(filters: filters) }
|
6
|
+
let(:body) { 'body' }
|
7
|
+
subject(:processor) { described_class.new(input_wiki) }
|
8
|
+
|
9
|
+
class ReverseFilter
|
10
|
+
def initialize(body)
|
11
|
+
@body = body
|
12
|
+
end
|
13
|
+
|
14
|
+
def run
|
15
|
+
@body.reverse
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe '#run' do
|
20
|
+
context 'without any filters' do
|
21
|
+
it 'returns same revision body' do
|
22
|
+
expect(processor.run(body)).to eql body
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
context 'with reverse filter' do
|
27
|
+
let(:filters) { [ReverseFilter] }
|
28
|
+
|
29
|
+
it 'returns reversed body' do
|
30
|
+
expect(processor.run(body)).to eql body.reverse
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Caramelize::RemoveTableTabLineEndings do
|
4
|
+
let(:filter) { described_class.new(body) }
|
5
|
+
subject { filter.run}
|
6
|
+
|
7
|
+
describe '#run' do
|
8
|
+
context 'table with tabs at unix line-endings' do
|
9
|
+
let(:body) { "cell1\t|cell2\t|\t\t\n" }
|
10
|
+
|
11
|
+
it 'removes tabs at end of line' do
|
12
|
+
is_expected.to eq "cell1\t|cell2\t|\n"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'with spaces on line ending' do
|
17
|
+
let(:body) { "cell1\t|cell2\t|\t \n" }
|
18
|
+
|
19
|
+
it 'removes spaces at end of line' do
|
20
|
+
is_expected.to eq "cell1\t|cell2\t|\n"
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'replace in full file' do
|
24
|
+
let(:body) { File.open(File.join(['spec', 'fixtures', 'markup', 'table-tab-line-endings-input.textile']), 'r').read }
|
25
|
+
|
26
|
+
it 'returns as expected' do
|
27
|
+
output_text = File.open(File.join(['spec', 'fixtures', 'markup', 'table-tab-line-endings-output.textile']), 'r').read
|
28
|
+
is_expected.to eq output_text
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'table with tabs at windows line-endings' do
|
34
|
+
let(:body) { "cell1\t|cell2\t|\t\t\r\n" }
|
35
|
+
|
36
|
+
it 'removes tabs at end of line' do
|
37
|
+
is_expected.to eq "cell1\t|cell2\t|\n"
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'with spaces and windows line-endings' do
|
42
|
+
let(:body) { "cell1\t|cell2\t|\t \r\n" }
|
43
|
+
|
44
|
+
it 'removes spaces at end of line' do
|
45
|
+
is_expected.to eq "cell1\t|cell2\t|\n"
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Caramelize::SwapWikiLinks do
|
4
|
+
describe '#run' do
|
5
|
+
let(:filter) { described_class.new(body) }
|
6
|
+
subject { filter.run }
|
7
|
+
|
8
|
+
context 'wiki link with title' do
|
9
|
+
let(:body) { '[[statistics|Driver & Team Statistics]]' }
|
10
|
+
|
11
|
+
it 'swaps title and target' do
|
12
|
+
is_expected.to eq '[[Driver & Team Statistics|statistics]]'
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
context 'wiki title with spaces' do
|
17
|
+
let(:body) { '[[Release 1 0]]' }
|
18
|
+
|
19
|
+
it 'replaces space with dashes' do
|
20
|
+
is_expected.to eq '[[Release 1 0|release_1_0]]'
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
context 'wiki title with dashes' do
|
25
|
+
let(:body) { '[[Release-1.0]]' }
|
26
|
+
|
27
|
+
it 'removes dots' do
|
28
|
+
is_expected.to eq '[[Release-1.0|release-10]]'
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'wiki link with spaces and without title' do
|
33
|
+
let(:body) { '[[Intra wiki link]]' }
|
34
|
+
|
35
|
+
it 'simples link to hyperlink' do
|
36
|
+
is_expected.to eq '[[Intra wiki link|intra_wiki_link]]'
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'replace in full file' do
|
40
|
+
let(:body) { File.open(File.join(['spec', 'fixtures', 'markup', 'swap-links-input.textile']), 'r').read }
|
41
|
+
|
42
|
+
it 'returns as expected' do
|
43
|
+
output_text = File.open(File.join(['spec', 'fixtures', 'markup', 'swap-links-output.textile']), 'r').read
|
44
|
+
is_expected.to eq output_text
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,198 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Caramelize::Wikka2Markdown do
|
4
|
+
let(:filter) { described_class.new(body) }
|
5
|
+
|
6
|
+
describe '#run' do
|
7
|
+
subject { filter.run }
|
8
|
+
|
9
|
+
xcontext 'keep line breaks' do
|
10
|
+
let(:body) { "line1\nline2" }
|
11
|
+
|
12
|
+
it { is_expected.to eq "line1 \nline2" }
|
13
|
+
end
|
14
|
+
|
15
|
+
context 'headline h1' do
|
16
|
+
let(:body) { '======Headline======' }
|
17
|
+
|
18
|
+
it { is_expected.to eq '# Headline' }
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'headline h2' do
|
22
|
+
let(:body) { '=====Headline=====' }
|
23
|
+
|
24
|
+
it { is_expected.to eq '## Headline' }
|
25
|
+
end
|
26
|
+
|
27
|
+
context 'headline h3' do
|
28
|
+
let(:body) { '====Headline===='}
|
29
|
+
|
30
|
+
it { is_expected.to eq '### Headline' }
|
31
|
+
end
|
32
|
+
|
33
|
+
context 'headline h4' do
|
34
|
+
let(:body) { '===Headline===' }
|
35
|
+
|
36
|
+
it { is_expected.to eq '#### Headline' }
|
37
|
+
end
|
38
|
+
|
39
|
+
context 'headline h5' do
|
40
|
+
let(:body) { '==Headline==' }
|
41
|
+
|
42
|
+
it { is_expected.to eq '##### Headline' }
|
43
|
+
end
|
44
|
+
|
45
|
+
context 'bold' do
|
46
|
+
let(:body) { '**Text is bold**' }
|
47
|
+
|
48
|
+
it { is_expected.to eq '**Text is bold**' }
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'italic' do
|
52
|
+
let(:body) { '//Text is italic//' }
|
53
|
+
|
54
|
+
it { is_expected.to eq '*Text is italic*' }
|
55
|
+
end
|
56
|
+
|
57
|
+
context 'underline' do
|
58
|
+
let(:body) { '__Text is underlined__' }
|
59
|
+
|
60
|
+
it { is_expected.to eq '<u>Text is underlined</u>' }
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'line break' do
|
64
|
+
let(:body) { 'Text is---\nbroken to two lines.' }
|
65
|
+
|
66
|
+
it { is_expected.to eq 'Text is \nbroken to two lines.' }
|
67
|
+
end
|
68
|
+
|
69
|
+
context 'unordered list entry' do
|
70
|
+
context 'tab based' do
|
71
|
+
let(:body) { "\t-unordered list entry" }
|
72
|
+
|
73
|
+
it { is_expected.to eq '* unordered list entry' }
|
74
|
+
end
|
75
|
+
|
76
|
+
context 'also tab based' do
|
77
|
+
let(:body) { "~-unordered list entry" }
|
78
|
+
|
79
|
+
it { is_expected.to eq '* unordered list entry' }
|
80
|
+
end
|
81
|
+
|
82
|
+
context 'space based' do
|
83
|
+
let(:body) { " -unordered list entry" }
|
84
|
+
|
85
|
+
it { is_expected.to eq '* unordered list entry' }
|
86
|
+
end
|
87
|
+
|
88
|
+
context 'tab based with space' do
|
89
|
+
let(:body) { "\t- unordered list entry" }
|
90
|
+
|
91
|
+
it { is_expected.to eq '* unordered list entry' }
|
92
|
+
end
|
93
|
+
|
94
|
+
context 'also tab based with space' do
|
95
|
+
let(:body) { "~- unordered list entry" }
|
96
|
+
|
97
|
+
it { is_expected.to eq '* unordered list entry' }
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'space based with space' do
|
101
|
+
let(:body) { " - unordered list entry" }
|
102
|
+
|
103
|
+
it { is_expected.to eq '* unordered list entry' }
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
context 'ordered list entry' do
|
108
|
+
context 'without space' do
|
109
|
+
let(:body) { "~1)ordered list entry" }
|
110
|
+
|
111
|
+
it { is_expected.to eq '1. ordered list entry' }
|
112
|
+
end
|
113
|
+
|
114
|
+
context 'with space' do
|
115
|
+
let(:body) { "~1) ordered list entry" }
|
116
|
+
|
117
|
+
it { is_expected.to eq '1. ordered list entry' }
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
context 'wikilink' do
|
122
|
+
context 'only url' do
|
123
|
+
let(:body) { '[[LemmaLemma]]' }
|
124
|
+
|
125
|
+
it { is_expected.to eq '[[LemmaLemma]]' }
|
126
|
+
end
|
127
|
+
|
128
|
+
context 'url and pipe title' do
|
129
|
+
let(:body) { '[[SandBox|Test your formatting skills]]' }
|
130
|
+
|
131
|
+
it { is_expected.to eq '[[Test your formatting skills|SandBox]]' }
|
132
|
+
end
|
133
|
+
|
134
|
+
context 'url and title' do
|
135
|
+
let(:body) { '[[SandBox Test your formatting skills]]' }
|
136
|
+
|
137
|
+
it { is_expected.to eq '[[Test your formatting skills|SandBox]]' }
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
context 'hyperlink' do
|
142
|
+
context 'only url' do
|
143
|
+
let(:body) { '[[http://target]]' }
|
144
|
+
|
145
|
+
it { is_expected.to eq '<http://target>' }
|
146
|
+
end
|
147
|
+
|
148
|
+
context 'url with title' do
|
149
|
+
let(:body) { '[[http://target Title]]' }
|
150
|
+
|
151
|
+
it { is_expected.to eq '[Title](http://target)' }
|
152
|
+
end
|
153
|
+
|
154
|
+
context 'url with pipe' do
|
155
|
+
let(:body) { '[[http://target|Title]]' }
|
156
|
+
|
157
|
+
it { is_expected.to eq '[Title](http://target)' }
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
context 'code block' do
|
162
|
+
let(:body) do
|
163
|
+
<<-EOS
|
164
|
+
Text before
|
165
|
+
|
166
|
+
%%
|
167
|
+
std::cin >> input;
|
168
|
+
++stat[input];
|
169
|
+
%%
|
170
|
+
|
171
|
+
Text after
|
172
|
+
|
173
|
+
%%
|
174
|
+
std::cin >> input;
|
175
|
+
++stat[input];
|
176
|
+
%%
|
177
|
+
|
178
|
+
EOS
|
179
|
+
end
|
180
|
+
let(:expected_result) do
|
181
|
+
<<-EOS
|
182
|
+
Text before
|
183
|
+
|
184
|
+
std::cin >> input;
|
185
|
+
++stat[input];
|
186
|
+
|
187
|
+
Text after
|
188
|
+
|
189
|
+
std::cin >> input;
|
190
|
+
++stat[input];
|
191
|
+
|
192
|
+
EOS
|
193
|
+
end
|
194
|
+
|
195
|
+
it { is_expected.to eq expected_result }
|
196
|
+
end
|
197
|
+
end
|
198
|
+
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Caramelize::InputWiki::Wiki do
|
4
|
+
subject(:wiki) { described_class.new }
|
5
|
+
|
6
|
+
|
7
|
+
describe '#latest_revisions' do
|
8
|
+
let(:page1) { double }
|
9
|
+
let(:page2) { double }
|
10
|
+
let(:page3) { double }
|
11
|
+
|
12
|
+
context 'no pages' do
|
13
|
+
it 'return empty array' do
|
14
|
+
expect(wiki.latest_revisions).to eq []
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
context 'pages with revisions' do
|
19
|
+
it 'returns list of latest pages' do
|
20
|
+
wiki.titles = %w[allosaurus brachiosaurus]
|
21
|
+
allow(wiki).to receive(:revisions_by_title)
|
22
|
+
.with('allosaurus').and_return([page1, page2])
|
23
|
+
allow(wiki).to receive(:revisions_by_title)
|
24
|
+
.with('brachiosaurus').and_return([page3])
|
25
|
+
|
26
|
+
expect(wiki.latest_revisions).to eq([page2, page3])
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
describe '#revisions_by_author' do
|
32
|
+
context 'revisions is empty' do
|
33
|
+
context 'and titles is empty' do
|
34
|
+
it 'returns empty array' do
|
35
|
+
allow(wiki).to receive(:titles).and_return []
|
36
|
+
expect(wiki.revisions_by_title('title')).to eq []
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
context 'revisions are given' do
|
42
|
+
context 'and title given' do
|
43
|
+
it 'returns empty array' do
|
44
|
+
pages = []
|
45
|
+
home_1 = OpenStruct.new(title: 'Home', time: Time.parse('2015-01-23'))
|
46
|
+
pages << home_1
|
47
|
+
pages << OpenStruct.new(title: 'Example', time: Time.parse('2015-01-20'))
|
48
|
+
pages << OpenStruct.new(title: 'Authors', time: Time.parse('2015-01-30'))
|
49
|
+
home_2 = OpenStruct.new(title: 'Home', time: Time.parse('2014-01-23'))
|
50
|
+
pages << home_2
|
51
|
+
allow(wiki).to receive(:revisions).and_return pages
|
52
|
+
expect(wiki.revisions_by_title('Home')).to eq [home_2, home_1]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Caramelize::OutputWiki::Gollum do
|
4
|
+
let(:gollum_output) { described_class.new('wiki.git') }
|
5
|
+
before do
|
6
|
+
allow(gollum_output).to receive(:initialize_repository).and_return true
|
7
|
+
end
|
8
|
+
|
9
|
+
describe '#commit_revision' do
|
10
|
+
let(:title) { 'title' }
|
11
|
+
let(:author) { double(name: 'Steven Universe', email: 'steven@example.com') }
|
12
|
+
let(:input_page) do
|
13
|
+
double(author: author,
|
14
|
+
body: 'body',
|
15
|
+
commit_message: 'done',
|
16
|
+
time: Time.now,
|
17
|
+
title: title,
|
18
|
+
path: title)
|
19
|
+
end
|
20
|
+
let(:gollum_page) do
|
21
|
+
double(:gollum_page,
|
22
|
+
name: 'title',
|
23
|
+
format: :markdown)
|
24
|
+
end
|
25
|
+
let(:markup) { :markdown }
|
26
|
+
let(:gollum) { double(:gollum) }
|
27
|
+
|
28
|
+
before do
|
29
|
+
allow(Gollum::Wiki).to receive(:new).and_return(gollum)
|
30
|
+
end
|
31
|
+
|
32
|
+
context 'page exists' do
|
33
|
+
before do
|
34
|
+
allow(gollum).to receive(:page).with(title).and_return(gollum_page)
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'updates page' do
|
38
|
+
expect(gollum).to receive(:update_page).once.and_return(true)
|
39
|
+
gollum_output.commit_revision(input_page, markup)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
context 'page does not exist yet' do
|
44
|
+
before do
|
45
|
+
allow(gollum).to receive(:page).with(title).and_return(nil)
|
46
|
+
end
|
47
|
+
|
48
|
+
it 'creates page' do
|
49
|
+
expect(gollum).to receive(:write_page).once
|
50
|
+
gollum_output.commit_revision(input_page, markup)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe '#commit_history' do
|
56
|
+
pending
|
57
|
+
end
|
58
|
+
|
59
|
+
describe '#commit_namespace_overview' do
|
60
|
+
let(:namespaces) do
|
61
|
+
[
|
62
|
+
OpenStruct.new(identifier: 'velociraptors', name: 'Velociraptor'),
|
63
|
+
OpenStruct.new(identifier: 'allosaurus', name: 'Allosaurus')
|
64
|
+
]
|
65
|
+
end
|
66
|
+
|
67
|
+
context '2 pages in namespaces' do
|
68
|
+
it 'commits page' do
|
69
|
+
allow(gollum_output).to receive(:commit_revision)
|
70
|
+
gollum_output.commit_namespace_overview(namespaces)
|
71
|
+
expect(gollum_output).to have_received(:commit_revision)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
describe '#build_commit' do
|
77
|
+
let(:page) do
|
78
|
+
Caramelize::Page.new( title: 'Feathered Dinosaurs',
|
79
|
+
message: 'Dinosaurs really had feathers, do not forget!',
|
80
|
+
time: Time.parse('2015-02-12'),
|
81
|
+
body: 'Dinosaurs are awesome and have feathers!',
|
82
|
+
author: OpenStruct.new(name: 'Jeff Goldblum', email: 'jeff.g@example.com') )
|
83
|
+
end
|
84
|
+
|
85
|
+
let(:expected_hash) do
|
86
|
+
{
|
87
|
+
message: 'Dinosaurs really had feathers, do not forget!',
|
88
|
+
time: Time.parse('2015-02-12'),
|
89
|
+
name: 'Jeff Goldblum',
|
90
|
+
email: 'jeff.g@example.com'
|
91
|
+
}
|
92
|
+
end
|
93
|
+
|
94
|
+
it 'builds commit hash' do
|
95
|
+
expect(gollum_output.build_commit(page)).to eq expected_hash
|
96
|
+
end
|
97
|
+
|
98
|
+
context 'page has message' do
|
99
|
+
it 'uses page.title' do
|
100
|
+
expect(gollum_output.build_commit(page)[:message])
|
101
|
+
.to eq 'Dinosaurs really had feathers, do not forget!'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
context 'page has no message' do
|
106
|
+
it 'should create message "Edit in page Feathered Dinosaurs"' do
|
107
|
+
page.message = ''
|
108
|
+
expect(gollum_output.build_commit(page)[:message])
|
109
|
+
.to eq 'Edit in page Feathered Dinosaurs'
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|