flora 0.12.0 → 1.0.1

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: a11f85e869328faff7d9244556f4b6c27ea30460c1ecbb9a691737d10f24daaa
4
- data.tar.gz: 7a43d4c8821ce2e8d6899cdae404bb47a1db66c54032ac5cbc123e18cee394af
3
+ metadata.gz: ffea457870b0ae28e03a55fe032421257ea9eceb19ff282727c01b888a24c05b
4
+ data.tar.gz: 91d44f3cefd76ee01cbc834163d075564bf5ea9df5adb1487d143e089868d07a
5
5
  SHA512:
6
- metadata.gz: fc3287be925560cb0ee03df33c04d8cb2de6a811345dc75385359afa94236a5500cac5c5a969b50bfb66b44117d4266e986fd86f624d84e38a83c995ffa503f0
7
- data.tar.gz: 4eae0b7459aad9100e813ab916be76ef9c7c0aac7d814ac9524e358a98a06a6fb83f4e869f1bfccca426c2af3c3376cc405d93e3a8fe55950054b5f3a2992dd2
6
+ metadata.gz: b2fb5f500e39eb1f4f143445bc460a0a501a1bfca8459c8eb0d50ddaee3d66882c731fb823ffc4b3dc2717b5186df6be9d42810cb0d0293497434f06d171c9ec
7
+ data.tar.gz: bf5d0b9c5d00e2bff223d3aec48b1225e397adea87eac7a780903a1d337e275efefe09a15323508f28c83b19adc6a303b4a6d504adac62663b5067e1b7f5b43c
data/.document ADDED
@@ -0,0 +1,2 @@
1
+ lib
2
+ *.md
data/.woodpecker.yml ADDED
@@ -0,0 +1,16 @@
1
+ when:
2
+ - event: push
3
+ - event: pull_request
4
+
5
+ matrix:
6
+ RUBY_VERSION:
7
+ - 4.0
8
+ - 3.4
9
+
10
+ steps:
11
+ - name: build
12
+ image: ruby:${RUBY_VERSION}-alpine
13
+ commands:
14
+ - apk add alpine-sdk yaml-dev
15
+ - bundle install
16
+ - rake
data/lib/flora/config.rb CHANGED
@@ -1,27 +1,31 @@
1
1
  # This is the user-visible configuration class. It lives in ROOT/_config.rb.
2
2
  class Flora::Config
3
3
 
4
- def initialize(file, flora)
4
+ def initialize(file, plugin_manager)
5
5
  @file = file
6
- @flora = flora
7
- load if @file.exist?
6
+ @plugin_manager = plugin_manager
8
7
  end
9
8
 
10
9
 
11
10
  def use(mod)
12
- @flora.load_plugin(mod)
11
+ @plugin_manager.load(mod)
12
+
13
+ # TODO: ideally, this would live in PluginManager, but it needs to pass
14
+ # Config.
15
+ mod.loaded(self) if mod.respond_to?(:loaded)
13
16
  end
14
17
 
15
18
 
16
19
  def extend_view(mod)
17
- Kernel.include(mod)
20
+ @plugin_manager.global_load(mod)
18
21
  end
19
22
 
20
23
 
21
- private
24
+ # :nodoc:
25
+ def load
26
+ return unless @file.exist?
22
27
 
23
- def load
24
- instance_eval(@file.read)
25
- end
28
+ instance_eval(@file.read)
29
+ end
26
30
 
27
31
  end
data/lib/flora/factory.rb CHANGED
@@ -14,10 +14,10 @@ class Flora::Factory
14
14
 
15
15
  @project.blueprints.each do |blueprint|
16
16
  out_filename = out_dir.join(blueprint.page_name)
17
+ @logger.debug("[Flora] #{blueprint.file} => #{out_filename}")
18
+
17
19
  FileUtils.mkdir_p(out_filename.dirname) unless out_filename.dirname.exist?
18
20
  out_filename.write(blueprint.render)
