jekyll-carve 0.1.1 → 0.1.2

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: f2fa9dda47fb228d8edba3147048e720d9ee7995a14c38b9f4cac9cf8bfe8f6e
4
- data.tar.gz: 83252c17f31ccdf1f34b64785dbf928fc96619e2a35e243facef5543a71439b8
3
+ metadata.gz: 1fd1b876054b8355dc08cdf7569c46cafd968c9e8874c65cd4e6e2866f29d6e7
4
+ data.tar.gz: c4866d9b82ff1d2bf963eb8945fa4a67b70b94aaa4d3e78b300229d369cefc4d
5
5
  SHA512:
6
- metadata.gz: d9230de7ea7aed19ff33d752aee2c53c8529cf622e742134e41b22d188f61460ea2813b1f34ea82c3544101281004d4e0c42c18b44c5af4f4174bfd52cc178cb
7
- data.tar.gz: 4f50c860251b43b10ea16807c6f8624226c6399a3044c1d1a063020fceb70ab994aa03b9161370ec0d6d472757a0ca544360fa25f0bb6dd757c4d1de58b8435c
6
+ metadata.gz: b0c225c70722c0ed014f0ade97aae1b534992dd6830b02acf47a27b1914fe92208f478aefb72ec071794be0fd9e33f75be25a365fe55ef5f3b7be0b8e23b1f0a
7
+ data.tar.gz: 6c86ca36aec167aa996d1b01df87113e9e0ef8796f97a0cde5c4a1e4f6badc6b0edbad271886c7cd192a7e1167f7c23042952e74641907cae0e1a87f7e65f2e2
data/README.md CHANGED
@@ -130,6 +130,52 @@ reaches the output.
130
130
  > front matter, from a comment system, or from anything else a visitor can
131
131
  > influence.
132
132
 
133
+ ### `carve.includes` - pulling one file into another
134
+
135
+ A Carve page can pull another file in with `{{ path }}`. It stays literal until
136
+ the site asks:
137
+
138
+ ```yaml
139
+ carve:
140
+ includes: true
141
+ ```
142
+
143
+ Includes need carve-lang 0.1.4 or later. On an older engine, a page that
144
+ reaches them stops the build with a message naming the upgrade.
145
+
146
+ Paths resolve relative to the file that wrote them, and nothing resolves outside
147
+ the containment root. The root is Jekyll's source directory unless
148
+ `carve.include_root` names another one, and an include that would leave it is
149
+ not expanded.
150
+
151
+ ```yaml
152
+ carve:
153
+ includes: true
154
+ include_root: /srv/shared/fragments
155
+ ```
156
+
157
+ `carve.include_root` must be an **absolute** path. A relative one is refused
158
+ rather than resolved, because resolving it lands on whatever directory the build
159
+ ran from, which is not a root anyone chose. The page being rendered has to sit
160
+ inside the root too.
161
+
162
+ A target that cannot be read is reported against the page that asked for it, and
163
+ the directive is left as written. The message does not say whether the file was
164
+ missing or refused by containment: both report `include-unresolved`, so a page
165
+ cannot be used to probe the filesystem. The class is at `--verbose`.
166
+
167
+ Under `jekyll serve`, a page is rebuilt when a target it read changes, because
168
+ every resolved target is registered with Jekyll's regeneration path.
169
+
170
+ A conversion with no page behind it - the converter called directly on a String -
171
+ leaves the directive literal, since there is no file for a relative path to
172
+ resolve against.
173
+
174
+ > [!NOTE]
175
+ > Fragments under the source directory are pages in their own right, so Jekyll
176
+ > will publish them. Exclude them in `_config.yml`, or keep them outside the
177
+ > source and point `include_root` at the tree above both.
178
+
133
179
  ## Usage
134
180
 
135
181
  Create a page with a `.crv` extension. It MUST begin with Jekyll
@@ -193,32 +239,4 @@ has already removed the leading block by the time Carve runs.
193
239
 
194
240
  ## Development
195
241
 
