flora 0.12.0 → 1.0.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: a11f85e869328faff7d9244556f4b6c27ea30460c1ecbb9a691737d10f24daaa
4
- data.tar.gz: 7a43d4c8821ce2e8d6899cdae404bb47a1db66c54032ac5cbc123e18cee394af
3
+ metadata.gz: 0333fa46d5b40eb06534baaba23d9fd099a2f1384814feb560ff4cdc38702863
4
+ data.tar.gz: ae45ab23580875aa64eddaae0c806cbb1c7f4be9f5cba7283aa3923ffb978802
5
5
  SHA512:
6
- metadata.gz: fc3287be925560cb0ee03df33c04d8cb2de6a811345dc75385359afa94236a5500cac5c5a969b50bfb66b44117d4266e986fd86f624d84e38a83c995ffa503f0
7
- data.tar.gz: 4eae0b7459aad9100e813ab916be76ef9c7c0aac7d814ac9524e358a98a06a6fb83f4e869f1bfccca426c2af3c3376cc405d93e3a8fe55950054b5f3a2992dd2
6
+ metadata.gz: c4dcbb9a3237d1d3c7156894555e859532fe63d978d4845ec608367a8044cc43ee4c9f6e15eef0c3c07a77a1d1848a0f1486b8360a01956e9b48e1638c1eca78
7
+ data.tar.gz: a5863189034686838ca35636aea54b07247da127c3cd540b945d8fed9c47873ba42b9bc2346e8675232230bcf846e3f630cb79fcf0328a474fb43cfb83582cdd
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
 
@@ -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
@@ -1,6 +1,6 @@
1
- # A Blueprint that includes Nestable is something that can be nested into a
1
+ # A Blueprint that includes HasLayout is something that can be nested into a
2
2
  # Lilac-based layout.
3
- module Flora::Project::Blueprint::Nestable
3
+ module Flora::Project::Blueprint::HasLayout
4
4
 
5
5
  def render
6
6
  # The Html DSL uses a global variable internally that needs to be reset every time.
@@ -19,16 +19,15 @@ module Flora::Project::Blueprint::Nestable
19
19
 
20
20
  def find_layouts
21
21
  layouts = []
22
+ current = @file.parent
22
23
 
23
- @file.ascend do |dir|
24
- maybe_layout = dir.join('_layout.rb')
24
+ loop do
25
+ maybe_layout = current.join('./_layout.rb')
25
26
  layouts << maybe_layout if maybe_layout.exist?
26
- end
27
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?
28
+ break if current == @project.dir
29
+ current = current.parent
30
+ end
32
31
 
33
32
  layouts
34
33
  end
@@ -40,12 +39,13 @@ module Flora::Project::Blueprint::Nestable
40
39
  end
41
40
 
42
41
  cont = -> { render_internal(layouts, i-1, &block) }
43
- eval_with_block(layouts[i-1].read, &cont)
42
+ filename = layouts[i-1].to_s
43
+ eval_with_block(layouts[i-1].read, filename, &cont)
44
44
  end
45
45
 
46
46
 
47
- def eval_with_block(str, &block)
48
- eval(str)
47
+ def eval_with_block(str, filename, &block)
48
+ eval(str, binding, filename)
49
49
  end
50
50
 
51
51
  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.0'
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.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Nick Vladimiroff
@@ -142,6 +142,7 @@ executables:
142
142
  extensions: []
143
143
  extra_rdoc_files: []
144
144
  files:
145
+ - ".woodpecker.yml"
145
146
  - LICENSE.txt
146
147
  - README.md
147
148
  - Rakefile
@@ -152,14 +153,15 @@ files:
152
153
  - lib/flora/config.rb
153
154
  - lib/flora/factory.rb
154
155
  - lib/flora/lilac.rb
156
+ - lib/flora/plugin_manager.rb
155
157
  - lib/flora/plugins/blog.rb
156
158
  - lib/flora/plugins/blog/post.rb
157
159
  - lib/flora/plugins/redirector.rb
158
160
  - lib/flora/plugins/static_files.rb
159
161
  - lib/flora/project.rb
160
162
  - lib/flora/project/blueprint.rb
163
+ - lib/flora/project/blueprint/has_layout.rb
161
164
  - lib/flora/project/blueprint/markdown.rb
162
- - lib/flora/project/blueprint/nestable.rb
163
165
  - lib/flora/project/blueprint/ruby_html.rb
164
166
  - lib/flora/version.rb
165
167
  - tmp/.keep
@@ -176,7 +178,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
176
178
  requirements:
177
179
  - - ">="
178
180
  - !ruby/object:Gem::Version
179
- version: 3.2.0
181
+ version: 3.4.0
180
182
  required_rubygems_version: !ruby/object:Gem::Requirement
181
183
  requirements:
182
184
  - - ">="