19
-
20
- @logger.debug("[Flora] #{blueprint.file} => #{out_filename}")
21
21
  end
22
22
  end
23
23
 
data/lib/flora/lilac.rb CHANGED
@@ -87,4 +87,13 @@ module Flora::Lilac
87
87
  node
88
88
  end
89
89
 
90
+
91
+ def text(string = nil, &block)
92
+ string ||= block.call if block_given?
93
+
94
+ node = { tag: 'text', opts: {}, text: string, children: [] }
95
+ $flora_added << node
96
+ node
97
+ end
98
+
90
99
  end
@@ -0,0 +1,28 @@
1
+ class Flora::PluginManager
2
+
3
+ def initialize(logger)
4
+ @logger = logger
5
+ @pluggables = {}
6
+ end
7
+
8
+
9
+ def create_pluggable_class(klass)
10
+ @pluggables[klass] = Class.new(klass)
11
+ end
12
+
13
+
14
+ def load(mod)
15
+ @pluggables.each do |base_class, anon_class|
16
+ name = "#{base_class.name.split('::').last}Methods"
17
+ anon_class.include(mod.const_get(name)) if mod.const_defined?(name)
18
+ end
19
+
20
+ @logger.debug("[Flora] Plugin #{mod} loaded")
21
+ end
22
+
23
+
24
+ def global_load(mod)
25
+ Kernel.include(mod)
26
+ end
27
+
28
+ end
@@ -68,7 +68,6 @@ module Flora::Plugins::Blog
68
68
  end
69
69
 
70
70
 
71
-
72
71
  # Nokogiri's builder uses method_missing, and some of Lilac's tags are
73
72
  # named the same as some of the Atom tags we want to use which causes
74
73
  # conflicts. Use this method to get around that!
@@ -81,17 +80,15 @@ module Flora::Plugins::Blog
81
80
 
82
81
  module ProjectMethods
83
82
 
84
- def posts
85
- @dir.glob('posts/**.md').map { Post.new(it, @dir) }.sort_by(&:date).reverse
83
+ def self.included(base)
84
+ base.class_eval do
85
+ blueprint_classes.prepend(Flora::Plugins::Blog::BlogPost)
86
+ end
86
87
  end
87
88
 
88
- end
89
-
90
-
91
- module BlueprintMethods
92
89
 
93
90
  def posts
94
- @project.posts
91
+ @dir.glob('posts/**.md').map { Post.new(it, @dir) }.sort_by(&:date).reverse
95
92
  end
96
93
 
97
94
  end
@@ -109,7 +106,7 @@ module Flora::Plugins::Blog
109
106
  end
110
107
 
111
108
 
112
- module Config
109
+ module ConfigMethods
113
110
 
114
111
  def self.included(base)
115
112
  base.class_eval do
@@ -119,4 +116,18 @@ module Flora::Plugins::Blog
119
116
 
120
117
  end
121
118
 
119
+
120
+ module ViewExtensions
121
+
122
+ def posts
123
+ @project.posts
124
+ end
125
+
126
+ end
127
+
128
+
129
+ def self.loaded(config)
130
+ config.extend_view(ViewExtensions)
131
+ end
132
+
122
133
  end
@@ -26,7 +26,7 @@ module Flora::Plugins::Redirector
26
26
  end
27
27
 
28
28
 
29
- module Config
29
+ module ConfigMethods
30
30
 
31
31
  def self.included(base)
32
32
  base.class_eval do
@@ -19,7 +19,18 @@ module Flora::Plugins::StaticFiles
19
19
  end
20
20
 
21
21
 
22
- module Config
22
+ module ProjectMethods
23
+
24
+ def self.included(base)
25
+ base.class_eval do
26
+ blueprint_classes.prepend(Flora::Plugins::StaticFiles::StaticFile)
27
+ end
28
+ end
29
+
30
+ end
31
+
32
+
33
+ module ConfigMethods
23
34
 
24
35
  def self.included(base)
25
36
  base.class_eval do
