flutterby 0.0.13 → 0.0.14

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 2c87a6c4e67963aa5d66c2225d60b9e228a2d852
4
- data.tar.gz: 896782e01a481af93bc5da7d9a277bc9252d9f29
3
+ metadata.gz: 98bf6013901069664a2281251125dbc4f2b82bf1
4
+ data.tar.gz: 677647c5e11088fbf5091fe7aa3e5a121a48c37a
5
5
  SHA512:
6
- metadata.gz: 52dcaab598ca23c831ff348a3fdeb56124a4ce663c45c36ba47d4211370d602f8b1e0389de27fc9ec830b79abe0761dcbfe5c64a47834de46f17b75418424e72
7
- data.tar.gz: 760b7537767012d5b6d044c537d461bfd52514c2feeb5bf114d4213de1805add17e09eaff1225cb2af0381076f1c6f4f2f99201a644a638dce558ebee55d5392
6
+ metadata.gz: cd149d0e3efa73a1692fc9b940f09dbaef6379864721c396b6f7103d624973477da40fcdab67e600051b7d5c53b5cc9445a871b4f37136a60506c5c2929410a1
7
+ data.tar.gz: 94648d86b9e989b8dc523a345aa4b655e54c13e1faf6fa58766a2174faffde2eeefb9458968e3496cc69207f462ea8328256fefaa38f1f4d9946b50594088dd4
data/lib/flutterby.rb CHANGED
@@ -10,7 +10,6 @@ require "flutterby/version"
10
10
  require "flutterby/node"
11
11
  require "flutterby/filters"
12
12
  require "flutterby/view"
13
- require "flutterby/server"
14
13
 
15
14
 
16
15
  module Flutterby
@@ -22,7 +21,9 @@ module Flutterby
22
21
  name ||= ::File.basename(fs_path)
23
22
 
24
23
  if ::File.exist?(fs_path)
25
- Node.new(name, fs_path: fs_path, parent: parent)
24
+ Node.new(name, fs_path: fs_path, parent: parent).tap do |node|
25
+ node.preprocess!
26
+ end
26
27
  else
27
28
  raise "Path #{fs_path} could not be found."
28
29
  end
data/lib/flutterby/cli.rb CHANGED
@@ -1,5 +1,8 @@
1
- require 'commander'
2
1
  require 'flutterby'
2
+ require 'flutterby/exporter'
3
+ require "flutterby/server"
4
+
5
+ require 'commander'
3
6
  require 'benchmark'
4
7
 
5
8
  Flutterby.logger.level = Logger::INFO
@@ -32,7 +35,7 @@ Commander.configure do
32
35
 
33
36
  # Export site
34
37
  say color("💾 Exporting site...", :bold)
35
- root.export(into: options.out)
38
+ Flutterby::Exporter.new(root).export!(into: options.out)
36
39
  end
37
40
 
38
41
  say color("✅ Done. (took #{sprintf "%.2f", time}s)", :green, :bold)
@@ -0,0 +1,31 @@
1
+ module Flutterby
2
+ class Exporter
3
+ def initialize(root)
4
+ @root = root
5
+ end
6
+
7
+ def export!(into:)
8
+ @root.paths.each do |path, node|
9
+ if node.should_publish?
10
+ path = ::File.expand_path(::File.join(into, node.url))
11
+
12
+ if node.file?
13
+ # Make sure directory exists
14
+ FileUtils.mkdir_p(::File.dirname(path))
15
+
16
+ # Write file
17
+ ::File.write(path, node.render)
18
+ logger.info "Exported #{node.url}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+
24
+
25
+ private
26
+
27
+ def logger
28
+ @logger ||= Flutterby.logger
29
+ end
30
+ end
31
+ end
@@ -1,43 +1,40 @@
1
1
  module Flutterby
2
2
  module Filters
3
- def apply!(file)
4
- body = file.source
3
+ def apply!(node)
4
+ node.body = node.source
5
5
 
6
6
  # Apply all filters
7
- file.filters.each do |filter|
8
- meth = "process_#{filter}"
7
+ node.filters.each do |filter|
8
+ meth = "process_#{filter}!"
9
9
 
