jekyll_plugin_support 1.0.3 → 3.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.
Files changed (37) hide show
  1. checksums.yaml +4 -4
  2. data/.rubocop.yml +17 -1
  3. data/CHANGELOG.md +33 -6
  4. data/README.md +787 -30
  5. data/jekyll_plugin_support.gemspec +13 -11
  6. data/lib/block/jekyll_plugin_support_block.rb +31 -15
  7. data/lib/block/jekyll_plugin_support_block_noarg.rb +0 -2
  8. data/lib/generator/jekyll_plugin_support_generator.rb +1 -7
  9. data/lib/helper/jekyll_plugin_helper.rb +6 -6
  10. data/lib/helper/jekyll_plugin_helper_class.rb +8 -3
  11. data/lib/hooks/a_page.rb +69 -0
  12. data/lib/hooks/all_collections_hooks.rb +61 -0
  13. data/lib/hooks/all_files.rb +48 -0
  14. data/lib/hooks/class_methods.rb +50 -0
  15. data/lib/jekyll_all_collections/all_collections_tag.rb +157 -0
  16. data/lib/jekyll_plugin_support/jekyll_plugin_support_class.rb +61 -28
  17. data/lib/jekyll_plugin_support/jekyll_plugin_support_spec_support.rb +1 -3
  18. data/lib/jekyll_plugin_support/version.rb +1 -1
  19. data/lib/jekyll_plugin_support.rb +18 -13
  20. data/lib/tag/jekyll_plugin_support_tag.rb +26 -15
  21. data/lib/tag/jekyll_plugin_support_tag_noarg.rb +0 -2
  22. data/lib/util/mslinn_binary_search.rb +152 -0
  23. data/lib/util/send_chain.rb +56 -0
  24. data/spec/all_collections_tag/all_collections_tag_sort_spec.rb +112 -0
  25. data/spec/bsearch_spec.rb +50 -0
  26. data/spec/custom_error_spec.rb +9 -9
  27. data/spec/date_sort_spec.rb +84 -0
  28. data/spec/jekyll_plugin_helper_options_spec.rb +7 -3
  29. data/spec/liquid_variable_parsing_spec.rb +8 -8
  30. data/spec/mslinn_binary_search_spec.rb +47 -0
  31. data/spec/send_chain_spec.rb +72 -0
  32. data/spec/send_spec.rb +28 -0
  33. data/spec/sorted_lru_files_spec.rb +82 -0
  34. data/spec/spec_helper.rb +2 -0
  35. data/spec/status_persistence.txt +3 -9
  36. data/spec/testable_spec.rb +38 -0
  37. metadata +42 -5
data/README.md CHANGED
@@ -1,22 +1,26 @@
1
1
  # `jekyll_plugin_support` [![Gem Version](https://badge.fury.io/rb/jekyll_plugin_support.svg)](https://badge.fury.io/rb/jekyll_plugin_support)
2
2
 
3
- `Jekyll_plugin_support` is a Ruby gem that provides a framework for writing and testing Jekyll plugins.
3
+ After writing over two dozen Jekyll plugins, I distilled the common code into `Jekyll_plugin_support`.
4
+ This F/OSS Ruby gem facilitates writing and testing Jekyll plugins and handles the standard housekeeping that every Jekyll
5
+ inline and block tag plugin requires.
6
+ Logging, parsing arguments, obtaining references to site and page objects, etc. are all handled.
7
+ The result is faster Jekyll plugin writing with fewer bugs.
4
8
 
5
9
  `Jekyll_plugin_support` can be used to create simple Jekyll plugins in
6
10
  the `_plugins/` directory of your Jekyll project, or gem-based Jekyll plugins.
7
11
 
8
12
  At present, only Jekyll tags and blocks are supported.
9
13
 
10
- Plugins that use `jekyll_plugin_support` include:
14
+ Public plugins that use `jekyll_plugin_support` include:
11
15
 
12
16
  <ul style="columns: 2">
13
- <li><a href='https://www.mslinn.com/jekyll_plugins/jekyll_all_collections'><code>jekyll_all_collections</code></a></li>
14
17
  <li><a href='https://www.mslinn.com/jekyll_plugins/jekyll_badge'><code>jekyll_badge</code></a></li>
18
+ <li><a href='https://www.mslinn.com/jekyll_plugins/jekyll_draft'><code>jekyll_draft</code></a></li>
15
19
  <li><a href='https://www.mslinn.com/jekyll_plugins/jekyll_emoji'><code>jekyll_emoji</code></a></li>
16
20
  <li><a href='https://www.mslinn.com/jekyll_plugins/jekyll_flexible_include.html'><code>jekyll_flexible_include</code></a></li>
21
+ <li><a href='https://www.mslinn.com/jekyll_plugins/jekyll_google_translate'><code>jekyll_google_translate</code></a></li>
17
22
  <li><a href='https://www.mslinn.com/jekyll_plugins/jekyll_href.html'><code>jekyll_href</code></a></li>
18
23
  <li><a href='https://www.mslinn.com/jekyll_plugins/jekyll_img.html'><code>jekyll_img</code></a></li>
19
- <li><a href='https://www.mslinn.com/jekyll_plugins/jekyll_plugin_template.html'><code>jekyll_plugin_template</code></a></li>
20
24
  <li><a href='https://www.mslinn.com/jekyll_plugins/jekyll_outline.html'><code>jekyll_outline</code></a></li>
21
25
  <li><a href='https://www.mslinn.com/jekyll_plugins/jekyll_pre.html'><code>jekyll_pre</code></a></li>
22
26
  <li><a href='https://www.mslinn.com/jekyll_plugins/jekyll_quote.html'><code>jekyll_quote</code></a></li>
@@ -33,55 +37,87 @@ Jekyll plugin tags created from `jekyll_plugin_support` framework automatically
33
37
  1. Boilerplate is removed, so you can focus on the required logic and output.
34
38
  2. Arguments are parsed for keywords and name/value parameters.
35
39
  3. Single or double quotes can be used for arguments and parameters.