@@ -0,0 +1,54 @@
1
+ # A Blueprint that includes HasLayout is something that can be nested into a
2
+ # Lilac-based layout.
3
+ module Flora::Project::Blueprint::HasLayout
4
+
5
+ def render
6
+ # The Html DSL uses a global variable internally that needs to be reset every time.
7
+ $flora_added = []
8
+
9
+ layouts = find_layouts
10
+ tree = render_internal(layouts, layouts.size) do
11
+ render_lilac
12
+ end
13
+
14
+ Flora::Lilac.to_html(tree)
15
+ end
16
+
17
+
18
+ private
19
+
20
+ def find_layouts
21
+ layouts = []
22
+ current = @file.parent
23
+
24
+ loop do
25
+ maybe_layout = current.join('./_layout.rb')
26
+ layouts << maybe_layout if maybe_layout.exist?
27
+
28
+ break if current == @project.dir
29
+ current = current.parent
30
+ end
31
+
32
+ layouts
33
+ end
34
+
35
+
36
+ def render_internal(layouts, i, &block)
37
+ if i <= 0
38
+ return block.call
39
+ end
40
+
41
+ cont = -> { render_internal(layouts, i-1, &block) }
42
+ filename = layouts[i-1].to_s
43
+ eval_with_block(layouts[i-1].read, filename, &cont)
44
+ end
45
+
46
+
47
+ # This exists because the eval'd code will call yield, and yield will always
48
+ # grab the current block passed into the current function. We need that to be
49
+ # a specific proc, not the one passed into render_internal!
50
+ def eval_with_block(str, filename, &block)
51
+ eval(str, binding, filename)
52
+ end
53
+
54
+ end
@@ -1,6 +1,6 @@
1
1
  class Flora::Project::Blueprint::Markdown < Flora::Project::Blueprint
2
2
 
3
- include Flora::Project::Blueprint::Nestable
3
+ include Flora::Project::Blueprint::HasLayout
4
4
 
5
5
 
6
6
  def self.recognize(file, config)
@@ -1,6 +1,6 @@
1
1
  class Flora::Project::Blueprint::RubyHtml < Flora::Project::Blueprint
2
2
 
3
- include Flora::Project::Blueprint::Nestable
3
+ include Flora::Project::Blueprint::HasLayout
4
4
 
5
5
 
6
6
  def self.recognize(file, config)
@@ -11,7 +11,7 @@ class Flora::Project::Blueprint::RubyHtml < Flora::Project::Blueprint
11
11
  private
12
12
 
13
13
  def render_lilac
14
- instance_eval(@file.read)
14
+ instance_eval(@file.read, @file.to_s)
15
15
  end
16
16
 
17
17
  end
@@ -1,17 +1,11 @@
1
- # A Blueprint is something can be rendered into a File. Sometimes a Blueprint
2
- # looks very different than the File it renders into, but other times (like for
1
+ # A Blueprint is something can be rendered into a page. Sometimes a Blueprint
2
+ # looks very different than the page it renders into, but other times (like for
3
3
  # static files), nothing changes.
4
4
  class Flora::Project::Blueprint
5
5
 
6
6
  attr_reader(:file)
7
7
 
8
8
 
9
- # :nodoc:
10
- def self.types
11
- ObjectSpace.each_object(Class).select { it < self }
12
- end
13
-
14
-
15
9
  # Returns true if this Blueprint is appropriate for the file.
16
10
  #
17
11
  # TODO: should this be an instance method somewhere instead?
@@ -28,15 +22,15 @@ class Flora::Project::Blueprint
28
22
  end
29
23
 
30
24
 
31
- # The name of the Page this Blueprint makes. By default this is just the
25
+ # The name of the page this Blueprint makes. By default this is just the
32
26
  # filename, but .html, but subclasses can override this.
33
27
  def page_name
34
28
  @file.relative_path_from(@project.dir).sub_ext('.html').to_s
35
29
  end
36
30
 
37
31
 
