flora 0.4.0 → 0.6.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b44217297cbd020d161b21d49829be840dd7ec4b99327108aa1b0c14e4f3286
4
- data.tar.gz: 32871444dfee770b17d99e57aa4753e1f8a40e02d3743e7e52e3a1ef77098a8d
3
+ metadata.gz: 2a7d4bc41b49cc7dba0f424be3b8c3bd62ba19fae76d1d1129dad0875f38ab2c
4
+ data.tar.gz: a52d14ffc096179aba36b232fa76826d29a28ab5f2283cde85b533c2c2bba959
5
5
  SHA512:
6
- metadata.gz: 6567dab062a120dca84dabec2246266c5f482fcc2ba8899c541f2d4fd5a0801f7e6c7a9ef8f4af47896495dcf1768ce6eb763eea9df8b33db935a70fdc561617
7
- data.tar.gz: f24f358cb9c3da51053b2c1301f2d5c0f85aaebadf1956558f623839e8c24acc241157b77962f7772e83a6e7d3542a768e488d0efbd6785878ba5431386dd747
6
+ metadata.gz: 92282602b69d8dcdea03660ff8d3446d59b1555d994abf6c7643d1a4ae688c6f1d88b09732d68d40d8c877d9feb39b6067451e4473d281e4e948c1640a9a5fde
7
+ data.tar.gz: de0ae5863a9f75933528d14ee8e8034a16154a207831f958372214990cf544ad2ffc6b1b909454feae15ab62b401120d95bf25b46b92048ea8189a83f93dda71
data/lib/flora/app.rb CHANGED
@@ -2,6 +2,62 @@ require 'rack'
2
2
 
3
3
  class Flora::App
4
4
 
5
+ # TODO: make this support multiple apps.
6
+ OUT_DIR = '/tmp/flora/'
7
+
8
+
9
+ class Rebuilder
10
+
11
+ def initialize(app, dir, flora)
12
+ @app = app
13
+ @dir = Pathname.new(dir)
14
+ @flora = flora
15
+ @last_built = Time.at(0)
16
+ end
17
+
18
+
19
+ def call(env)
20
+ if should_rebuild?
21
+ @flora.build(OUT_DIR)
22
+ @last_built = Time.now.utc
23
+ # TODO: also trigger a reload in the browser
24
+ end
25
+
26
+ @app.call(env)
27
+ end
28
+
29
+
30
+ private
31
+
32
+ def should_rebuild?
33
+ # TODO: this is probably slow. I'm sure there's an easier kqueue-esq way
34
+ # of doing this.
35
+ @dir.find do |file|
36
+ return true if file.stat.mtime > @last_built
37
+ end
38
+
39
+ false
40
+ end
41
+
42
+ end
43
+
44
+
45
+ class Reloader
46
+
47
+ def initialize(app, flora)
48
+ @app = app
49
+ @flora = flora
50
+ end
51
+
52
+ def call(env)
53
+ @flora.reload_blueprint
54
+
55
+ @app.call(env)
56
+ end
57
+
58
+ end
59
+
60
+
5
61
  # UGLY. Is there a better way to do this?
6
62
  class StaticWithoutHtml
7
63
 
@@ -25,12 +81,13 @@ class Flora::App
25
81
 
26
82
  def self.app(path)
27
83
  flora = Flora.new(path)
28
- # TODO: rebuild every req?
29
- flora.build('/tmp/flora/')
30
84
 
31
85
  Rack::Builder.new do
32
- use StaticWithoutHtml, '/tmp/flora'
33
- use Rack::Static, urls: [''], root: '/tmp/flora', index: 'index.html'
86
+ use Rebuilder, path, flora
87
+ use Reloader, flora
88
+ use StaticWithoutHtml, OUT_DIR
89
+ use Rack::Static, urls: [''], root: OUT_DIR, index: 'index.html'
90
+
34
91
  map "/" do
35
92
  run ->(env) do
36
93
  [404, {'Content-Type' => 'text/plain'}, ['Page Not Found!']]
@@ -1,4 +1,4 @@
1
- module Flora::Engine::Page::Html
1
+ module Flora::Blueprint::Page::Html
2
2
 