10
- # Set the file's extension to the requested filter. The filter
11
- # itself can, of course, override this (eg. the "md" filter can default
12
- # the extension to "html".)
13
- #
14
- file.ext = filter
15
-
16
- # Now apply the actual filter!
17
- #
18
10
  if Filters.respond_to?(meth)
19
- body = Filters.send(meth, body, file)
11
+ Filters.send(meth, node)
20
12
  end
21
13
  end
14
+ end
22
15
 
23
- file.body = body
16
+ def process_rb!(node)
17
+ # extend the node
18
+ mod = Module.new
19
+ mod.class_eval(node.body)
20
+ node.extend(mod)
21
+ node.filter! if node.respond_to?(:filter!)
24
22
  end
25
23
 
26
- def process_erb(input, file)
27
- tilt("erb", input).render(file.view)
24
+ def process_erb!(node)
25
+ node.body = tilt("erb", node.body).render(node.view)
28
26
  end
29
27
 
30
- def process_slim(input, file)
31
- tilt("slim", input).render(file.view)
28
+ def process_slim!(node)
29
+ node.body = tilt("slim", node.body).render(node.view)
32
30
  end
33
31
 
34
- def process_md(input, file)
35
- file.ext = "html"
36
- Slodown::Formatter.new(input).complete.to_s
32
+ def process_md!(node)
33
+ node.body = Slodown::Formatter.new(node.body).complete.to_s
37
34
  end
38
35
 
39
- def process_scss(input, file)
40
- Sass::Engine.new(input, syntax: :scss).render
36
+ def process_scss!(node)
37
+ node.body = Sass::Engine.new(node.body, syntax: :scss).render
41
38
  end
42
39
 
43
40
  def tilt(format, body)
@@ -3,26 +3,22 @@ require 'benchmark'
3
3
  module Flutterby
4
4
  class Node
5
5
  attr_accessor :parent, :ext, :source, :body
6
- attr_reader :name, :filters, :fs_path, :data, :children
6
+ attr_reader :name, :filters, :fs_path, :data, :children, :paths
7
7
 
8
- def initialize(name, parent: nil, fs_path: nil)
9
- @parent = parent
10
- @data = {}
8
+ def initialize(name, parent: nil, fs_path: nil, source: nil)
9
+ @fs_path = fs_path ? ::File.expand_path(fs_path) : nil
10
+ @source = source
11
11
 
12
12
  # Extract name, extension, and filters from given name
13
13
  parts = name.split(".")
14
14
  @name = parts.shift
15
+ @ext = parts.shift
15
16
  @filters = parts.reverse
16
17
 
17
- # We're assuming the extension is the name of the final filter
18
- # that will be applied. This may not be always correct, since filters
19
- # can also change a file's extension.
20
- #
21
- @ext = @filters.last
22
-
23
- # If a filesystem path was given, read the node from disk
24
- if fs_path
25
- @fs_path = ::File.expand_path(fs_path)
18
+ # Register this node with its parent
19
+ if parent
20
+ @parent = parent
21
+ parent.children << self
26
22
  end
27
23
 
28
24
  reload!
@@ -32,13 +28,10 @@ module Flutterby
32
28
  # Children
33
29
  #
34
30
 
35
- def reset_children!
36
- @children = []
37
- end
38
-
39
- def add_child(node)
40
- node.parent = self
41
- children << node
31
+ def register!
32
+ if file?
33
+ root.paths[url] = self
34
+ end
42
35
  end
43
36
 
44
37
  def find_child(name)
@@ -145,51 +138,71 @@ module Flutterby
145
138
  #
146
139
 
147
140
  def reload!
148
- @body = nil
149
- reset_children!
141
+ @body = nil
142
+ @data = {}
143
+ @children = []
144
+ @paths = {}
145
+
146
+ load_from_filesystem! if @fs_path
147
+ end
148
+
149
+ def preprocess!
150
+ # First of all, we want to make sure all nodes have their
151
+ # available extensions loaded.
152
+ #
153
+ walk_tree do |node|
154
+ node.load_extension! if node.should_publish?
155
+ end
150
156
 