36
- 4. Important variables are defined.
40
+ 4. Important variables are predefined.
37
41
  5. Error handling is standardized, and includes an automatically defined error type
38
42
  and corresponding CSS tag for each Jekyll tag.
39
- 6. Liquid variables can be passed as parameters to tags, and used in the body of block tags.
40
- 7. Registration is automatic, and important configuration details are reported during registration.
43
+ 6. Jekyll and Liquid variables, including `layout`, `page` and `include` variables,
44
+ can be passed as parameters to tags, and used in the body of block tags.
45
+ 7. Plugin registration is integrated, and important configuration details are reported during registration.
41
46
  8. A custom logger is created for each tag, independent of the default Jekyll logger.
42
47
  9. Variables can be defined in `_config.yml`, and optionally have different values for debug mode,
43
48
  production mode and test mode.
44
49
  10. An attribution message is available.
45
50
  11. Draft pages are automatically detected.
51
+ 12. A demonstration website is provided for easy testing of every plugin.
52
+ 13. Visual Studio Code debugging is set up for the plugin code and the demo website.
53
+ 14. Plugins can be subclassed.
54
+ 15. [`Nugem`](https://mslinn.com/ruby/6800-nugem.html) can create working scaffolding for new plugins built
55
+ using `jekyll_plugin_support`.
56
+ 16. Four new attributes are added to
57
+ [`site`](https://jekyllrb.com/docs/variables/#site-variables):
46
58
 
47
- In addition, a demonstration website is provided for easy testing of your plugins.
59
+ a. `all_collections` includes all documents in all collections.
48
60
 
61
+ b. `all_documents` includes `all_collections` plus all standalone pages.
62
+
63
+ c. `everything` includes `all_documents` plus all static files.
64
+
65
+ d. `sorted_lru_files` is used by a new binary search lookup for matching page suffixes.
66
+ The `jekyll_href` and `jekyll_draft` plugins use this feature.
49
67
 
50
68
  ## Installation
51
69
 
70
+ ### For A Jekyll Website
71
+
52
72
  `Jekyll_plugin_support` is packaged as a Ruby gem.
53
- If your project is a custom plugin that will reside in a Jekyll project’s `_plugins` directory,
73
+ If you want to write a custom Jekyll plugin that will reside in a Jekyll project’s `_plugins` directory,
54
74
  add the following line to your Jekyll plugin’s `Gemfile`.
55
75
 
56
76
  ```ruby
57
77
  group :jekyll_plugins do
58
78
  # ...
59
- gem 'jekyll_plugin_support', '>= 0.8.0'
79
+ gem 'jekyll_plugin_support', '>= 1.1.0'
60
80
  # ...
61
81
  end
62
82
  ```
63
83
 
64
- Otherwise, if your custom plugin will be packaged into a gem, add the following to your plugin’s `.gemspec`:
84
+ Run the standard `jekyll_plugin_support` setup procedure:
85
+
86
+ ```shell
87
+ $ bin/setup
88
+ ```
89
+
90
+
91
+ ### As a Gem Dependency
92
+
93
+ If your custom plugin will be packaged into a gem, add the following to your plugin’s `.gemspec`:
65
94
 
66
95
  ```ruby
67
96
  Gem::Specification.new do |spec|
68
97
  # ...
69
- spec.add_dependency 'jekyll_plugin_support', '>= 0.8.0'
98
+ spec.add_dependency 'jekyll_plugin_support', '>= 1.1.0'
70
99
  # ...
71
100
  end
72
101
  ```
73
102
 
74
- Install the `jekyll_plugin_support` Ruby gem and mark it as a dependency of your project by typing:
103
+ Install the `jekyll_plugin_support` gem into your plugin project in the usual manner:
104
+
105
+ ```shell
106
+ $ bundle
107
+ ```
108
+
109
+ Copy the CSS classes from
110
+ [`demo/assets/css/jekyll_plugin_support.css`](demo/assets/css/jekyll_plugin_support.css)
111
+ to your Jekyll project&rsquo;s CSS file.
75
112
 
76
- ```shell
77
- $ bundle
78
- ```
79
113
 
80
- Copy the CSS classes from `demo/assets/css/jekyll_plugin_support.css` to your Jekyll project&rsquo;s CSS file.
81
114
 
82
115
 
83
116
  ## About `jekyll_plugin_support`
84
117
 
118
+ This Jekyll plugin includes a generator,
119
+ triggered by a high-priority hook, and a block tag called `all_collections`.
120
+
85
121
  `JekyllSupport::JekyllBlock` and `JekyllSupport::JekyllTag`
86
122
  provide support for [Jekyll tag block plugins](https://jekyllrb.com/docs/plugins/tags/#tag-blocks)
87
123
  and [Jekyll inline tag plugins](https://jekyllrb.com/docs/plugins/tags/), respectively.
@@ -104,13 +140,21 @@ Instead, define a method called `render_impl`.
104
140
  For inline tags, `render_impl` does not accept any parameters.
105
141
  For block tags, a single parameter is required, which contains text passed from your block in the page.
106
142
 
107
- Your implementation of render_impl can parse parameters passed to the tag / block tag, as described in
143
+ Your implementation of render_impl can parse parameters passed to your tag, as described in
108
144
  [Tag Parameter Parsing](http://mslinn.com/jekyll/10100-jekyll-plugin-background.html#params).
109
145
 
146
+ In addition, within <code>render_impl</code>,
147
+ the arguments passed to the tag will have been tokenized and parsed,
148
+ with Jekyll and Liquid variables substituted for their values,
149
+ and all the public Jekyll variables will be available as instance variables.
150
+ Error handling will also have been set up,
151
+ and access to your tag's entry within <code>_config.yml</code> will have been set up.
152
+
153
+
110
154
 
111
155
  ## General Usage
112
156
 
113
- Please see the [`demo/`](demo/) project for a well-documented set of demonstration Jekyll plugins that are built from `jekyll_plugin_support`.
157
+ Please see the [`demo/`](demo/) project for a well-documented set of demonstration Jekyll plugins built from `jekyll_plugin_support`.
114
158
  Additional information is available [here](https://mslinn.com/jekyll/10200-jekyll-plugin-background.html) and the
115
159
  [`jekyll_plugin_support`](https://www.mslinn.com/jekyll_plugins/jekyll_plugin_support.html) documentation.
116
160
 
@@ -141,7 +185,196 @@ because both `JekyllSupport` classes define this method.
141
185
 
142
186
  Instead, define a method called `render_impl`.
143
187
  For inline tags, `render_impl` does not accept any parameters.
144
- For block tags, a single parameter is required, which contains any text enclosed within your block.
188
+ For block tags, a single parameter is required, which receives any text enclosed within your block by the website author.
189
+
190
+
191
+ ### New `Site` Attributes
192
+
193
+ No explicit initialization or setup is required.
194
+ Jekyll plugins can access the value of
195
+ `site.all_collections`, `site.all_documents` and `site.everything`;
196
+ however, Liquid code in Jekyll pages and documents cannot.
197
+
198
+
199
+ ### Excluding Files
200
+
201
+ There are two ways to exclude files from the new `site` attributes.
202
+
203
+ 1) The [`exclude` entry in `_config.yml`](https://jekyllrb.com/docs/configuration/options#global-configuration)
204
+ can be used as it normally would.
205
+
206
+ 2) Adding the following entry to a page&rsquo;s front matter causes that page to be excluded
207
+ from the collection created by this plugin:
208
+
209
+ ```html
210
+ ---
211
+ exclude_from_all: true
212
+ ---
213
+ ```
214
+
215
+ ### `all_collections` Plugin Usage
216
+
217
+ Jekyll generators and tags receive an enhanced version of the `site` Jekyll variable.
218
+
219
+
220
+ #### From a Custom Plugin
221
+
222
+ In the following example of how to use the `all_collections` plugin in a custom plugin,
223
+ the `do_something_with` function processes all `Jekyll::Page`s, `Jekyll:Document`s, and static files.
224
+
225
+ ```ruby
226
+ @site.everything.each do |apage|
227
+ do_something_with apage
228
+ end
229
+ ```
230
+
231
+
232
+ #### Using the Block Tag
233
+
234
+ The general form of the Jekyll tag, including all options, is:
235
+
236
+ ```html
237
+ {% all_collections
238
+ date_column='date|last_modified'
239
+ heading='All Posts'
240
+ id='asdf'
241
+ sort_by='SORT_KEYS'
242
+ %}
243
+ ```
244
+
245
+
246
+ ##### `date_column` Attribute
247
+
248
+ One of two date columns can be displayed in the generated HTML:
249
+ either `date` (when the article was originally written),
250
+ or `last_modified`.
251
+ The default value for the `date_column` attribute is `date`.
252
+
253
+
254
+ ##### `heading` Attribute
255
+
256
+ If no `heading` attribute is specified, a heading will automatically be generated, which contains the `sort_by` values,
257
+ for example:
258
+
259
+ ```html
260
+ {% all_collections id='abcdef' sort_by="last_modified" %}
261
+ ```
262
+
263
+ The above generates a heading like:
264
+
265
+ ```html
266
+ <h2 id="abcdef">All Posts Sorted By last_modified</h2>
267
+ ```
268
+
269
+ To suppress both a `h2` heading (and the enclosed `id`) from being generated,
270
+ specify an empty string for the value of `heading`:
271
+
272
+ ```html
273
+ {% all_collections heading='' %}
274
+ ```
275
+
276
+
277
+ ##### `id` Attribute
278
+
279
+ If your Jekyll layout employs [`jekyll-toc`](https://github.com/allejo/jekyll-toc), then `id` attributes are important.
280
+ The `jekyll-toc` include checks for `id` attributes in `h2` ... `h6` tags, and if found,
281
+ and if the attribute value is enclosed in double quotes
282
+ (`id="my_id"`, not single quotes like `id='my_id'`),
283
+ then the heading is included in the table of contents.
284
+
285
+ To suppress an `id` from being generated,
286
+ and thereby preventing the heading from appearing in the automatically generated table of contents from `jekyll-toc`,
287
+ specify an empty string for the value of `id`, like this:
288
+
289
+ ```html
290
+ {% all_collections id='' %}
291
+ ```
292
+
293
+
294
+ ##### `SORT_KEYS` Values
295
+
296
+ `SORT_KEYS` specifies how to sort the collection.
297
+ Values can include one or more of the following attributes:
298
+ `date`, `destination`, `draft`, `label`, `last_modified`, `last_modified_at`, `path`, `relative_path`,
299
+ `title`, `type`, and `url`.
300
+ Ascending sorts are the default; however, a descending sort can be achieved by prepending `-` before an attribute.
301
+
302
+ To specify more than one sort key, provide a comma-delimited string of values.
303
+ Included spaces are ignored.
304
+ For example, specify the primary sort key as `draft`,
305
+ the secondary sort key as `last_modified`,
306
+ and the tertiary key as `label`:
307
+
308
+ ```html
309
+ {% all_collections
310
+ date_column='last_modified'
311
+ heading='All Posts'
312
+ id='asdf'
313
+ sort_by='draft, last_modified, label'
314
+ %}
315
+ ```
316
+
317
+
318
+ #### Liquid Usage Examples
319
+
320
+ Here is a short Jekyll page, including front matter,
321
+ demonstrating this plugin being invoked with all default attribute values:
322
+
323
+ ```html
324
+ ---
325
+ description: "
326
+ Dump of all collections, sorted by date originally written, newest to oldest.
327
+ The heading text will be <code>All Posts Sorted By -date</code>
328
+ "
329
+ layout: default
330
+ title: Testing, 1, 2, 3
331
+ ---
332
+ {% all_collections %}
333
+ ```
334
+
335
+ Following are examples of how to specify the sort parameters.
336
+
337
+ **Explicitly express the default sort**<br>
338
+ (sort by the date originally written, newest to oldest):
339
+
340
+ ```html
341
+ {% all_collections sort_by="-date" %}
342
+ ```
343
+
344
+ Sort by date, from oldest to newest:
345
+
346
+ ```html
347
+ {% all_collections sort_by="date" %}
348
+ ```
349
+
350
+ **Sort by the date last modified, oldest to newest:**
351
+
352
+ ```html
353
+ {% all_collections sort_by="last_modified" %}
354
+ ```
355
+
356
+ **Sort by the date last modified, newest to oldest:**
357
+
358
+ ```html
359
+ {% all_collections sort_by="-last_modified" %}
360
+ ```
361
+
362
+ **Several attributes can be specified as sort criteria**<br>
363
+ by passing them as a comma-delimited string.
364
+ Included spaces are ignored:
365
+
366
+ ```html
367
+ {% all_collections sort_by="-last_modified, -date" %}
368
+ {% all_collections sort_by="-last_modified, title" %}
369
+ {% all_collections sort_by="-last_modified, -date, title" %}
370
+ ```
371
+
372
+ **The following two examples produce the same output:**
373
+
374
+ ```html
375
+ {% all_collections sort_by="-last_modified,-date" %}
376
+ {% all_collections sort_by="-last_modified, -date" %}
377
+ ```
145
378
 
146
379
 
147
380
  ## Predefined Plugin Variables
@@ -150,10 +383,12 @@ For block tags, a single parameter is required, which contains any text enclosed
150
383
 
151
384
  * `@argument_string` Unparsed markup passed as a parameter to your block tag and inline tag.
152
385
 
386
+ * `@argv` returns any remaining tokens after `parameter_specified?` has been invoked.
387
+
153
388
  * [`@attribution`](#subclass-attribution) Attribution markup
154
389
 
155
390
  * [`@config`](https://docs.github.com/en/pages/setting-up-a-github-pages-site-with-jekyll/about-github-pages-and-jekyll#configuring-jekyll-in-your-github-pages-site)
156
- [YAML](https://yaml.org/) Jekyll site configuration file
391
+ The [YAML](https://yaml.org/) Jekyll site configuration file
157
392
 
158
393
  * [`@helper`](https://github.com/mslinn/jekyll_plugin_support/blob/master/lib/jekyll_plugin_helper.rb)
159
394
  `JekyllPluginHelper` instance for your plugin.
@@ -165,7 +400,7 @@ For block tags, a single parameter is required, which contains any text enclosed
165
400
  * [`@mode`](https://jekyllrb.com/docs/configuration/environments/)
166
401
  Indicates `production`, `test` or `development` mode.
167
402
 
168
- * [`@page`](https://jekyllrb.com/docs/variables/#page-variables) Page variables
403
+ * [`@page`](https://jekyllrb.com/docs/variables/#page-variables) `Jekyll::Page` variables
169
404
 
170
405
  * [`@paginator`](https://jekyllrb.com/docs/variables/#page-variables) Pagination variables
171
406
 
@@ -322,7 +557,7 @@ call `@helper.remaining_markup` to obtain the remaining markup that was passed t
322
557
 
323
558
  `jekyll_plugin_support` provides support for
324
559
  [Liquid variables](https://shopify.github.io/liquid/tags/variable/)
325
- to be defined in `_config.yml`, in a section called `liquid-vars`.
560
+ to be defined in `_config.yml`, in a section called `liquid_vars`.
326
561
  These variables behave exactly like Liquid variables defined by `assign` and `capture` expressions,
327
562
  except they are global in scope; these variables are available in every Jekyll web page.
328
563
 
@@ -339,7 +574,7 @@ Liquid variables defined in this manner are intended to be embedded in a webpage
339
574
  They are can be used like any other Liquid variable.
340
575
 
341
576
 
342
- ### Variable Expansion
577
+ ## Variable Expansion
343
578
 
344
579
  Jekyll expands Liquid variable references during the page rendering process.
345
580
  Jekyll does not expand Liquid variable references passes as parameters to tag and block plugins, however.
@@ -444,7 +679,27 @@ Similarly, the letters `y` and `z` are pronounced {{y}} and {{z}}.
444
679
  ```
445
680
 
446
681
 
447
- ### Automatically Created Error Classes
682
+ ### Evaluating Include Variables
683
+
684
+ This information is only useful if a plugin might be executed from within an included file.
685
+
686
+ While Liquid handles regular variables, Jekyll has special handling for variables defined by include parameters.
687
+ For example, the following defines a variable in the `include` scope called `var1`
688
+ when processing the body of an included file:
689
+
690
+ ```html
691
+ {% include myfile.html var1='value1' %}
692
+ ```
693
+
694
+ You can obtain the value of this variable from the `render_impl` method of a
695
+ `JekyllSupport::JekyllTag` or `JekyllSupport::JekyllBlock` subclass as follows:
696
+
697
+ ```ruby
698
+ @var1 = @scopes.first['include']&.[]('var1')
699
+ ```
700
+
701
+
702
+ ## Automatically Created Error Classes
448
703
 
449
704
  `JekyllSupport::JekyllBlock` and `JekyllSupport::JekyllTag` subclasses
450
705
  automatically create error classes, named after the subclass.
@@ -452,7 +707,8 @@ automatically create error classes, named after the subclass.
452
707
  For example, if you create a `JekyllSupport::JekyllBlock` subclass called `DemoBlockTag`,
453
708
  the automatically generated error class will be called `DemoBlockTagError`.
454
709
 
455
- Although you could use it as you would any other error class, `JekyllPluginSupport` provides some helper methods.
710
+ Although you could use it as you would any other error class, `JekyllPluginSupport`
711
+ provides additional helper methods.
456
712
  These methods fill in the page path and line number that caused the error, shorten the stack trace,
457
713
  log an error message, and can be used to return an HTML-friendly version of the message to the web page.
458
714
 
@@ -482,7 +738,7 @@ Error class methods have been provided for standardized and convenient error han
482
738
  * `html_message` - The same as `logger_message`, but constructed with HTML.
483
739
 
484
740
 
485
- ### Self-Reporting Upon Registration
741
+ ## Self-Reporting Upon Registration
486
742
 
487
743
  When each tag is registered, it self-reports, for example:
488
744
 
@@ -514,6 +770,83 @@ If your tag or block plugin only needs access to the raw arguments passed from t
514
770
  without tokenization, and you expect that the plugin might be invoked with large amounts of text,
515
771
  derive your plugin from `JekyllBlockNoArgParsing` or `JekyllTagNoArgParsing`.
516
772
 
773
+ ## Writing Plugins
774
+
775
+ The following minimal examples define `VERSION`,
776
+ which is important because `JekyllPluginHelper.register` logs that value when registering the plugin.
777
+
778
+ This is how you would define plugins in the `_plugins` directory
779
+
780
+ **Boilerplate for an inline tag plugin**
781
+
782
+ ```ruby
783
+ require 'jekyll_plugin_support'
784
+
785
+ module Jekyll
786
+ class DemoTag < JekyllSupport::JekyllTag
787
+ VERSION = '0.1.0'.freeze
788
+
789
+ def render_impl
790
+ @helper.gem_file __FILE__ # Enables attribution; only works when plugin is a gem
791
+ # Your Jekyll plugin logic goes here
792
+ end
793
+
794
+ JekyllPluginHelper.register(self, 'demo_tag')
795
+ end
796
+ end
797
+ ```
798
+
799
+ **Boilerplate for a tag block plugin**
800
+
801
+ ```ruby
802
+ require 'jekyll_plugin_support'
803
+
804
+ module Jekyll
805
+ class DemoBlock < JekyllSupport::JekyllBlock
806
+ VERSION = '0.1.0'.freeze
807
+
808
+ def render_impl(text)
809
+ @helper.gem_file __FILE__ # Enables attribution; only works when plugin is a gem
810
+ # Your Jekyll plugin logic goes here
811
+ end
812
+
813
+ JekyllPluginHelper.register(self, 'demo_block')
814
+ end
815
+ end
816
+ ```
817
+
818
+ If your plugin is packaged as a gem, then you might need to include `version.rb` into the plugin class.
819
+ For example, if your version module looks like this:
820
+
821
+ **lib/my_plugin/version.rb**:
822
+
823
+ ```ruby
824
+ module MyPluginVersion
825
+ VERSION = '0.5.0'.freeze
826
+ end
827
+ ```
828
+
829
+ Then your plugin can incorporate the VERSION constant into your plugin like this:
830
+
831
+ **lib/my_plugin.rb**:
832
+
833
+ ```ruby
834
+ require 'jekyll_plugin_support'
835
+ require_relative 'my_plugin/version'
836
+
837
+ module Jekyll
838
+ class MyBlock < JekyllSupport::JekyllBlock
839
+ include MyPluginVersion
840
+
841
+ def render_impl(text)
842
+ @helper.gem_file __FILE__ # Enables attribution; only works when plugin is a gem
843
+ # Your code here
844
+ end
845
+
846
+ JekyllPluginHelper.register(self, 'demo_block')
847
+ end
848
+ end
849
+ ```
517
850
 
518
851
  ## Attribution
519
852
 
@@ -583,7 +916,7 @@ An alternative attribution string can be specified properties can be output usin
583
916
  {% my_tag attribution="Generated by the #{name} #{version} Jekyll plugin, written by #{author} #{date}" %}
584
917
  ```
585
918
 
586
- ## Subclassing
919
+ ## Subclassing Plugins
587
920
 
588
921
  Jekyll plugins created using `jekyll_plugin_support` are implemented as Ruby classes.
589
922
  If you would like to create a version of an existing Jekyll plugin, you will need to subclass the plugin.
@@ -599,6 +932,133 @@ redef_without_warning :VERSION, '0.1.0'.freeze
599
932
  ```
600
933
 
601
934
 
935
+ ## Generator
936
+
937
+ This plugin incorporates a generator, which adds four new attributes to
938
+ [`site`](https://jekyllrb.com/docs/variables/#site-variables):
939
+ `all_collections`, `all_documents`, `everything`, and `sorted_lru_files`.
940
+
941
+ These three attributes can be referenced as `site.everything`, `site.all_collections`
942
+ and `site.all_documents`.
943
+
944
+ * `all_collections` includes all documents in all collections.
945
+
946
+ * `all_documents` includes `all_collections` plus all standalone pages.
947
+
948
+ * `everything` includes `all_documents` plus all static files.
949
+
950
+ * `sorted_lru_files` is used by a new binary search lookup for matching page suffixes.
951
+ Currently only `jekyll_href` and `jekyll_draft` use this feature.
952
+
953
+
954
+ ### Collection Management
955
+
956
+ Jekyll provides inconsistent attributes for
957
+ [`site.pages`](https://jekyllrb.com/docs/pages/),
958
+ [`site.posts`](https://jekyllrb.com/docs/posts/) and
959
+ [`site.static_files`](https://jekyllrb.com/docs/static-files/).
960
+
961
+
962
+ * While the `url` attributes of items in `site.posts` and `site.pages` start with a slash (/),
963
+ `site.static_files` items do not have a `url` attribute.
964
+ * Static files have a `relative_path` attribute, which starts with a slash (/),
965
+ but although that attribute is also provided in `site.posts` and `site.pages`,
966
+ those values do not start with a slash.
967
+ * Paths ending with a slash (`/`) imply that a file called `index.html` should be fetched.
968
+ * HTML redirect files created by the
969
+ [`jekyll-redirect-from`](https://github.com/jekyll/jekyll-redirect-from) Jekyll plugin,
970
+ which are included in `site.static_files`, should be ignored.
971
+
972
+ These inconsistencies mean that combining the standard three collections of files
973
+ provided as `site` attributes will create a new collection that is difficult
974
+ to process consistently:
975
+
976
+ ```ruby
977
+ # This pseudocode creates `oops`, which is problematic to process consistently
978
+ oops = site.all_collections + site.pages + site.static_files
979
+ ```
980
+
981
+ `oops`, above, is difficult to process because of inconsistencies in the provided attributes
982
+ and how the attributes are constructed.
983
+
984
+
985
+ ### Solving The Problem
986
+
987
+ The generator normalizes these inconsistencies by utilizing the `APage` class
988
+ and filtering out HTML redirect files.
989
+
990
+ The `all_collections` collection contains `APage` representations of `site.collections`.
991
+
992
+ The `all_documents` collection contains `APage` representations of `site.pages`.
993
+
994
+ The `everything` collection contains `APage` representations of:
995
+
996
+ ```text
997
+ # Pseudocode
998
+ site.collections + site.pages + site.static_files - HTML_redirect_files
999
+ ```
1000
+
1001
+
1002
+ ## The `APage` Class
1003
+
1004
+ The `site.all_collections`, `site.all_documents` and `site.everything` attributes
1005
+ consist of arrays of [`APage`](lib/hooks/a_page.rb) instances.
1006
+
1007
+ The `APage` class has the following attributes:
1008
+ `content` (HTML or Markdown), `data` (array), `date` (Ruby Date), `description`, `destination`,
1009
+ `draft` (Boolean), `ext`, `href`, `label`, `last_modified` or `last_modified_at` (Ruby Date),
1010
+ `layout`, `origin`, `path`, `relative_path`, `tags`, `title`, `type`, and `url`.
1011
+
1012
+ * `href` always starts with a slash.
1013
+ This value is consistent with `a href` values in website HTML.
1014
+ Paths ending with a slash (`/`) have `index.html` appended so the path specifies an actual file.
1015
+
1016
+ * `origin` indicates the original source of the item.
1017
+ Possible values are `collection`, `individual_page` and `static_file`.
1018
+ Knowing the origin of each item allows code to process each type of item appropriately.
1019
+
1020
+
1021
+ ## `all_collections` Block Tag
1022
+
1023
+ The `all_collections` block tag creates a formatted listing of Jekyll files.
1024
+ The ordering is configurable; by default, the listing is sorted by `date`, newest to oldest.
1025
+ The `all_collections` tag has a `data_source` parameter that specifies which new property to report on
1026
+ (`all_collections`, `all_documents`, or `everything`).
1027
+
1028
+
1029
+ ## Requirements
1030
+
1031
+ All the pages in the Jekyll website must have an implicit date
1032
+ (for example, all posts are assigned this property by Jekyll),
1033
+ or an explicit `date` set in front matter, like this:
1034
+
1035
+ ```html
1036
+ ---
1037
+ date: 2022-01-01
1038
+ ---
1039
+ ```
1040
+
1041
+ If a front matter variable called `last_modified` or `last_modified_at` exists,
1042
+ its value will be converted to a Ruby `Date`:
1043
+
1044
+ ```html
1045
+ ---
1046
+ last_modified: 2023-01-01
1047
+ ---
1048
+ ```
1049
+
1050
+ Or:
1051
+
1052
+ ```html
1053
+ ---
1054
+ last_modified_at: 2023-01-01
1055
+ ---
1056
+ ```
1057
+
1058
+ Otherwise, if `last_modified` or `last_modified_at` is not present in the front matter for a page,
1059
+ the `date` value will be used last modified date value.
1060
+
1061
+
602
1062
  ## Development
603
1063
 
604
1064
  After checking out the `jekyll_plugin_suppprt` repository, run `bin/setup` to install dependencies.
@@ -632,6 +1092,51 @@ jekyll_plugin_support (0.1.0)
632
1092
  ```
633
1093
 
634
1094
 
1095
+ ### Build and Install Locally
1096
+
1097
+ To build and install this gem onto your local machine, run:
1098
+
1099
+ ```shell
1100
+ $ bundle exec rake install
1101
+ jekyll_all_collections 0.3.8 built to pkg/jekyll_all_collections-0.3.8.gem.
1102
+ jekyll_all_collections (0.3.8) installed.
1103
+ ```
1104
+
1105
+ Examine the newly built gem:
1106
+
1107
+ ```shell
1108
+ $ gem info jekyll_all_collections
1109
+
1110
+ *** LOCAL GEMS ***
1111
+
1112
+ jekyll_all_collections (0.3.8)
1113
+ Author: Mike Slinn
1114
+ Homepage:
1115
+ https://www.mslinn.com/jekyll_plugins/jekyll_all_collections.html
1116
+ License: MIT
1117
+ Installed at (0.3.8): /home/mslinn/.rbenv/versions/3.2.2/lib/ruby/gems/3.2.0
1118
+
1119
+ Provides normalized collections and extra functionality for Jekyll websites.
1120
+ ```
1121
+
1122
+
1123
+ ### Build and Push to RubyGems
1124
+
1125
+ To release a new version:
1126
+
1127
+ 1. Update the version number in `version.rb`.
1128
+ 2. Add an entry in `CHANGELOG.md` describing the changes since the last release.
1129
+ 3. Commit all changes to git; if you don't the next step might fail with a confusing error message.
1130
+ 4. Run the following:
1131
+
1132
+ ```shell
1133
+ $ bundle exec rake release
1134
+ ```
1135
+
1136
+ The above creates a git tag for the version, commits the created tag,
1137
+ and pushes the new `.gem` file to [RubyGems.org](https://rubygems.org).
1138
+
1139
+
635
1140
  ### Pry Breakpoint On StandardError
636
1141
 
637
1142
  A `pry` breakpoint will be set in the `StandardError` handler if `pry_on_standard_error: true`
@@ -645,11 +1150,259 @@ blah:
645
1150
  ```
646
1151
 
647
1152
 
1153
+ ## Debugging
1154
+
1155
+ You can control the verbosity of log output by adding the following to `_config.yml` in your Jekyll project:
1156
+
1157
+ ```yaml
1158
+ plugin_loggers:
1159
+ AllCollectionsTag::AllCollectionsTag: warn
1160
+ ```
1161
+
1162
+ 1. First set breakpoints in the Ruby code that interests you.
1163
+
1164
+ 2. You have several options for initiating a debug session:
1165
+
1166
+ 1. Use the **Debug Demo** Visual Studio Code launch configuration.
1167
+
1168
+ 2. Type the `demo/_bin/debug` command, without the `-r` options shown above.
1169
+
1170
+ ```console
1171
+ ... lots of output as bundle update runs...
1172
+ Bundle updated!
1173
+
1174
+ INFO PluginMetaLogger: Loaded AllCollectionsHooks v0.2.0 :site, :pre_render, :normal hook plugin.
1175
+ INFO PluginMetaLogger: Loaded DraftFilter plugin.
1176
+ INFO PluginMetaLogger: Loaded all_collections v0.2.0 tag plugin.
1177
+ Configuration file: /mnt/_/work/jekyll/my_plugins/jekyll_all_collections/demo/_config.yml
1178
+ Cleaner: Removing /mnt/_/work/jekyll/my_plugins/jekyll_all_collections/demo/_site...
1179
+ Cleaner: Removing /mnt/_/work/jekyll/my_plugins/jekyll_all_collections/demo/.jekyll-metadata...
1180
+ Cleaner: Removing /mnt/_/work/jekyll/my_plugins/jekyll_all_collections/demo/.jekyll-cache...
1181
+ Cleaner: Nothing to do for .sass-cache.
1182
+ Fast Debugger (ruby-debug-ide 0.7.3, debase 0.2.5.beta2, file filtering is supported) listens on 0.0.0.0:1234
1183
+ ```
1184
+
1185
+ 3. Run `bin/attach` and pass the directory name of a Jekyll website that has a suitable script called `_bin/debug`.
1186
+ The `demo` subdirectory fits this description.
1187
+
1188
+ ```console
1189
+ $ bin/attach demo
1190
+ Successfully uninstalled jekyll_all_collections-0.1.2
1191
+ jekyll_all_collections 0.1.2 built to pkg/jekyll_all_collections-0.1.2.gem.
1192
+ jekyll_all_collections (0.1.2) installed.
1193
+ Fast Debugger (ruby-debug-ide 0.7.3, debase 0.2.4.1, file filtering is supported) listens on 0.0.0.0:1234
1194
+ ```
1195
+
1196
+
1197
+ 3. Attach to the debugger process if required.
1198
+ This git repo includes two [Visual Studio Code launch configurations](./.vscode/launch.json) for this purpose labeled
1199
+ **Attach rdbg** and **Attach with ruby_lsp**.
1200
+
1201
+ 4. Point your web browser to http://localhost:4444
1202
+
1203
+ If a debugging session terminates abruptly and leaves ports tied up,
1204
+ run the `demo/_bin/release_port` script.
1205
+
1206
+
648
1207
  ## Demonstration Plugins and Website
649
1208
 
650
- A demo / test website is provided in the `demo` directory.
1209
+ A demo / test website is provided in the [`demo`](demo) directory.
651
1210
  It can be used to debug the plugin or to run freely.
652
1211
 
1212
+ ### Examining the Demo Plugins
1213
+
1214
+ The following example plugins use
1215
+ [Ruby’s squiggly heredoc operator](https://ruby-doc.org/core-2.5.0/doc/syntax/literals_rdoc.html#label-Here+Documents) (`<<~`).
1216
+ The squiggly heredoc operator removes the outermost indentation.
1217
+ This provides easy-to-read multiline text literals.
1218
+
1219
+ **demo/_plugins/demo_tag.rb**:
1220
+
1221
+ ```ruby
1222
+ require 'jekyll_plugin_support'
1223
+
1224
+ # Use the JekyllSupport module namespace so the self methods are automajically found
1225
+ module JekyllSupport
1226
+ DemoInlineTagError = JekyllSupport.define_error
1227
+
1228
+ class DemoTag < JekyllTag
1229
+ VERSION = '0.1.2'.freeze
1230
+ # JekyllSupport.redef_without_warning 'VERSION', '0.1.2'.freeze
1231
+
1232
+ def render_impl
1233
+ @demo_tag_error = @helper.parameter_specified? 'raise_demo_tag_error'
1234
+ @keyword1 = @helper.parameter_specified? 'keyword1'
1235
+ @keyword2 = @helper.parameter_specified? 'keyword2'
1236
+ @name1 = @helper.parameter_specified? 'name1'
1237
+ @name2 = @helper.parameter_specified? 'name2'
1238
+ @standard_error = @helper.parameter_specified? 'raise_standard_error'
1239
+
1240
+ if @tag_config
1241
+ @die_on_demo_tag_error = @tag_config['die_on_demo_tag_error'] == true
1242
+ @die_on_standard_error = @tag_config['die_on_standard_error'] == true
1243
+ end
1244
+
1245
+ raise DemoInlineTagError, 'This DemoInlineTagError error is expected.' if @demo_tag_error
1246
+ raise StandardError, 'This StandardError error is expected.' if @standard_error
1247
+
1248
+ # _infinity = 1 / 0 if @standard_error # Not required
1249
+
1250
+ output
1251
+ rescue DemoInlineTagError => e # jekyll_plugin_support handles StandardError
1252
+ @logger.error { e.logger_message }
1253
+ exit! 1 if @die_on_demo_tag_error
1254
+
1255
+ e.html_message
1256
+ end
1257
+
1258
+ private
1259
+
1260
+ def output
1261
+ <<~END_OUTPUT
1262
+ <pre># jekyll_plugin_support becomes able to perform variable substitution after this variable is defined.
1263
+ # The value could be updated at a later stage, but no need to add that complexity unless there is a use case.
1264
+ @argument_string="#{@argument_string}"
1265
+
1266
+ @helper.argv=
1267
+ #{@helper.argv&.join("\n ")}
1268
+
1269
+ # Liquid variable name/value pairs
1270
+ @helper.params=
1271
+ #{@helper.params&.map { |k, v| "#{k}=#{v}" }&.join("\n ")}
1272
+
1273
+ # The keys_values property serves no purpose any more, consider it deprecated
1274
+ @helper.keys_values=
1275
+ #{(@helper.keys_values&.map { |k, v| "#{k}=#{v}" })&.join("\n ")}
1276
+
1277
+ @layout='#{@layout}'
1278
+ @page.keys='#{@page.keys}'
1279
+
1280
+ remaining_markup='#{@helper.remaining_markup}'
1281
+
1282
+ @keyword1='#{@keyword1}'
1283
+ @keyword2='#{@keyword2}'
1284
+ @name1='#{@name1}'
1285
+ @name2='#{@name2}'</pre>
1286
+ END_OUTPUT
1287
+ end
1288
+
1289
+ JekyllPluginHelper.register(self, 'demo_inline_tag')
1290
+ end
1291
+ end
1292
+ ```
1293
+
1294
+ **demo/_plugins/demo_block.rb**:
1295
+
1296
+ ```ruby
1297
+ require 'cgi'
1298
+ require 'jekyll_plugin_support'
1299
+
1300
+ # Use the JekyllSupport module namespace so the self methods are automajically found
1301
+ module JekyllSupport
1302
+ DemoBlockError = JekyllSupport.define_error
1303
+
1304
+ class DemoBlock < JekyllBlock
1305
+ VERSION = '0.1.2'.freeze
1306
+
1307
+ def render_impl(text)
1308
+ @demo_block_error = @helper.parameter_specified? 'raise_demo_block_error'
1309
+ @keyword1 = @helper.parameter_specified? 'keyword1'
1310
+ @keyword2 = @helper.parameter_specified? 'keyword2'
1311
+ @name1 = @helper.parameter_specified? 'name1'
1312
+ @name2 = @helper.parameter_specified? 'name2'
1313
+ @standard_error = @helper.parameter_specified? 'raise_standard_error'
1314
+
1315
+ if @tag_config
1316
+ @die_on_demo_block_error = @tag_config['die_on_demo_block_error'] == true
1317
+ @die_on_standard_error = @tag_config['die_on_standard_error'] == true
1318
+ end
1319
+
1320
+ raise DemoBlockTagError, 'This DemoBlockTagError error is expected.' if @demo_block_error
1321
+ raise StandardError, 'This StandardError error is expected.' if @standard_error
1322
+
1323
+ # _infinity = 1 / 0 if @standard_error # Not required
1324
+
1325
+ output text
1326
+ rescue DemoBlockTagError => e # jekyll_plugin_support handles StandardError
1327
+ @logger.error { e.logger_message }
1328
+ exit! 1 if @die_on_demo_block_error
1329
+
1330
+ e.html_message
1331
+ end
1332
+
1333
+ private
1334
+
1335
+ def output(text)
1336
+ <<~END_OUTPUT
1337
+ <pre>@helper.tag_name=#{@helper.tag_name}
1338
+
1339
+ @mode=#{@mode}
1340
+
1341
+ # jekyll_plugin_support becomes able to perform variable substitution after this variable is defined.
1342
+ # The value could be updated at a later stage, but no need to add that complexity unless there is a use case.
1343
+ @argument_string="#{@argument_string}"
1344
+
1345
+ @helper.argv=
1346
+ #{@helper.argv&.join("\n ")}
1347
+
1348
+ # Liquid variable name/value pairs
1349
+ @helper.params=
1350
+ #{@helper.params&.map { |k, v| "#{k}=#{v}" }&.join("\n ")}
1351
+
1352
+ # The keys_values property serves no purpose any more, consider it deprecated
1353
+ @helper.keys_values=
1354
+ #{(@helper.keys_values&.map { |k, v| "#{k}=#{v}" })&.join("\n ")}
1355
+
1356
+ @helper.remaining_markup='#{@helper.remaining_markup}'
1357
+
1358
+ @envs=#{@envs.keys.sort.join(', ')}
1359
+
1360
+ @config['url']='#{@config['url']}'
1361
+
1362
+ @site.collection_names=#{@site.collection_names&.sort&.join(', ')}
1363
+
1364
+ @page['description']=#{@page['description']}
1365
+
1366
+ @page['path']=#{@page['path']}
1367
+
1368
+ @keyword1=#{@keyword1}
1369
+
1370
+ @keyword2=#{@keyword2}
1371
+
1372
+ @name1=#{@name1}
1373
+
1374
+ @name2=#{@name2}
1375
+
1376
+ text=#{text}</pre>
1377
+ END_OUTPUT
1378
+ end
1379
+
1380
+ JekyllPluginHelper.register(self, 'demo_block_tag')
1381
+ end
1382
+ end
1383
+ ```
1384
+
1385
+ The following is an example of no_arg_parsing optimization.
1386
+
1387
+ ```ruby
1388
+ require 'jekyll_plugin_support'
1389
+
1390
+ # Use the JekyllSupport module namespace so the self methods are automajically found
1391
+ module JekyllSupport
1392
+ class DemoTagNoArgs < JekyllTagNoArgParsing
1393
+ VERSION = '0.1.0'.freeze
1394
+
1395
+ def render_impl
1396
+ <<~END_OUTPUT
1397
+ The raw arguments passed to this <code>DemoTagNoArgs</code> instance are:<br>
1398
+ <code>#{@argument_string}</code>
1399
+ END_OUTPUT
1400
+ end
1401
+
1402
+ JekyllPluginHelper.register(self, 'demo_inline_tag_no_arg')
1403
+ end
1404
+ end
1405
+ ```
653
1406
 
654
1407
  ### Run Freely
655
1408
 
@@ -668,6 +1421,10 @@ It can be used to debug the plugin or to run freely.
668
1421
 
669
1422
  1. Set breakpoints in Visual Studio Code.
670
1423
 
1424
+ 2. Run the **Debug Demo development** or **Debug Demo production** launch configuration.
1425
+
1426
+ Alternatively, you can:
1427
+
671
1428
  2. Initiate a debug session from the command line by running the `demo/_bin/debug` script:
672
1429
 
673
1430
  ```shell
@@ -694,7 +1451,7 @@ It can be used to debug the plugin or to run freely.
694
1451
  ```
695
1452
 
696
1453
  3. Once the `DEBUGGER: wait for debugger connection...` message appears,
697
- run the Visual Studio Code launch configuration called `Attach with rdbg`.
1454
+ run the Visual Studio Code launch configuration called **Attach with rdbg**.
698
1455
 
699
1456
  4. View the generated website,
700
1457
  which might be at [`http://localhost:4444`](http://localhost:4444),