38
- # Render a Blueprint into a Page for a Website. This is the String contents
39
- # of whatever the Page in the final Website looks like.
32
+ # Render a Blueprint into a page for a Website. This is the String contents
33
+ # of whatever the page in the final Website looks like.
40
34
  def render
41
35
  # Override in subclasses!
42
36
  end
data/lib/flora/project.rb CHANGED
@@ -9,6 +9,10 @@ class Flora::Project
9
9
 
10
10
  IGNORES = ['.git/*', 'lib/**', '**_*.rb']
11
11
 
12
+ def self.blueprint_classes
13
+ @blueprint_classes ||= [Blueprint::Markdown, Blueprint::RubyHtml]
14
+ end
15
+
12
16
 
13
17
  def initialize(dir, loader, config)
14
18
  @dir = dir
@@ -38,7 +42,7 @@ class Flora::Project
38
42
  next if IGNORES.any? { file.fnmatch((@dir / it).to_s) }
39
43
  next if file.directory?
40
44
 
41
- klass = Blueprint.types.find {
45
+ klass = self.class.blueprint_classes.find {
42
46
  it.recognize(file.relative_path_from(@dir), @config)
43
47
  }
44
48
 
data/lib/flora/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  class Flora
2
- VERSION = '0.12.0'
2
+ VERSION = '1.0.1'
3
3
  end
data/lib/flora.rb CHANGED
@@ -5,6 +5,7 @@ require 'fileutils'
5
5
  require 'logger'
6
6
 
7
7
  loader = Zeitwerk::Loader.for_gem(warn_on_extra_files: false)
8
+ loader.do_not_eager_load("#{__dir__}/flora/plugins")
8
9
  loader.setup
9
10
 
10
11
  class Flora
@@ -16,19 +17,20 @@ class Flora
16
17
  dir = Pathname.new(dir)
17
18
 
18
19
  @logger = Logger.new(STDOUT, level: ENV['FLORA_LOG'] || 'info')
20
+ @plugin_manager = PluginManager.new(@logger)
19
21
 
20
22
  # Inject Lilac into the Kernel so it's available everywhere. Just
21
23
  # instance_eval isn't enough because it'll be missing in lib/ code.
22
24
  #
23
25
  # TODO: is there a less disruptive way to do this?
24
- Kernel.include(Flora::Lilac)
26
+ @plugin_manager.global_load(Flora::Lilac)
25
27
 
26
28
  # The classes that are pluggable get their own instances to avoid conflicting
27
29
  # with other instances of Flora in the same process. This is mostly for the
28
30
  # unit tests. Maybe one day we can use Ruby::Box or something here instead.
29
- @config_class = Class.new(Config)
30
- @project_class = Class.new(Project)
31
- @factory_class = Class.new(Factory)
31
+ @config_class = @plugin_manager.create_pluggable_class(Config)
32
+ @project_class = @plugin_manager.create_pluggable_class(Project)
33
+ @factory_class = @plugin_manager.create_pluggable_class(Factory)
32
34
 
33
35
  # This has to be loaded before Config so Config can reference lib/ code.
34
36
  @project_loader = Zeitwerk::Loader.new
@@ -38,7 +40,9 @@ class Flora
38
40
  @project_loader.enable_reloading
39
41
  @project_loader.setup
40
42
 
41
- @config = @config_class.new(dir.join('_config.rb'), self)
43
+ @config = @config_class.new(dir.join('_config.rb'), @plugin_manager)
44
+ @config.load
45
+
42
46
  @project = @project_class.new(dir, @project_loader, @config)
43
47
  @factory = @factory_class.new(@project, @config, @logger)
44
48
  end
@@ -60,17 +64,6 @@ class Flora
60
64
  end
61
65
 
62
66
 
63
- # TODO: it would be nice if this wasn't exposed here. It's just for Config#plugin.
64
- def load_plugin(mod)
65
- @config_class.include(mod::Config) if defined?(mod::Config)
66
- @project_class.include(mod::ProjectMethods) if defined?(mod::ProjectMethods)
67
- @factory_class.include(mod::FactoryMethods) if defined?(mod::FactoryMethods)
68
-
69
- # TODO: make this a little more resilient (like the other classes).
70
- Flora::Project::Blueprint.include(mod::BlueprintMethods) if defined?(mod::BlueprintMethods)
71
- end
72
-
73
-
74
67
  private
75
68
 
76
69
  def bench
@@ -80,8 +73,3 @@ class Flora
80
73
  end
81
74
 
82
75
  end
83
-
84
- # Built-in Blueprints need to be explicitly loaded or else they won't show up
85
- # in Blueprint.types.
86
- loader.load_file("#{__dir__}/flora/project/blueprint/ruby_html.rb")
87
- loader.load_file("#{__dir__}/flora/project/blueprint/markdown.rb")
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.12.0
4
+ version: 1.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Vladimiroff
@@ -142,6 +142,8 @@ executables:
142
142
  extensions: []
143
143
  extra_rdoc_files: []
144
144
  files:
145
+ - ".document"
146
+ - ".woodpecker.yml"
145
147
  - LICENSE.txt
146
148
  - README.md
147
149
  - Rakefile
@@ -152,14 +154,15 @@ files:
152
154
  - lib/flora/config.rb
153
155
  - lib/flora/factory.rb
154
156
  - lib/flora/lilac.rb
157
+ - lib/flora/plugin_manager.rb
155
158
  - lib/flora/plugins/blog.rb
156
159
  - lib/flora/plugins/blog/post.rb
157
160
  - lib/flora/plugins/redirector.rb
158
161
  - lib/flora/plugins/static_files.rb
159
162
  - lib/flora/project.rb
160
163
  - lib/flora/project/blueprint.rb
164
+ - lib/flora/project/blueprint/has_layout.rb
161
165
  - lib/flora/project/blueprint/markdown.rb
162
- - lib/flora/project/blueprint/nestable.rb
163
166
  - lib/flora/project/blueprint/ruby_html.rb
164
167
  - lib/flora/version.rb
165
168
  - tmp/.keep
@@ -176,7 +179,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
176
179
  requirements:
177
180
  - - ">="
178
181
  - !ruby/object:Gem::Version
179
- version: 3.2.0
182
+ version: 3.4.0
180
183
  required_rubygems_version: !ruby/object:Gem::Requirement
181
184
  requirements:
182
185
  - - ">="
@@ -1,51 +0,0 @@
1
- # A Blueprint that includes Nestable is something that can be nested into a
2
- # Lilac-based layout.
3
- module Flora::Project::Blueprint::Nestable
4
-
5
- def render
6
- # The Html DSL uses a global variable internally that needs to be reset every time.
7
- $flora_added = []
8
-
9
- layouts = find_layouts
10
- tree = render_internal(layouts, layouts.size) do
11
- render_lilac
12
- end
13
-
14
- Flora::Lilac.to_html(tree)
15
- end
16
-
17
-
18
- private
19
-
20
- def find_layouts
21
- layouts = []
22
-
23
- @file.ascend do |dir|
24
- maybe_layout = dir.join('_layout.rb')
25
- layouts << maybe_layout if maybe_layout.exist?
26
- end
27
-
28
- # Ascend doesn't go to the top level directory.
29
- # TODO: Make this code less brittle.
30
- maybe_layout = Pathname.new('./_layout.rb')
31
- layouts << maybe_layout if maybe_layout.exist?
32
-
33
- layouts
34
- end
35
-
36
-
37
- def render_internal(layouts, i, &block)
38
- if i <= 0
39
- return block.call
40
- end
41
-
42
- cont = -> { render_internal(layouts, i-1, &block) }
43
- eval_with_block(layouts[i-1].read, &cont)
44
- end
45
-
46
-
47
- def eval_with_block(str, &block)
48
- eval(str)
49
- end
50
-
51
- end