nanoc 1.6.2 → 2.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/ChangeLog +27 -0
- data/Rakefile +34 -27
- data/bin/nanoc +153 -49
- data/lib/nanoc.rb +15 -33
- data/lib/nanoc/base/auto_compiler.rb +124 -0
- data/lib/nanoc/base/compiler.rb +55 -0
- data/lib/nanoc/base/core_ext/hash.rb +34 -0
- data/lib/nanoc/base/data_source.rb +53 -0
- data/lib/nanoc/base/enhancements.rb +89 -0
- data/lib/nanoc/base/filter.rb +16 -0
- data/lib/nanoc/base/layout_processor.rb +33 -0
- data/lib/nanoc/base/page.rb +155 -0
- data/lib/nanoc/base/page_proxy.rb +31 -0
- data/lib/nanoc/base/plugin.rb +19 -0
- data/lib/nanoc/base/plugin_manager.rb +33 -0
- data/lib/nanoc/base/site.rb +143 -0
- data/lib/nanoc/data_sources/database.rb +259 -0
- data/lib/nanoc/data_sources/filesystem.rb +308 -0
- data/lib/nanoc/data_sources/trivial.rb +145 -0
- data/lib/nanoc/filters/erb.rb +34 -0
- data/lib/nanoc/filters/haml.rb +16 -0
- data/lib/nanoc/filters/markaby.rb +15 -0
- data/lib/nanoc/filters/markdown.rb +13 -0
- data/lib/nanoc/filters/rdoc.rb +14 -0
- data/lib/nanoc/filters/smartypants.rb +13 -0
- data/lib/nanoc/filters/textile.rb +13 -0
- data/lib/nanoc/layout_processors/erb.rb +35 -0
- data/lib/nanoc/layout_processors/haml.rb +18 -0
- data/lib/nanoc/layout_processors/markaby.rb +16 -0
- metadata +37 -30
- data/lib/nanoc/compiler.rb +0 -145
- data/lib/nanoc/core_ext.rb +0 -1
- data/lib/nanoc/core_ext/array.rb +0 -17
- data/lib/nanoc/core_ext/hash.rb +0 -43
- data/lib/nanoc/core_ext/string.rb +0 -13
- data/lib/nanoc/core_ext/yaml.rb +0 -10
- data/lib/nanoc/creator.rb +0 -180
- data/lib/nanoc/enhancements.rb +0 -101
- data/lib/nanoc/filters.rb +0 -7
- data/lib/nanoc/filters/eruby_filter.rb +0 -39
- data/lib/nanoc/filters/haml_filter.rb +0 -18
- data/lib/nanoc/filters/liquid_filter.rb +0 -47
- data/lib/nanoc/filters/markaby_filter.rb +0 -15
- data/lib/nanoc/filters/markdown_filter.rb +0 -13
- data/lib/nanoc/filters/rdoc_filter.rb +0 -15
- data/lib/nanoc/filters/sass_filter.rb +0 -13
- data/lib/nanoc/filters/smartypants_filter.rb +0 -13
- data/lib/nanoc/filters/textile_filter.rb +0 -13
- data/lib/nanoc/page.rb +0 -171
- data/lib/nanoc/page_drop.rb +0 -18
- data/lib/nanoc/page_proxy.rb +0 -30
@@ -0,0 +1,308 @@
|
|
1
|
+
module Nanoc::DataSource::Filesystem
|
2
|
+
|
3
|
+
class FilesystemDataSource < Nanoc::DataSource
|
4
|
+
|
5
|
+
########## Attributes ##########
|
6
|
+
|
7
|
+
identifier :filesystem
|
8
|
+
|
9
|
+
########## Preparation ##########
|
10
|
+
|
11
|
+
def up
|
12
|
+
end
|
13
|
+
|
14
|
+
def down
|
15
|
+
end
|
16
|
+
|
17
|
+
def setup
|
18
|
+
# Create page
|
19
|
+
FileManager.create_file 'content/content.txt' do
|
20
|
+
"I'm a brand new root page. Please edit me!\n"
|
21
|
+
end
|
22
|
+
FileManager.create_file 'content/content.yaml' do
|
23
|
+
"# Built-in\n" +
|
24
|
+
"\n" +
|
25
|
+
"# Custom\n" +
|
26
|
+
"title: \"A New Root Page\"\n"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Create page defaults
|
30
|
+
FileManager.create_file 'meta.yaml' do
|
31
|
+
"# This file contains the default values for all metafiles.\n" +
|
32
|
+
"# Other metafiles can override the contents of this one.\n" +
|
33
|
+
"\n" +
|
34
|
+
"# Built-in\n" +
|
35
|
+
"custom_path: none\n" +
|
36
|
+
"extension: \"html\"\n" +
|
37
|
+
"filename: \"index\"\n" +
|
38
|
+
"filters_post: []\n" +
|
39
|
+
"filters_pre: []\n" +
|
40
|
+
"is_draft: false\n" +
|
41
|
+
"layout: \"default\"\n" +
|
42
|
+
"skip_output: false\n" +
|
43
|
+
"\n" +
|
44
|
+
"# Custom\n"
|
45
|
+
end
|
46
|
+
|
47
|
+
# Create template
|
48
|
+
FileManager.create_file 'templates/default/default.txt' do
|
49
|
+
"Hi, I'm a new page!\n"
|
50
|
+
end
|
51
|
+
FileManager.create_file 'templates/default/default.yaml' do
|
52
|
+
"# Built-in\n" +
|
53
|
+
"\n" +
|
54
|
+
"# Custom\n" +
|
55
|
+
"title: \"A New Page\"\n"
|
56
|
+
end
|
57
|
+
|
58
|
+
# Create layout
|
59
|
+
FileManager.create_file 'layouts/default.erb' do
|
60
|
+
"<html>\n" +
|
61
|
+
" <head>\n" +
|
62
|
+
" <title><%= @page.title %></title>\n" +
|
63
|
+
" </head>\n" +
|
64
|
+
" <body>\n" +
|
65
|
+
"<%= @page.content %>\n" +
|
66
|
+
" </body>\n" +
|
67
|
+
"</html>\n"
|
68
|
+
end
|
69
|
+
|
70
|
+
# Create code
|
71
|
+
FileManager.create_file 'lib/default.rb' do
|
72
|
+
"\# All files in the 'lib' directory will be loaded\n" +
|
73
|
+
"\# before nanoc starts compiling.\n" +
|
74
|
+
"\n" +
|
75
|
+
"def html_escape(str)\n" +
|
76
|
+
" str.gsub('&', '&').str('<', '<').str('>', '>').str('\"', '"')\n" +
|
77
|
+
"end\n" +
|
78
|
+
"alias h html_escape\n"
|
79
|
+
end
|
80
|
+
|
81
|
+
end
|
82
|
+
|
83
|
+
########## Loading data ##########
|
84
|
+
|
85
|
+
# The filesystem data source stores its pages in nested directories. Each
|
86
|
+
# directory represents a single page. The root directory is the 'content'
|
87
|
+
# directory.
|
88
|
+
#
|
89
|
+
# Every directory has a content file and a meta file. The content file
|
90
|
+
# contains the actual page content, while the meta file contains the
|
91
|
+
# page's metadata.
|
92
|
+
#
|
93
|
+
# Both content files and meta files are named after its parent directory
|
94
|
+
# (i.e. page). For example, a page named 'foo' will have a directory named
|
95
|
+
# 'foo', with e.g. a 'foo.markdown' content file and a 'foo.yaml' meta
|
96
|
+
# file.
|
97
|
+
#
|
98
|
+
# Content file extensions are ignored by nanoc. The content file extension
|
99
|
+
# does not determine the filters to run on it; the meta file defines the
|
100
|
+
# list of filters. The meta file extension must always be 'yaml', though.
|
101
|
+
#
|
102
|
+
# Content files can also have the 'index' basename. Similarly, meta files
|
103
|
+
# can have the 'meta' basename. For example, a parent directory named
|
104
|
+
# 'foo' can have an 'index.txt' content file and a 'meta.yaml' meta file.
|
105
|
+
# This is to preserve backward compatibility.
|
106
|
+
def pages
|
107
|
+
meta_filenames.inject([]) do |pages, filename|
|
108
|
+
# Read metadata
|
109
|
+
meta = (YAML.load_file(filename) || {}).clean
|
110
|
+
|
111
|
+
if meta[:is_draft]
|
112
|
+
# Skip drafts
|
113
|
+
pages
|
114
|
+
else
|
115
|
+
# Get extra info
|
116
|
+
path = filename.sub(/^content/, '').sub(/[^\/]+\.yaml$/, '')
|
117
|
+
file = content_file_for_dir(File.dirname(filename))
|
118
|
+
extras = {
|
119
|
+
:path => path,
|
120
|
+
:file => file,
|
121
|
+
:uncompiled_content => file.read
|
122
|
+
}
|
123
|
+
|
124
|
+
# Add to list of pages
|
125
|
+
pages + [ meta.merge(extras) ]
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
# The page defaults are loaded from a 'meta.yaml' file
|
131
|
+
def page_defaults
|
132
|
+
(YAML.load_file('meta.yaml') || {}).clean
|
133
|
+
end
|
134
|
+
|
135
|
+
# Layouts are stored as files in the 'layouts' directory. Each layout has
|
136
|
+
# a basename (the part before the extension) and an extension. Unlike page
|
137
|
+
# content files, the extension _is_ used for determining the layout
|
138
|
+
# processor; which extension maps to which layout processor is defined in
|
139
|
+
# the layout processors.
|
140
|
+
def layouts
|
141
|
+
Dir["layouts/*"].reject { |f| f =~ /~$/ }.map do |filename|
|
142
|
+
# Get layout details
|
143
|
+
extension = File.extname(filename)
|
144
|
+
name = File.basename(filename, extension)
|
145
|
+
content = File.read(filename)
|
146
|
+
|
147
|
+
# Build hash for layout
|
148
|
+
{ :name => name, :content => content, :extension => extension }
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
# Templates are located in the 'templates' directroy. Every template is a
|
153
|
+
# directory consisting of a content file and a meta file, both named after
|
154
|
+
# the template. This is very similar to the way pages are stored, except
|
155
|
+
# that templates cannot be nested.
|
156
|
+
def templates
|
157
|
+
meta_filenames('templates').inject([]) do |templates, filename|
|
158
|
+
# Get template name
|
159
|
+
name = filename.sub(/^templates\/(.*)\/[^\/]+\.yaml$/, '\1')
|
160
|
+
|
161
|
+
# Get file names
|
162
|
+
meta_filename = filename
|
163
|
+
content_filenames = Dir['templates/' + name + '/' + name + '.*'] +
|
164
|
+
Dir['templates/' + name + '/index.*'] -
|
165
|
+
Dir['templates/' + name + '/*.yaml' ]
|
166
|
+
|
167
|
+
# Read files
|
168
|
+
extension = nil
|
169
|
+
content = nil
|
170
|
+
content_filenames.each do |content_filename|
|
171
|
+
content = File.read(content_filename)
|
172
|
+
extension = File.extname(content_filename)
|
173
|
+
end
|
174
|
+
meta = File.read(meta_filename)
|
175
|
+
|
176
|
+
# Add it to the list of templates
|
177
|
+
templates + [{
|
178
|
+
:name => name,
|
179
|
+
:extension => extension,
|
180
|
+
:content => content,
|
181
|
+
:meta => meta
|
182
|
+
}]
|
183
|
+
end
|
184
|
+
end
|
185
|
+
|
186
|
+
# Code is stored in '.rb' files in the 'lib' directory. Code can reside
|
187
|
+
# in sub-directories.
|
188
|
+
def code
|
189
|
+
Dir['lib/**/*.rb'].sort.inject('') { |m, f| m + File.read(f) + "\n" }
|
190
|
+
end
|
191
|
+
|
192
|
+
########## Creating data ##########
|
193
|
+
|
194
|
+
# Creating a page creates a page directory with the name of the page in
|
195
|
+
# the 'content' directory, as well as a content file named xxx.txt and a
|
196
|
+
# meta file named xxx.yaml (with xxx being the name of the page).
|
197
|
+
def create_page(path, template)
|
198
|
+
# Make sure path does not start or end with a slash
|
199
|
+
sanitized_path = path.gsub(/^\/+|\/+$/, '')
|
200
|
+
|
201
|
+
# Get paths
|
202
|
+
dir_path = 'content/' + sanitized_path
|
203
|
+
name = sanitized_path.sub(/.*\/([^\/]+)$/, '\1')
|
204
|
+
meta_path = dir_path + '/' + name + '.yaml'
|
205
|
+
content_path = dir_path + '/' + name + template[:extension]
|
206
|
+
|
207
|
+
# Make sure the page doesn't exist yet
|
208
|
+
error "A page named '#{path}' already exists." if File.exist?(meta_path)
|
209
|
+
|
210
|
+
# Create index and meta file
|
211
|
+
FileManager.create_file(meta_path) { template[:meta] }
|
212
|
+
FileManager.create_file(content_path) { template[:content] }
|
213
|
+
end
|
214
|
+
|
215
|
+
# Creating a layout creates a single file in the 'layouts' directory,
|
216
|
+
# named xxx.erb (with xxx being the name of the layout).
|
217
|
+
def create_layout(name)
|
218
|
+
# Get details
|
219
|
+
path = 'layouts/' + name + '.erb'
|
220
|
+
|
221
|
+
# Make sure the layout doesn't exist yet
|
222
|
+
error "A layout named '#{name}' already exists." if File.exist?(path)
|
223
|
+
|
224
|
+
# Create layout file
|
225
|
+
FileManager.create_file(path) do
|
226
|
+
"<html>\n" +
|
227
|
+
" <head>\n" +
|
228
|
+
" <title><%= @page.title %></title>\n" +
|
229
|
+
" </head>\n" +
|
230
|
+
" <body>\n" +
|
231
|
+
"<%= @page.content %>\n" +
|
232
|
+
" </body>\n" +
|
233
|
+
"</html>\n"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Creating a template creates a template directory with the name of the
|
238
|
+
# template in the 'templates' directory, as well as a content file named
|
239
|
+
# xxx.txt and a meta file named xxx.yaml (with xxx being the name of the
|
240
|
+
# template).
|
241
|
+
def create_template(name)
|
242
|
+
# Get paths
|
243
|
+
meta_path = 'templates/' + name + '/' + name + '.yaml'
|
244
|
+
content_path = 'templates/' + name + '/' + name + '.txt'
|
245
|
+
|
246
|
+
# Make sure the template doesn't exist yet
|
247
|
+
error "A template named '#{name}' already exists." if File.exist?(meta_path)
|
248
|
+
|
249
|
+
# Create index and meta file
|
250
|
+
FileManager.create_file(meta_path) { "# Built-in\n\n# Custom\ntitle: A New Page\n" }
|
251
|
+
FileManager.create_file(content_path) { "Hi, I'm new here!\n" }
|
252
|
+
end
|
253
|
+
|
254
|
+
private
|
255
|
+
|
256
|
+
########## Custom functions ##########
|
257
|
+
|
258
|
+
# Returns the list of meta files in the given (optional) base directory.
|
259
|
+
def meta_filenames(base='content')
|
260
|
+
# Find all possible meta file names
|
261
|
+
filenames = Dir[base + '/**/*.yaml']
|
262
|
+
|
263
|
+
# Filter out invalid meta files
|
264
|
+
good_filenames = []
|
265
|
+
bad_filenames = []
|
266
|
+
filenames.each do |filename|
|
267
|
+
if filename =~ /meta\.yaml$/ or filename =~ /([^\/]+)\/\1\.yaml$/
|
268
|
+
good_filenames << filename
|
269
|
+
else
|
270
|
+
bad_filenames << filename
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
# Warn about bad filenames
|
275
|
+
unless bad_filenames.empty?
|
276
|
+
error "The following files appear to be meta files, " +
|
277
|
+
"but have an invalid name:\n - " +
|
278
|
+
bad_filenames.join("\n - ")
|
279
|
+
end
|
280
|
+
|
281
|
+
good_filenames
|
282
|
+
end
|
283
|
+
|
284
|
+
# Returns a File object for the content file in the given directory
|
285
|
+
def content_file_for_dir(dir)
|
286
|
+
# Find all files
|
287
|
+
filename_glob_1 = dir.sub(/([^\/]+)$/, '\1/\1.*')
|
288
|
+
filename_glob_2 = dir.sub(/([^\/]+)$/, '\1/index.*')
|
289
|
+
filenames = Dir[filename_glob_1] + Dir[filename_glob_2]
|
290
|
+
|
291
|
+
# Reject meta files
|
292
|
+
filenames.reject! { |f| f =~ /\.yaml$/ }
|
293
|
+
|
294
|
+
# Reject backups
|
295
|
+
filenames.reject! { |f| f =~ /~$/ }
|
296
|
+
|
297
|
+
# Make sure there is only one content file
|
298
|
+
if filenames.size != 1
|
299
|
+
error "Expected 1 content file in #{dir} but found #{filenames.size}"
|
300
|
+
end
|
301
|
+
|
302
|
+
# Read content file
|
303
|
+
File.new(filenames.first)
|
304
|
+
end
|
305
|
+
|
306
|
+
end
|
307
|
+
|
308
|
+
end
|
@@ -0,0 +1,145 @@
|
|
1
|
+
# This is the module where the trivial data source will live in. It's not
|
2
|
+
# really _necessary_ to create a separate module/namespace for each data
|
3
|
+
# source, but it can be useful, especially when the data source needs extra
|
4
|
+
# classes (for example, the database data source defines uses ActiveRecord, so
|
5
|
+
# it needs classes for each table).
|
6
|
+
module Nanoc::DataSource::Trivial
|
7
|
+
|
8
|
+
# This is the implementation of a trivial data source. It doesn't do much
|
9
|
+
# except return bogus data. It is meant to be a very simple example of a
|
10
|
+
# data source, and it should be quite useful for those who want to write
|
11
|
+
# their own data sources.
|
12
|
+
class TrivialDataSource < Nanoc::DataSource
|
13
|
+
|
14
|
+
########## Attributes ##########
|
15
|
+
|
16
|
+
# DataSource.identifier defines the name for this data source. The first
|
17
|
+
# and only argument is the data source name as a symbol.
|
18
|
+
identifier :trivial
|
19
|
+
|
20
|
+
########## Preparation ##########
|
21
|
+
|
22
|
+
# DataSource#up is run before compiling. This is the place where you
|
23
|
+
# should initialize the data source, if necessary. You don't need to
|
24
|
+
# implement it; you can leave it out if you don't need initialization.
|
25
|
+
# This is the ideal place to connect to the database, for example.
|
26
|
+
# If your data source requires any special libraries, require them here
|
27
|
+
# using 'nanoc_require'.
|
28
|
+
def up
|
29
|
+
end
|
30
|
+
|
31
|
+
# DataSource#down is run after compiling. This is where you should clean
|
32
|
+
# up any resources you used during the site compilation. You don't need to
|
33
|
+
# implement it; you can leave it out if there's nothing to clean up. For
|
34
|
+
# example, this is a good place to close the connection to the database,
|
35
|
+
# if you have one.
|
36
|
+
def down
|
37
|
+
end
|
38
|
+
|
39
|
+
# DataSource#setup is run when the site is created. This is the place
|
40
|
+
# where you should create the data source for the first time. You don't
|
41
|
+
# need to implement it; you can leave it out if there's nothing to set up.
|
42
|
+
# For example, if you're using a database, this is where you should create
|
43
|
+
# the necessary tables for the data source to function properly.
|
44
|
+
def setup
|
45
|
+
error "Sorry. The trivial data source isn't competent enough."
|
46
|
+
end
|
47
|
+
|
48
|
+
########## Loading data ##########
|
49
|
+
|
50
|
+
# DataSource#pages returns an array of hashes that represent pages. Each
|
51
|
+
# hash must have at least the :uncompiled_content and :path keys. You can
|
52
|
+
# include other metadata in this hash, though.
|
53
|
+
def pages
|
54
|
+
[
|
55
|
+
{ :uncompiled_content => 'Hi!', :path => '/' },
|
56
|
+
{ :uncompiled_content => 'Hello there.', :path => '/about/' }
|
57
|
+
]
|
58
|
+
end
|
59
|
+
|
60
|
+
# Datasource#page_defaults returns a hash with default values for page
|
61
|
+
# metadata. This hash can be anything, even an empty hash if you wish.
|
62
|
+
def page_defaults
|
63
|
+
{ :layout => 'quux' }
|
64
|
+
end
|
65
|
+
|
66
|
+
# DataSource#layouts returns an array of hashes that represent layouts.
|
67
|
+
# Each hash must have the :name, :content and :extension keys. The
|
68
|
+
# :extension key determines the layout processor that will be used (they
|
69
|
+
# are defined in layout_processors/*.rb).
|
70
|
+
def layouts
|
71
|
+
[
|
72
|
+
{
|
73
|
+
:name => 'quux',
|
74
|
+
:content => "<html>\n" +
|
75
|
+
" <head>\n" +
|
76
|
+
" <title><%= @page.title %></title>\n" +
|
77
|
+
" </head>\n" +
|
78
|
+
" <body>\n" +
|
79
|
+
"<%= @page.content %>\n" +
|
80
|
+
" </body>\n" +
|
81
|
+
"</html>",
|
82
|
+
:extension => '.erb'
|
83
|
+
}
|
84
|
+
]
|
85
|
+
end
|
86
|
+
|
87
|
+
# DataSource#templates returns an array of hashes that represent page
|
88
|
+
# templates. These page templates are used used by DataSource#create_page
|
89
|
+
# to create pages using a template. Each hash must have the :name key for
|
90
|
+
# identifying the template. Apart from that, you can structure the hash
|
91
|
+
# like you desire. I recommend having :content (for the page content) and
|
92
|
+
# :meta (for the page metadata) keys. Note that in this example, the value
|
93
|
+
# corresponding to the :meta key is a hash, but it could just as well have
|
94
|
+
# been a YAML-formatted string. Just make sure that what
|
95
|
+
# DataSource#templates serves is what DataSource#create_page expects.
|
96
|
+
def templates
|
97
|
+
[
|
98
|
+
{
|
99
|
+
:name => 'default',
|
100
|
+
:content => 'Hi, I am a new page. Please edit me!',
|
101
|
+
:meta => { :title => 'A New Page' }
|
102
|
+
}
|
103
|
+
]
|
104
|
+
end
|
105
|
+
|
106
|
+
# DataSource#code returns a string containing custom code which will be
|
107
|
+
# loaded before the site is compiled. This can be code for custom filters
|
108
|
+
# and layout processors, but pretty much any code can be put in there
|
109
|
+
# (global helper functions are very useful). It is possible to override
|
110
|
+
# methods of built-in nanoc classes, but doing so will likely cause
|
111
|
+
# massive breakage, so doing so is not recommended.
|
112
|
+
def code
|
113
|
+
"def foo ; 'bar' ; end"
|
114
|
+
end
|
115
|
+
|
116
|
+
########## Creating data ##########
|
117
|
+
|
118
|
+
# DataSource#create_page is run when a page is created. This function
|
119
|
+
# should create a new page with the given name and using the given
|
120
|
+
# template. The template is a hash taken the array of hashes returned by
|
121
|
+
# DataSource#templates, so make sure that what DataSource#templates
|
122
|
+
# returns is what DataSource#create_page expects. This trivial data source
|
123
|
+
# doesn't have a permanent storage, so it can't create any pages.
|
124
|
+
def create_page(path, template)
|
125
|
+
error "Sorry. The trivial data source isn't competent enough."
|
126
|
+
end
|
127
|
+
|
128
|
+
# DataSource#create_layout is run when a layout is created. This function
|
129
|
+
# should create a new layout with the given name. This trivial data source
|
130
|
+
# doesn't have a permanent storage, so it can't create any layouts.
|
131
|
+
def create_layout(name)
|
132
|
+
error "Sorry. The trivial data source isn't competent enough."
|
133
|
+
end
|
134
|
+
|
135
|
+
# DataSource#create_template is run when a template is created. This
|
136
|
+
# function should create a new template with the given name. This trivial
|
137
|
+
# data source doesn't have a permanent storage, so it can't create any
|
138
|
+
# templates.
|
139
|
+
def create_template(name)
|
140
|
+
error "Sorry. The trivial data source isn't competent enough."
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
end
|