coffeebrew_jekyll_archives 0.2.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 43b2a4c983a82213e4a7c864e991616436db32853816d4c6fb3ba160b190d01b
4
+ data.tar.gz: 7547594cf6862c22b962435fdee631e5890609fb2b377a1ac42540e01a7a49fd
5
+ SHA512:
6
+ metadata.gz: cee0c42d0a3177f017bc335d1b0156372db4590c76f81122f2d9d6a1838f52317cdc2c9f5acbbbf7d36379904970c18e3c94db72ca01f21602fabb89593cb7ef
7
+ data.tar.gz: ffdccf2bc5624e70802c2c6b8fbdd0d3402846f4030a8493447182c22d0bbe177a8cab0808dab9ce57d0a818426b5bd38b278c131cac243578f37732722c2112
data/CHANGELOG.md ADDED
@@ -0,0 +1,7 @@
1
+ # Changelog
2
+
3
+ ## Version 0.2.0
4
+
5
+ [View version doc](https://github.com/coffeebrewapps/coffeebrew_jekyll_archives/blob/v0.2.0/README.md)
6
+
7
+ - Initial release
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Coffee Brew Apps
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,626 @@
1
+ # Jekyll Archives Plugin
2
+
3
+ A Jekyll plugin to generate site post archives.
4
+
5
+ [![Continuous Integration](https://github.com/coffeebrewapps/coffeebrew_jekyll_archives/actions/workflows/ruby.yml/badge.svg)](https://github.com/coffeebrewapps/coffeebrew_jekyll_archives/actions/workflows/ruby.yml) [![Gem Version](https://badge.fury.io/rb/coffeebrew_jekyll_archives.svg)](https://badge.fury.io/rb/coffeebrew_jekyll_archives)
6
+
7
+ ## Installation
8
+
9
+ Add this line to your site's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'coffeebrew_jekyll_archives'
13
+ ```
14
+
15
+ And then add this line to your site's `_config.yml`:
16
+
17
+ ```yml
18
+ plugins:
19
+ - coffeebrew_jekyll_archives
20
+ ```
21
+
22
+ Upon building the Jekyll site, the plugin will automatically generate the archive indexes using the following default
23
+ configuration:
24
+
25
+ ```yml
26
+ ---
27
+ archives:
28
+ navigation:
29
+ data: "navigation"
30
+ name: "Archives"
31
+ before:
32
+ after:
33
+ depths: 3
34
+ title_format:
35
+ root:
36
+ type: "string"
37
+ style: "/"
38
+ year:
39
+ type: "date"
40
+ style: "%Y"
41
+ month:
42
+ type: "date"
43
+ style: "%b, %Y"
44
+ day:
45
+ type: "date"
46
+ style: "%d %b, %Y"
47
+ root_dir: "/"
48
+ root_basename: "archives"
49
+ index_root: "/archives"
50
+ index_basename: "index"
51
+ permalink:
52
+ root: "/:root_dir"
53
+ year: "/:root_dir/:index_root/:year"
54
+ month: "/:root_dir/:index_root/:year/:month"
55
+ day: "/:root_dir/:index_root/:year/:month/:day"
56
+ ```
57
+
58
+ An example of the generated directory structure is as such:
59
+
60
+ ```bash
61
+ _site/
62
+ ├── archives
63
+ │   └── 2023
64
+ │   ├── 03
65
+ │   │   ├── 30
66
+ │   │   │   └── index.html
67
+ │   │   └── index.html
68
+ │   └── index.html
69
+ ├── archives.html
70
+ └── index.html
71
+ ```
72
+
73
+ ## Configuration
74
+
75
+ You can override any of the default configuration values above. The plugin will perform a simple validation on your
76
+ overrides according to these rules:
77
+
78
+ ### Navigation config
79
+
80
+ This tells the plugin how to generate navigation menu.
81
+
82
+ | Key | Allowed Value(s) | Default | Remark |
83
+ | --- | --- | --- | --- |
84
+ | data | String | navigation | This tells the plugin where in `site.data[]` to get the navigation data. Usually this would be set in `site.data["navigation"]`. |
85
+ | name | String | Archives | This is the name rendered in the navigation menu item. |
86
+ | before | String | nil | This tells the plugin before which existing menu item to insert the archives' root index menu item. |
87
+ | after | String | nil | This tells the plugin after which existing menu item to insert the archives' root index menu item. |
88
+
89
+ When `before` and `after` are `nil`, the archives' root index menu item will be appended last.
90
+
91
+ If both `before` and `after` are configured, `before` config will be used and `after` will be ignored.
92
+
93
+ ### Depths config
94
+
95
+ This tells the plugin what indexes to generate.
96
+
97
+ There are 3 levels of depths:
98
+
99
+ | Level | Description |
100
+ | --- | --- |
101
+ | 1 | Generate yearly index only |
102
+ | 2 | Generate yearly and monthly indexes |
103
+ | 3 | Generate yearly, monthly and daily indexes |
104
+
105
+ ### Title format config
106
+
107
+ This tells the plugin what is the format of the title to be used for the index entries.
108
+
109
+ There are 4 titles that can be generated:
110
+
111
+ | Level | Description |
112
+ | --- | --- |
113
+ | root | This will be used for the root index entry |
114
+ | year | This will be used for the yearly index entries |
115
+ | month | This will be used for the monthly index entries |
116
+ | day | This will be used for the daily index entries |
117
+
118
+ For each title format, you can use one of these types:
119
+
120
+ | Type | Description |
121
+ | --- | --- |
122
+ | string | A string that uses format syntax and use available variables: year, month, day, eg. `Archive of %{year}-%{month}-%{day}` will be interpreted as `Archive of 2023-03-12` |
123
+ | date | A string that strictly uses Ruby date identifiers, the plugin will call `date.strftime()` on the format string, eg. `%d %b, %Y` will be interpreted as `12 Mar, 2023` |
124
+
125
+ ### Directory and path config
126
+
127
+ This tells the plugin how to create the directory structure and index page file name.
128
+
129
+ | Key | Allowed Value(s) | Default | Remark |
130
+ | --- | --- | --- | --- |
131
+ | root_dir | String | / | This tells the plugin what is the root directory to create the root index page. |
132
+ | root_basename | String | archives | This tells the plugin what is the root index page file name, with `.html` as the extension. |
133
+ | index_root | String | /archives | This tells the plugin what is the root directory for the yearly/monthly/daily index pages to be generated. |
134
+ | index_basename | String | index | This tells the plugin what is the file name to use for the yearly/monthly/daily index pages, with `.html` as the extension. |
135
+
136
+ ### Permalink config
137
+
138
+ This tells the plugin how to create the directory structure of the yearly/monthly/daily index pages, and the permalink
139
+ for the index pages will follow the directory structure.
140
+
141
+ There are 4 levels of pages that can be generated:
142
+
143
+ | Level | Description |
144
+ | --- | --- |
145
+ | root | This will be used for the root index entry |
146
+ | year | This will be used for the yearly index entries |
147
+ | month | This will be used for the monthly index entries |
148
+ | day | This will be used for the daily index entries |
149
+
150
+ A few placeholders are available to be used in the permalink:
151
+
152
+ | Field | Description |
153
+ | --- | --- |
154
+ | root_dir | This will be the root directory where the root index page is created. |
155
+ | index_root | This will be the root directory where the yearly/monthly/daily index pages are created. |
156
+ | year | This will be the current year of the current index page. If the current page is the root index, then `year` will always be `0001`. |
157
+ | month | This will be the current month of the current index page. If the current page is a yearly index, then `month` will always be `01`. |
158
+ | day | This will be the current day of the current index page. If the current page is a yearly or monthly index, then `day` will always be `01`. |
159
+
160
+ ### Validation
161
+
162
+ If the config overrides have invalid structure, keys or values, the plugin will raise a
163
+ `Jekyll::Errors::InvalidConfigurationError` during build.
164
+
165
+ ## Layout
166
+
167
+ The plugin does not provide a default layout. You will need to create your own layout in `_layouts` and configure the
168
+ defaults in `_config.yml`:
169
+
170
+ ```yml
171
+ ---
172
+ defaults:
173
+ - scope:
174
+ type: "archives"
175
+ values:
176
+ layout: "archive"
177
+ permalink: /:path/:basename:output_ext
178
+ ```
179
+
180
+ In addition to Jekyll's default page data, you can also use the page data generated by the plugin in the layout:
181
+
182
+ | Field | Description |
183
+ | --- | --- |
184
+ | dir | The current index page directory. |
185
+ | sub_pages | The current index page's sub-pages according to the hierarchy of: 1) root, 2) yearly, 3) monthly, 4) daily. If the current page is the daily index page, then there will be no sub-pages. |
186
+ | root | The root index page absolute url. This is useful if you need to highlight the archives' root navigation menu item while displaying the nested index pages. |
187
+ | ancestors | The ancestors of the current page. This is useful if you need to render a tree view of the ancestor pages relative to the current page. |
188
+ | parent | The immediate parent of the current page. |
189
+ | collection | The posts collection under the current index page, eg. if the current page is a monthly index page, then all the current month's posts will be available. |
190
+ | title | The title generated for the current page according to the `title_format` config. |
191
+ | year | The current year of the current index page. If the current page is the root index, then `year` will always be `0001`. |
192
+ | month | The current month of the current index page. If the current page is a yearly index, then `month` will always be `01`. |
193
+ | day | The current day of the current index page. If the current page is a yearly or monthly index, then `day` will always be `01`. |
194
+
195
+ An example of a layout that is used by the plugin's test cases:
196
+
197
+ ```html
198
+ ---
199
+ layout: default
200
+ ---
201
+ <h1>Archives of {{ page.title }}</h1>
202
+ <div class="archives">
203
+ {% for ancestor in page.ancestors %}
204
+ <ul>
205
+ <li>
206
+ <i class="fa-solid fa-calendar-days"></i>
207
+ <a href="{{ ancestor.url }}">{{ ancestor.title }}</a>
208
+ </li>
209
+ {% endfor %}
210
+ <ul>
211
+ <li>
212
+ <i class="fa-solid fa-calendar-days"></i>
213
+ <a href="{{ page.url }}">{{ page.title }}</a>
214
+ </li>
215
+ {% if page.sub_pages.size > 0 %}
216
+ <ul>
217
+ {% for sub_page in page.sub_pages %}
218
+ <li>
219
+ <i class="fa-solid fa-calendar-days"></i>
220
+ <a href="{{ sub_page.url }}">{{ sub_page.title }}</a>
221
+ </li>
222
+ {% endfor %}
223
+ </ul>
224
+ {% else %}
225
+ <ul>
226
+ {% for item in page.collection %}
227
+ <li>
228
+ <i class="fa-solid fa-file"></i>
229
+ <a href="{{ item.url }}">{{ item.date | date: "%d %b, %Y" }} - {{ item.title }}</a>
230
+ </li>
231
+ {% endfor %}
232
+ </ul>
233
+ {% endif %}
234
+ </ul>
235
+ {% for ancestor in page.ancestors %}
236
+ </ul>
237
+ {% endfor %}
238
+ </div>
239
+ ```
240
+
241
+ The resulting index pages are as such:
242
+
243
+ ### Root
244
+
245
+ Generated at: `_site/archives.html`.
246
+
247
+ ```html
248
+ <!DOCTYPE html>
249
+ <html>
250
+ <head>
251
+ <meta charset="utf-8">
252
+ <title>/</title>
253
+ </head>
254
+ <body>
255
+ <nav>
256
+ <a href="/">Home</a>
257
+ <a href="/about.html">About</a>
258
+ <a href="/articles.html">Articles</a>
259
+ <a href="/projects.html">Projects</a>
260
+ <a href="/archives.html">Archives</a>
261
+ </nav>
262
+
263
+ <div class="container">
264
+ <h1>Archives of /</h1>
265
+ <div class="archives">
266
+ <ul>
267
+ <li>
268
+ <i class="fa-solid fa-calendar-days"></i>
269
+ <a href="/archives.html">/</a>
270
+ </li>
271
+ <ul>
272
+ <li>
273
+ <i class="fa-solid fa-calendar-days"></i>
274
+ <a href="/archives/2021/index.html">2021</a>
275
+ </li>
276
+ <li>
277
+ <i class="fa-solid fa-calendar-days"></i>
278
+ <a href="/archives/2022/index.html">2022</a>
279
+ </li>
280
+ <li>
281
+ <i class="fa-solid fa-calendar-days"></i>
282
+ <a href="/archives/2023/index.html">2023</a>
283
+ </li>
284
+ </ul>
285
+ </ul>
286
+ </div>
287
+ </div>
288
+ </body>
289
+ </html>
290
+ ```
291
+
292
+ ### Yearly index
293
+
294
+ Generated at: `_site/2021/index.html` for the year of 2021.
295
+
296
+ Note: Header and navigation elements omitted for clarity.
297
+
298
+ ```html
299
+ <div class="container">
300
+ <h1>Archives of 2021</h1>
301
+ <div class="archives">
302
+ <ul>
303
+ <li>
304
+ <i class="fa-solid fa-calendar-days"></i>
305
+ <a href="/archives.html">/</a>
306
+ </li>
307
+ <ul>
308
+ <li>
309
+ <i class="fa-solid fa-calendar-days"></i>
310
+ <a href="/archives/2021/index.html">2021</a>
311
+ </li>
312
+ <ul>
313
+ <li>
314
+ <i class="fa-solid fa-calendar-days"></i>
315
+ <a href="/archives/2021/03/index.html">Mar, 2021</a>
316
+ </li>
317
+ <li>
318
+ <i class="fa-solid fa-calendar-days"></i>
319
+ <a href="/archives/2021/05/index.html">May, 2021</a>
320
+ </li>
321
+ </ul>
322
+ </ul>
323
+ </ul>
324
+ </div>
325
+ </div>
326
+ ```
327
+
328
+ ### Monthly index
329
+
330
+ Generated at: `_site/2021/03/index.html` for the month of March 2021.
331
+
332
+ Note: Header and navigation elements omitted for clarity.
333
+
334
+ ```html
335
+ <div class="container">
336
+ <h1>Archives of Mar, 2021</h1>
337
+ <div class="archives">
338
+ <ul>
339
+ <li>
340
+ <i class="fa-solid fa-calendar-days"></i>
341
+ <a href="/archives.html">/</a>
342
+ </li>
343
+ <ul>
344
+ <li>
345
+ <i class="fa-solid fa-calendar-days"></i>
346
+ <a href="/archives/2021/index.html">2021</a>
347
+ </li>
348
+ <ul>
349
+ <li>
350
+ <i class="fa-solid fa-calendar-days"></i>
351
+ <a href="/archives/2021/03/index.html">Mar, 2021</a>
352
+ </li>
353
+ <ul>
354
+ <li>
355
+ <i class="fa-solid fa-calendar-days"></i>
356
+ <a href="/archives/2021/03/12/index.html">12 Mar, 2021</a>
357
+ </li>
358
+ <li>
359
+ <i class="fa-solid fa-calendar-days"></i>
360
+ <a href="/archives/2021/03/28/index.html">28 Mar, 2021</a>
361
+ </li>
362
+ </ul>
363
+ </ul>
364
+ </ul>
365
+ </ul>
366
+ </div>
367
+ </div>
368
+ ```
369
+
370
+ ### Daily index
371
+
372
+ Generated at: `_site/2021/03/12/index.html` for the day of 12 March 2021.
373
+
374
+ Note: Header and navigation elements omitted for clarity.
375
+
376
+ ```html
377
+ <div class="container">
378
+ <h1>Archives of 12 Mar, 2021</h1>
379
+ <div class="archives">
380
+ <ul>
381
+ <li>
382
+ <i class="fa-solid fa-calendar-days"></i>
383
+ <a href="/archives.html">/</a>
384
+ </li>
385
+ <ul>
386
+ <li>
387
+ <i class="fa-solid fa-calendar-days"></i>
388
+ <a href="/archives/2021/index.html">2021</a>
389
+ </li>
390
+ <ul>
391
+ <li>
392
+ <i class="fa-solid fa-calendar-days"></i>
393
+ <a href="/archives/2021/03/index.html">Mar, 2021</a>
394
+ </li>
395
+ <ul>
396
+ <li>
397
+ <i class="fa-solid fa-calendar-days"></i>
398
+ <a href="/archives/2021/03/12/index.html">12 Mar, 2021</a>
399
+ </li>
400
+ <ul>
401
+ <li>
402
+ <i class="fa-solid fa-file"></i>
403
+ <a href="/2021/03/12/test-post-1.html">12 Mar, 2021 - This is test post 1</a>
404
+ </li>
405
+ </ul>
406
+ </ul>
407
+ </ul>
408
+ </ul>
409
+ </ul>
410
+ </div>
411
+ </div>
412
+ ```
413
+
414
+ ## Contributing
415
+
416
+ Contribution to the gem is very much welcome!
417
+
418
+ 1. Fork it (https://github.com/coffeebrewapps/coffeebrew_jekyll_archives/fork).
419
+ 2. Create your feature branch (`git checkout -b my-new-feature`).
420
+ 3. Make sure you have setup the repo correctly so that you can run RSpec and Rubocop on your changes. Read more under the [Development](#development) section.
421
+ 4. Commit your changes (`git commit -am 'Add some feature'`).
422
+ 5. If you have added something that is worth mentioning in the README, please also update the README.md accordingly and commit the changes.
423
+ 6. Push to the branch (`git push origin my-new-feature`).
424
+ 7. Create a new Pull Request.
425
+
426
+ The repo owner will try to respond to a new PR as soon as possible.
427
+
428
+ ## Development
429
+
430
+ We want to provide a robust gem as much as possible for the users, so writing test cases will be required for any new
431
+ feature.
432
+
433
+ If you are contributing to the gem, please make sure you have setup your development environment correctly so that
434
+ RSpec and Rubocop can run properly.
435
+
436
+ 1. After forking the repo, go into the repo directory (`cd coffeebrew_jekyll_archives/`).
437
+ 2. Make sure you have the correct Ruby version installed. This gem requires Ruby >= 2.7.0.
438
+ 3. Once you have the correct Ruby version, install the development dependencies (`bundle install`).
439
+ 4. To test that you have everything installed correctly, run the test cases (`bundle exec rspec`).
440
+ 5. You should see all test cases pass successfully.
441
+
442
+ ### Source directory structure
443
+
444
+ All the gem logic lives in the `/lib` directory:
445
+
446
+ ```bash
447
+ lib
448
+ ├── coffeebrew_jekyll_archives
449
+ │   ├── config.yml
450
+ │   ├── generator.rb
451
+ │   ├── page.rb
452
+ │   ├── validator.rb
453
+ │   └── version.rb
454
+ └── coffeebrew_jekyll_archives.rb
455
+ ```
456
+
457
+ The files that are currently in the repo:
458
+
459
+ | File | Description |
460
+ | --- | --- |
461
+ | `lib/coffeebrew_jekyll_archives/config.yml` | This contains the default configuration for the plugin to generate the archive indexes. |
462
+ | `lib/coffeebrew_jekyll_archives/generator.rb` | This is the generator that reads the configuration and generate the index pages. |
463
+ | `lib/coffeebrew_jekyll_archives/page.rb` | This is the abstract model of the index pages. |
464
+ | `lib/coffeebrew_jekyll_archives/validator.rb` | This validates the configuration. |
465
+ | `lib/coffeebrew_jekyll_archives/version.rb` | This contains the version number of the gem. |
466
+ | `lib/coffeebrew_jekyll_archives.rb` | This is the entry point of the gem, and it loads the dependencies. |
467
+
468
+ ### Test cases directory structure
469
+
470
+ All the test cases and fixtures live in the `/spec` directory:
471
+
472
+ Note: Some files have been omitted for clarity.
473
+
474
+ ```bash
475
+ spec
476
+ ├── coffeebrew_jekyll_archives_spec.rb
477
+ ├── dest
478
+ ├── fixtures
479
+ │   ├── _config.yml
480
+ │   ├── _data
481
+ │   │   └── navigation.yml
482
+ │   ├── _includes
483
+ │   │   └── navigation.html
484
+ │   ├── _layouts
485
+ │   │   ├── archive.html
486
+ │   │   └── default.html
487
+ │   └── _posts
488
+ │   ├── 2021-03-12-test-post-1.md
489
+ │   ├── 2021-03-28-test-post-2.md
490
+ │   ├── 2021-05-03-test-post-3.md
491
+ │   ├── 2021-05-03-test-post-4.md
492
+ │   ├── 2022-01-27-test-post-5.md
493
+ │   ├── 2022-03-12-test-post-6.md
494
+ │   ├── 2022-11-23-test-post-7.md
495
+ │   └── 2023-02-21-test-post-8.md
496
+ ├── scenarios
497
+ │   ├── default
498
+ │   │   ├── _site
499
+ │   │   │   ├── archives
500
+ │   │   │   │   ├── 2021
501
+ │   │   │   │   │   ├── 03
502
+ │   │   │   │   │   │   ├── 12
503
+ │   │   │   │   │   │   │   └── index.html
504
+ │   │   │   │   │   │   ├── 28
505
+ │   │   │   │   │   │   │   └── index.html
506
+ │   │   │   │   │   │   └── index.html
507
+ │   │   │   │   │   ├── 05
508
+ │   │   │   │   │   │   ├── 03
509
+ │   │   │   │   │   │   │   └── index.html
510
+ │   │   │   │   │   │   └── index.html
511
+ │   │   │   │   │   └── index.html
512
+ │   │   │   │   └── 2023
513
+ │   │   │   │   ├── 02
514
+ │   │   │   │   │   ├── 21
515
+ │   │   │   │   │   │   └── index.html
516
+ │   │   │   │   │   └── index.html
517
+ │   │   │   │   └── index.html
518
+ │   │   │   └── archives.html
519
+ │   │   └── context.rb
520
+ │   └── invalid_config_keys
521
+ │   └── context.rb
522
+ └── spec_helper.rb
523
+ ```
524
+
525
+ The files that are currently in the repo:
526
+
527
+ | File | Description |
528
+ | --- | --- |
529
+ | `spec/coffeebrew_jekyll_archives_spec.rb` | This is the main RSpec file to be executed. It contains all the contexts of various scenarios. |
530
+ | `spec/dest/` | This directory is where generated files are located. It will be deleted immediately after each context is executed. |
531
+ | `spec/fixtures/` | This directory follows the Jekyll site source structure and contains the minimal files required to generate the archive index pages. |
532
+ | `spec/fixtures/_posts` | This directory contains the test posts, you can add more to it to test your new feature. |
533
+ | `spec/scenarios/` | This directory contains the expected files of various test scenarios. |
534
+ | `spec/scenarios/<scenario>/` | This is the scenario name. |
535
+ | `spec/scenarios/<scenario>/_site/` | This directory contains the expected archive index pages. |
536
+ | `spec/scenarios/<scenario>/context.rb` | This is the file that sets up the context for the test case. |
537
+ | `spec/spec_helper.rb` | This contains RSpec configuration and certain convenience methods for the main RSpec file. |
538
+
539
+ ### Writing test cases
540
+
541
+ There is a certain convention to follow when writing new test scenarios. The recommendation is to use the rake tasks
542
+ provided in the gem to generate the scenario files.
543
+
544
+ For success scenarios, run:
545
+
546
+ ```bash
547
+ bundle exec rake coffeebrew:jekyll:archives:test:create_success[test_scenario]
548
+ ```
549
+
550
+ This will generate a placeholder file and directory:
551
+
552
+ ```bash
553
+ spec
554
+ ├── coffeebrew_jekyll_archives_spec.rb
555
+ ├── scenarios
556
+ │   └── test_scenario
557
+ │      ├── _site
558
+ │       └── context.rb
559
+ └── spec_helper.rb
560
+ ```
561
+
562
+ Where the `context.rb` file will be pre-populated:
563
+
564
+ ```ruby
565
+ # frozen_string_literal: true
566
+
567
+ CONTEXT_TEST_SCENARIO = "when using test_scenario config"
568
+
569
+ RSpec.shared_context CONTEXT_TEST_SCENARIO do
570
+ let(:scenario) { "test_scenario" }
571
+ let(:overrides) {} # TODO: remove if unused
572
+ let(:generated_files) {} # TODO: remove if unused
573
+ let(:expected_files) do
574
+ [
575
+ ]
576
+ end
577
+ end
578
+ ```
579
+
580
+ For failure scenarios, run:
581
+
582
+ ```bash
583
+ bundle exec rake coffeebrew:jekyll:archives:test:create_failure[test_scenario]
584
+ ```
585
+
586
+ This will generate a placeholder file and directory:
587
+
588
+ ```bash
589
+ spec
590
+ ├── coffeebrew_jekyll_archives_spec.rb
591
+ ├── scenarios
592
+ │   └── test_scenario
593
+ │       └── context.rb
594
+ └── spec_helper.rb
595
+ ```
596
+
597
+ Where the `context.rb` file will be pre-populated:
598
+
599
+ ```ruby
600
+ # frozen_string_literal: true
601
+
602
+ CONTEXT_TEST_SCENARIO = "when using test_scenario config"
603
+
604
+ RSpec.shared_context CONTEXT_TEST_SCENARIO do
605
+ let(:scenario) { "test_scenario" }
606
+ let(:generated_files) { [] }
607
+ let(:expected_files) { [] }
608
+ let(:overrides) do
609
+ {
610
+ }
611
+ end
612
+
613
+ let(:expected_errors) do
614
+ [
615
+ ]
616
+ end
617
+ end
618
+ ```
619
+
620
+ If you do write other test cases that are not asserting the generated files like above, you can initiate your
621
+ convention. The repo owner will evaluate the PR and accept the convention if it fits the repo existing convention, or
622
+ will recommend an alternative if it doesn't.
623
+
624
+ ## License
625
+
626
+ See the [LICENSE](LICENSE) file.
@@ -0,0 +1,30 @@
1
+ ---
2
+ archives:
3
+ navigation:
4
+ data: "navigation"
5
+ name: "Archives"
6
+ before:
7
+ after:
8
+ depths: 3
9
+ title_format:
10
+ root:
11
+ type: "string"
12
+ style: "/"
13
+ year:
14
+ type: "date"
15
+ style: "%Y"
16
+ month:
17
+ type: "date"
18
+ style: "%b, %Y"
19
+ day:
20
+ type: "date"
21
+ style: "%d %b, %Y"
22
+ root_dir: "/"
23
+ root_basename: "archives"
24
+ index_root: "/archives"
25
+ index_basename: "index"
26
+ permalink:
27
+ root: "/:root_dir"
28
+ year: "/:root_dir/:index_root/:year"
29
+ month: "/:root_dir/:index_root/:year/:month"
30
+ day: "/:root_dir/:index_root/:year/:month/:day"
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "./page"
4
+ require_relative "./validator"
5
+
6
+ module Coffeebrew
7
+ module Jekyll
8
+ module Archives
9
+ class Generator < ::Jekyll::Generator
10
+ safe true
11
+ priority :lowest
12
+
13
+ def generate(site)
14
+ @site = site
15
+
16
+ validate!
17
+
18
+ @site.pages << root_page
19
+ @site.data["archives"] = root_page
20
+
21
+ return unless navigation_config
22
+
23
+ generate_navigation
24
+ end
25
+
26
+ private
27
+
28
+ def config
29
+ @config ||= ::Jekyll::Utils.deep_merge_hashes(default_config["archives"], @site.config["archives"].to_h)
30
+ end
31
+
32
+ def validate!
33
+ validator = Coffeebrew::Jekyll::Archives::Validator.new(config)
34
+ validator.validate!
35
+ end
36
+
37
+ def root_page
38
+ @root_page ||= Coffeebrew::Jekyll::Archives::Page.new(
39
+ :root, @site, config, nil, @site.posts.docs, config["depths"],
40
+ year: 1, month: 1, day: 1
41
+ )
42
+ end
43
+
44
+ def navigation_config
45
+ @navigation_config ||= config["navigation"]
46
+ end
47
+
48
+ def navigation_data
49
+ @navigation_data ||= @site.data[navigation_config["data"]]
50
+ end
51
+
52
+ def default_config_path
53
+ @default_config_path ||= File.expand_path("config.yml", __dir__)
54
+ end
55
+
56
+ def default_config
57
+ @default_config ||= YAML.safe_load(File.read(default_config_path))
58
+ end
59
+
60
+ def menu_data
61
+ @menu_data ||= {
62
+ "name" => navigation_config["name"],
63
+ "link" => root_page.url
64
+ }
65
+ end
66
+
67
+ def find_menu_index(menu_name)
68
+ navigation_data.index do |menu|
69
+ menu["name"] == menu_name
70
+ end
71
+ end
72
+
73
+ def insert_position
74
+ @insert_position ||= if navigation_config["before"]
75
+ [navigation_config["before"], 0]
76
+ elsif navigation_config["after"]
77
+ [navigation_config["after"], 1]
78
+ else
79
+ []
80
+ end
81
+ end
82
+
83
+ def generate_navigation
84
+ if insert_position.empty?
85
+ navigation_data << menu_data
86
+ else
87
+ menu_name, offset = insert_position
88
+ found_index = find_menu_index(menu_name)
89
+ navigation_data.insert(found_index + offset, menu_data)
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
95
+ end
@@ -0,0 +1,181 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coffeebrew
4
+ module Jekyll
5
+ module Archives
6
+ class Page < ::Jekyll::Page # rubocop:disable Metrics/ClassLength
7
+ HIERARCHY = {
8
+ "root" => "year",
9
+ "year" => "month",
10
+ "month" => "day"
11
+ }.freeze
12
+
13
+ DEPTHS = {
14
+ "root" => 0,
15
+ "year" => 1,
16
+ "month" => 2,
17
+ "day" => 3
18
+ }.freeze
19
+
20
+ attr_reader :type, :date, :parent, :collection
21
+
22
+ def initialize(type, site, config, parent, collection, depths, year:, month:, day:) # rubocop:disable Lint/MissingSuper, Metrics/ParameterLists
23
+ @type = type
24
+ @site = site
25
+ @config = config
26
+ @base = site.source
27
+ @date = Date.new(year.to_i, month.to_i, day.to_i)
28
+ @ext = ".html"
29
+ @parent = parent
30
+ @collection = collection
31
+ @depths = depths
32
+ build_data
33
+ end
34
+
35
+ def sub_pages
36
+ @sub_pages ||= if current_depth < @depths
37
+ child_type = HIERARCHY[type.to_s].to_sym
38
+ group_collection_by(collection, child_type).each_with_object([]) do |(attr, items), pages|
39
+ fields = date_fields.merge(child_type => attr)
40
+ child_page = Page.new(child_type, @site, @config, self, items, @depths, **fields)
41
+ add_child_page(pages, child_page)
42
+ end
43
+ else
44
+ []
45
+ end
46
+ end
47
+
48
+ def url_placeholders
49
+ {
50
+ path: dir,
51
+ basename: basename,
52
+ output_ext: output_ext
53
+ }
54
+ end
55
+
56
+ def ancestors
57
+ @ancestors ||= begin
58
+ arr = []
59
+ current_parent = parent
60
+ while current_parent
61
+ arr.unshift(current_parent)
62
+ current_parent = current_parent.parent
63
+ end
64
+ arr
65
+ end
66
+ end
67
+
68
+ def dir
69
+ @dir ||= begin
70
+ format = @config.dig("permalink", type.to_s)
71
+ ::Jekyll::URL.new(
72
+ template: format,
73
+ placeholders: dir_placeholders,
74
+ permalink: nil
75
+ ).to_s
76
+ end
77
+ end
78
+
79
+ def year
80
+ @year ||= date.strftime("%Y")
81
+ end
82
+
83
+ def month
84
+ @month ||= date.strftime("%m")
85
+ end
86
+
87
+ def day
88
+ @day ||= date.strftime("%d")
89
+ end
90
+
91
+ def title
92
+ @title ||= case title_format_type
93
+ when :date
94
+ date.strftime(title_format_style)
95
+ when :string
96
+ format(title_format_style, year: year, month: month, day: day)
97
+ end
98
+ end
99
+
100
+ def basename
101
+ @basename ||= case type.to_sym
102
+ when :root
103
+ @config["root_basename"]
104
+ else
105
+ @config["index_basename"]
106
+ end
107
+ end
108
+
109
+ def name
110
+ @name ||= "#{basename}#{ext}"
111
+ end
112
+
113
+ private
114
+
115
+ def date_fields
116
+ @date_fields ||= { year: year, month: month, day: day }
117
+ end
118
+
119
+ def add_child_page(pages, child_page)
120
+ pages << child_page
121
+ @site.pages << child_page
122
+ end
123
+
124
+ def dir_placeholders
125
+ @dir_placeholders ||= {
126
+ root_dir: @config["root_dir"],
127
+ index_root: @config["index_root"],
128
+ year: year,
129
+ month: month,
130
+ day: day
131
+ }
132
+ end
133
+
134
+ def current_depth
135
+ @current_depth ||= DEPTHS[type.to_s]
136
+ end
137
+
138
+ def title_format
139
+ @title_format ||= @config.dig("title_format", type.to_s)
140
+ end
141
+
142
+ def title_format_type
143
+ @title_format_type ||= title_format["type"].to_sym
144
+ end
145
+
146
+ def title_format_style
147
+ @title_format_style ||= title_format["style"]
148
+ end
149
+
150
+ def root_index_path
151
+ @root_index_path ||= "#{@config['root_dir']}#{@config['root_basename']}.html"
152
+ end
153
+
154
+ def build_data # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
155
+ @data = {
156
+ "dir" => dir,
157
+ "sub_pages" => sub_pages,
158
+ "root" => root_index_path,
159
+ "ancestors" => ancestors,
160
+ "parent" => parent,
161
+ "collection" => collection,
162
+ "title" => title,
163
+ "year" => year,
164
+ "month" => month,
165
+ "day" => day
166
+ }
167
+
168
+ data.default_proc = proc do |_, key|
169
+ site.frontmatter_defaults.find(relative_path, :archives, key)
170
+ end
171
+ end
172
+
173
+ def group_collection_by(collection, attr)
174
+ collection.group_by do |item|
175
+ item.date.send(attr)
176
+ end
177
+ end
178
+ end
179
+ end
180
+ end
181
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coffeebrew
4
+ module Jekyll
5
+ module Archives
6
+ class Validator
7
+ ALLOWED_CONFIG_KEYS = {
8
+ "navigation" => {
9
+ "data" => [],
10
+ "name" => [],
11
+ "before" => [],
12
+ "after" => []
13
+ },
14
+ "depths" => [1, 2, 3],
15
+ "title_format" => {
16
+ "root" => {
17
+ "type" => %w[string date],
18
+ "style" => []
19
+ },
20
+ "year" => {
21
+ "type" => %w[string date],
22
+ "style" => []
23
+ },
24
+ "month" => {
25
+ "type" => %w[string date],
26
+ "style" => []
27
+ },
28
+ "day" => {
29
+ "type" => %w[string date],
30
+ "style" => []
31
+ }
32
+ },
33
+ "root_dir" => [],
34
+ "root_basename" => [],
35
+ "index_root" => [],
36
+ "index_basename" => [],
37
+ "permalink" => {
38
+ "root" => [],
39
+ "year" => [],
40
+ "month" => [],
41
+ "day" => []
42
+ }
43
+ }.freeze
44
+
45
+ def initialize(config)
46
+ @config = config
47
+ @errors = []
48
+ end
49
+
50
+ def validate!
51
+ parse(@config, ALLOWED_CONFIG_KEYS, ["archives"])
52
+
53
+ return if @errors.empty?
54
+
55
+ ::Jekyll.logger.error "'archives' config is set incorrectly."
56
+ ::Jekyll.logger.error "Errors:", @errors
57
+
58
+ raise ::Jekyll::Errors::InvalidConfigurationError, "'archives' config is set incorrectly."
59
+ end
60
+
61
+ private
62
+
63
+ def parse(hash, allowed_keys, parent_key)
64
+ hash.each do |key, configured_value|
65
+ allowed_values = allowed_keys[key]
66
+ new_parent_key = parent_key + [key]
67
+
68
+ next if configured_in_allowed_values?(allowed_values, configured_value)
69
+
70
+ if same_hash_type?(allowed_values, configured_value)
71
+ next parse(configured_value, allowed_values, new_parent_key)
72
+ end
73
+
74
+ add_error(new_parent_key, allowed_values, configured_value)
75
+ end
76
+ end
77
+
78
+ def same_hash_type?(allowed_values, configured_value)
79
+ allowed_values.is_a?(Hash) && configured_value.is_a?(Hash)
80
+ end
81
+
82
+ def primitive?(value)
83
+ value.nil? || value.is_a?(String) || value.is_a?(Numeric)
84
+ end
85
+
86
+ def configured_in_allowed_values?(allowed_values, configured_value)
87
+ allowed_values.is_a?(Array) && primitive?(configured_value) &&
88
+ (allowed_values.empty? || allowed_values.include?(configured_value))
89
+ end
90
+
91
+ def add_error(parent_key, allowed_values, configured_value)
92
+ @errors << { key: parent_key.join("."), expected: allowed_values, got: configured_value }
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Coffeebrew
4
+ module Jekyll
5
+ module Archives
6
+ VERSION = "0.2.0"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "jekyll"
4
+ require "coffeebrew_jekyll_archives/generator"
5
+
6
+ module Coffeebrew
7
+ module Jekyll
8
+ module Archives
9
+ end
10
+ end
11
+ end
metadata ADDED
@@ -0,0 +1,175 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: coffeebrew_jekyll_archives
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.2.0
5
+ platform: ruby
6
+ authors:
7
+ - Coffee Brew Apps
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-04-06 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: jekyll
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '4.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '5.0'
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - ">="
28
+ - !ruby/object:Gem::Version
29
+ version: '4.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '5.0'
33
+ - !ruby/object:Gem::Dependency
34
+ name: pry
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ - !ruby/object:Gem::Dependency
48
+ name: bundler
49
+ requirement: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ - !ruby/object:Gem::Dependency
62
+ name: rake
63
+ requirement: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - "~>"
66
+ - !ruby/object:Gem::Version
67
+ version: 13.0.6
68
+ type: :development
69
+ prerelease: false
70
+ version_requirements: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - "~>"
73
+ - !ruby/object:Gem::Version
74
+ version: 13.0.6
75
+ - !ruby/object:Gem::Dependency
76
+ name: rspec
77
+ requirement: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - "~>"
80
+ - !ruby/object:Gem::Version
81
+ version: 3.12.0
82
+ type: :development
83
+ prerelease: false
84
+ version_requirements: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - "~>"
87
+ - !ruby/object:Gem::Version
88
+ version: 3.12.0
89
+ - !ruby/object:Gem::Dependency
90
+ name: rubocop-jekyll
91
+ requirement: !ruby/object:Gem::Requirement
92
+ requirements:
93
+ - - "~>"
94
+ - !ruby/object:Gem::Version
95
+ version: 0.13.0
96
+ type: :development
97
+ prerelease: false
98
+ version_requirements: !ruby/object:Gem::Requirement
99
+ requirements:
100
+ - - "~>"
101
+ - !ruby/object:Gem::Version
102
+ version: 0.13.0
103
+ - !ruby/object:Gem::Dependency
104
+ name: rubocop-rake
105
+ requirement: !ruby/object:Gem::Requirement
106
+ requirements:
107
+ - - "~>"
108
+ - !ruby/object:Gem::Version
109
+ version: 0.6.0
110
+ type: :development
111
+ prerelease: false
112
+ version_requirements: !ruby/object:Gem::Requirement
113
+ requirements:
114
+ - - "~>"
115
+ - !ruby/object:Gem::Version
116
+ version: 0.6.0
117
+ - !ruby/object:Gem::Dependency
118
+ name: rubocop-rspec
119
+ requirement: !ruby/object:Gem::Requirement
120
+ requirements:
121
+ - - "~>"
122
+ - !ruby/object:Gem::Version
123
+ version: 2.19.0
124
+ type: :development
125
+ prerelease: false
126
+ version_requirements: !ruby/object:Gem::Requirement
127
+ requirements:
128
+ - - "~>"
129
+ - !ruby/object:Gem::Version
130
+ version: 2.19.0
131
+ description: A Jekyll plugin to generate blog post archives
132
+ email:
133
+ - coffeebrewapps@gmail.com
134
+ executables: []
135
+ extensions: []
136
+ extra_rdoc_files:
137
+ - README.md
138
+ - CHANGELOG.md
139
+ - LICENSE
140
+ files:
141
+ - CHANGELOG.md
142
+ - LICENSE
143
+ - README.md
144
+ - lib/coffeebrew_jekyll_archives.rb
145
+ - lib/coffeebrew_jekyll_archives/config.yml
146
+ - lib/coffeebrew_jekyll_archives/generator.rb
147
+ - lib/coffeebrew_jekyll_archives/page.rb
148
+ - lib/coffeebrew_jekyll_archives/validator.rb
149
+ - lib/coffeebrew_jekyll_archives/version.rb
150
+ homepage: https://coffeebrewapps.com/coffeebrew_jekyll_archives
151
+ licenses:
152
+ - MIT
153
+ metadata:
154
+ allowed_push_host: https://rubygems.org
155
+ post_install_message:
156
+ rdoc_options: []
157
+ require_paths:
158
+ - lib/coffeebrew_jekyll_archives
159
+ - lib
160
+ required_ruby_version: !ruby/object:Gem::Requirement
161
+ requirements:
162
+ - - ">="
163
+ - !ruby/object:Gem::Version
164
+ version: 2.7.0
165
+ required_rubygems_version: !ruby/object:Gem::Requirement
166
+ requirements:
167
+ - - ">="
168
+ - !ruby/object:Gem::Version
169
+ version: '0'
170
+ requirements: []
171
+ rubygems_version: 3.4.6
172
+ signing_key:
173
+ specification_version: 4
174
+ summary: A Jekyll plugin to generate blog post archives
175
+ test_files: []