bridgetown-core 0.19.3 → 0.20.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 +4 -4
- data/bridgetown-core.gemspec +1 -1
- data/lib/bridgetown-core.rb +30 -11
- data/lib/bridgetown-core/cleaner.rb +7 -1
- data/lib/bridgetown-core/collection.rb +173 -77
- data/lib/bridgetown-core/commands/base.rb +9 -0
- data/lib/bridgetown-core/commands/configure.rb +4 -0
- data/lib/bridgetown-core/commands/console.rb +4 -0
- data/lib/bridgetown-core/concerns/data_accessible.rb +1 -0
- data/lib/bridgetown-core/concerns/site/configurable.rb +7 -3
- data/lib/bridgetown-core/concerns/site/content.rb +57 -15
- data/lib/bridgetown-core/concerns/site/processable.rb +1 -0
- data/lib/bridgetown-core/concerns/site/renderable.rb +26 -0
- data/lib/bridgetown-core/concerns/site/writable.rb +11 -1
- data/lib/bridgetown-core/concerns/validatable.rb +1 -0
- data/lib/bridgetown-core/configuration.rb +39 -19
- data/lib/bridgetown-core/converter.rb +14 -0
- data/lib/bridgetown-core/converters/identity.rb +0 -9
- data/lib/bridgetown-core/converters/markdown.rb +14 -4
- data/lib/bridgetown-core/converters/markdown/kramdown_parser.rb +3 -0
- data/lib/bridgetown-core/current.rb +10 -0
- data/lib/bridgetown-core/document.rb +6 -14
- data/lib/bridgetown-core/drops/collection_drop.rb +1 -1
- data/lib/bridgetown-core/drops/page_drop.rb +4 -0
- data/lib/bridgetown-core/drops/resource_drop.rb +81 -0
- data/lib/bridgetown-core/drops/site_drop.rb +33 -8
- data/lib/bridgetown-core/drops/unified_payload_drop.rb +4 -0
- data/lib/bridgetown-core/entry_filter.rb +10 -23
- data/lib/bridgetown-core/errors.rb +0 -2
- data/lib/bridgetown-core/filters.rb +2 -1
- data/lib/bridgetown-core/generators/prototype_generator.rb +37 -19
- data/lib/bridgetown-core/layout.rb +2 -2
- data/lib/bridgetown-core/liquid_renderer/file.rb +1 -0
- data/lib/bridgetown-core/liquid_renderer/table.rb +1 -0
- data/lib/bridgetown-core/model/base.rb +138 -0
- data/lib/bridgetown-core/model/builder_origin.rb +40 -0
- data/lib/bridgetown-core/model/file_origin.rb +119 -0
- data/lib/bridgetown-core/model/origin.rb +38 -0
- data/lib/bridgetown-core/page.rb +9 -1
- data/lib/bridgetown-core/plugin_manager.rb +0 -2
- data/lib/bridgetown-core/publisher.rb +7 -1
- data/lib/bridgetown-core/reader.rb +25 -12
- data/lib/bridgetown-core/readers/data_reader.rb +3 -4
- data/lib/bridgetown-core/readers/post_reader.rb +1 -1
- data/lib/bridgetown-core/regenerator.rb +8 -1
- data/lib/bridgetown-core/related_posts.rb +1 -1
- data/lib/bridgetown-core/renderer.rb +5 -12
- data/lib/bridgetown-core/resource/base.rb +275 -0
- data/lib/bridgetown-core/resource/destination.rb +49 -0
- data/lib/bridgetown-core/resource/permalink_processor.rb +179 -0
- data/lib/bridgetown-core/resource/taxonomy_term.rb +25 -0
- data/lib/bridgetown-core/resource/taxonomy_type.rb +47 -0
- data/lib/bridgetown-core/resource/transformer.rb +173 -0
- data/lib/bridgetown-core/ruby_template_view.rb +4 -0
- data/lib/bridgetown-core/site.rb +9 -1
- data/lib/bridgetown-core/static_file.rb +33 -10
- data/lib/bridgetown-core/url.rb +1 -0
- data/lib/bridgetown-core/utils.rb +40 -40
- data/lib/bridgetown-core/utils/platforms.rb +1 -0
- data/lib/bridgetown-core/version.rb +2 -2
- data/lib/site_template/webpack.config.js.erb +8 -6
- metadata +28 -21
- data/lib/bridgetown-core/page_without_a_file.rb +0 -17
- data/lib/bridgetown-core/readers/collection_reader.rb +0 -23
- data/lib/bridgetown-core/utils/exec.rb +0 -26
- data/lib/bridgetown-core/utils/internet.rb +0 -37
- data/lib/bridgetown-core/utils/win_tz.rb +0 -75
@@ -25,6 +25,15 @@ module Bridgetown
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
+
desc "dream", "There's a place where that idea still exists as a reality"
|
29
|
+
def dream
|
30
|
+
puts ""
|
31
|
+
puts "🎶 The Dream of the 90s is Alive in Portland... ✨"
|
32
|
+
puts " https://youtu.be/U4hShMEk1Ew"
|
33
|
+
puts " https://youtu.be/0_HGqPGp9iY"
|
34
|
+
puts ""
|
35
|
+
end
|
36
|
+
|
28
37
|
desc "help <command>", "Show detailed command usage information and exit"
|
29
38
|
def help(subcommand = nil)
|
30
39
|
if subcommand && respond_to?(subcommand)
|
@@ -50,6 +50,10 @@ module Bridgetown
|
|
50
50
|
configuration = set_color configuration, :blue, :bold
|
51
51
|
say configuration
|
52
52
|
end
|
53
|
+
say "\n"
|
54
|
+
|
55
|
+
docs_url = "https://www.bridgetownrb.com/docs/bundled-configurations".yellow.bold
|
56
|
+
say "For more info, check out the docs at: #{docs_url}"
|
53
57
|
end
|
54
58
|
|
55
59
|
def configurations
|
@@ -28,6 +28,7 @@ module Bridgetown
|
|
28
28
|
|
29
29
|
def console
|
30
30
|
require "irb"
|
31
|
+
require "irb/ext/save-history"
|
31
32
|
require "amazing_print" unless options[:"bypass-ap"]
|
32
33
|
|
33
34
|
Bridgetown.logger.info "Starting:", "Bridgetown v#{Bridgetown::VERSION.magenta}" \
|
@@ -50,6 +51,7 @@ module Bridgetown
|
|
50
51
|
IRB.setup(nil)
|
51
52
|
workspace = IRB::WorkSpace.new
|
52
53
|
irb = IRB::Irb.new(workspace)
|
54
|
+
IRB.conf[:IRB_RC]&.call(irb.context)
|
53
55
|
IRB.conf[:MAIN_CONTEXT] = irb.context
|
54
56
|
eval("site = $BRIDGETOWN_SITE", workspace.binding, __FILE__, __LINE__)
|
55
57
|
Bridgetown.logger.info "Console:", "Now loaded as " + "site".cyan + " variable."
|
@@ -68,6 +70,8 @@ module Bridgetown
|
|
68
70
|
end
|
69
71
|
irb.eval_input
|
70
72
|
end
|
73
|
+
ensure
|
74
|
+
IRB.conf[:AT_EXIT].each(&:call)
|
71
75
|
end
|
72
76
|
end
|
73
77
|
end
|
@@ -30,11 +30,15 @@ class Bridgetown::Site
|
|
30
30
|
configure_include_paths
|
31
31
|
configure_file_read_opts
|
32
32
|
|
33
|
-
self.permalink_style = config["permalink"].to_sym
|
33
|
+
self.permalink_style = (config["permalink"] || "pretty").to_sym
|
34
34
|
|
35
35
|
@config
|
36
36
|
end
|
37
37
|
|
38
|
+
def uses_resource?
|
39
|
+
config[:content_engine] == "resource"
|
40
|
+
end
|
41
|
+
|
38
42
|
def defaults_reader
|
39
43
|
@defaults_reader ||= Bridgetown::DefaultsReader.new(self)
|
40
44
|
end
|
@@ -78,7 +82,7 @@ class Bridgetown::Site
|
|
78
82
|
# @return [Array<String>] Return an array of updated paths if multiple paths given.
|
79
83
|
def in_root_dir(*paths)
|
80
84
|
paths.reduce(root_dir) do |base, path|
|
81
|
-
Bridgetown.sanitized_path(base, path)
|
85
|
+
Bridgetown.sanitized_path(base, path.to_s)
|
82
86
|
end
|
83
87
|
end
|
84
88
|
|
@@ -91,7 +95,7 @@ class Bridgetown::Site
|
|
91
95
|
# @return [Array<String>] Return an array of updated paths if multiple paths given.
|
92
96
|
def in_source_dir(*paths)
|
93
97
|
paths.reduce(source) do |base, path|
|
94
|
-
Bridgetown.sanitized_path(base, path)
|
98
|
+
Bridgetown.sanitized_path(base, path.to_s)
|
95
99
|
end
|
96
100
|
end
|
97
101
|
|
@@ -32,6 +32,18 @@ class Bridgetown::Site
|
|
32
32
|
end
|
33
33
|
end
|
34
34
|
|
35
|
+
def resources_grouped_by_taxonomy(taxonomy)
|
36
|
+
@post_attr_hash[taxonomy.label] ||= begin
|
37
|
+
taxonomy.terms.transform_values { |terms| terms.map(&:resource).sort.reverse }
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def taxonomies
|
42
|
+
taxonomy_types.transform_values do |taxonomy|
|
43
|
+
resources_grouped_by_taxonomy(taxonomy)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
35
47
|
# Returns a hash of "tags" using {#post_attr_hash} where each tag is a key
|
36
48
|
# and each value is a post which contains the key.
|
37
49
|
# @example
|
@@ -41,7 +53,7 @@ class Bridgetown::Site
|
|
41
53
|
# @return [Hash{String, Array<Post>}] Returns a hash of all tags and their corresponding posts
|
42
54
|
# @see post_attr_hash
|
43
55
|
def tags
|
44
|
-
post_attr_hash("tags")
|
56
|
+
uses_resource? ? taxonomies.tag : post_attr_hash("tags")
|
45
57
|
end
|
46
58
|
|
47
59
|
# Returns a hash of "categories" using {#post_attr_hash} where each tag is
|
@@ -54,7 +66,7 @@ class Bridgetown::Site
|
|
54
66
|
# their corresponding posts
|
55
67
|
# @see post_attr_hash
|
56
68
|
def categories
|
57
|
-
post_attr_hash("categories")
|
69
|
+
uses_resource? ? taxonomies.category : post_attr_hash("categories")
|
58
70
|
end
|
59
71
|
|
60
72
|
# Returns the value of `data["site_metadata"]` or creates a new instance of
|
@@ -121,19 +133,25 @@ class Bridgetown::Site
|
|
121
133
|
# An array of collection names.
|
122
134
|
# @return [Array<String>] an array of collection names from the configuration,
|
123
135
|
# or an empty array if the `config["collections"]` key is not set.
|
124
|
-
# @raise ArgumentError Raise an error if `config["collections"]` is not
|
125
|
-
# an Array or a Hash
|
126
136
|
def collection_names
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
+
Array(config.collections&.keys)
|
138
|
+
end
|
139
|
+
|
140
|
+
# @return [Array<Bridgetown::Resource::TaxonomyType>]
|
141
|
+
def taxonomy_types
|
142
|
+
@taxonomy_types ||= config.taxonomies.map do |label, key_or_metadata|
|
143
|
+
key = key_or_metadata
|
144
|
+
tax_metadata = if key_or_metadata.is_a? Hash
|
145
|
+
key = key_or_metadata["key"]
|
146
|
+
key_or_metadata.reject { |k| k == "key" }
|
147
|
+
else
|
148
|
+
HashWithDotAccess::Hash.new
|
149
|
+
end
|
150
|
+
|
151
|
+
[label, Bridgetown::Resource::TaxonomyType.new(
|
152
|
+
site: self, label: label, key: key, metadata: tax_metadata
|
153
|
+
),]
|
154
|
+
end.to_h.with_dot_access
|
137
155
|
end
|
138
156
|
|
139
157
|
# Get all documents.
|
@@ -155,11 +173,29 @@ class Bridgetown::Site
|
|
155
173
|
documents.select(&:write?)
|
156
174
|
end
|
157
175
|
|
158
|
-
# Get all
|
176
|
+
# Get all documents.
|
177
|
+
# @return [Array<Document>] an array of documents from the
|
178
|
+
# configuration
|
179
|
+
def resources
|
180
|
+
collections.each_with_object(Set.new) do |(_, collection), set|
|
181
|
+
set.merge(collection.resources)
|
182
|
+
end.to_a
|
183
|
+
end
|
184
|
+
|
185
|
+
def resources_to_write
|
186
|
+
resources.select(&:write?)
|
187
|
+
end
|
188
|
+
|
189
|
+
# Get all posts. Deprecated, to be removed in v1.0.
|
159
190
|
#
|
160
191
|
# @return [Collection] Returns {#collections}`["posts"]`, creating it if need be
|
161
192
|
# @see Collection
|
162
193
|
def posts
|
194
|
+
unless @wrote_deprecation_msg
|
195
|
+
Bridgetown::Deprecator.deprecation_message "Call site.collections.posts " \
|
196
|
+
"instead of site.posts (Ruby code)"
|
197
|
+
end
|
198
|
+
@wrote_deprecation_msg ||= true
|
163
199
|
collections["posts"] ||= Bridgetown::Collection.new(self, "posts")
|
164
200
|
end
|
165
201
|
|
@@ -177,7 +213,13 @@ class Bridgetown::Site
|
|
177
213
|
#
|
178
214
|
# @return [Array]
|
179
215
|
def contents
|
216
|
+
return resources if uses_resource?
|
217
|
+
|
180
218
|
pages + documents
|
181
219
|
end
|
220
|
+
|
221
|
+
def add_generated_page(generated_page)
|
222
|
+
generated_pages << generated_page
|
223
|
+
end
|
182
224
|
end
|
183
225
|
end
|
@@ -29,6 +29,26 @@ class Bridgetown::Site
|
|
29
29
|
end
|
30
30
|
end
|
31
31
|
|
32
|
+
def matched_converters_for_convertible(convertible)
|
33
|
+
@layout_converters ||= {}
|
34
|
+
|
35
|
+
if convertible.is_a?(Bridgetown::Layout) && @layout_converters[convertible]
|
36
|
+
return @layout_converters[convertible]
|
37
|
+
end
|
38
|
+
|
39
|
+
matches = converters.select do |converter|
|
40
|
+
if converter.method(:matches).arity == 1
|
41
|
+
converter.matches(convertible.extname)
|
42
|
+
else
|
43
|
+
converter.matches(convertible.extname, convertible)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
@layout_converters[convertible] = matches if convertible.is_a?(Bridgetown::Layout)
|
48
|
+
|
49
|
+
matches
|
50
|
+
end
|
51
|
+
|
32
52
|
# Renders all documents
|
33
53
|
# @return [void]
|
34
54
|
def render_docs
|
@@ -38,6 +58,12 @@ class Bridgetown::Site
|
|
38
58
|
render_regenerated document
|
39
59
|
end
|
40
60
|
end
|
61
|
+
|
62
|
+
collection.resources.each do |resource|
|
63
|
+
render_with_locale(resource) do
|
64
|
+
resource.transform!
|
65
|
+
end
|
66
|
+
end
|
41
67
|
end
|
42
68
|
end
|
43
69
|
|
@@ -27,11 +27,21 @@ class Bridgetown::Site
|
|
27
27
|
#
|
28
28
|
# @return [void]
|
29
29
|
def each_site_file
|
30
|
-
%w(pages static_files_to_write docs_to_write).each do |type|
|
30
|
+
%w(pages static_files_to_write docs_to_write resources_to_write).each do |type|
|
31
31
|
send(type).each do |item|
|
32
32
|
yield item
|
33
33
|
end
|
34
34
|
end
|
35
35
|
end
|
36
|
+
|
37
|
+
def resources_cache_manifest
|
38
|
+
resources.each_with_object({}) do |resource, hsh|
|
39
|
+
next if resource.relative_url == ""
|
40
|
+
|
41
|
+
hsh[resource.relative_url] = {
|
42
|
+
id: resource.model.id,
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
36
46
|
end
|
37
47
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module Bridgetown
|
4
|
+
# TODO: to be retired once the Resource engine is made official
|
4
5
|
module Validatable
|
5
6
|
# FIXME: there should be ONE TRUE METHOD to read the YAML frontmatter
|
6
7
|
# in the entire project. Both this and the equivalent Document method
|
@@ -23,6 +23,9 @@ module Bridgetown
|
|
23
23
|
"includes_dir" => "_includes",
|
24
24
|
"partials_dir" => "_partials",
|
25
25
|
"collections" => {},
|
26
|
+
"taxonomies" => {
|
27
|
+
category: { key: "categories", title: "Category" }, tag: { key: "tags", title: "Tag" },
|
28
|
+
},
|
26
29
|
|
27
30
|
# Handling Reading
|
28
31
|
"include" => [".htaccess", "_redirects", ".well-known"],
|
@@ -32,6 +35,7 @@ module Bridgetown
|
|
32
35
|
"markdown_ext" => "markdown,mkdown,mkdn,mkd,md",
|
33
36
|
"strict_front_matter" => false,
|
34
37
|
"slugify_categories" => true,
|
38
|
+
"slugify_mode" => "pretty",
|
35
39
|
|
36
40
|
# Filtering Content
|
37
41
|
"limit_posts" => 0,
|
@@ -56,7 +60,7 @@ module Bridgetown
|
|
56
60
|
# Output Configuration
|
57
61
|
"available_locales" => ["en"],
|
58
62
|
"default_locale" => "en",
|
59
|
-
"permalink" =>
|
63
|
+
"permalink" => nil, # default is set according to content engine
|
60
64
|
"timezone" => nil, # use the local timezone
|
61
65
|
|
62
66
|
"quiet" => false,
|
@@ -95,8 +99,8 @@ module Bridgetown
|
|
95
99
|
# user_config - a Hash or Configuration of overrides.
|
96
100
|
#
|
97
101
|
# Returns a Configuration filled with defaults.
|
98
|
-
def from(user_config)
|
99
|
-
Utils.deep_merge_hashes(
|
102
|
+
def from(user_config, starting_defaults = DEFAULTS)
|
103
|
+
Utils.deep_merge_hashes(starting_defaults.deep_dup, Configuration[user_config])
|
100
104
|
.merge_environment_specific_options!
|
101
105
|
.add_default_collections
|
102
106
|
.add_default_excludes
|
@@ -242,23 +246,38 @@ module Bridgetown
|
|
242
246
|
self
|
243
247
|
end
|
244
248
|
|
245
|
-
def add_default_collections
|
249
|
+
def add_default_collections # rubocop:todo all
|
246
250
|
# It defaults to `{}`, so this is only if someone sets it to null manually.
|
247
|
-
return self if self[
|
251
|
+
return self if self[:collections].nil?
|
248
252
|
|
249
253
|
# Ensure we have a hash.
|
250
|
-
if self[
|
251
|
-
self[
|
254
|
+
if self[:collections].is_a?(Array)
|
255
|
+
self[:collections] = self[:collections].each_with_object({}) do |collection, hash|
|
252
256
|
hash[collection] = {}
|
253
257
|
end
|
254
258
|
end
|
255
259
|
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
260
|
+
# Setup default collections
|
261
|
+
self[:collections][:posts] = {} unless self[:collections][:posts]
|
262
|
+
self[:collections][:posts][:output] = true
|
263
|
+
self[:collections][:posts][:sort_direction] ||= "descending"
|
264
|
+
|
265
|
+
if self[:content_engine] == "resource"
|
266
|
+
self[:permalink] = "pretty" if self[:permalink].blank?
|
267
|
+
self[:collections][:pages] = {} unless self[:collections][:pages]
|
268
|
+
self[:collections][:pages][:output] = true
|
269
|
+
self[:collections][:pages][:permalink] ||= "/:path/"
|
270
|
+
|
271
|
+
self[:collections][:data] = {} unless self[:collections][:data]
|
272
|
+
self[:collections][:data][:output] = false
|
273
|
+
|
274
|
+
unless self[:collections][:posts][:permalink]
|
275
|
+
self[:collections][:posts][:permalink] = self[:permalink]
|
276
|
+
end
|
277
|
+
else
|
278
|
+
self[:permalink] = "date" if self[:permalink].blank?
|
279
|
+
unless self[:collections][:posts][:permalink]
|
280
|
+
self[:collections][:posts][:permalink] = style_to_permalink(self[:permalink])
|
262
281
|
end
|
263
282
|
end
|
264
283
|
|
@@ -284,9 +303,9 @@ module Bridgetown
|
|
284
303
|
self["ruby_in_front_matter"]
|
285
304
|
end
|
286
305
|
|
287
|
-
#
|
288
|
-
def style_to_permalink(permalink_style)
|
289
|
-
case permalink_style.to_sym
|
306
|
+
# Deprecated, to be removed when Bridgetown goes Resource-only
|
307
|
+
def style_to_permalink(permalink_style) # rubocop:todo Metrics/CyclomaticComplexity
|
308
|
+
case permalink_style.to_s.to_sym
|
290
309
|
when :pretty
|
291
310
|
"/:categories/:year/:month/:day/:title/"
|
292
311
|
when :simple
|
@@ -303,7 +322,6 @@ module Bridgetown
|
|
303
322
|
permalink_style.to_s
|
304
323
|
end
|
305
324
|
end
|
306
|
-
# rubocop:enable Metrics/CyclomaticComplexity #
|
307
325
|
|
308
326
|
def check_include_exclude
|
309
327
|
%w(include exclude).each do |option|
|
@@ -314,8 +332,10 @@ module Bridgetown
|
|
314
332
|
"'#{option}' should be set as an array, but was: #{self[option].inspect}."
|
315
333
|
end
|
316
334
|
|
317
|
-
|
318
|
-
|
335
|
+
unless self[:include].include?("_pages") || self[:content_engine] == "resource"
|
336
|
+
# add _pages to includes set
|
337
|
+
self[:include] << "_pages"
|
338
|
+
end
|
319
339
|
|
320
340
|
self
|
321
341
|
end
|
@@ -23,6 +23,16 @@ module Bridgetown
|
|
23
23
|
@config = config
|
24
24
|
end
|
25
25
|
|
26
|
+
# Logic to do the content conversion.
|
27
|
+
#
|
28
|
+
# @param content [String] content of file (without front matter).
|
29
|
+
# @param convertible [Bridgetown::Document, Bridgetown::Layout, Bridgetown::Resource::Base]
|
30
|
+
#
|
31
|
+
# @return [String] the converted content.
|
32
|
+
def convert(content, convertible = nil) # rubocop:disable Lint/UnusedMethodArgument
|
33
|
+
content
|
34
|
+
end
|
35
|
+
|
26
36
|
# Does the given extension match this converter's list of acceptable extensions?
|
27
37
|
#
|
28
38
|
# @param [String] ext
|
@@ -42,5 +52,9 @@ module Bridgetown
|
|
42
52
|
def output_ext(_ext)
|
43
53
|
".html"
|
44
54
|
end
|
55
|
+
|
56
|
+
def inspect
|
57
|
+
"#<#{self.class}#{self.class.extname_list ? " #{self.class.extname_list.join(", ")}" : nil}>"
|
58
|
+
end
|
45
59
|
end
|
46
60
|
end
|
@@ -25,15 +25,6 @@ module Bridgetown
|
|
25
25
|
def output_ext(ext)
|
26
26
|
ext
|
27
27
|
end
|
28
|
-
|
29
|
-
# Logic to do the content conversion.
|
30
|
-
#
|
31
|
-
# content - String content of file (without front matter).
|
32
|
-
#
|
33
|
-
# Returns a String of the converted content.
|
34
|
-
def convert(content)
|
35
|
-
content
|
36
|
-
end
|
37
28
|
end
|
38
29
|
end
|
39
30
|
end
|