nesta 0.11.1 → 0.12.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.gitmodules +6 -0
- data/.travis.yml +9 -4
- data/CHANGES +18 -2
- data/Gemfile +1 -1
- data/Gemfile.lock +70 -54
- data/LICENSE +1 -1
- data/RELEASING.md +5 -6
- data/Rakefile +20 -3
- data/lib/nesta/app.rb +4 -8
- data/lib/nesta/commands/command.rb +1 -2
- data/lib/nesta/commands/demo/content.rb +23 -5
- data/lib/nesta/commands/theme/install.rb +9 -7
- data/lib/nesta/helpers.rb +14 -0
- data/lib/nesta/models.rb +26 -22
- data/lib/nesta/navigation.rb +1 -1
- data/lib/nesta/version.rb +1 -1
- data/nesta.gemspec +10 -11
- data/templates/config/config.yml +1 -1
- data/{spec → test}/fixtures/nesta-plugin-test/Gemfile +0 -0
- data/{spec → test}/fixtures/nesta-plugin-test/Rakefile +0 -0
- data/{spec → test}/fixtures/nesta-plugin-test/lib/nesta-plugin-test.rb +0 -0
- data/{spec → test}/fixtures/nesta-plugin-test/lib/nesta-plugin-test/init.rb +0 -0
- data/{spec → test}/fixtures/nesta-plugin-test/lib/nesta-plugin-test/version.rb +0 -0
- data/{spec → test}/fixtures/nesta-plugin-test/nesta-plugin-test.gemspec +0 -0
- data/test/integration/atom_feed_test.rb +178 -0
- data/test/integration/commands/demo/content_test.rb +31 -0
- data/test/integration/commands/edit_test.rb +21 -0
- data/test/integration/commands/new_test.rb +120 -0
- data/test/integration/commands/plugin/create_test.rb +128 -0
- data/test/integration/commands/theme/create_test.rb +35 -0
- data/test/integration/commands/theme/enable_test.rb +22 -0
- data/test/integration/commands/theme/install_test.rb +62 -0
- data/test/integration/default_theme_test.rb +220 -0
- data/test/integration/overrides_test.rb +118 -0
- data/test/integration/route_handlers_test.rb +96 -0
- data/test/integration/sitemap_test.rb +85 -0
- data/test/integration_test_helper.rb +61 -0
- data/test/support/model_factory.rb +169 -0
- data/test/support/silence_commands_during_tests.rb +5 -0
- data/test/support/temporary_files.rb +33 -0
- data/test/support/test_configuration.rb +19 -0
- data/test/test_helper.rb +26 -0
- data/test/unit/commands_test.rb +23 -0
- data/test/unit/config_test.rb +138 -0
- data/test/unit/file_model_test.rb +71 -0
- data/test/unit/menu_test.rb +82 -0
- data/test/unit/page_test.rb +571 -0
- data/test/unit/path_test.rb +41 -0
- data/test/unit/plugin_test.rb +47 -0
- data/views/master.sass +1 -1
- metadata +81 -85
- data/smoke-test.sh +0 -107
- data/spec/atom_spec.rb +0 -141
- data/spec/commands/demo/content_spec.rb +0 -65
- data/spec/commands/edit_spec.rb +0 -27
- data/spec/commands/new_spec.rb +0 -88
- data/spec/commands/plugin/create_spec.rb +0 -97
- data/spec/commands/system_spec.rb +0 -25
- data/spec/commands/theme/create_spec.rb +0 -41
- data/spec/commands/theme/enable_spec.rb +0 -44
- data/spec/commands/theme/install_spec.rb +0 -56
- data/spec/config_spec.rb +0 -127
- data/spec/model_factory.rb +0 -92
- data/spec/models_spec.rb +0 -700
- data/spec/overrides_spec.rb +0 -132
- data/spec/page_spec.rb +0 -560
- data/spec/path_spec.rb +0 -28
- data/spec/plugin_spec.rb +0 -51
- data/spec/sitemap_spec.rb +0 -105
- data/spec/spec_helper.rb +0 -114
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require_relative '../../../../lib/nesta/commands'
|
3
|
+
|
4
|
+
describe 'nesta theme:create' do
|
5
|
+
include TemporaryFiles
|
6
|
+
|
7
|
+
def theme_path(path = '')
|
8
|
+
File.join('themes', 'theme-name', path)
|
9
|
+
end
|
10
|
+
|
11
|
+
before do
|
12
|
+
FileUtils.mkdir_p(project_root)
|
13
|
+
end
|
14
|
+
|
15
|
+
after do
|
16
|
+
remove_temp_directory
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'creates default files in the theme directory' do
|
20
|
+
Dir.chdir(project_root) do
|
21
|
+
Nesta::Commands::Theme::Create.new('theme-name').execute
|
22
|
+
end
|
23
|
+
assert_exists_in_project theme_path('README.md')
|
24
|
+
assert_exists_in_project theme_path('app.rb')
|
25
|
+
end
|
26
|
+
|
27
|
+
it 'copies default view templates into views directory' do
|
28
|
+
Dir.chdir(project_root) do
|
29
|
+
Nesta::Commands::Theme::Create.new('theme-name').execute
|
30
|
+
end
|
31
|
+
%w(layout.haml page.haml master.sass).each do |template|
|
32
|
+
assert_exists_in_project theme_path("views/#{template}")
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require_relative '../../../../lib/nesta/commands'
|
3
|
+
|
4
|
+
describe 'nesta theme:enable' do
|
5
|
+
include TemporaryFiles
|
6
|
+
|
7
|
+
before do
|
8
|
+
FileUtils.mkdir_p(File.join(project_root, 'config'))
|
9
|
+
File.open(File.join(project_root, 'config', 'config.yml'), 'w').close
|
10
|
+
end
|
11
|
+
|
12
|
+
after do
|
13
|
+
remove_temp_directory
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'enables the theme' do
|
17
|
+
Dir.chdir(project_root) do
|
18
|
+
Nesta::Commands::Theme::Enable.new('theme-name').execute
|
19
|
+
assert_match /^theme: theme-name/, File.read('config/config.yml')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
require_relative '../../../support/silence_commands_during_tests'
|
3
|
+
require_relative '../../../../lib/nesta/commands'
|
4
|
+
|
5
|
+
Nesta::Commands::Theme::Install.send(:include, SilenceCommandsDuringTests)
|
6
|
+
|
7
|
+
describe 'nesta theme:install' do
|
8
|
+
include TemporaryFiles
|
9
|
+
|
10
|
+
def theme_name
|
11
|
+
'test'
|
12
|
+
end
|
13
|
+
|
14
|
+
def repo_url
|
15
|
+
"../../fixtures/nesta-theme-#{theme_name}.git"
|
16
|
+
end
|
17
|
+
|
18
|
+
def theme_dir
|
19
|
+
project_path("themes/#{theme_name}")
|
20
|
+
end
|
21
|
+
|
22
|
+
before do
|
23
|
+
FileUtils.mkdir_p(project_root)
|
24
|
+
end
|
25
|
+
|
26
|
+
after do
|
27
|
+
remove_temp_directory
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'clones the repository' do
|
31
|
+
in_temporary_project do
|
32
|
+
Nesta::Commands::Theme::Install.new(repo_url).execute
|
33
|
+
assert File.directory?(theme_dir), 'theme not cloned'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
it "removes the theme's .git directory" do
|
38
|
+
in_temporary_project do
|
39
|
+
Nesta::Commands::Theme::Install.new(repo_url).execute
|
40
|
+
refute File.exist?("#{theme_dir}/.git"), '.git folder found'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
it 'enables the freshly installed theme' do
|
45
|
+
in_temporary_project do
|
46
|
+
Nesta::Commands::Theme::Install.new(repo_url).execute
|
47
|
+
assert_match /theme: #{theme_name}/, File.read('config/config.yml')
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
it 'determines name of theme from name of repository' do
|
52
|
+
url = 'git://foobar.com/path/to/nesta-theme-the-name.git'
|
53
|
+
command = Nesta::Commands::Theme::Install.new(url)
|
54
|
+
assert_equal 'the-name', command.theme_name
|
55
|
+
end
|
56
|
+
|
57
|
+
it "falls back to name of repo when theme name doesn't match correct format" do
|
58
|
+
url = 'git://foobar.com/path/to/mytheme.git'
|
59
|
+
command = Nesta::Commands::Theme::Install.new(url)
|
60
|
+
assert_equal 'mytheme', command.theme_name
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,220 @@
|
|
1
|
+
require 'integration_test_helper'
|
2
|
+
|
3
|
+
describe 'Default theme' do
|
4
|
+
include Nesta::IntegrationTest
|
5
|
+
|
6
|
+
def in_categories(*categories)
|
7
|
+
{ 'categories' => categories.map(&:path).join(', ') }
|
8
|
+
end
|
9
|
+
|
10
|
+
it 'includes description meta tag' do
|
11
|
+
with_temp_content_directory do
|
12
|
+
model = create(:page, metadata: { 'Description' => 'A good page' })
|
13
|
+
visit model.path
|
14
|
+
xpath = "//meta[@name='description'][@content='A good page']"
|
15
|
+
assert_has_xpath xpath, visible: false
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
it 'includes keywords meta tag' do
|
20
|
+
with_temp_content_directory do
|
21
|
+
model = create(:page, metadata: { 'Keywords' => 'good, content' })
|
22
|
+
visit model.path
|
23
|
+
xpath = "//meta[@name='keywords'][@content='good, content']"
|
24
|
+
assert_has_xpath xpath, visible: false
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
it "doesn't include Google Analytics JavaScript snippet by default" do
|
29
|
+
with_temp_content_directory do
|
30
|
+
visit '/'
|
31
|
+
assert_has_no_css 'script', text: 'google-analytics.com'
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
it 'includes Google Analytics JavaScript when configured' do
|
36
|
+
analytics_code = { 'google_analytics_code' => 'UA-1234' }
|
37
|
+
stub_config(temp_content.merge('test' => analytics_code)) do
|
38
|
+
visit '/'
|
39
|
+
assert_nil all('script').find { |s| s[:src].match /analytics\.js/ }
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'displays site title' do
|
44
|
+
site_config = {
|
45
|
+
'title' => 'My blog',
|
46
|
+
'subtitle' => 'about stuff',
|
47
|
+
}
|
48
|
+
stub_config(temp_content.merge(site_config)) do
|
49
|
+
visit '/'
|
50
|
+
assert_has_css 'h1', text: 'My blog'
|
51
|
+
assert_has_css 'h2', text: 'about stuff'
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
describe 'menus' do
|
56
|
+
def create_pages_in_menu
|
57
|
+
pages
|
58
|
+
end
|
59
|
+
|
60
|
+
it "doesn't include menu markup if menu not configured" do
|
61
|
+
with_temp_content_directory do
|
62
|
+
visit '/'
|
63
|
+
assert_has_no_css 'ul.menu'
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'only displays first two levels of menu items' do
|
68
|
+
with_temp_content_directory do
|
69
|
+
level1, level2, level3 = (0..2).map { create(:page) }
|
70
|
+
text = "%s\n %s\n %s\n" % [level1, level2, level3].map(&:abspath)
|
71
|
+
create_menu(text)
|
72
|
+
visit '/'
|
73
|
+
assert_has_css "ul.menu li a:contains('#{level1.link_text}')"
|
74
|
+
assert_has_css "ul.menu li ul li a:contains('#{level2.link_text}')"
|
75
|
+
assert_has_no_css "ul.menu a:contains('#{level3.link_text}')"
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_page_and_menu
|
80
|
+
model = create(:page)
|
81
|
+
create_menu(model.path)
|
82
|
+
model
|
83
|
+
end
|
84
|
+
|
85
|
+
it "highlights current page's menu item" do
|
86
|
+
with_temp_content_directory do
|
87
|
+
model = create_page_and_menu
|
88
|
+
visit model.path
|
89
|
+
assert_has_css "ul.menu li[class='current']:contains('#{model.link_text}')"
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
it "highlights current page's menu item when app mounted at /prefix" do
|
94
|
+
Capybara.app = Rack::Builder.new do
|
95
|
+
map '/prefix' do
|
96
|
+
run Nesta::App.new
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
with_temp_content_directory do
|
101
|
+
model = create_page_and_menu
|
102
|
+
visit "/prefix/#{model.path}"
|
103
|
+
assert_has_css "ul.menu li[class='current']:contains('#{model.link_text}')"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'only displays read more link for summarised pages' do
|
109
|
+
with_temp_content_directory do
|
110
|
+
category = create(:category)
|
111
|
+
metadata = in_categories(category)
|
112
|
+
summarised = create(:page, metadata: metadata.merge('summary' => 'Summary'))
|
113
|
+
not_summarised = create(:page, metadata: metadata)
|
114
|
+
visit category.path
|
115
|
+
assert_has_css 'li:nth-child(1) p', text: summarised.read_more
|
116
|
+
assert_has_no_css 'li:nth-child(2) p', text: not_summarised.read_more
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
it 'displays page summaries or full content of unsummarised pages' do
|
121
|
+
with_temp_content_directory do
|
122
|
+
category = create(:category)
|
123
|
+
metadata = in_categories(category)
|
124
|
+
summarised = create(:page,
|
125
|
+
content: 'Summarised content',
|
126
|
+
metadata: metadata.merge(summary: 'Summary'))
|
127
|
+
not_summarised = create(:page,
|
128
|
+
content: 'Unsummarised content',
|
129
|
+
metadata: metadata)
|
130
|
+
visit category.path
|
131
|
+
|
132
|
+
# Page with a summary
|
133
|
+
assert_has_css 'li:nth-child(1) p', text: 'Summary'
|
134
|
+
assert_has_no_css 'li:nth-child(1) p', text: 'content'
|
135
|
+
|
136
|
+
# Page without a summary
|
137
|
+
assert_has_css 'li:nth-child(2) p', text: 'Unsummarised content'
|
138
|
+
assert_has_no_css 'li:nth-child(2) p', text: 'Summary'
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
it 'displays contents of page' do
|
143
|
+
with_temp_content_directory do
|
144
|
+
model = create(:page, content: 'Body of page')
|
145
|
+
visit model.path
|
146
|
+
assert_has_css 'p', text: 'Body of page'
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
describe 'category' do
|
151
|
+
it 'displays its "articles heading" above the articles' do
|
152
|
+
with_temp_content_directory do
|
153
|
+
category = create(:category, metadata: {
|
154
|
+
'articles heading' => 'Articles on this topic'
|
155
|
+
})
|
156
|
+
create(:article, metadata: in_categories(category))
|
157
|
+
visit category.path
|
158
|
+
assert_has_css 'h1', text: 'Articles on this topic'
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
it 'links to articles in category using article title' do
|
163
|
+
with_temp_content_directory do
|
164
|
+
category = create(:category)
|
165
|
+
article = create(:article, metadata: in_categories(category))
|
166
|
+
visit category.path
|
167
|
+
link_text, href = article.link_text, article.abspath
|
168
|
+
assert_has_css "ol h1 a[href$='#{href}']", text: link_text
|
169
|
+
end
|
170
|
+
end
|
171
|
+
end
|
172
|
+
|
173
|
+
describe 'article' do
|
174
|
+
it 'displays the date' do
|
175
|
+
with_temp_content_directory do
|
176
|
+
article = create(:article)
|
177
|
+
visit article.path
|
178
|
+
assert_has_css "time[datetime='#{article.date}']"
|
179
|
+
end
|
180
|
+
end
|
181
|
+
|
182
|
+
it 'links to parent page in breadcrumb' do
|
183
|
+
with_temp_content_directory do
|
184
|
+
parent = create(:category)
|
185
|
+
article = create(:article, path: "#{parent.path}/child")
|
186
|
+
visit article.path
|
187
|
+
href, link_text = parent.abspath, parent.link_text
|
188
|
+
assert_has_css "nav.breadcrumb a[href='#{href}']", text: link_text
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
it 'links to its categories at end of article' do
|
193
|
+
with_temp_content_directory do
|
194
|
+
categories = [create(:category), create(:category)]
|
195
|
+
article = create(:article, metadata: in_categories(*categories))
|
196
|
+
visit article.path
|
197
|
+
categories.each do |category|
|
198
|
+
href, link_text = category.abspath, category.link_text
|
199
|
+
assert_has_css "p.meta a[href$='#{href}']", text: link_text
|
200
|
+
end
|
201
|
+
end
|
202
|
+
end
|
203
|
+
|
204
|
+
it 'displays comments from Disqus' do
|
205
|
+
stub_config(temp_content.merge('disqus_short_name' => 'mysite')) do
|
206
|
+
article = create(:article)
|
207
|
+
visit article.path
|
208
|
+
assert_has_css '#disqus_thread'
|
209
|
+
assert_has_css 'script[src*="mysite.disqus.com"]', visible: false
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
it "doesn't use Disqus if it's not configured" do
|
214
|
+
with_temp_content_directory do
|
215
|
+
visit create(:article).path
|
216
|
+
assert_has_no_css '#disqus_thread'
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require 'integration_test_helper'
|
2
|
+
|
3
|
+
describe 'Overriding files in gem and themes' do
|
4
|
+
include Nesta::IntegrationTest
|
5
|
+
|
6
|
+
def in_nesta_project(config = {}, &block)
|
7
|
+
app_root = temp_path('root')
|
8
|
+
content_config = { 'content' => File.join(app_root, 'content') }
|
9
|
+
stub_config(content_config.merge(config)) do
|
10
|
+
with_app_root(app_root) do
|
11
|
+
yield
|
12
|
+
end
|
13
|
+
end
|
14
|
+
ensure
|
15
|
+
remove_temp_directory
|
16
|
+
end
|
17
|
+
|
18
|
+
def theme_name
|
19
|
+
'my-theme'
|
20
|
+
end
|
21
|
+
|
22
|
+
def create_fixture(type, name, content)
|
23
|
+
base_path = {
|
24
|
+
local: Nesta::Path.local,
|
25
|
+
theme: Nesta::Path.themes(theme_name)
|
26
|
+
}[type]
|
27
|
+
path = File.join(base_path, name)
|
28
|
+
FileUtils.mkdir_p(File.dirname(path))
|
29
|
+
open(path, 'w') { |file| file.write(content) }
|
30
|
+
end
|
31
|
+
|
32
|
+
def create_app_file(type)
|
33
|
+
create_fixture(type, 'app.rb', "FROM_#{type.to_s.upcase} = true")
|
34
|
+
end
|
35
|
+
|
36
|
+
describe 'app.rb' do
|
37
|
+
it 'loads app.rb from configured theme' do
|
38
|
+
in_nesta_project('theme' => theme_name) do
|
39
|
+
create_app_file(:theme)
|
40
|
+
Nesta::Overrides.load_theme_app
|
41
|
+
assert Object.const_get(:FROM_THEME), 'should load app.rb in theme'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
it 'loads both local and theme app.rb files' do
|
46
|
+
in_nesta_project('theme' => theme_name) do
|
47
|
+
create_app_file(:local)
|
48
|
+
Nesta::Overrides.load_local_app
|
49
|
+
create_app_file(:theme)
|
50
|
+
Nesta::Overrides.load_theme_app
|
51
|
+
assert Object.const_get(:FROM_THEME), 'should load app.rb in theme'
|
52
|
+
assert Object.const_get(:FROM_LOCAL), 'should load local app.rb'
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def create_view(type, name, content)
|
58
|
+
create_fixture(type, File.join('views', name), content)
|
59
|
+
end
|
60
|
+
|
61
|
+
describe 'rendering stylesheets' do
|
62
|
+
it 'renders Sass stylesheets' do
|
63
|
+
in_nesta_project do
|
64
|
+
create_view(:local, 'master.sass', "body\n width: 10px * 2")
|
65
|
+
visit '/css/master.css'
|
66
|
+
assert_match /width: 20px;/, body, 'should match /width: 20px;/'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'renders SCSS stylesheets' do
|
71
|
+
in_nesta_project do
|
72
|
+
create_view(:local, 'master.scss', "body {\n width: 10px * 2;\n}")
|
73
|
+
visit '/css/master.css'
|
74
|
+
assert_match /width: 20px;/, body, 'should match /width: 20px;/'
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
it 'renders stylesheet in the the gem if no others found' do
|
79
|
+
in_nesta_project do
|
80
|
+
visit '/css/master.css'
|
81
|
+
assert_equal 200, page.status_code
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def create_haml(type, name, content)
|
87
|
+
create_view(type, 'layout.haml', '= yield')
|
88
|
+
create_view(type, name, content)
|
89
|
+
end
|
90
|
+
|
91
|
+
describe 'rendering Haml' do
|
92
|
+
it 'uses local template in place of default' do
|
93
|
+
in_nesta_project do
|
94
|
+
create_haml(:local, 'page.haml', '%p Local template')
|
95
|
+
visit create(:category).abspath
|
96
|
+
assert_has_xpath '//p', text: 'Local template'
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it 'uses theme template in place of default' do
|
101
|
+
in_nesta_project('theme' => theme_name) do
|
102
|
+
create_haml(:theme, 'page.haml', '%p Theme template')
|
103
|
+
visit create(:category).abspath
|
104
|
+
assert_has_xpath '//p', text: 'Theme template'
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
it 'prioritise local templates over theme templates' do
|
109
|
+
in_nesta_project('theme' => theme_name) do
|
110
|
+
create_haml(:local, 'page.haml', '%p Local template')
|
111
|
+
create_haml(:theme, 'page.haml', '%p Theme template')
|
112
|
+
visit create(:category).abspath
|
113
|
+
assert_has_xpath '//p', text: 'Local template'
|
114
|
+
assert_has_no_xpath '//p', text: 'Theme template'
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|