coffeebrew_jekyll_archives 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []