bridgetown-core 0.18.6 → 0.20.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (97) hide show
  1. checksums.yaml +4 -4
  2. data/bin/bridgetown +6 -0
  3. data/bridgetown-core.gemspec +4 -4
  4. data/lib/bridgetown-core.rb +46 -13
  5. data/lib/bridgetown-core/cleaner.rb +7 -1
  6. data/lib/bridgetown-core/collection.rb +173 -77
  7. data/lib/bridgetown-core/commands/apply.rb +4 -3
  8. data/lib/bridgetown-core/commands/base.rb +9 -0
  9. data/lib/bridgetown-core/commands/concerns/actions.rb +0 -5
  10. data/lib/bridgetown-core/commands/configure.rb +66 -0
  11. data/lib/bridgetown-core/commands/console.rb +4 -0
  12. data/lib/bridgetown-core/commands/new.rb +28 -1
  13. data/lib/bridgetown-core/commands/plugins.rb +1 -0
  14. data/lib/bridgetown-core/concerns/data_accessible.rb +1 -0
  15. data/lib/bridgetown-core/concerns/site/configurable.rb +7 -3
  16. data/lib/bridgetown-core/concerns/site/content.rb +57 -15
  17. data/lib/bridgetown-core/concerns/site/processable.rb +1 -0
  18. data/lib/bridgetown-core/concerns/site/renderable.rb +26 -0
  19. data/lib/bridgetown-core/concerns/site/writable.rb +11 -1
  20. data/lib/bridgetown-core/concerns/validatable.rb +1 -0
  21. data/lib/bridgetown-core/configuration.rb +39 -19
  22. data/lib/bridgetown-core/configurations/.keep +0 -0
  23. data/lib/bridgetown-core/configurations/bt-postcss.rb +26 -0
  24. data/lib/bridgetown-core/configurations/bt-postcss/postcss.config.js +21 -0
  25. data/lib/bridgetown-core/configurations/minitesting.rb +95 -0
  26. data/lib/bridgetown-core/configurations/netlify.rb +6 -0
  27. data/lib/bridgetown-core/configurations/netlify/netlify.sh +14 -0
  28. data/lib/bridgetown-core/configurations/netlify/netlify.toml +44 -0
  29. data/lib/bridgetown-core/configurations/purgecss.rb +49 -0
  30. data/lib/bridgetown-core/configurations/stimulus.rb +49 -0
  31. data/lib/bridgetown-core/configurations/swup.rb +37 -0
  32. data/lib/bridgetown-core/configurations/tailwindcss.rb +29 -0
  33. data/lib/bridgetown-core/configurations/tailwindcss/css_imports.css +4 -0
  34. data/lib/bridgetown-core/configurations/tailwindcss/postcss.config.js +12 -0
  35. data/lib/bridgetown-core/configurations/turbo.rb +16 -0
  36. data/lib/bridgetown-core/converter.rb +14 -0
  37. data/lib/bridgetown-core/converters/erb_templates.rb +6 -13
  38. data/lib/bridgetown-core/converters/identity.rb +0 -9
  39. data/lib/bridgetown-core/converters/markdown.rb +14 -4
  40. data/lib/bridgetown-core/converters/markdown/kramdown_parser.rb +3 -0
  41. data/lib/bridgetown-core/current.rb +10 -0
  42. data/lib/bridgetown-core/document.rb +6 -14
  43. data/lib/bridgetown-core/drops/collection_drop.rb +1 -1
  44. data/lib/bridgetown-core/drops/page_drop.rb +4 -0
  45. data/lib/bridgetown-core/drops/resource_drop.rb +81 -0
  46. data/lib/bridgetown-core/drops/site_drop.rb +33 -8
  47. data/lib/bridgetown-core/drops/unified_payload_drop.rb +4 -0
  48. data/lib/bridgetown-core/entry_filter.rb +10 -23
  49. data/lib/bridgetown-core/errors.rb +0 -2
  50. data/lib/bridgetown-core/filters.rb +4 -3
  51. data/lib/bridgetown-core/generator.rb +2 -1
  52. data/lib/bridgetown-core/generators/prototype_generator.rb +37 -19
  53. data/lib/bridgetown-core/layout.rb +2 -2
  54. data/lib/bridgetown-core/liquid_renderer/file.rb +1 -0
  55. data/lib/bridgetown-core/liquid_renderer/table.rb +1 -0
  56. data/lib/bridgetown-core/model/base.rb +138 -0
  57. data/lib/bridgetown-core/model/builder_origin.rb +40 -0
  58. data/lib/bridgetown-core/model/file_origin.rb +119 -0
  59. data/lib/bridgetown-core/model/origin.rb +38 -0
  60. data/lib/bridgetown-core/page.rb +9 -1
  61. data/lib/bridgetown-core/plugin.rb +2 -26
  62. data/lib/bridgetown-core/plugin_manager.rb +0 -2
  63. data/lib/bridgetown-core/publisher.rb +7 -1
  64. data/lib/bridgetown-core/reader.rb +25 -12
  65. data/lib/bridgetown-core/readers/data_reader.rb +3 -4
  66. data/lib/bridgetown-core/readers/post_reader.rb +1 -1
  67. data/lib/bridgetown-core/regenerator.rb +8 -1
  68. data/lib/bridgetown-core/related_posts.rb +1 -1
  69. data/lib/bridgetown-core/renderer.rb +5 -12
  70. data/lib/bridgetown-core/resource/base.rb +275 -0
  71. data/lib/bridgetown-core/resource/destination.rb +49 -0
  72. data/lib/bridgetown-core/resource/permalink_processor.rb +179 -0
  73. data/lib/bridgetown-core/resource/taxonomy_term.rb +25 -0
  74. data/lib/bridgetown-core/resource/taxonomy_type.rb +47 -0
  75. data/lib/bridgetown-core/resource/transformer.rb +173 -0
  76. data/lib/bridgetown-core/ruby_template_view.rb +13 -2
  77. data/lib/bridgetown-core/site.rb +9 -1
  78. data/lib/bridgetown-core/static_file.rb +33 -10
  79. data/lib/bridgetown-core/url.rb +1 -0
  80. data/lib/bridgetown-core/utils.rb +40 -40
  81. data/lib/bridgetown-core/utils/platforms.rb +1 -0
  82. data/lib/bridgetown-core/version.rb +2 -2
  83. data/lib/site_template/README.md +70 -0
  84. data/lib/site_template/frontend/javascript/index.js.erb +11 -0
  85. data/lib/site_template/frontend/styles/index.css +19 -0
  86. data/lib/site_template/{package.json → package.json.erb} +14 -8
  87. data/lib/site_template/postcss.config.js.erb +11 -0
  88. data/lib/site_template/src/_components/footer.liquid +1 -1
  89. data/lib/site_template/src/_posts/0000-00-00-welcome-to-bridgetown.md.erb +1 -1
  90. data/lib/site_template/{webpack.config.js → webpack.config.js.erb} +47 -5
  91. metadata +67 -42
  92. data/lib/bridgetown-core/page_without_a_file.rb +0 -14
  93. data/lib/bridgetown-core/readers/collection_reader.rb +0 -23
  94. data/lib/bridgetown-core/utils/exec.rb +0 -26
  95. data/lib/bridgetown-core/utils/internet.rb +0 -37
  96. data/lib/bridgetown-core/utils/win_tz.rb +0 -75
  97. data/lib/site_template/frontend/javascript/index.js +0 -3
