lifer 0.8.0 → 0.10.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 +4 -4
- data/CHANGELOG.md +37 -0
- data/Gemfile.lock +6 -6
- data/lib/lifer/builder/html/from_any.rb +66 -0
- data/lib/lifer/builder/html/from_erb.rb +10 -12
- data/lib/lifer/builder/html/from_liquid/drops/entry_drop.rb +13 -6
- data/lib/lifer/builder/html/from_liquid/filters.rb +1 -1
- data/lib/lifer/builder/html/from_liquid/liquid_env.rb +0 -2
- data/lib/lifer/builder/html/from_liquid.rb +16 -29
- data/lib/lifer/builder/html.rb +1 -0
- data/lib/lifer/builder/rss.rb +17 -5
- data/lib/lifer/collection.rb +2 -2
- data/lib/lifer/entry.rb +66 -27
- data/lib/lifer/utilities.rb +4 -1
- data/lib/lifer/version.rb +1 -1
- data/lib/lifer.rb +14 -0
- data/locales/en.yml +1 -1
- metadata +5 -5
- data/lib/lifer/builder/html/from_liquid/layout_tag.rb +0 -66
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: f1d33c1f633f27b80005a2e2e7c1919f43d86a5fdba9f12523dd4dcdb4e47422
|
4
|
+
data.tar.gz: 37927e9a39b49363ee0de3f34294c4e8ccf82b83a7a73b8d86ebc1bae8243947
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: db511f0ace1e617851b7e4de0fd12f9a615c283db318d3c05013cdadbae40ab89cd6be06bc429f4c443caf5d1c67f1d385276f4596788eadbfe3d22916e9c20d
|
7
|
+
data.tar.gz: 917d189530c40b4c15fb57a9d1bfce28a809b00fd07267b5a2485997adcb8c3e557dcf242def3a515297b908a5000902a1e7fcbe4bff29240791a88681b2fa1f
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,42 @@
|
|
1
1
|
## Next
|
2
2
|
|
3
|
+
## v0.10.0
|
4
|
+
|
5
|
+
These commits collect work to let all layout files (either Liquid or ERB files)
|
6
|
+
provide a reference to parent, or "root", layout files that should wrap the via
|
7
|
+
frontmatter.
|
8
|
+
|
9
|
+
Previously, we did this for Liquid layouts using the custom `layout` tag. But
|
10
|
+
because there was no equivalent tag for ERB files, I realized it would be less
|
11
|
+
work to just provide the value via frontmatter. The same way for every type of
|
12
|
+
layout file.
|
13
|
+
|
14
|
+
End users (just me?) must update their Lifer project Liquid layouts accordingly:
|
15
|
+
|
16
|
+
```diff
|
17
|
+
- {% layout "layouts/root_layout.html.liquid" %}
|
18
|
+
+ ---
|
19
|
+
+ layout: layouts/root_layout.html.liquid
|
20
|
+
+ ---
|
21
|
+
|
22
|
+
Layout content.
|
23
|
+
```
|
24
|
+
|
25
|
+
## v0.9.0
|
26
|
+
|
27
|
+
Atom feeds now support entries with both `#published_at` and `#updated_at`
|
28
|
+
timestamp. There is no standard equivalent way to provide this functonality for
|
29
|
+
RSS-format feeds, unfortunately. As part of this change, we removed all
|
30
|
+
`Entry#date` methods in favour of `Entry#published_at` onces. In Atom feeds, if
|
31
|
+
an article has no last updated date, the publication date is used instead.
|
32
|
+
|
33
|
+
Additionally, this release includes a new environment variable:
|
34
|
+
`LIFER_UNPARALLELIZED`. You can use this environment variable to run `lifer
|
35
|
+
build` or `lifer serve` without any parallelization turned on. This could be
|
36
|
+
useful for reproducing bugs and so on. Example usage:
|
37
|
+
|
38
|
+
LIFER_UNPARALLELIZED=1 lifer build
|
39
|
+
|
3
40
|
## v0.8.0
|
4
41
|
|
5
42
|
### Tags
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
lifer (0.
|
4
|
+
lifer (0.10.0)
|
5
5
|
i18n (< 2)
|
6
6
|
kramdown (~> 2.4)
|
7
7
|
liquid (~> 5.6, < 6)
|
@@ -32,9 +32,9 @@ GEM
|
|
32
32
|
irb (~> 1.10)
|
33
33
|
reline (>= 0.3.8)
|
34
34
|
diff-lcs (1.5.1)
|
35
|
-
ffi (1.17.
|
36
|
-
ffi (1.17.
|
37
|
-
ffi (1.17.
|
35
|
+
ffi (1.17.2-arm64-darwin)
|
36
|
+
ffi (1.17.2-x86_64-darwin)
|
37
|
+
ffi (1.17.2-x86_64-linux-gnu)
|
38
38
|
i18n (1.14.7)
|
39
39
|
concurrent-ruby (~> 1.0)
|
40
40
|
io-console (0.7.2)
|
@@ -44,7 +44,7 @@ GEM
|
|
44
44
|
kramdown (2.5.1)
|
45
45
|
rexml (>= 3.3.9)
|
46
46
|
language_server-protocol (3.17.0.3)
|
47
|
-
liquid (5.8.
|
47
|
+
liquid (5.8.6)
|
48
48
|
bigdecimal
|
49
49
|
strscan (>= 3.1.1)
|
50
50
|
listen (3.9.0)
|
@@ -107,7 +107,7 @@ GEM
|
|
107
107
|
sorbet-runtime (>= 0.5.10782)
|
108
108
|
sorbet-runtime (0.5.11558)
|
109
109
|
stringio (3.1.1)
|
110
|
-
strscan (3.1.
|
110
|
+
strscan (3.1.5)
|
111
111
|
xpath (3.2.0)
|
112
112
|
nokogiri (~> 1.8)
|
113
113
|
yard (0.9.37)
|
@@ -0,0 +1,66 @@
|
|
1
|
+
class Lifer::Builder
|
2
|
+
class HTML
|
3
|
+
# A base class for all HTML builder adapters. The methods provided by this
|
4
|
+
# class are either required or reusable by builder subclasses. See the
|
5
|
+
# committed HTML builder adapter classes for example implementations.
|
6
|
+
class FromAny
|
7
|
+
class << self
|
8
|
+
# Build and render an entry.
|
9
|
+
#
|
10
|
+
# @param entry [Lifer::Entry] The entry to be rendered.
|
11
|
+
# @return [String] The rendered entry.
|
12
|
+
def build(entry:)
|
13
|
+
new(entry: entry).render
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# The base class does not provide a render method, but any subclass
|
18
|
+
# should be expected to.
|
19
|
+
#
|
20
|
+
# @raise [NotImplementedError]
|
21
|
+
def render
|
22
|
+
raise NotImplementedError,
|
23
|
+
"subclasses must implement a custom `#render` method"
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
# The frontmatter provided by the layout file.
|
29
|
+
#
|
30
|
+
# @return [Hash] The frontmatter represented as a hash.
|
31
|
+
def frontmatter
|
32
|
+
return {} unless frontmatter?
|
33
|
+
|
34
|
+
Lifer::Utilities.symbolize_keys(
|
35
|
+
YAML.load layout_file_contents(raw: true)[Lifer::FRONTMATTER_REGEX, 1],
|
36
|
+
permitted_classes: [Time]
|
37
|
+
)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Checks whether frontmatter is present in the layout file.
|
41
|
+
#
|
42
|
+
# @return [boolean]
|
43
|
+
def frontmatter?
|
44
|
+
@frontmatter ||=
|
45
|
+
layout_file_contents(raw: true).match?(Lifer::FRONTMATTER_REGEX)
|
46
|
+
end
|
47
|
+
|
48
|
+
# The contents of the layout file.
|
49
|
+
#
|
50
|
+
# @param raw [boolean] Whether to include or exclude frontmatter from
|
51
|
+
# the contents.
|
52
|
+
# @return [String] The contents of the layout file.
|
53
|
+
def layout_file_contents(raw: false)
|
54
|
+
cache_variable = "@layout_file_contents_#{raw}"
|
55
|
+
cached_value = instance_variable_get cache_variable
|
56
|
+
|
57
|
+
return cached_value if cached_value
|
58
|
+
|
59
|
+
contents = File.read layout_file
|
60
|
+
contents = contents.gsub(Lifer::FRONTMATTER_REGEX, "") unless raw
|
61
|
+
|
62
|
+
instance_variable_set cache_variable, contents
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
@@ -23,23 +23,21 @@ class Lifer::Builder::HTML
|
|
23
23
|
# </body>
|
24
24
|
# </html>
|
25
25
|
#
|
26
|
-
class FromERB
|
27
|
-
class << self
|
28
|
-
# Build and render an entry.
|
29
|
-
#
|
30
|
-
# @param entry [Lifer::Entry] The entry to be rendered.
|
31
|
-
# @return [String] The rendered entry.
|
32
|
-
def build(entry:)
|
33
|
-
new(entry: entry).render
|
34
|
-
end
|
35
|
-
end
|
36
|
-
|
26
|
+
class FromERB < FromAny
|
37
27
|
# Reads the entry as ERB, given our renderer context (see the documentation
|
38
28
|
# for `#build_binding_context`) and renders the production-ready entry.
|
39
29
|
#
|
40
30
|
# @return [String] The rendered entry.
|
41
31
|
def render
|
42
|
-
ERB.new(
|
32
|
+
document = ERB.new(layout_file_contents).result context
|
33
|
+
|
34
|
+
return document unless (relative_layout_path = frontmatter[:layout])
|
35
|
+
|
36
|
+
document_binding = binding.tap { |binding|
|
37
|
+
binding.local_variable_set :content, document
|
38
|
+
}
|
39
|
+
layout_path = "%s/%s" % [Lifer.root, relative_layout_path]
|
40
|
+
ERB.new(File.read layout_path).result(document_binding)
|
43
41
|
end
|
44
42
|
|
45
43
|
private
|
@@ -4,7 +4,9 @@ module Lifer::Builder::HTML::FromLiquid::Drops
|
|
4
4
|
#
|
5
5
|
# @example Usage
|
6
6
|
# <h1>{{ entry.title }}</h1>
|
7
|
-
# <small>
|
7
|
+
# <small>
|
8
|
+
# Published on <datetime>{{ entry.published_at }}</datetime>
|
9
|
+
# </small>
|
8
10
|
#
|
9
11
|
class EntryDrop < Liquid::Drop
|
10
12
|
attr_accessor :lifer_entry, :collection
|
@@ -30,11 +32,6 @@ module Lifer::Builder::HTML::FromLiquid::Drops
|
|
30
32
|
# @return [String]
|
31
33
|
def content = (@content ||= lifer_entry.to_html)
|
32
34
|
|
33
|
-
# The entry date (as a string).
|
34
|
-
#
|
35
|
-
# @return [String]
|
36
|
-
def date = (@date ||= lifer_entry.date)
|
37
|
-
|
38
35
|
# The entry frontmatter data.
|
39
36
|
#
|
40
37
|
# @return [FrontmatterDrop]
|
@@ -50,6 +47,11 @@ module Lifer::Builder::HTML::FromLiquid::Drops
|
|
50
47
|
# @return [String] The entry permalink.
|
51
48
|
def permalink = (@permalink ||= lifer_entry.permalink)
|
52
49
|
|
50
|
+
# The entry publication date (as a string).
|
51
|
+
#
|
52
|
+
# @return [String]
|
53
|
+
def published_at = (@published_at ||= lifer_entry.published_at)
|
54
|
+
|
53
55
|
# The summary of the entry.
|
54
56
|
#
|
55
57
|
# @return [String] The summary of the entry.
|
@@ -59,5 +61,10 @@ module Lifer::Builder::HTML::FromLiquid::Drops
|
|
59
61
|
#
|
60
62
|
# @return [String] The entry title.
|
61
63
|
def title = (@title ||= lifer_entry.title)
|
64
|
+
|
65
|
+
# The entry's last updated date (as a string).
|
66
|
+
#
|
67
|
+
# @return [String]
|
68
|
+
def updated_at = (@updated_at ||= lifer_entry.updated_at)
|
62
69
|
end
|
63
70
|
end
|
@@ -2,7 +2,7 @@
|
|
2
2
|
# In many cases these utilities exist to be pseudo-compatible with Jekyll.
|
3
3
|
#
|
4
4
|
# @example A filter in a Liquid template.
|
5
|
-
# {{ entry.
|
5
|
+
# {{ entry.published_at | date_to_xmlschema }}
|
6
6
|
#
|
7
7
|
module Lifer::Builder::HTML::FromLiquid::Filters
|
8
8
|
# @!visibility private
|
@@ -22,8 +22,6 @@ class Lifer::Builder::HTML::FromLiquid
|
|
22
22
|
Liquid::LocalFileSystem.new(Lifer.root, "%s.html.liquid")
|
23
23
|
|
24
24
|
environment.register_filter Lifer::Builder::HTML::FromLiquid::Filters
|
25
|
-
environment.register_tag "layout",
|
26
|
-
Lifer::Builder::HTML::FromLiquid::LayoutTag
|
27
25
|
end
|
28
26
|
end
|
29
27
|
end
|
@@ -1,10 +1,5 @@
|
|
1
1
|
require "liquid"
|
2
2
|
|
3
|
-
require_relative "from_liquid/drops"
|
4
|
-
require_relative "from_liquid/filters"
|
5
|
-
require_relative "from_liquid/layout_tag"
|
6
|
-
require_relative "from_liquid/liquid_env"
|
7
|
-
|
8
3
|
class Lifer::Builder::HTML
|
9
4
|
# If the HTML builder is given a Liquid template, it uses this class to parse
|
10
5
|
# the Liquid into HTML. Lifer project metadata is provided as context. For
|
@@ -28,16 +23,10 @@ class Lifer::Builder::HTML
|
|
28
23
|
# </body>
|
29
24
|
# </html>
|
30
25
|
#
|
31
|
-
class FromLiquid
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
# @param entry [Lifer::Entry] The entry to render.
|
36
|
-
# @return [String] The rendered entry, ready for output.
|
37
|
-
def build(entry:) = new(entry:).render
|
38
|
-
end
|
39
|
-
|
40
|
-
attr_accessor :entry, :layout_file
|
26
|
+
class FromLiquid < FromAny
|
27
|
+
require_relative "from_liquid/drops"
|
28
|
+
require_relative "from_liquid/filters"
|
29
|
+
require_relative "from_liquid/liquid_env"
|
41
30
|
|
42
31
|
# Reads the entry as Liquid, given our document context, and renders
|
43
32
|
# an entry.
|
@@ -49,13 +38,23 @@ class Lifer::Builder::HTML
|
|
49
38
|
.parse(entry.to_html, **parse_options)
|
50
39
|
.render(context, **render_options)
|
51
40
|
)
|
41
|
+
document = Liquid::Template
|
42
|
+
.parse(layout_file_contents, **parse_options)
|
43
|
+
.render(document_context, **render_options)
|
44
|
+
|
45
|
+
return document unless (relative_layout_path = frontmatter[:layout])
|
46
|
+
|
47
|
+
layout_path = "%s/%s" % [Lifer.root, relative_layout_path]
|
48
|
+
document_context = context.merge! "content" => document
|
52
49
|
Liquid::Template
|
53
|
-
.parse(
|
50
|
+
.parse(File.read layout_path, **parse_options)
|
54
51
|
.render(document_context, **render_options)
|
55
52
|
end
|
56
53
|
|
57
54
|
private
|
58
55
|
|
56
|
+
attr_accessor :entry, :layout_file
|
57
|
+
|
59
58
|
def initialize(entry:)
|
60
59
|
@entry = entry
|
61
60
|
@layout_file = entry.collection.layout_file
|
@@ -79,19 +78,6 @@ class Lifer::Builder::HTML
|
|
79
78
|
}
|
80
79
|
end
|
81
80
|
|
82
|
-
# @private
|
83
|
-
# It's possible for the provided layout to request a parent layout, which
|
84
|
-
# makes this method a bit complicated.
|
85
|
-
#
|
86
|
-
# @return [String] A Liquid layout document, ready for parsing.
|
87
|
-
def layout
|
88
|
-
contents = File.read layout_file
|
89
|
-
|
90
|
-
return contents unless contents.match?(/\{%\s*#{LayoutTag::NAME}.*%\}/)
|
91
|
-
|
92
|
-
contents + "\n{% #{LayoutTag::ENDNAME} %}"
|
93
|
-
end
|
94
|
-
|
95
81
|
def liquid_environment = (@liquid_environment ||= LiquidEnv.global)
|
96
82
|
|
97
83
|
def parse_options
|
@@ -103,6 +89,7 @@ class Lifer::Builder::HTML
|
|
103
89
|
|
104
90
|
def render_options
|
105
91
|
{
|
92
|
+
registers: {file_system: liquid_environment.file_system},
|
106
93
|
strict_variables: true,
|
107
94
|
strict_filters: true
|
108
95
|
}
|
data/lib/lifer/builder/html.rb
CHANGED
data/lib/lifer/builder/rss.rb
CHANGED
@@ -65,8 +65,7 @@ class Lifer::Builder::RSS < Lifer::Builder
|
|
65
65
|
# The name of the format type, as needed by `RSS::Maker`, used by default by
|
66
66
|
# this feed builder.
|
67
67
|
#
|
68
|
-
|
69
|
-
|
68
|
+
DEFAULT_MAKER_FORMAT_NAME = FORMATS[:rss]
|
70
69
|
|
71
70
|
self.name = :rss
|
72
71
|
self.settings = [
|
@@ -96,7 +95,7 @@ class Lifer::Builder::RSS < Lifer::Builder
|
|
96
95
|
|
97
96
|
return FORMATS[format] if FORMATS.keys.include? format
|
98
97
|
|
99
|
-
|
98
|
+
DEFAULT_MAKER_FORMAT_NAME
|
100
99
|
end
|
101
100
|
|
102
101
|
# Traverses and renders an RSS feed for feedable collection.
|
@@ -220,8 +219,21 @@ class Lifer::Builder::RSS < Lifer::Builder
|
|
220
219
|
rss_feed_item.link = lifer_entry.permalink
|
221
220
|
rss_feed_item.title = lifer_entry.title
|
222
221
|
rss_feed_item.summary = lifer_entry.summary
|
223
|
-
|
224
|
-
|
222
|
+
|
223
|
+
if feed_format(lifer_entry.collection) == "atom"
|
224
|
+
rss_feed_item.content.content = lifer_entry.to_html
|
225
|
+
rss_feed_item.published = lifer_entry.published_at
|
226
|
+
|
227
|
+
# Note: RSS does not provide a standard way to share last updated
|
228
|
+
# timestamps at all, while Atom does. This is the reason there is no
|
229
|
+
# equivalent call in the condition for RSS feeds.
|
230
|
+
#
|
231
|
+
rss_feed_item.updated =
|
232
|
+
lifer_entry.updated_at(fallback: lifer_entry.published_at)
|
233
|
+
else
|
234
|
+
rss_feed_item.content_encoded = lifer_entry.to_html
|
235
|
+
rss_feed_item.pubDate = lifer_entry.published_at.to_time.rfc2822
|
236
|
+
end
|
225
237
|
end
|
226
238
|
end
|
227
239
|
|
data/lib/lifer/collection.rb
CHANGED
@@ -57,9 +57,9 @@ class Lifer::Collection
|
|
57
57
|
cached_entries_variable,
|
58
58
|
case order
|
59
59
|
when :latest
|
60
|
-
@entries_collection.sort_by { |entry| entry.
|
60
|
+
@entries_collection.sort_by { |entry| entry.published_at }.reverse
|
61
61
|
when :oldest
|
62
|
-
@entries_collection.sort_by { |entry| entry.
|
62
|
+
@entries_collection.sort_by { |entry| entry.published_at }
|
63
63
|
end
|
64
64
|
)
|
65
65
|
end
|
data/lib/lifer/entry.rb
CHANGED
@@ -39,10 +39,6 @@ module Lifer
|
|
39
39
|
#
|
40
40
|
FILENAME_DATE_FORMAT = /^(\d{4}-\d{1,2}-\d{1,2})-/
|
41
41
|
|
42
|
-
# We expect frontmatter to be provided in the following format.
|
43
|
-
#
|
44
|
-
FRONTMATTER_REGEX = /^---\n(.*)---\n/m
|
45
|
-
|
46
42
|
# If tags are represented in YAML frontmatter as a string, they're split on
|
47
43
|
# commas and/or spaces.
|
48
44
|
#
|
@@ -147,27 +143,7 @@ module Lifer
|
|
147
143
|
def body
|
148
144
|
return full_text.strip unless frontmatter?
|
149
145
|
|
150
|
-
full_text.gsub(FRONTMATTER_REGEX, "").strip
|
151
|
-
end
|
152
|
-
|
153
|
-
# Since text files would only store dates as simple strings, it's nice to
|
154
|
-
# attempt to convert those into Ruby date or datetime objects.
|
155
|
-
#
|
156
|
-
# @return [Time] A Ruby representation of the date and time provided by the
|
157
|
-
# entry frontmatter or filename.
|
158
|
-
def date
|
159
|
-
date_data = frontmatter[:date] || filename_date
|
160
|
-
|
161
|
-
case date_data
|
162
|
-
when Time then date_data
|
163
|
-
when String then DateTime.parse(date_data).to_time
|
164
|
-
else
|
165
|
-
Lifer::Message.log("entry.no_date_metadata", filename: file)
|
166
|
-
Lifer::Entry::DEFAULT_DATE
|
167
|
-
end
|
168
|
-
rescue ArgumentError => error
|
169
|
-
Lifer::Message.error("entry.date_error", filename: file, error:)
|
170
|
-
Lifer::Entry::DEFAULT_DATE
|
146
|
+
full_text.gsub(Lifer::FRONTMATTER_REGEX, "").strip
|
171
147
|
end
|
172
148
|
|
173
149
|
def feedable?
|
@@ -188,7 +164,8 @@ module Lifer
|
|
188
164
|
return {} unless frontmatter?
|
189
165
|
|
190
166
|
Lifer::Utilities.symbolize_keys(
|
191
|
-
YAML.load
|
167
|
+
YAML.load full_text[Lifer::FRONTMATTER_REGEX, 1],
|
168
|
+
permitted_classes: [Time]
|
192
169
|
)
|
193
170
|
end
|
194
171
|
|
@@ -232,6 +209,27 @@ module Lifer
|
|
232
209
|
# @return [String] The absolute URI path to the entry.
|
233
210
|
def path = permalink(host: "/")
|
234
211
|
|
212
|
+
# The entry's publication date. The published date can be inferred in a few
|
213
|
+
# ways. The priority is:
|
214
|
+
#
|
215
|
+
# 1. the frontmatter's `published_at` field
|
216
|
+
# 2. the frontmatter's `published` field
|
217
|
+
# 3. the frontamtter's `date` field
|
218
|
+
# 4. The date in the filename.
|
219
|
+
#
|
220
|
+
# Since text files would only store dates as simple strings, it's nice to
|
221
|
+
# attempt to convert those into Ruby date or datetime objects.
|
222
|
+
#
|
223
|
+
# @return [Time] A Ruby representation of the date and time provided by the
|
224
|
+
# entry frontmatter or filename.
|
225
|
+
def published_at
|
226
|
+
date_for frontmatter[:published_at],
|
227
|
+
frontmatter[:published],
|
228
|
+
frontmatter[:date],
|
229
|
+
filename_date,
|
230
|
+
missing_metadata_translation_key: "entry.no_published_at_metadata"
|
231
|
+
end
|
232
|
+
|
235
233
|
# If given a summary in the frontmatter of the entry, we can use this to
|
236
234
|
# provide a summary.
|
237
235
|
#
|
@@ -263,6 +261,26 @@ module Lifer
|
|
263
261
|
raise NotImplementedError, I18n.t("shared.not_implemented_method")
|
264
262
|
end
|
265
263
|
|
264
|
+
# The entry's last updated date. In the frontmatter, the last updated date
|
265
|
+
# can be specified using one of two fields. In priority order:
|
266
|
+
#
|
267
|
+
# 1. the `updated_at` field
|
268
|
+
# 2. the `updated` field
|
269
|
+
#
|
270
|
+
# The developer could set a fallback value as a fallback. For example, when
|
271
|
+
# building RSS feeds one might want the value of `#published_at` if there is
|
272
|
+
# no last updated date.
|
273
|
+
#
|
274
|
+
# @param fallback [Time, String, NilClass] Provide datetime data, a string
|
275
|
+
# that parses to a datetime object, or nil.
|
276
|
+
# @return [Time] A Ruby representation of the date and time provided by the
|
277
|
+
# entry frontmatter.
|
278
|
+
def updated_at(fallback: nil)
|
279
|
+
date_for frontmatter[:updated_at],
|
280
|
+
frontmatter[:updated],
|
281
|
+
default_date: fallback
|
282
|
+
end
|
283
|
+
|
266
284
|
private
|
267
285
|
|
268
286
|
# It is conventional for users to use spaces or commas to delimit tags in
|
@@ -278,12 +296,33 @@ module Lifer
|
|
278
296
|
end.uniq
|
279
297
|
end
|
280
298
|
|
299
|
+
def date_for(
|
300
|
+
*candidate_date_fields,
|
301
|
+
default_date: Lifer::Entry::DEFAULT_DATE,
|
302
|
+
missing_metadata_translation_key: nil
|
303
|
+
)
|
304
|
+
date_data = candidate_date_fields.detect(&:itself)
|
305
|
+
|
306
|
+
case date_data
|
307
|
+
when Time then date_data
|
308
|
+
when String then DateTime.parse(date_data).to_time
|
309
|
+
else
|
310
|
+
if (translation_string = missing_metadata_translation_key)
|
311
|
+
Lifer::Message.log(translation_string, filename: file)
|
312
|
+
end
|
313
|
+
default_date
|
314
|
+
end
|
315
|
+
rescue ArgumentError => error
|
316
|
+
Lifer::Message.error("entry.date_error", filename: file, error:)
|
317
|
+
default_date
|
318
|
+
end
|
319
|
+
|
281
320
|
def filename_date
|
282
321
|
return unless file && File.basename(file).match?(FILENAME_DATE_FORMAT)
|
283
322
|
|
284
323
|
File.basename(file).match(FILENAME_DATE_FORMAT)[1]
|
285
324
|
end
|
286
325
|
|
287
|
-
def frontmatter? = (full_text && full_text.match?(FRONTMATTER_REGEX))
|
326
|
+
def frontmatter? = (full_text && full_text.match?(Lifer::FRONTMATTER_REGEX))
|
288
327
|
end
|
289
328
|
end
|
data/lib/lifer/utilities.rb
CHANGED
@@ -76,7 +76,10 @@ module Lifer::Utilities
|
|
76
76
|
# @raise [Exception] Any exception thrown by a child process.
|
77
77
|
# @return [Array] The mapped results of the operation.
|
78
78
|
def parallelized(collection, &block)
|
79
|
-
|
79
|
+
options = {}
|
80
|
+
options[:in_threads] = 0 if Lifer.parallelism_disabled?
|
81
|
+
|
82
|
+
results = Parallel.map(collection, **options) do |collection_item|
|
80
83
|
begin
|
81
84
|
yield collection_item
|
82
85
|
rescue => error
|
data/lib/lifer/version.rb
CHANGED
data/lib/lifer.rb
CHANGED
@@ -24,6 +24,10 @@ module Lifer
|
|
24
24
|
"(\\/\\.)+" # Contains a dot directory.
|
25
25
|
] | IGNORE_DIRECTORIES.map { |d| "^(#{d})" }
|
26
26
|
|
27
|
+
# We expect frontmatter in any file to be provided in the following format.
|
28
|
+
#
|
29
|
+
FRONTMATTER_REGEX = /^---\n(.*)---\n/m
|
30
|
+
|
27
31
|
class << self
|
28
32
|
# The first time `Lifer.brain` is referenced, we build a new `Lifer::Brain`
|
29
33
|
# object that is used and reused until the current process has ended.
|
@@ -96,6 +100,16 @@ module Lifer
|
|
96
100
|
# project would be built to.
|
97
101
|
def output_directory = brain.output_directory
|
98
102
|
|
103
|
+
# Returns false if the Lifer project will be built with parallelism. This
|
104
|
+
# should return false almost always--unless you've explicitly set the
|
105
|
+
# `LIFER_UNPARALLELIZED` environment variable before running the program.
|
106
|
+
#
|
107
|
+
# This method is used internally by Lifer to determine whether features that
|
108
|
+
# would normally run in parallel should *not* run in parallel for some reason.
|
109
|
+
#
|
110
|
+
# @return [boolean] Returns whether parallelism is disabled.
|
111
|
+
def parallelism_disabled? = ENV["LIFER_UNPARALLELIZED"].is_a?(String)
|
112
|
+
|
99
113
|
# Register new settings so that they are "safe" and can be read from a Lifer
|
100
114
|
# configuration file. Unregistered settings are ignored.
|
101
115
|
#
|
data/locales/en.yml
CHANGED
@@ -33,7 +33,7 @@ en:
|
|
33
33
|
date_error: "[%{filename}]: %{error}"
|
34
34
|
feedable_error: >
|
35
35
|
please set `%{entry_class}.include_in_feeds` to true or false
|
36
|
-
|
36
|
+
no_published_at_metadata: "[%{filename}]: no `published_at` metadata"
|
37
37
|
not_found: >
|
38
38
|
file "%{file}" does not exist
|
39
39
|
config:
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: lifer
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.10.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- benjamin wil
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2025-
|
11
|
+
date: 2025-05-10 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: i18n
|
@@ -159,6 +159,7 @@ files:
|
|
159
159
|
- lib/lifer/brain.rb
|
160
160
|
- lib/lifer/builder.rb
|
161
161
|
- lib/lifer/builder/html.rb
|
162
|
+
- lib/lifer/builder/html/from_any.rb
|
162
163
|
- lib/lifer/builder/html/from_erb.rb
|
163
164
|
- lib/lifer/builder/html/from_liquid.rb
|
164
165
|
- lib/lifer/builder/html/from_liquid/drops.rb
|
@@ -170,7 +171,6 @@ files:
|
|
170
171
|
- lib/lifer/builder/html/from_liquid/drops/tag_drop.rb
|
171
172
|
- lib/lifer/builder/html/from_liquid/drops/tags_drop.rb
|
172
173
|
- lib/lifer/builder/html/from_liquid/filters.rb
|
173
|
-
- lib/lifer/builder/html/from_liquid/layout_tag.rb
|
174
174
|
- lib/lifer/builder/html/from_liquid/liquid_env.rb
|
175
175
|
- lib/lifer/builder/rss.rb
|
176
176
|
- lib/lifer/builder/txt.rb
|
@@ -210,8 +210,8 @@ licenses:
|
|
210
210
|
- MIT
|
211
211
|
metadata:
|
212
212
|
allowed_push_host: https://rubygems.org
|
213
|
-
homepage_uri: https://github.com/benjaminwil/lifer/blob/v0.
|
214
|
-
source_code_uri: https://github.com/benjaminwil/lifer/tree/v0.
|
213
|
+
homepage_uri: https://github.com/benjaminwil/lifer/blob/v0.10.0/README.md
|
214
|
+
source_code_uri: https://github.com/benjaminwil/lifer/tree/v0.10.0
|
215
215
|
changelog_uri: https://github.com/benjaminwil/lifer/blob/main/CHANGELOG.md
|
216
216
|
post_install_message:
|
217
217
|
rdoc_options: []
|
@@ -1,66 +0,0 @@
|
|
1
|
-
class Lifer::Builder::HTML::FromLiquid
|
2
|
-
# Note that if you want to learn more about the shape of this class, check out
|
3
|
-
# `Liquid::Block` in the `liquid` gem.
|
4
|
-
#
|
5
|
-
# The layout tag is a bit magic. The idea here is to emulate how Jekyll
|
6
|
-
# handles `layout:` YAML frontmatter within entries to change the normal
|
7
|
-
# parent layout to an override parent layout--but without the need for
|
8
|
-
# frontmatter.
|
9
|
-
#
|
10
|
-
# The reason we took this strategy was to avoid pre-processing every entry for
|
11
|
-
# frontmatter when we didn't need to. Maybe in the long run this was a bad
|
12
|
-
# call? I don't know.
|
13
|
-
#
|
14
|
-
# @example Usage from a Liquid template.
|
15
|
-
# {% layout "path/to/my_liquid_layout_template" %}
|
16
|
-
#
|
17
|
-
# (The required `endlayout` tag will be appended to the end of the file
|
18
|
-
# on render if you do not insert it yourself.
|
19
|
-
#
|
20
|
-
class LayoutTag < Liquid::Block
|
21
|
-
# The name of the tag in Liquid templates, `layout`.
|
22
|
-
#
|
23
|
-
NAME = :layout
|
24
|
-
|
25
|
-
# The end name of the tag in Liquid templates, `endlayout`.
|
26
|
-
#
|
27
|
-
ENDNAME = ("end%s" % NAME).to_sym
|
28
|
-
|
29
|
-
def initialize(layout, path, options)
|
30
|
-
@path = path.delete("\"").strip
|
31
|
-
super
|
32
|
-
end
|
33
|
-
|
34
|
-
# A layout tag wraps an entire document and outputs it inside of whatever
|
35
|
-
# the `@layout` is. This lets a child document specify a parernt layout!
|
36
|
-
# Very confusing stuff.
|
37
|
-
#
|
38
|
-
# @param context [Liquid::Context] All of the context of the Liquid
|
39
|
-
# document that would be rendered.
|
40
|
-
# @return [String] A rendered document.
|
41
|
-
def render(context)
|
42
|
-
document_context = context.environments.first
|
43
|
-
parse_options = document_context["parse_options"]
|
44
|
-
liquid_file_system = parse_options[:environment].file_system
|
45
|
-
render_options = document_context["render_options"]
|
46
|
-
|
47
|
-
current_layout_file = File
|
48
|
-
.read(document_context["entry"]["collection"]["layout_file"])
|
49
|
-
.gsub(/\{%\s*#{tag_name}.+%\}/, "")
|
50
|
-
|
51
|
-
content_with_layout = Liquid::Template
|
52
|
-
.parse(current_layout_file, **parse_options)
|
53
|
-
.render(document_context, **render_options)
|
54
|
-
|
55
|
-
Liquid::Template
|
56
|
-
.parse(
|
57
|
-
liquid_file_system.read_template_file(@path),
|
58
|
-
**parse_options
|
59
|
-
)
|
60
|
-
.render(
|
61
|
-
document_context.merge({"content" => content_with_layout}),
|
62
|
-
**render_options
|
63
|
-
)
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|