151
- # Load contents from filesystem
157
+ # Now do another pass, prerendering stuff where necessary,
158
+ # extracting data, registering URLs to be exported, etc.
152
159
  #
160
+ walk_tree do |node|
161
+ node.render_body! if node.should_prerender?
162
+ node.extract_data!
163
+ node.register! if node.should_publish?
164
+ end
165
+ end
166
+
167
+ def should_prerender?
168
+ should_publish? && !folder? &&
169
+ (["json", "yaml", "rb"] & filters).any?
170
+ end
171
+
172
+ def load_from_filesystem!
153
173
  if @fs_path
154
174
  if ::File.directory?(fs_path)
155
175
  Dir[::File.join(fs_path, "*")].each do |entry|
156
- if node = Flutterby.from(entry, parent: self)
157
- add_child(node)
158
- end
176
+ name = ::File.basename(entry)
177
+ Flutterby::Node.new(name, parent: self, fs_path: entry)
159
178
  end
160
179
  else
161
180
  @source = ::File.read(fs_path)
162
-
163
- # Extract date from name
164
- if name =~ %r{^(\d\d\d\d\-\d\d?\-\d\d?)\-}
165
- @data['date'] = Time.parse($1)
166
- end
167
-
168
- # Read remaining data from frontmatter. Data in frontmatter
169
- # will always have precedence!
170
- @data.merge! parse_frontmatter
171
-
172
- # Do some extra processing depending on extension
173
- meth = "read_#{ext}"
174
- send(meth) if respond_to?(meth)
175
181
  end
176
182
  end
177
-
178
- # If this node is the root node, perform some preprocessing
179
- if root?
180
- preprocess!
181
- end
182
183
  end
183
184
 
184
- def preprocess!
185
- walk_tree do |node|
186
- node.render_body! if node.should_preprocess?
185
+ def extract_data!
186
+ # Extract date from name
187
+ if name =~ %r{^(\d\d\d\d\-\d\d?\-\d\d?)\-}
188
+ data['date'] = Time.parse($1)
187
189
  end
190
+
191
+ # Read remaining data from frontmatter. Data in frontmatter
192
+ # will always have precedence!
193
+ parse_frontmatter!
194
+
195
+ # Do some extra processing depending on extension
196
+ meth = "read_#{ext}!"
197
+ send(meth) if respond_to?(meth)
188
198
  end
189
199
 
190
- def should_preprocess?
191
- should_publish? && !folder? &&
192
- (filters.include?("json") || filters.include?("yaml"))
200
+ def load_extension!
201
+ if extension = sibling("_node.rb")
202
+ mod = Module.new
203
+ mod.class_eval(extension.body)
204
+ extend mod
205
+ end
193
206
  end
194
207
 
195
208
  #
@@ -234,66 +247,30 @@ module Flutterby
234
247
 
235
248
 
236
249
 
237
- #
238
- # Exporting
239
- #
240
-
241
- def export(into:)
242
- if should_publish?
243
- time = Benchmark.realtime do
244
- write_static(into: into)
245
- end
246
-
247
- logger.info "Exported #{url}"
248
- end
249
- end
250
-
251
- def write_static(into:)
252
- if folder?
253
- # write children, acting as a directory
254
-
255
- path = full_fs_path(base: into)
256
- Dir.mkdir(path) unless ::File.exists?(path)
257
-
258
- children.each do |child|
259
- child.export(into: path)
260
- end
261
- else
262
- # write a file
263
- ::File.write(full_fs_path(base: into), render)
264
- end
265
- end
266
-
267
- def should_publish?
268
- !name.start_with?("_")
269
- end
270
-
271
250
 
272
251
  #
273
252
  # Front Matter Parsing
274
253
  #
275
254
 