3
3
  TAGS = %i[
4
4
  a abbr address area article aside audio
@@ -1,4 +1,4 @@
1
- class Flora::Engine::Page::Layout
1
+ class Flora::Blueprint::Page::Layout
2
2
 
3
3
  def initialize(layouts)
4
4
  @layouts = layouts
@@ -1,13 +1,13 @@
1
- class Flora::Engine::Page::MarkdownPage < Flora::Engine::Page
1
+ class Flora::Blueprint::Page::MarkdownPage < Flora::Blueprint::Page
2
2
 
3
- def render
4
- silence_warnings do
5
- raw_html(Kramdown::Document.new(@file.read).to_html)
6
- end
7
- end
3
+ private
8
4
 
5
+ def render_tree
6
+ silence_warnings do
7
+ raw_html(Kramdown::Document.new(@file.read).to_html)
8
+ end
9
+ end
9
10
 
10
- private
11
11
 
12
12
  # Remove this if kramdown ever fixes its one warning.
13
13
  def silence_warnings
@@ -0,0 +1,12 @@
1
+ class Flora::Blueprint::Page::RubyPage < Flora::Blueprint::Page
2
+
3
+ private
4
+
5
+ def render_tree
6
+ # The Html DSL uses a global variable internally that needs to be reset every time.
7
+ $flora_added = []
8
+
9
+ instance_eval(@file.read)
10
+ end
11
+
12
+ end
@@ -0,0 +1,49 @@
1
+ # A page is something can be turned into HTML.
2
+ class Flora::Blueprint::Page
3
+
4
+ attr_reader(:blueprint, :file, :route)
5
+
6
+
7
+ def self.for(file)
8
+ # TODO: make this a little more extensible so plugins can implement new page
9
+ # types.
10
+ case file.extname
11
+ when '.rb'
12
+ RubyPage
13
+ when '.md'
14
+ MarkdownPage
15
+ end
16
+ end
17
+
18
+
19
+ def initialize(blueprint, file, route)
20
+ @blueprint = blueprint
21
+ @file = file
22
+ @route = route
23
+ @layout = find_layouts
24
+ end
25
+
26
+
27
+ def render
28
+ tree = @layout.render do
29
+ render_tree
30
+ end
31
+
32
+ Html.to_html(tree)
33
+ end
34
+
35
+
36
+ private
37
+
38
+ def find_layouts
39
+ layouts = []
40
+
41
+ @file.ascend do |dir|
42
+ maybe_layout = dir.join('_layout.rb')
43
+ layouts << maybe_layout if maybe_layout.exist?
44
+ end
45
+
46
+ Layout.new(layouts)
47
+ end
48
+
49
+ end
@@ -0,0 +1,64 @@
1
+ class Flora::Blueprint
2
+
3
+ attr_reader(:dir)
4
+
5
+ IGNORES = ['.git/*', 'lib/*', '**_layout.rb', '_config.rb']
6
+
7
+
8
+ def initialize(dir, config, page_modules)
9
+ @dir = dir
10
+ @config = config
11
+ @page_modules = page_modules
12
+
13
+ @pages = load_pages
14
+
15
+ @loader = Zeitwerk::Loader.new
16
+ if has_supporting_code?
17
+ @loader.push_dir(@dir.join('lib').to_s)
18
+ end
19
+ @loader.enable_reloading
20
+ @loader.setup
21
+ end
22
+
23
+
24
+ def each_page(&block)
25
+ @pages.each(&block)
26
+ end
27
+
28
+
29
+ def reload
30
+ @loader.reload
31
+ @pages = load_pages
32
+ end
33
+
34
+
35
+ private
36
+
37
+ def load_pages
38
+ pages = []
39
+
40
+ @dir.find do |file|
41
+ next if IGNORES.any? { file.fnmatch((@dir / it).to_s) }
42
+ next if file.directory?
43
+ next unless supported_file_type?(file)
44
+
45
+ route = file.relative_path_from(@dir).sub_ext('.html').to_s
46
+ page = Page.for(file).new(self, file, route)
47
+ @page_modules.each { |mod| page.extend(mod) }
48
+ pages << page
49
+ end
50
+
51
+ pages
52
+ end
53
+
54
+
55
+ def supported_file_type?(file)
56
+ file.extname == '.rb' || file.extname == '.md'
57
+ end
58
+
59
+
60
+ def has_supporting_code?
61
+ @dir.join('lib').exist?
62
+ end
63
+
64
+ end
@@ -0,0 +1,21 @@
1
+ class Flora::Config
2
+
3
+ def initialize(file, flora)
4
+ @file = file
5
+ @flora = flora
6
+ load if @file.exist?
7
+ end
8
+
9
+
10
+ def plugin(mod)
11
+ @flora.load_plugin(mod)
12
+ end
13
+
14
+
15
+ private
16
+
17
+ def load
18
+ instance_eval(@file.read)
19
+ end
20
+
21
+ end
@@ -0,0 +1,20 @@
1
+ class Flora::Factory
2
+
3
+ def initialize(blueprint, config)
4
+ @blueprint = blueprint
5
+ @config = config
6
+ end
7
+
8
+
9
+ # Assemble a blueprint and put it into a directory.
10
+ def assemble(out_dir)
11
+ out_dir.mkdir unless out_dir.exist?
12
+
13
+ @blueprint.each_page do |page|
14
+ out_filename = out_dir.join(page.route)
15
+ out_filename.dirname.mkdir unless out_filename.dirname.exist?
16
+ out_filename.write(page.render)
17
+ end
18
+ end
19
+
20
+ end
@@ -3,7 +3,7 @@ module Flora::Plugins::Blog
3
3
  module PageMethods
4
4
 
5
5
  def posts
6
- @root.glob('posts/**').map { Post.new(it, @root) }
6
+ @blueprint.dir.glob('posts/**').map { Post.new(it, @blueprint.dir) }
7
7
  end
8
8
 
9
9
  end
@@ -1,12 +1,12 @@
1
1
  module Flora::Plugins::Redirector
2
2
 
3
- module EngineMethods
3
+ module FactoryMethods
4
4
 
5
- def build(*)
5
+ def assemble(out_dir)
6
6
  super
7
7
 
8
8
  @config.redirects.each do |src, dest|
9
- out_filename = @out_dir.join(src + '.html')
9
+ out_filename = out_dir.join(src + '.html')
10
10
  out_filename.dirname.mkdir unless out_filename.dirname.exist?
11
11
  out_filename.write(build_redir_file(dest))
12
12
  end
@@ -20,14 +20,14 @@ module Flora::Plugins::StaticFiles
20
20
  end
21
21
 
22
22
 
23
- module EngineMethods
23
+ module FactoryMethods
24
24
 
25
- def build(*)
25
+ def assemble(out_dir)
26
26
  super
27
27
 
28
28
  base = @config.static_files_dir || 'public'
29
- copier = Copier.new(@path.join(base))
30
- copier.copy_to(@out_dir.join(base))
29
+ copier = Copier.new(@blueprint.dir.join(base))
30
+ copier.copy_to(out_dir.join(base))
31
31
  end
32
32
 
33
33
  end
data/lib/flora/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Flora
2
- VERSION = "0.4.0"
2
+ VERSION = "0.6.0"
3
3
  end
data/lib/flora.rb CHANGED
@@ -8,14 +8,45 @@ loader.setup
8
8
  class Flora
9
9
 
10
10
  def initialize(dir)
11
- # A new class so plugins only affect one instance.
12
- @engine = Class.new(Engine).new(dir)
13
- @engine.configure
11
+ dir = Pathname.new(dir)
12
+
13
+ # Inject HTML helpers into Kernel so they're available everywhere. Just
14
+ # instance_eval isn't enough because they'll be missing in lib/ code.
15
+ #
16
+ # TODO: is there a less disruptive way to do this?
17
+ Kernel.prepend(Flora::Blueprint::Page::Html)
18
+
19
+ # The classes that are pluggable get their own instances to avoid conflicting
20
+ # with other instances of Flora in the same process. This is mostly for the
21
+ # unit tests. Maybe one day we can use Ruby::Box or something here instead.
22
+ @config_class = Class.new(Config)
23
+ @factory_class = Class.new(Factory)
24
+
25
+ # Page plugins get mixed in directly to the page instances by Blueprint.
26
+ @page_modules = []
27
+
28
+ @config = @config_class.new(dir.join('_config.rb'), self)
29
+ @blueprint = Blueprint.new(dir, @config, @page_modules)
30
+ @factory = @factory_class.new(@blueprint, @config)
14
31
  end
15
32
 
16
33
 
17
34
  def build(out)
18
- @engine.build(out)
35
+ @factory.assemble(Pathname.new(out))
36
+ end
37
+
38
+
39
+ def reload_blueprint
40
+ @blueprint.reload
41
+ end
42
+
43
+
44
+ # TODO: it would be nice if this wasn't exposed here. It's just for Config#plugin.
45
+ def load_plugin(mod)
46
+ @factory_class.include(mod::FactoryMethods) if defined?(mod::FactoryMethods)
47
+ @config_class.include(mod::Config) if defined?(mod::Config)
48
+
49
+ @page_modules << mod::PageMethods if defined?(mod::PageMethods)
19
50
  end
20
51
 
21
52
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flora
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.4.0
4
+ version: 0.6.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Vladimiroff
@@ -106,14 +106,15 @@ files:
106
106
  - exe/flora
107
107
  - lib/flora.rb
108
108
  - lib/flora/app.rb
109
+ - lib/flora/blueprint.rb
110
+ - lib/flora/blueprint/page.rb
111
+ - lib/flora/blueprint/page/html.rb
112
+ - lib/flora/blueprint/page/layout.rb
113
+ - lib/flora/blueprint/page/markdown_page.rb
114
+ - lib/flora/blueprint/page/ruby_page.rb
109
115
  - lib/flora/cli.rb
110
- - lib/flora/engine.rb
111
- - lib/flora/engine/config.rb
112
- - lib/flora/engine/page.rb
113
- - lib/flora/engine/page/html.rb
114
- - lib/flora/engine/page/layout.rb
115
- - lib/flora/engine/page/markdown_page.rb
116
- - lib/flora/engine/page/ruby_page.rb
116
+ - lib/flora/config.rb
117
+ - lib/flora/factory.rb
117
118
  - lib/flora/plugins/blog.rb
118
119
  - lib/flora/plugins/blog/post.rb
119
120
  - lib/flora/plugins/redirector.rb
@@ -1,12 +0,0 @@
1
- class Flora::Engine::Config
2
-
3
- def initialize(plugin_loader)
4
- @plugin_loader = plugin_loader
5
- end
6
-
7
-
8
- def plugin(mod)
9
- @plugin_loader.load_plugin(mod)
10
- end
11
-
12
- end
@@ -1,8 +0,0 @@
1
- class Flora::Engine::Page::RubyPage < Flora::Engine::Page
2
-
3
- def render
4
- super
5
- instance_eval(@file.read)
6
- end
7
-
8
- end
@@ -1,64 +0,0 @@
1
- # A page is something can be turned into HTML. It might also have a layout to render into.
2
- class Flora::Engine::Page
3
-
4
- IGNORES = ['.git/*', 'lib/*', '**_layout.rb', '_config.rb']
5
-
6
-
7
- class << self
8
-
9
- def each(path)
10
- path.find do |file|
11
- next if IGNORES.any? { file.fnmatch((path / it).to_s) }
12
- next if file.directory?
13
- next unless supported_file_type?(file)
14
-
15
- yield(self.for(path, file))
16
- end
17
- end
18
-
19
-
20
- def for(root, file)
21
- case file.extname
22
- when '.rb'
23
- RubyPage.new(root, file)
24
- when '.md'
25
- MarkdownPage.new(root, file)
26
- end
27
- end
28
-
29
-
30
- private
31
-
32
- def supported_file_type?(file)
33
- file.extname == '.rb' || file.extname == '.md'
34
- end
35
-
36
- end
37
-
38
-
39
- def initialize(root, file)
40
- @root = root
41
- @file = file
42
- end
43
-
44
-
45
- def render
46
- end
47
-
48
-
49
- def outname
50
- # TODO: this might sub something in the middle instead of just the ext.
51
- @file.relative_path_from(@root).sub(@file.extname, '.html')
52
- end
53
-
54
-
55
- def url
56
- @file.relative_path_from(@root).sub(@file.extname, '')
57
- end
58
-
59
-
60
- def dir
61
- @file.dirname
62
- end
63
-
64
- end
data/lib/flora/engine.rb DELETED
@@ -1,69 +0,0 @@
1
- class Flora::Engine
2
-
3
- def initialize(path)
4
- @path = Pathname.new(path)
5
- @config = Config.new(self)
6
-
7
- @site_loader = Zeitwerk::Loader.new
8
-
9
- if site_has_supporting_code?
10
- setup_site_loader
11
- end
12
-
13
- Kernel.prepend(Flora::Engine::Page::Html)
14
- end
15
-
16
-
17
- def configure
18
- config_file = @path.join('_config.rb')
19
- @config.instance_eval(config_file.read) if config_file.exist?
20
- end
21
-
22
-
23
- def build(out_dir)
24
- @out_dir = Pathname.new(out_dir)
25
- @out_dir.mkdir unless @out_dir.exist?
26
-
27
- Page.each(@path) do |page|
28
- layouts = []
29
- page.dir.ascend do |dir|
30
- maybe_layout = dir.join('_layout.rb')
31
- layouts << maybe_layout if maybe_layout.exist?
32
- break if dir == @path
33
- end
34
-
35
- $flora_added = []
36
- tree = Page::Layout.new(layouts).render do
37
- page.render
38
- end
39
- html = Page::Html.to_html(tree)
40
-
41
- out_filename = @out_dir.join(page.outname)
42
- out_filename.dirname.mkdir unless out_filename.dirname.exist?
43
- out_filename.write(html)
44
- end
45
-
46
- end
47
-
48
-
49
- def load_plugin(mod)
50
- self.class.include(mod::EngineMethods) if defined?(mod::EngineMethods)
51
- Page::RubyPage.include(mod::PageMethods) if defined?(mod::PageMethods)
52
- Config.include(mod::Config) if defined?(mod::Config)
53
- end
54
-
55
-
56
- private
57
-
58
- def site_has_supporting_code?
59
- @path.join('lib').exist?
60
- end
61
-
62
-
63
- def setup_site_loader
64
- @site_loader.push_dir(@path.join('lib').to_s)
65
- @site_loader.enable_reloading
66
- @site_loader.setup
67
- end
68
-
69
- end