re-plugin-pack 2025.6.1__py3-none-any.whl

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.
@@ -0,0 +1,6 @@
1
+ from .plugins.date_normalizer import DateNormalizer
2
+ from .plugins.drafts import Drafts
3
+ from .plugins.latest_entries import LatestEntries
4
+ from .plugins.next_prev import NextPrevPlugin
5
+
6
+ __all__ = [LatestEntries, NextPrevPlugin, Drafts, DateNormalizer]
@@ -0,0 +1,49 @@
1
+ import datetime
2
+ from typing import Any
3
+
4
+ from dateutil import parser as date_parser
5
+ from render_engine import Collection, Page, Site
6
+ from render_engine.plugins import hook_impl
7
+
8
+
9
+ class DateNormalizer:
10
+ default_settings = {}
11
+
12
+ @staticmethod
13
+ @hook_impl
14
+ def pre_build_site(
15
+ site: Site,
16
+ settings: dict[str, Any],
17
+ ) -> None:
18
+ """
19
+ Normalize dates in pages to be datetime for proper functioning
20
+
21
+ :param site: Site
22
+ :param settings: Settings object
23
+ :return: None
24
+ """
25
+ for entry in site.route_list.values():
26
+ match entry:
27
+ case Page():
28
+ DateNormalizer._handle_date(entry)
29
+ case Collection():
30
+ for page in entry:
31
+ DateNormalizer._handle_date(page)
32
+
33
+ @staticmethod
34
+ def _handle_date(entry: Page) -> None:
35
+ """
36
+ Normalize the date attribute in a Page
37
+
38
+ :return: None
39
+ """
40
+ if hasattr(entry, 'date'):
41
+ match entry.date:
42
+ case datetime.date():
43
+ entry.date = date_parser.parse(str(entry.date))
44
+ case str():
45
+ entry.date = date_parser.parse(entry.date)
46
+ case datetime.datetime():
47
+ pass
48
+ case _:
49
+ raise AttributeError(f'Unknown date type: {type(entry.date)} {entry.date}')
@@ -0,0 +1,24 @@
1
+ """Plugin to enable skipping draft pages"""
2
+
3
+ from render_engine import Collection, Site
4
+ from render_engine.plugins import hook_impl
5
+
6
+
7
+ class Drafts:
8
+ default_settings = {'show_drafts': True}
9
+
10
+ @staticmethod
11
+ @hook_impl
12
+ def pre_build_collection(collection: Collection, site: Site, settings: dict):
13
+ """
14
+ If enabled, skip pages with a draft attribute that is set to True
15
+
16
+ :param collection: Collection object to run on
17
+ :param site: Site object
18
+ :param settings: Settings for the plugin
19
+ """
20
+ settings = settings['Drafts']
21
+ if settings['show_drafts']:
22
+ print('Showing draft pages.')
23
+ return
24
+ collection.pages = [page for page in collection if not getattr(page, 'draft', False)]
@@ -0,0 +1,133 @@
1
+ """Plugin for giving the latest entries"""
2
+
3
+ from render_engine import Collection, Page, Site
4
+ from render_engine.plugins import hook_impl
5
+
6
+
7
+ class LatestEntries:
8
+ """
9
+ Adds a dictionary to the index page's template_vars:
10
+
11
+ default_settings = {
12
+ "collection": ["url"],
13
+ "entries": ["title", "url"],
14
+ "pages": ["index"],
15
+ "max_entries": 3,
16
+ }
17
+
18
+ To override the default number of entries add to the registration:
19
+ {<collection title>: <max entries>}
20
+ To disable set max entries to 0 for that collection
21
+ To print all entries for a given collection set max entries to a negative number
22
+
23
+ Can be registered on the Site object:
24
+ app.register_plugins(LatestEntries, LatestEntries=<settings overrides>)
25
+
26
+ Can be registered for an individual Page include it in the definition of the page:
27
+ @app.page
28
+ class MyPage(Page):
29
+ plugins = [
30
+ (
31
+ LatestEntries,
32
+ {
33
+ "Collection1": 5, # Include the 5 most recent entries
34
+ "Collection2": 0, # Exclude this collection
35
+ "Collection3": -1, # Include all entries in the collection
36
+ },
37
+ )
38
+ ]
39
+ NOTE: If registering the plugin for an individual page the "page" part of the settings
40
+ is irrelevant and not looked at. That settings is for when registering for the site.
41
+
42
+ NOTE: "Most recent" is defined by the sort order for the given collection.
43
+ The default for a Collection is "title" and the default for a Blog is date.
44
+ You can override the sort order (which will affect the sorting for everything,
45
+ just this plugin) by initializing the Collection object with the following attribute:
46
+ sort_by = <sort attribute>
47
+
48
+ To use put this into your index.html template or content:
49
+ {% for collection, collection_data in COLLECTIONS.items() %}
50
+ Most recent <a href="{{ collection_data['url'] }}">{{ collection }}</a> entries:
51
+ <ul>
52
+ {% for entry in collection_data['entries'] %}
53
+ <li><a href="{{ entry['url'] }}">{{ entry['title'] }}</a></li>
54
+ {% endfor %}
55
+ </ul>
56
+ {% endfor %}
57
+
58
+ NOTE: Above example is using the default settings.
59
+ """
60
+
61
+ default_settings = {
62
+ 'collection': ['url'],
63
+ 'entries': ['title', 'url'],
64
+ 'pages': ['index'],
65
+ 'max_entries': 3,
66
+ }
67
+
68
+ @staticmethod
69
+ @hook_impl
70
+ def pre_build_site(site: Site, settings: dict):
71
+ """
72
+ Pre-build site hook
73
+
74
+ :param site: Site object
75
+ :param settings: Settings dictionary [Not currently used but required by the hook]
76
+ """
77
+ settings = settings['LatestEntries']
78
+ pages = settings.get('pages', list())
79
+ for page_entry in pages:
80
+ page = site.route_list[page_entry]
81
+ LatestEntries.build_latest_entries(site=site, page=page, settings=settings)
82
+
83
+ @staticmethod
84
+ @hook_impl
85
+ def render_content(page: Page, settings: dict, site: Site):
86
+ """
87
+ Render content hook
88
+
89
+ :param page: The page to update
90
+ :param settings: The settings for the plugin
91
+ :param site: The Site object.
92
+ """
93
+ LatestEntries.build_latest_entries(site, page, settings['LatestEntries'])
94
+
95
+ @staticmethod
96
+ def build_latest_entries(site: Site, page: Page, settings: dict):
97
+ """
98
+ Update the page with the latest entries.
99
+
100
+ :param page: The page to update
101
+ :param settings: The settings for the plugin
102
+ :param site: The Site object.
103
+ """
104
+ collections = dict()
105
+ for name, collection in site.route_list.items():
106
+ if isinstance(collection, Collection):
107
+ if not (max_entries := settings.get(collection.title, settings['max_entries'])):
108
+ continue
109
+ if max_entries < 0:
110
+ max_entries = len(collection.sorted_pages)
111
+ collections[collection.title] = {'entries': list()}
112
+ for attr in settings['collection']:
113
+ if attr == 'url':
114
+ value = f'/{collection.routes[0]}/'
115
+ else:
116
+ value = getattr(collection, attr, None)
117
+ collections[collection.title][attr] = value
118
+ for entry in collection.sorted_pages[:max_entries]:
119
+ entry_dict = dict()
120
+ for attr in settings['entries']:
121
+ if attr == 'url':
122
+ value = f'/{entry.routes[0]}/{entry._slug}.html'
123
+ else:
124
+ value = getattr(entry, attr, None)
125
+ entry_dict[attr] = value
126
+ collections[collection.title]['entries'].append(entry_dict)
127
+
128
+ if not hasattr(page, 'template_vars'):
129
+ page.template_vars = dict()
130
+ if existing_collections := page.template_vars.get('COLLECTIONS'):
131
+ existing_collections.update(collections)
132
+ else:
133
+ page.template_vars['COLLECTIONS'] = collections
@@ -0,0 +1,57 @@
1
+ """Plugin to add next & prev links to items in a collection"""
2
+
3
+ from render_engine import Collection, Site
4
+ from render_engine.plugins import hook_impl
5
+
6
+
7
+ class NextPrevPlugin:
8
+ default_settings = {}
9
+
10
+ @staticmethod
11
+ @hook_impl
12
+ def pre_build_collection(collection: Collection, site: Site, settings: dict):
13
+ """
14
+ Update a list of pages with links for the previous and next posts abd the URL for the Collection archive.
15
+
16
+ Can be registered with the Site or one or more Collections.
17
+
18
+ Note that if the sort is reversed for the collection (which is the default for Blog) you will
19
+ want to have `reversed: true` for the settings of this plugin.
20
+
21
+ Example implementation:
22
+ {% if collection_url %}
23
+ <hr/>
24
+ <p style="text-align: center;">
25
+ {% if prev_url %}
26
+ Previous: <a href="{{ prev_url }}">{{ prev_title }}</a>&nbsp;&nbsp;&nbsp;&nbsp;
27
+ {% endif %}
28
+ {% if next_url %}
29
+ &nbsp;&nbsp;&nbsp;&nbsp;Next: <a href="{{ next_url }}">{{ next_title }}</a>
30
+ {% endif %}
31
+ </p>
32
+ {% endif %}
33
+
34
+ :param collection: Collection instance
35
+ :param site: Site object - not used but part of the prototype for this hook
36
+ :param settings: Plugin settings
37
+ """
38
+ settings = settings['NextPrevPlugin']
39
+ reverse = collection.sort_reverse
40
+ pages = collection.sorted_pages[::-1] if reverse else collection.sorted_pages
41
+ collection_url = collection.routes[0]
42
+ max_page = len(pages) - 1
43
+ for i, page in enumerate(pages):
44
+ template_vars = dict()
45
+ if i < max_page:
46
+ template_vars['next_url'] = pages[i + 1].url_for()
47
+ template_vars['next_title'] = pages[i + 1].title
48
+ for attr in settings.get('additional_attributes', list()):
49
+ template_vars[f'next_{attr}'] = getattr(pages[i + 1], attr, None)
50
+ if i > 0:
51
+ template_vars['prev_url'] = pages[i - 1].url_for()
52
+ template_vars['prev_title'] = pages[i - 1].title
53
+ for attr in settings.get('additional_attributes', list()):
54
+ template_vars[f'prev_{attr}'] = getattr(pages[i - 1], attr, None)
55
+ template_vars['collection_url'] = collection_url
56
+ template_vars['collection_title'] = collection._title
57
+ page.template_vars = template_vars
@@ -0,0 +1,380 @@
1
+ Metadata-Version: 2.4
2
+ Name: re-plugin-pack
3
+ Version: 2025.6.1
4
+ Summary: Plugin pack for render-engine static site generator
5
+ Author: Dan Shernicoff
6
+ Author-email: Dan Shernicoff <dan@brassnet.biz>
7
+ License-Expression: MIT
8
+ License-File: LICENSE.md
9
+ Requires-Dist: render-engine>=2025.6.1
10
+ Requires-Python: >=3.10
11
+ Project-URL: Repository, https://github.com/brass75/re-plugin-pack
12
+ Project-URL: homepage, https://github.com/brass75/re-plugin-pack
13
+ Description-Content-Type: text/markdown
14
+
15
+ # re-plugin-pack
16
+
17
+ [![Tests](https://github.com/brass75/re_plugin_pack/actions/workflows/test.yml/badge.svg?branch=main)](https://github.com/brass75/re_plugin_pack/actions/workflows/test.yml)
18
+ [![Mastodon Follow](https://img.shields.io/mastodon/follow/109552736199041636?domain=https%3A%2F%2Ftwit.social&style=flat)](https://twit.social/@brass75)
19
+ [![Ruff](https://img.shields.io/endpoint?url=https://raw.githubusercontent.com/astral-sh/ruff/main/assets/badge/v2.json)](https://github.com/astral-sh/ruff)
20
+
21
+ This is a plugin pack for the `render-engine` static site generator. It currently includes the
22
+ following plugins:
23
+
24
+ - [`LatestEntries`](#latestentries)
25
+ - [`NextPrevPlugin`](#nextprevplugin)
26
+ - [`Drafts`](#drafts)
27
+ - [`DateNormalizer`](#datenormalizer)
28
+
29
+ ## Installation
30
+
31
+ To install just run:
32
+
33
+ ```shell
34
+ pip install re-plugin-pack
35
+ ```
36
+
37
+ Once installed you can access the plugins by importing `re_plugin_pack`:
38
+
39
+ ```python
40
+ import re_plugin_pack
41
+ from re_plugin_pack import <plugin>
42
+ ```
43
+
44
+ ## `LatestEntries`
45
+
46
+ This plugin will add a list of the latest entries for one or more `Collection`s to a given `Page`.
47
+ It can be configured at the Page or `Site` levels and can be configured to provide the latest entries
48
+ for any number of `Collection`s. The entries used are in the sort order as defined in the `Collection`
49
+ object.
50
+
51
+ ### Settings
52
+
53
+ The plugin has the following default settings:
54
+
55
+ ```python
56
+ {
57
+ "collection": ["url"],
58
+ "entries": ["title", "url"],
59
+ "pages": ["index"],
60
+ "max_entries": 3,
61
+ }
62
+ ```
63
+
64
+ - `collection` - a list of attributes to include for the collection.
65
+ - `entries` - a list of attributes to include for a given entry.
66
+ - `pages` - a list of pages to add the `COLLECTIONS` entry to it's `template_vars` (Only needed when running as a
67
+ `Site` plugin)
68
+ - `max_entries` - The maximum number of entries to include for a collection. The default is 3. `-1` indicates
69
+ that all entries should be populated.
70
+
71
+ In addition to the default settings entries can be added for each `Collection` defining how many
72
+ entries it should include:
73
+
74
+ - Positive integer - Include up to the number specified entries. For example: `'Collection1': 5`
75
+ will include the 5 latest entries in the `template_vars`.
76
+ - `0` - Exclude this collection. For example: `'Collection2': 0` will not include `Collection2`
77
+ in the `template_vars`.
78
+ - Negative integer - Include all entries. For example: `'Collection3': -1` will include all
79
+ entries for `Collection3` in the `template_vars`
80
+
81
+ ### `Page` level configuration
82
+
83
+ To configure at the `Page` level just include `(LatestEntris, <settings_dict)` in the `plugins`
84
+ attribute when declaring the `Page`. If no settings overrides are needed, include `LatestEntries`
85
+ by itself in the `plugin` attribute.
86
+
87
+ Example:
88
+
89
+ ```python
90
+ from render_engine import Site, Page
91
+ from re-plugin-pack import LatestEntries
92
+
93
+ app = Site()
94
+ @app.page
95
+ class MyPage(Page):
96
+ plugins = [
97
+ (
98
+ LatestEntries,
99
+ {
100
+ "Collection1": 5, # Include the 5 most recent entries
101
+ "Collection2": 0, # Exclude this collection
102
+ "Collection3": -1, # Include all entries in the collection
103
+ },
104
+ )
105
+ ]
106
+ ...
107
+ ```
108
+
109
+ ### `Site` level configuration
110
+
111
+ To configure at the `Site` level call `app.register_plugins(LatestEntries, LatestEntries=<settings>)`
112
+ if settings overrides are being used. To configure without additional settings, call
113
+ `app.register_plugin(LatestEntries)`.
114
+
115
+ Example:
116
+
117
+ ```python
118
+ from render_engine import Site, Page
119
+ from re-plugin-pack import LatestEntries
120
+
121
+ app = Site()
122
+
123
+ # The following will use the default settings overriding the pages to run on to be page1 and page2
124
+ app.register_plugins(LatestEntries, LatestEntries={'pages': ['page1', 'page2']})
125
+
126
+ # The following will only enable for the Blog collection:
127
+ app.register_plugins(LatestEntries, LatestEntries={'max_entries': 0, 'Blog': 3})
128
+ ```
129
+
130
+ NOTE: If configuring at the `Site` level the registration should be done _after_ adding all
131
+ `Page` entries to the `Site`. When adding a `Page` to a `Site` all plugins registered to the
132
+ `Site` are automatically registered to the new `Page` object.
133
+
134
+ ### Including in the `Page` template
135
+
136
+ The latest entries will be added to the `COLLECTIONS` key of the `template_vars` for the `Page`
137
+ and can be accessed in the template as `COLLECTIONS`.
138
+
139
+ Example template implementation:
140
+
141
+ ```
142
+ {% for collection, collection_data in COLLECTIONS.items() %}
143
+ Most recent <a href="{{ collection_data['url'] }}">{{ collection }}</a> entries:
144
+ <ul>
145
+ {% for entry in collection_data['entries'] %}
146
+ <li><a href="{{ entry['url'] }}">{{ entry['title'] }}</a></li>
147
+ {% endfor %}
148
+ </ul>
149
+ {% endfor %}
150
+ ```
151
+
152
+ ## `NextPrevPlugin`
153
+
154
+ This plugin will give each page in a `Collection` access to certain attributes of the next and
155
+ previous pages in the collection. By default, only the URL and title of those pages will be
156
+ made available, however additional attributes may also be requested via the settings. If an
157
+ attributes is in the list to include but does not exist in the page(s) `None` will be used.
158
+ This plugin runs at the `Collection` level and can be registered at the `Site` or `Collection`
159
+ level.
160
+
161
+ ### Settings
162
+
163
+ This plugin has no default settings. If you wish to add additional attributes, include in the
164
+ registration a settings dictionary with a key of `additional_attributes` having a list of
165
+ attributes as its value.
166
+
167
+ ### `Site` level configuration
168
+
169
+ To configure at the `Site` level call `app.register_plugins(NextPrevPlugin, NextPrevPlugin=<settings>)`
170
+ if settings overrides are being used. To configure without additional settings, call
171
+ `app.register_plugin(NextPrevPlugin)`.
172
+
173
+ Example:
174
+
175
+ ```python
176
+ from render_engine import Site, Page
177
+ from re-plugin-pack import NextPrevPlugin
178
+
179
+ app = Site()
180
+
181
+ # The following will include the date attribute in in the data made available.
182
+ app.register_plugins(NextPrevPlugin, NextPrevPlugin={'additional_attributes': ['date']})
183
+
184
+ # The following will not include any additional attributes
185
+ app.register_plugins(NextPrevPlugin)
186
+ ```
187
+
188
+ NOTE: If configuring at the `Site` level the registration should be done _after_ adding all
189
+ `Collection` entries to the `Site`. When adding a `Collection` to a `Site` all plugins
190
+ registered to the `Site` are automatically registered to the new `Collection` object.
191
+
192
+ ### `Collection` level configuration
193
+
194
+ To configure at the `Page` level just include `(NextPrevPlugin, <settings_dict)` in the `plugins`
195
+ attribute when declaring the `Collection`. If no settings overrides are needed, include
196
+ `NextPrevPlugin` by itself in the `plugin` attribute.
197
+
198
+ Example:
199
+
200
+ ```python
201
+ from render_engine import Site, Page
202
+ from re-plugin-pack import NextPrevPlugin
203
+
204
+ app = Site()
205
+ @app.page
206
+ class MyPage(Page):
207
+ plugins = [
208
+ (
209
+ NextPrevPlugin,
210
+ {
211
+ 'additional_attributes': [ # This will make the date and description attributes available
212
+ 'date',
213
+ 'description',
214
+ ]
215
+ },
216
+ )
217
+ ]
218
+ ...
219
+ ```
220
+
221
+ ### Including in the `Collection` template
222
+
223
+ When run this plugin will add a dictionary with the requested data to the `template_vars` of
224
+ each page in the `Collection`. The dictionary added looks like:
225
+
226
+ ```python
227
+ {
228
+ 'collection_url': <url>,
229
+ 'collection_title': <title>,
230
+ 'next_url': <url>,
231
+ 'next_title': <title>,
232
+ 'prev_url': <url>,
233
+ 'prev_title': <title>,
234
+ }
235
+ ```
236
+
237
+ If `additional_attributes` are included in the settings, they will be in the dictionary as:
238
+ `prev_<attr>: <value>` and `next_<attr>: <value>`.
239
+
240
+ ```
241
+ {% if prev_url %}
242
+ <span class="prev_data">
243
+ Previous: <a href="{{ prev_url }}">{{ prev_title }}</a>
244
+ </span>
245
+ {% endif %}
246
+ {% if next_url %}
247
+ <span class="next_data">
248
+ Next: <a href="{{ next_url }}">{{ next_title }}</a>
249
+ </span>
250
+ {% endif %}
251
+ ```
252
+
253
+ ## `Drafts`
254
+
255
+ This is a plugin that will skip pages in a `Collection` that are marked as `draft`. This allows
256
+ you to have WIP posts and still make changes to other things without publishing things that are
257
+ not ready to be posted.
258
+
259
+ ### Settings
260
+
261
+ The `Drafts` plugin has a single setting:
262
+
263
+ ```python
264
+ {
265
+ 'show_drafts': True,
266
+ }
267
+ ```
268
+
269
+ When `show_drafts` is set to `False` the plugin will run, prior to rendering the `Collection`
270
+ and remove all pages in the collection that have a `True` value for the `draft` attribute will
271
+ not be rendered when building the site. This plugin runs at the `Collection` level and can be
272
+ registered either `Site`-wide or for individual collections.
273
+
274
+ **NOTE**: Plugins run in the reverse order of registration. If you are using both this and
275
+ [`NextPrevPlugin`](NextPrevPlugin) make sure that you register `NextPrevPlugin` (or any similar
276
+ plugin) _before_ you register `Drafts` or you might have unexpected results as pages that were
277
+ processed will have been removed.
278
+
279
+ ### Making sure that drafts show locally but not on your production site
280
+
281
+ In order to have this work effectively, so that you can see the draft posts locally when you
282
+ use `render-engine serve`, you will need to differentiate between the environments. To
283
+ accomplish this you can use an environment variable and add this line of code into your
284
+ `app.py`:
285
+
286
+ ```python
287
+ import os
288
+
289
+ ENABLE_DRAFTS = {"show_drafts": os.environ.get("SHOW_DRAFTS", False)}
290
+ ```
291
+
292
+ When you register the plugin make sure to include the `ENABLE_DRAFTS` as the settings
293
+ for the plugin:
294
+
295
+ ```python
296
+ import os
297
+ from re_plugin_pack import Drafts
298
+ from render_engine import Site, Collection
299
+
300
+ ENABLE_DRAFTS = {"show_drafts": os.environ.get("SHOW_DRAFTS", False)}app=Site()
301
+
302
+ # Either at the `Site` level:
303
+ app.register_plugins(Drafts, Drafts=ENABLE_DRAFTS)
304
+
305
+ # Or at the `Collection` level:
306
+ @app.collection
307
+ class MyCollection(Collection):
308
+ plugins=[(Drafts, ENABLE_DRAFTS)]
309
+ ...
310
+ ```
311
+
312
+ ### Using `Drafts` with `NextPrevPlugin` (or other, similar, `Collection` level plugins)
313
+
314
+ Make sure that you register plugins in the order that you want them to run. Remember that
315
+ any plugin that is registered with the `Site` prior to creating the `Collection` is included
316
+ in the `Collection` the will have been registered first and will run _after_ plugins that
317
+ are registered at the `Collection` level. Since plugins are invoked in the _reverse_ order
318
+ of registration `Drafts` should be the last plugin run for a `Collection`.
319
+
320
+ Example registration:
321
+
322
+ ```python
323
+ import os
324
+ from re_plugin_pack import Drafts, NextPrevPlugin
325
+ from render_engine import Site, Collection
326
+
327
+ ENABLE_DRAFTS = {"show_drafts": os.environ.get("SHOW_DRAFTS", False)}app=Site()
328
+
329
+ @app.collection
330
+ class MyCollection(Collection):
331
+ plugins = [
332
+ NextPrevPlugin,
333
+ (Drafts, ENABLE_DRAFTS),
334
+ ]
335
+ ```
336
+
337
+ The above order will run the `Drafts` plugin first on that collection.
338
+
339
+ ### Marking a post as a `draft`
340
+
341
+ To mark a post as a `draft` just add a truthy value for the `draft` attribute to the content:
342
+
343
+ ```
344
+ ---
345
+ title: WIP Post
346
+ date: 2025-06-01T02:49:18
347
+ draft: Yes
348
+ ---
349
+ This is a draft post
350
+ ```
351
+
352
+ ## `DateNormalizer`
353
+
354
+ The goal of this plugin is to normalize entries in the `date` attribute of pages. This is necessary
355
+ since valid entries include `datetime.date`, `datetime.datetime`, and date like strings. Since
356
+ inconsistencies in the values of these fields can cause issues with sorting:
357
+
358
+ ```
359
+ TypeError: '<' not supported between instances of 'datetime.datetime' and 'datetime.date'
360
+ ```
361
+
362
+ and rendering if using the `format_datetime` function it is important that the date-like objects
363
+ be normalized into `datetime.datetime` objects regardless of how they are set.
364
+
365
+ ### DateNormalizer Settings
366
+
367
+ `DateNormalizer` has no settings.
368
+
369
+ ### Configuration
370
+
371
+ `DateNormalizer` is a `Site` level plugins that runs prior to the site being rendered. It must
372
+ be registered at the site level:
373
+
374
+ ```python
375
+ from render_engine import Site, Page
376
+ from re-plugin-pack import DateNormalizer
377
+
378
+ app = Site()
379
+ app.register_plugins(DateNormalizer)
380
+ ```
@@ -0,0 +1,9 @@
1
+ re_plugin_pack/__init__.py,sha256=f300e825fc8fae585c98a05a35e948abf24704169690005f76871a134adcdd8e,250
2
+ re_plugin_pack/plugins/date_normalizer.py,sha256=c0b16878b4d8a87ee0cf005d9759768489f21e1e71d54f47b36761630ec1a60e,1431
3
+ re_plugin_pack/plugins/drafts.py,sha256=0672e4642d88e7b0cda4774756e36850ff4398a57e30dfab63855b6945596960,774
4
+ re_plugin_pack/plugins/latest_entries.py,sha256=9f0f4e75724a20c7c6217d19a5c1c0b14289ca5e3ddccfd85af9a4671df28713,5137
5
+ re_plugin_pack/plugins/next_prev.py,sha256=feeb42c98c358dd186462174a4332228016de15840004066ff9815ef7a0d51db,2469
6
+ re_plugin_pack-2025.6.1.dist-info/licenses/LICENSE.md,sha256=96b1a938d839ee7740846ef97e81bcced9ca178c8058dce405d2032484e48c41,1071
7
+ re_plugin_pack-2025.6.1.dist-info/WHEEL,sha256=d9bfa3dfe8454c349f284c03d27f39943327e72f63576bb2a4481e7e394e6484,79
8
+ re_plugin_pack-2025.6.1.dist-info/METADATA,sha256=d4f7651cf335a43c43138034c120f52775ed998c83464b7e9ddbdbd42b7a3489,12306
9
+ re_plugin_pack-2025.6.1.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: uv 0.7.15
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Dan Shernicoff
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.