276
- def parse_frontmatter
277
- data = {}
278
-
279
- # YAML Front Matter
280
- if @source.sub!(/\A\-\-\-\n(.+)\n\-\-\-\n/m, "")
281
- data.merge! YAML.load($1)
282
- end
255
+ def parse_frontmatter!
256
+ if @source
257
+ # YAML Front Matter
258
+ if @source.sub!(/\A\-\-\-\n(.+)\n\-\-\-\n/m, "")
259
+ data.merge! YAML.load($1)
260
+ end
283
261
 
284
- # TOML Front Matter
285
- if @source.sub!(/\A\+\+\+\n(.+)\n\+\+\+\n/m, "")
286
- data.merge! TOML.parse($1)
262
+ # TOML Front Matter
263
+ if @source.sub!(/\A\+\+\+\n(.+)\n\+\+\+\n/m, "")
264
+ data.merge! TOML.parse($1)
265
+ end
287
266
  end
288
-
289
- data
290
267
  end
291
268
 
292
- def read_json
269
+ def read_json!
293
270
  data.merge!(JSON.parse(body))
294
271
  end
295
272
 
296
- def read_yaml
273
+ def read_yaml!
297
274
  data.merge!(YAML.load(body))
298
275
  end
299
276
 
@@ -314,10 +291,18 @@ module Flutterby
314
291
  children.any?
315
292
  end
316
293
 
294
+ def file?
295
+ !folder?
296
+ end
297
+
317
298
  def page?
318
299
  !folder? && ext == "html"
319
300
  end
320
301
 
302
+ def should_publish?
303
+ !name.start_with?("_")
304
+ end
305
+
321
306
  def logger
322
307
  Flutterby.logger
323
308
  end
@@ -37,14 +37,15 @@ module Flutterby
37
37
  req = Rack::Request.new(env)
38
38
  res = Rack::Response.new([], 200, {})
39
39
 
40
- parts = req.path.split("/").reject(&:empty?)
41
-
42
- result = catch :halt do
43
- serve(@root, parts, req, res)
44
- end
40
+ # Look for target node in path registry
41
+ if node = find_node_for_path(req.path)
42
+ # Determine MIME type
43
+ mime_type = MIME::Types.type_for(node.ext) || "text/plain"
45
44
 
46
- case result
47
- when :error_404 then
45
+ # Build response
46
+ res.headers["Content-Type"] = mime_type
47
+ res.body = [node.render]
48
+ else
48
49
  res.status = 404
49
50
  res.headers["Content-Type"] = "text/html"
50
51
  res.body = ["404"]
@@ -53,30 +54,10 @@ module Flutterby
53
54
  res
54
55
  end
55
56
 
56
- def serve(node, parts, req, res)
57
- # halt if we're not supposed to serve current node
58
- throw :halt, :error_404 unless node.should_publish?
59
-
60
- # If there are parts left, find them and delegate to the next
61
- # node in the chain; otherwise, render this specific node.
62
- #
63
- if parts.any?
64
- if child = node.find(parts.shift)
65
- serve(child, parts, req, res)
66
- else
67
- throw :halt, :error_404
68
- end
69
- elsif child = node.find("index")
70
- serve(child, parts, req, res)
71
- else
72
- # Determine MIME type
73
- mime_type = MIME::Types.type_for(node.ext) || "text/plain"
74
-
75
- # Build response
76
- res.headers["Content-Type"] = mime_type
77
- res.body = [node.render]
78
- throw :halt
79
- end
57
+ def find_node_for_path(path)
58
+ @root.paths[path] ||
59
+ @root.paths[path + ".html"] ||
60
+ @root.paths[::File.join(path, "index.html")]
80
61
  end
81
62
  end
82
63
  end
@@ -1,3 +1,3 @@
1
1
  module Flutterby
2
- VERSION = "0.0.13"
2
+ VERSION = "0.0.14"
3
3
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flutterby
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.13
4
+ version: 0.0.14
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hendrik Mans
@@ -243,6 +243,7 @@ files:
243
243
  - flutterby.gemspec
244
244
  - lib/flutterby.rb
245
245
  - lib/flutterby/cli.rb
246
+ - lib/flutterby/exporter.rb
246
247
  - lib/flutterby/filters.rb
247
248
  - lib/flutterby/node.rb
248
249
  - lib/flutterby/server.rb