@@ -0,0 +1,275 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Resource
5
+ class Base
6
+ include Comparable
7
+ include Bridgetown::Publishable
8
+ include Bridgetown::LayoutPlaceable
9
+ include Bridgetown::LiquidRenderable
10
+
11
+ # @return [HashWithDotAccess::Hash]
12
+ attr_reader :data
13
+
14
+ # @return [Destination]
15
+ attr_reader :destination
16
+
17
+ # @return [Bridgetown::Model::Base]
18
+ attr_reader :model
19
+
20
+ # @return [Bridgetown::Site]
21
+ attr_reader :site
22
+
23
+ # @return [String]
24
+ attr_accessor :content, :untransformed_content, :output
25
+
26
+ # @!attribute [r] collection
27
+ # @return [Bridgetown::Collection] collection of this resource
28
+
29
+ # @!attribute [r] relative_path
30
+ # @return [Pathname] the relative path of source file or
31
+ # file-like origin
32
+
33
+ DATE_FILENAME_MATCHER = %r!^(?>.+/)*?(\d{2,4}-\d{1,2}-\d{1,2})-([^/]*)(\.[^.]+)$!.freeze
34
+
35
+ # @param site [Bridgetown::Site]
36
+ # @param origin [Bridgetown::Resource::Origin]
37
+ def initialize(model:)
38
+ @model = model
39
+ @site = model.site
40
+ self.data = HashWithDotAccess::Hash.new
41
+
42
+ trigger_hooks(:post_init)
43
+ end
44
+
45
+ # @return [Bridgetown::Collection]
46
+ def collection
47
+ model.collection
48
+ end
49
+
50
+ # @return [Pathname]
51
+ def relative_path
52
+ model.origin.relative_path
53
+ end
54
+
55
+ # @return [Bridgetown::Resource::Transformer]
56
+ def transformer
57
+ @transformer ||= Bridgetown::Resource::Transformer.new(self)
58
+ end
59
+
60
+ # @param new_data [HashWithDotAccess::Hash]
61
+ def data=(new_data)
62
+ unless new_data.is_a?(HashWithDotAccess::Hash)
63
+ raise "#{self.class} data should be of type HashWithDotAccess::Hash"
64
+ end
65
+
66
+ @data = new_data
67
+ @data.default_proc = proc do |_, key|
68
+ site.frontmatter_defaults.find(
69
+ relative_path.to_s,
70
+ collection.label.to_sym,
71
+ key.to_s
72
+ )
73
+ end
74
+ end
75
+
76
+ # @return [Bridgetown::Resource::Base]
77
+ def read!
78
+ self.data = model.data_attributes
79
+ self.content = model.content # could be nil
80
+
81
+ unless collection.data?
82
+ self.untransformed_content = content
83
+ determine_slug_and_date
84
+ normalize_categories_and_tags
85
+ import_taxonomies_from_data
86
+ end
87
+
88
+ @destination = Destination.new(self) if requires_destination?
89
+
90
+ trigger_hooks(:post_read)
91
+
92
+ self
93
+ end
94
+ alias_method :read, :read! # TODO: eventually use the bang version only
95
+
96
+ def transform!
97
+ transformer.process! unless collection.data?
98
+ end
99
+
100
+ def trigger_hooks(hook_name, *args)
101
+ Bridgetown::Hooks.trigger collection.label.to_sym, hook_name, self, *args if collection
102
+ Bridgetown::Hooks.trigger :resources, hook_name, self, *args
103
+ end
104
+
105
+ def around_hook(hook_suffix)
106
+ trigger_hooks :"pre_#{hook_suffix}"
107
+ yield
108
+ trigger_hooks :"post_#{hook_suffix}"
109
+ end
110
+
111
+ def relative_path_basename_without_prefix
112
+ return_path = Pathname.new("")
113
+ relative_path.each_filename do |filename|
114
+ if matches = DATE_FILENAME_MATCHER.match(filename) # rubocop:disable Lint/AssignmentInCondition
115
+ filename = matches[2] + matches[3]
116
+ end
117
+
118
+ return_path += filename unless filename.starts_with?("_")
119
+ end
120
+
121
+ (return_path.dirname + return_path.basename(".*")).to_s
122
+ end
123
+
124
+ def basename_without_ext
125
+ relative_path.basename(".*").to_s
126
+ end
127
+
128
+ # @return [String]
129
+ def extname
130
+ relative_path.extname
131
+ end
132
+
133
+ # @return [String, nil]
134
+ def permalink
135
+ data&.permalink
136
+ end
137
+
138
+ def path
139
+ (model.origin.respond_to?(:original_path) ? model.origin.original_path : relative_path).to_s
140
+ end
141
+
142
+ def absolute_url
143
+ format_url destination&.absolute_url
144
+ end
145
+
146
+ def relative_url
147
+ format_url destination&.relative_url
148
+ end
149
+ alias_method :id, :relative_url
150
+
151
+ def date
152
+ data["date"] ||= site.time # TODO: this doesn't reflect documented behavior
153
+ end
154
+
155
+ # @return [Hash<String, Hash<String => Bridgetown::Resource::TaxonomyType,
156
+ # Array<Bridgetown::Resource::TaxonomyTerm>>>]
157
+ def taxonomies
158
+ @taxonomies ||= site.taxonomy_types.values.each_with_object(
159
+ HashWithDotAccess::Hash.new
160
+ ) do |taxonomy, hsh|
161
+ hsh[taxonomy.label] = {
162
+ type: taxonomy,
163
+ terms: [],
164
+ }
165
+ end
166
+ end
167
+
168
+ def requires_destination?
169
+ collection.write? && data.config&.output != false
170
+ end
171
+
172
+ def write?
173
+ requires_destination? && site.publisher.publish?(self)
174
+ end
175
+
176
+ # Write the generated Document file to the destination directory.
177
+ #
178
+ # dest - The String path to the destination dir.
179
+ #
180
+ # Returns nothing.
181
+ def write(_dest = nil)
182
+ destination.write(output)
183
+ trigger_hooks(:post_write)
184
+ end
185
+
186
+ def to_s
187
+ output || content || ""
188
+ end
189
+
190
+ # Create a Liquid-understandable version of this resource.
191
+ #
192
+ # @return [Drops::DocumentDrop] represents this resource's data.
193
+ def to_liquid
194
+ @to_liquid ||= Drops::ResourceDrop.new(self)
195
+ end
196
+
197
+ def inspect
198
+ "#<#{self.class} [#{collection.label}] #{relative_path}>"
199
+ end
200
+
201
+ # Compare this document against another document.
202
+ # Comparison is a comparison between the 2 paths of the documents.
203
+ #
204
+ # Returns -1, 0, +1 or nil depending on whether this doc's path is less than,
205
+ # equal or greater than the other doc's path. See String#<=> for more details.
206
+ def <=>(other) # rubocop:todo Metrics/AbcSize
207
+ return nil unless other.respond_to?(:data)
208
+
209
+ if data.date.respond_to?(:to_datetime) && other.data.date.respond_to?(:to_datetime)
210
+ return data.date.to_datetime <=> other.data.date.to_datetime
211
+ end
212
+
213
+ cmp = data["date"] <=> other.data["date"]
214
+ cmp = path <=> other.path if cmp.nil? || cmp.zero?
215
+ cmp
216
+ end
217
+
218
+ def next_resource
219
+ pos = collection.resources.index { |item| item.equal?(self) }
220
+ collection.resources[pos + 1] if pos && pos < collection.resources.length - 1
221
+ end
222
+ alias_method :next_doc, :next_resource
223
+
224
+ def previous_resource
225
+ pos = collection.docs.index { |item| item.equal?(self) }
226
+ collection.resources[pos - 1] if pos&.positive?
227
+ end
228
+ alias_method :previous_doc, :previous_resource
229
+
230
+ private
231
+
232
+ def determine_slug_and_date
233
+ return unless relative_path.to_s =~ DATE_FILENAME_MATCHER
234
+
235
+ new_date, slug = Regexp.last_match.captures
236
+ modify_date(new_date)
237
+
238
+ slug.gsub!(%r!\.*\z!, "")
239
+ data.slug ||= slug
240
+ end
241
+
242
+ def modify_date(new_date)
243
+ if !data.date || data.date.to_i == site.time.to_i
244
+ data.date = Utils.parse_date(
245
+ new_date,
246
+ "Document '#{relative_path}' does not have a valid date in the #{model}."
247
+ )
248
+ end
249
+ end
250
+
251
+ def normalize_categories_and_tags
252
+ data.categories = Bridgetown::Utils.pluralized_array_from_hash(
253
+ data, :category, :categories
254
+ )
255
+ data.tags = Bridgetown::Utils.pluralized_array_from_hash(
256
+ data, :tag, :tags
257
+ )
258
+ end
259
+
260
+ def import_taxonomies_from_data
261
+ taxonomies.each do |_label, metadata|
262
+ Array(data[metadata.type.key]).each do |term|
263
+ metadata.terms << TaxonomyTerm.new(
264
+ resource: self, label: term, type: metadata.type
265
+ )
266
+ end
267
+ end
268
+ end
269
+
270
+ def format_url(url)
271
+ url.to_s.sub(%r{index\.html?$}, "").sub(%r{\.html?$}, "")
272
+ end
273
+ end
274
+ end
275
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Resource
5
+ class Destination
6
+ # @return [Bridgetown::Resource::Base]
7
+ attr_accessor :resource
8
+
9
+ # @return [String]
10
+ attr_accessor :output_ext
11
+
12
+ # @param resource [Bridgetown::Resource::Base]
13
+ def initialize(resource)
14
+ @resource = resource
15
+ @output_ext = resource.transformer.final_ext
16
+ end
17
+
18
+ def absolute_url
19
+ Addressable::URI.parse(
20
+ resource.site.config.url.to_s + relative_url
21
+ ).normalize.to_s
22
+ end
23
+
24
+ def relative_url
25
+ @processor ||= PermalinkProcessor.new(resource)
26
+ @processor.transform
27
+ end
28
+
29
+ def final_ext
30
+ output_ext || resource.extname
31
+ end
32
+
33
+ def output_path
34
+ path = URL.unescape_path(relative_url)
35
+ path = path.delete_prefix(resource.site.baseurl) if resource.site.baseurl.present?
36
+ path = resource.site.in_dest_dir(path)
37
+ path = File.join(path, "index.html") if relative_url.end_with? "/"
38
+ path
39
+ end
40
+
41
+ def write(output)
42
+ path = output_path
43
+ FileUtils.mkdir_p(File.dirname(path))
44
+ Bridgetown.logger.debug "Writing:", path
45
+ File.write(path, output, mode: "wb")
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,179 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bridgetown
4
+ module Resource
5
+ class PermalinkProcessor
6
+ # @return [Bridgetown::Resource::Base]
7
+ attr_accessor :resource
8
+
9
+ attr_accessor :slugify_mode
10
+
11
+ def self.placeholder_processors
12
+ @placeholder_processors || {}
13
+ end
14
+
15
+ def self.register_placeholder(key, block)
16
+ @placeholder_processors ||= {}
17
+ @placeholder_processors[key] = block
18
+ end
19
+
20
+ def initialize(resource)
21
+ @resource = resource
22
+ @slugify_mode = @resource.site.config.slugify_mode
23
+ end
24
+
25
+ def final_ext
26
+ resource.method(:destination).arity == 1 ? resource.extname : resource.destination.final_ext
27
+ end
28
+
29
+ def transform
30
+ permalink = resource.data.permalink ||
31
+ permalink_for_permalink_style(resource.collection.default_permalink)
32
+
33
+ # Strip out file extension and process each segment of a URL to swap out
34
+ # placeholders such as :categories or :title
35
+ url_segments = permalink.sub(%r{\.[^/]*$}, "").split("/")
36
+ new_url = url_segments.map do |segment|
37
+ segment.starts_with?(":") ? process_segment(segment.sub(%r{^:}, "")) : segment
38
+ end.select(&:present?).join("/")
39
+
40
+ # No relative URLs should ever end in /index.html
41
+ new_url.sub!(%r{/index$}, "") if final_ext == ".html"
42
+
43
+ add_baseurl finalize_permalink(new_url, permalink)
44
+ end
45
+
46
+ def process_segment(segment)
47
+ segment = segment.to_sym
48
+ if self.class.placeholder_processors[segment]
49
+ segment_value = self.class.placeholder_processors[segment].(resource)
50
+ if segment_value.is_a?(Hash)
51
+ segment_value[:raw_value]
52
+ elsif segment_value.is_a?(Array)
53
+ segment_value.map do |subsegment|
54
+ Utils.slugify(subsegment, mode: slugify_mode)
55
+ end.join("/")
56
+ else
57
+ Utils.slugify(segment_value, mode: slugify_mode)
58
+ end
59
+ else
60
+ segment
61
+ end
62
+ end
63
+
64
+ def permalink_for_permalink_style(permalink_style)
65
+ collection_prefix = ("/:collection" unless resource.collection.builtin?)
66
+
67
+ case permalink_style.to_sym
68
+ when :pretty
69
+ "#{collection_prefix}/:categories/:year/:month/:day/:slug/"
70
+ when :pretty_ext, :date
71
+ "#{collection_prefix}/:categories/:year/:month/:day/:slug.*"
72
+ when :simple
73
+ "#{collection_prefix}/:categories/:slug/"
74
+ when :simple_ext
75
+ "#{collection_prefix}/:categories/:slug.*"
76
+ else
77
+ permalink_style.to_s
78
+ end
79
+ end
80
+
81
+ # @param new_url [String]
82
+ # @param permalink [String]
83
+ def finalize_permalink(new_url, permalink)
84
+ # Handle .* style permalinks or files not ending in .html
85
+ if permalink.ends_with?(".*") || !%r{\.html?$}.match?(final_ext)
86
+ "/#{new_url}#{final_ext}"
87
+ # If permalink includes the file extension, add it back in to the URL
88
+ elsif permalink =~ %r{\.[^/]*$}
89
+ "/#{new_url}#{Regexp.last_match[0]}"
90
+ # Ensure index-style URLs get output correctly
91
+ elsif permalink.ends_with?("/")
92
+ "/#{new_url}/".sub(%r{^/index/$}, "/")
93
+ # We good :)
94
+ else
95
+ "/#{new_url}"
96
+ end
97
+ end
98
+
99
+ def add_baseurl(permalink)
100
+ resource.site.baseurl.present? ? "#{resource.site.baseurl}#{permalink}" : permalink
101
+ end
102
+
103
+ ### Default Placeholders Processors
104
+
105
+ # @param resource [Bridgetown::Resource::Base]
106
+ register_placeholder :path, ->(resource) do
107
+ { raw_value: resource.relative_path_basename_without_prefix }
108
+ end
109
+
110
+ # @param resource [Bridgetown::Resource::Base]
111
+ register_placeholder :name, ->(resource) do
112
+ resource.basename_without_ext
113
+ end
114
+
115
+ # @param resource [Bridgetown::Resource::Base]
116
+ register_placeholder :slug, ->(resource) do
117
+ resource.data.slug || placeholder_processors[:name].(resource)
118
+ end
119
+
120
+ # @param resource [Bridgetown::Resource::Base]
121
+ register_placeholder :title, ->(resource) do
122
+ resource.data.title || placeholder_processors[:slug].(resource)
123
+ end
124
+
125
+ # @param resource [Bridgetown::Resource::Base]
126
+ register_placeholder :locale, ->(resource) do
127
+ locale_data = resource.data.locale
128
+ resource.site.config.available_locales.include?(locale_data) ? locale_data : nil
129
+ end
130
+ register_placeholder :lang, placeholder_processors[:locale]
131
+
132
+ # @param resource [Bridgetown::Resource::Base]
133
+ register_placeholder :collection, ->(resource) do
134
+ resource.collection.label
135
+ end
136
+
137
+ # @param resource [Bridgetown::Resource::Base]
138
+ register_placeholder :categories, ->(resource) do
139
+ resource.taxonomies.category&.terms&.map(&:label)&.uniq
140
+ end
141
+
142
+ # YYYY
143
+ # @param resource [Bridgetown::Resource::Base]
144
+ register_placeholder :year, ->(resource) do
145
+ resource.date.strftime("%Y")
146
+ end
147
+
148
+ # MM: 01..12
149
+ # @param resource [Bridgetown::Resource::Base]
150
+ register_placeholder :month, ->(resource) do
151
+ resource.date.strftime("%m")
152
+ end
153
+
154
+ # DD: 01..31
155
+ # @param resource [Bridgetown::Resource::Base]
156
+ register_placeholder :day, ->(resource) do
157
+ resource.date.strftime("%d")
158
+ end
159
+
160
+ # D: 1..31
161
+ # @param resource [Bridgetown::Resource::Base]
162
+ register_placeholder :i_day, ->(resource) do
163
+ resource.date.strftime("%-d")
164
+ end
165
+
166
+ # M: 1..12
167
+ # @param resource [Bridgetown::Resource::Base]
168
+ register_placeholder :i_month, ->(resource) do
169
+ resource.date.strftime("%-m")
170
+ end
171
+
172
+ # YY: 00..99
173
+ # @param resource [Bridgetown::Resource::Base]
174
+ register_placeholder :short_year, ->(resource) do
175
+ resource.date.strftime("%y")
176
+ end
177
+ end
178
+ end
179
+ end