196
- ```sh
197
- bundle install
198
- bundle exec rspec # run the converter unit tests
199
- ```
200
-
201
- ### Which engine you are testing against
202
-
203
- `bundle install` here does **not** resolve `carve-lang` from RubyGems. `Gemfile`
204
- pins the engine to a carve-rb revision, so a development run measures one exact
205
- engine build rather than whatever the registry serves that day, and
206
- `script/verify_engine_pin.rb` refuses when the Gemfile, the resolved bundle and
207
- the loaded library disagree. CI runs it before the suite:
208
-
209
- ```sh
210
- bundle exec ruby script/verify_engine_pin.rb
211
- # carve-lang 0.1.2 from carve-rb b7f3a91a... (Gemfile pins b7f3a91a4192)
212
- ```
213
-
214
- An installed copy of this gem resolves differently, through the range
215
- `jekyll-carve.gemspec` declares - today `>= 0.1.1, < 0.2.0`. The two are
216
- deliberately not the same engine, and both are checked:
217
- `spec/engine_floor_spec.rb` asks whether the engine under `bundle exec rspec`
218
- still does what the floor claims, and `script/consumer_engine_probe.rb` asks the
219
- same of an actual `gem install`, daily in `Engine drift` and again before every
220
- release.
221
-
222
- There is no committed `Gemfile.lock`, and that is deliberate - the reasoning,
223
- including why a lockfile would be a weaker pin here and what it would cost the
224
- Ruby 3.1 job, is written at the top of `Gemfile`.
242
+ Contributor setup, testing, and maintenance notes are in the [development guide](docs/development.md).
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Jekyll
4
4
  module Carve
5
- VERSION = "0.1.1"
5
+ VERSION = "0.1.2"
6
6
  end
7
7
  end
data/lib/jekyll-carve.rb CHANGED
@@ -59,9 +59,52 @@ module Jekyll
59
59
  #
60
60
  # Returns the rendered HTML String.
61
61
  def convert(content)
62
- ::Carve.to_html(content.to_s,
63
- extensions: carve_extensions,
64
- symbols: carve_symbols)
62
+ document = Rendering.current
63
+ root = include_root
64
+ if root.nil? || document.nil? || document.path.to_s.empty?
65
+ return ::Carve.to_html(content.to_s,
66
+ extensions: carve_extensions,
67
+ symbols: carve_symbols)
68
+ end
69
+ # carve-lang added this entry point in 0.1.4; the floor stays lower for sites without includes.
70
+ unless ::Carve.respond_to?(:to_html_with_includes)
71
+ raise ArgumentError,
72
+ "carve.includes: the installed carve-lang (#{::Carve::VERSION}) has no " \
73
+ "to_html_with_includes; upgrade it to 0.1.4 or later, or set carve.includes: false"
74
+ end
75
+
76
+ begin
77
+ result = ::Carve.to_html_with_includes(content.to_s,
78
+ root: root,
79
+ source_path: document.path,
80
+ extensions: carve_extensions,
81
+ symbols: carve_symbols)
82
+ rescue ArgumentError => e
83
+ # A relative root, or a page outside it. Both are decided by the
84
+ # engine; what this adds is the key and the page to look at.
85
+ raise ArgumentError,
86
+ "carve.include_root: #{document.relative_path || document.path}: #{e.message}"
87
+ end
88
+ report_includes(result, document, root)
89
+ result[:value]
90
+ end
91
+
92
+ # The containment root configured under `carve.include_root`, or nil when
93
+ # `carve.includes` is off.
94
+ #
95
+ # A configured value is handed to the engine exactly as written. The
96
+ # engine refuses a relative root, and that refusal is what keeps
97
+ # containment off whatever directory the build ran from; expanding it
98
+ # here would mean it never fires. The default is Jekyll's own source
99
+ # directory, which this package derives rather than reads, so that one is
100
+ # expanded.
101
+ def include_root
102
+ return nil unless carve_config["includes"]
103
+
104
+ configured = carve_config["include_root"]
105
+ return configured.to_s if configured.is_a?(String) && !configured.empty?
106
+
107
+ File.expand_path(site_source)
65
108
  end
66
109
 
67
110
  # Carve extensions configured under `carve.extensions` in _config.yml.
@@ -129,6 +172,48 @@ module Jekyll
129
172
 
130
173
  private
131
174
 
175
+ # Surface what expansion degraded, and record what it read.
176
+ def report_includes(result, document, root)
177
+ page = document.relative_path || document.path
178
+ Array(result[:warnings]).each do |warning|
179
+ origin = warning[:file]
180
+ where = origin && origin != page ? "#{page} (#{origin})" : page
181
+ Jekyll.logger.warn "Carve:", "#{where}: #{warning[:rule]}: #{warning[:message]}"
182
+ end
183
+ suppressed = result[:suppressedWarnings].to_i
184
+ if suppressed.positive?
185
+ Jekyll.logger.warn "Carve:",
186
+ "#{page}: #{suppressed} further include warnings suppressed"
187
+ end
188
+ Array(result[:dependencies]).each do |dependency|
189
+ # A containment refusal and a missing file are the same warning, so a
190
+ # page cannot probe the filesystem (spec I7). The class that was
191
+ # collapsed is here, for the build's own log.
192
+ if dependency[:denial]
193
+ Jekyll.logger.debug "Carve:",
194
+ "#{page}: include #{dependency[:path]}: #{dependency[:denial]}"
195
+ next
196
+ end
197
+
198
+ register_dependency(document, File.join(root, dependency[:path].to_s))
199
+ end
200
+ end
201
+
202
+ # Tell Jekyll the page has to be rebuilt when this target changes.
203
+ #
204
+ # Only a RESOLVED target is registered. An unresolved dependency reports
205
+ # the directive as written rather than a path relative to the root, so
206
+ # joining it onto the root names the wrong file: a nested `missing.crv`
207
+ # would be recorded against the root rather than against the directory
208
+ # its parent sits in. A fragment appearing later is covered by Jekyll's
209
+ # own watch over the source directory.
210
+ def register_dependency(document, target)
211
+ regenerator = document.site&.regenerator
212
+ return unless regenerator.respond_to?(:add_dependency)
213
+
214
+ regenerator.add_dependency(document.path, target)
215
+ end
216
+
132
217
  # The `carve` table from _config.yml, or an empty Hash.
133
218
  def carve_config
134
219
  config = @config.is_a?(Hash) ? @config["carve"] : nil
@@ -247,9 +332,42 @@ module Jekyll
247
332
  end
248
333
  end
249
334
  end
335
+
336
+ # Which document Jekyll is rendering right now.
337
+ #
338
+ # `Jekyll::Converter#convert` is handed a String and nothing else, so a
339
+ # converter cannot see the file the body came from - and an include has to
340
+ # resolve against that file. The `:pre_render` hook carries it, and Jekyll
341
+ # renders documents one at a time, so the current one is a thread-local
342
+ # rather than a queue.
343
+ module Rendering
344
+ KEY = :jekyll_carve_document
345
+
346
+ module_function
347
+
348
+ def current
349
+ Thread.current[KEY]
350
+ end
351
+
352
+ def with(document)
353
+ Thread.current[KEY] = document
354
+ end
355
+
356
+ def clear
357
+ Thread.current[KEY] = nil
358
+ end
359
+ end
250
360
  end
251
361
  end
252
362
 
363
+ Jekyll::Hooks.register %i[pages documents], :pre_render do |document|
364
+ Jekyll::Carve::Rendering.with(document)
365
+ end
366
+
367
+ Jekyll::Hooks.register %i[pages documents], :post_render do |_document|
368
+ Jekyll::Carve::Rendering.clear
369
+ end
370
+
253
371
  # Resolve the symbol map once per build rather than once per process.
254
372
  #
255
373
  # `Site#reset` runs at the top of every `Site#process`, including every rebuild
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: jekyll-carve
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - markup-carve
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-27 00:00:00.000000000 Z
11
+ date: 2026-09-22 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: carve-lang