lifer 0.12.4 → 0.14.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 +29 -0
- data/Gemfile.lock +1 -1
- data/guix.scm +19 -0
- data/lib/lifer/asset.rb +82 -0
- data/lib/lifer/author.rb +106 -0
- data/lib/lifer/brain.rb +23 -7
- data/lib/lifer/builder/html/from_any.rb +1 -1
- data/lib/lifer/builder/html/from_erb.rb +18 -12
- data/lib/lifer/builder/html/from_liquid/drops/authors_drop.rb +33 -0
- data/lib/lifer/builder/html/from_liquid/drops/entry_drop.rb +3 -1
- data/lib/lifer/builder/html/from_liquid/drops.rb +1 -0
- data/lib/lifer/builder/html.rb +1 -1
- data/lib/lifer/builder/json_feed.rb +214 -0
- data/lib/lifer/builder/rss.rb +5 -4
- data/lib/lifer/builder/txt.rb +1 -1
- data/lib/lifer/builder.rb +1 -0
- data/lib/lifer/collection.rb +5 -2
- data/lib/lifer/config.rb +0 -2
- data/lib/lifer/entry/markdown.rb +0 -2
- data/lib/lifer/entry.rb +53 -3
- data/lib/lifer/selection.rb +4 -2
- data/lib/lifer/tag.rb +13 -2
- data/lib/lifer/templates/config.yaml +16 -2
- data/lib/lifer/uri_strategy.rb +1 -1
- data/lib/lifer/utilities.rb +19 -0
- data/lib/lifer/version.rb +1 -1
- data/lib/lifer.rb +36 -12
- data/locales/en.yml +1 -0
- metadata +13 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b4416a9edb890d8d98adc06d1baf7e58b695d85da827b48005d7285b6f5a1dbe
|
|
4
|
+
data.tar.gz: 0c45a40304601d473c946315ddea56860041339e029467f8bcecb603ca70a58a
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b8aa3c53f350191b59ff5105ac1130c62c4a2f690aca83794877b933e7d7bfa23e501d2c91bd90dfc4954a4db9edf57b5db8ea4c4bf75fe78aa3e3e3e7e20e0c
|
|
7
|
+
data.tar.gz: f9b729936ec8ef025d9bc6dc8b6deab6d4d63c1d281da50e99df5932cd1f245c581c911378a703f1ddb7e6a5167f21fa746a780ac9e9ad72f29721d93bf34215
|
data/CHANGELOG.md
CHANGED
|
@@ -1,4 +1,33 @@
|
|
|
1
1
|
## Next
|
|
2
|
+
## v0.14.0
|
|
3
|
+
|
|
4
|
+
This release includes some really nice improvements and bug fixes:
|
|
5
|
+
|
|
6
|
+
- When returning a list of entries belonging to a `Lifer::Collection` or a
|
|
7
|
+
`Lifer::Tag`, the entries are now returned in reverse chronological order
|
|
8
|
+
by default. (But you can pass the argument `order: :oldest` to get them
|
|
9
|
+
in reverse, if you want.)
|
|
10
|
+
- We fixed a bug where entries in `.html.erb` format that attempted to render
|
|
11
|
+
view partials would error out due to the `render` method not having been
|
|
12
|
+
defined with the context of the entry being built yet. This bug was a bit
|
|
13
|
+
unpleasant to reason about, but it resolves a major defect with ERB building.
|
|
14
|
+
- The included Lifer configuration file template now enables the
|
|
15
|
+
JSON Feed builder by default.
|
|
16
|
+
- Every `Lifer::Selection` name is now automatically registered as a setting so
|
|
17
|
+
that you can configure it like you configure any other collection of entries.
|
|
18
|
+
- Lastly, this release removes `Lifer.manifest`. (A collection of absolute
|
|
19
|
+
paths to every entry file ended up not being incredibly useful. We get
|
|
20
|
+
the same-ish functionality from `Lifer.entry_manifest`.)
|
|
21
|
+
|
|
22
|
+
## v0.13.0
|
|
23
|
+
|
|
24
|
+
This release allows projects to build JSON Feed 1.1. Now, a project can
|
|
25
|
+
build both Atom (or RSS 2.0) and JSON Feed, which is nice. In order to
|
|
26
|
+
support JSON Feed, we modelled out `Lifer::Asset` and `Lifer::Author`. This
|
|
27
|
+
is because JSON Feed explicitly supports author objects (with name, URL,
|
|
28
|
+
and avatars URLs), and images and banner images per feed item.
|
|
29
|
+
|
|
30
|
+
This release also improves the documentation for the `Lifer::Builder::RSS`.
|
|
2
31
|
|
|
3
32
|
## v0.12.4
|
|
4
33
|
|
data/Gemfile.lock
CHANGED
data/guix.scm
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
(use-modules (guix)
|
|
2
|
+
(guix build-system gnu)
|
|
3
|
+
((guix licenses) #:prefix license:)
|
|
4
|
+
(gnu packages ruby)
|
|
5
|
+
(gnu packages serialization))
|
|
6
|
+
|
|
7
|
+
(package
|
|
8
|
+
(name "ruby-lifer-dev")
|
|
9
|
+
(version "0.12.4-git")
|
|
10
|
+
(source #f)
|
|
11
|
+
(build-system gnu-build-system)
|
|
12
|
+
(inputs
|
|
13
|
+
(append (list ruby libyaml)))
|
|
14
|
+
(synopsis "Another Ruby-based static website generator")
|
|
15
|
+
(description
|
|
16
|
+
"A ruby-based static website generator focused on extensibility and having
|
|
17
|
+
few dependencies.")
|
|
18
|
+
(home-page "https://github.com/benjaminwil/lifer")
|
|
19
|
+
(license license:expat))
|
data/lib/lifer/asset.rb
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
class Lifer::Asset
|
|
2
|
+
# An asset is a file, often a multimedia file, that belongs to one or
|
|
3
|
+
# many entries. Right now, assets don't have much functionality of their
|
|
4
|
+
# own, but it's valuable to know what entries an asset has a relationship
|
|
5
|
+
# with. In the future, it may be valuable for us to build out functionality
|
|
6
|
+
# that allows users to have one or more asset hosts. This allows files that
|
|
7
|
+
# aren't checked into version control still be treated as entry dependencies.
|
|
8
|
+
#
|
|
9
|
+
class << self
|
|
10
|
+
# Builds or updates a Lifer asset. On update, this list of an asset's
|
|
11
|
+
# entries would get freshened.
|
|
12
|
+
#
|
|
13
|
+
# @param url [String] An absolute URL or path relative to the host root.
|
|
14
|
+
# @param entries [Array<Lifer::Entry>] An array of entries that the asset
|
|
15
|
+
# belongs to.
|
|
16
|
+
# @return [Lifer::Asset] The new or updated asset.
|
|
17
|
+
def build_or_update(url: nil, entries: [])
|
|
18
|
+
update(url:, entries:) || build(url:, entries:)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The default host for all assets.
|
|
22
|
+
#
|
|
23
|
+
# @return [String] A URL to a host. (Default: the configured global host.)
|
|
24
|
+
def default_host = Lifer.setting(:global, :host)
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def build(url:, entries:)
|
|
29
|
+
if (new_asset = new(url:, entries:))
|
|
30
|
+
Lifer.asset_manifest << new_asset
|
|
31
|
+
end
|
|
32
|
+
new_asset || false
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def update(url:, entries:)
|
|
36
|
+
normalized_url = Lifer::Utilities.uri_from url,
|
|
37
|
+
host: default_host,
|
|
38
|
+
object_type: self
|
|
39
|
+
|
|
40
|
+
if (asset = Lifer.asset_manifest.detect { _1.url == normalized_url })
|
|
41
|
+
asset.instance_variable_set :@entries,
|
|
42
|
+
(asset.instance_variable_get(:@entries) | entries)
|
|
43
|
+
end
|
|
44
|
+
asset || false
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
attr_reader :url, :entries
|
|
50
|
+
|
|
51
|
+
def initialize(url:, entries:)
|
|
52
|
+
normalized_url = Lifer::Utilities.uri_from url,
|
|
53
|
+
host: self.class.default_host,
|
|
54
|
+
object_type: self
|
|
55
|
+
|
|
56
|
+
@url = normalized_url
|
|
57
|
+
@entries = entries
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Checks whether a given URL matches the current asset's URL.
|
|
61
|
+
#
|
|
62
|
+
# @param url [String] A URL.
|
|
63
|
+
# @param host [String] The host URL. (Default: The configured global host
|
|
64
|
+
# URL.)
|
|
65
|
+
# @return [boolean] Whether the given URL matches the object's URL.
|
|
66
|
+
def match?(url:, host: self.class.default_host)
|
|
67
|
+
@url == Lifer::Utilities.uri_from(url, host:, object_type: self.class)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Gets the current URL. If given a host, the asset's true host will be
|
|
71
|
+
# replaced with the given host.
|
|
72
|
+
#
|
|
73
|
+
# @param host [String] A host URL. (Default: The configured global host URL.)
|
|
74
|
+
# @return [String] The URL to the current asset.
|
|
75
|
+
def url(host: self.class.default_host)
|
|
76
|
+
return @url if host == self.class.default_host
|
|
77
|
+
|
|
78
|
+
path = URI(@url).path
|
|
79
|
+
|
|
80
|
+
Lifer::Utilities.uri_from(path, host:, object_type: self.class)
|
|
81
|
+
end
|
|
82
|
+
end
|
data/lib/lifer/author.rb
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
module Lifer
|
|
2
|
+
# An author is a representation of a unique author of entries in the current
|
|
3
|
+
# project. This allows us to refer to an author's name in an entry's
|
|
4
|
+
# frontmatter, i.e.:
|
|
5
|
+
#
|
|
6
|
+
# ---
|
|
7
|
+
# title: My blog post
|
|
8
|
+
# author: Nat McCartney
|
|
9
|
+
# ---
|
|
10
|
+
#
|
|
11
|
+
# And let that reference load in a bunch of other metadata about the
|
|
12
|
+
# author. While it's possible to add all of this metadata at the entry level,
|
|
13
|
+
# the first entry loaded will be the source of truth for every reference
|
|
14
|
+
# back to the author. So it's preferrable to set the author metadata in your
|
|
15
|
+
# global configuration:
|
|
16
|
+
#
|
|
17
|
+
# # my-lifer.conf
|
|
18
|
+
# authors:
|
|
19
|
+
# - name: Nat McCartney
|
|
20
|
+
# url: https://example.com/nat
|
|
21
|
+
# avatar: https://example.com/nat.png
|
|
22
|
+
#
|
|
23
|
+
# Within a Lifer project, this allows you to do powerful things like get
|
|
24
|
+
# a list of entries by a unique author (or set of authors). It also allows
|
|
25
|
+
# you to provide author-specific URLs and avatar images in your website's
|
|
26
|
+
# JSON Feeds.
|
|
27
|
+
#
|
|
28
|
+
# The author's name is used as the primary identifier. We have tried to be
|
|
29
|
+
# smart about this so that, for example, "Nat McCartney", "nat mccartney",
|
|
30
|
+
# and "nat-mccartney" will all load up the same author object.
|
|
31
|
+
#
|
|
32
|
+
class Author
|
|
33
|
+
class << self
|
|
34
|
+
# Builds or updates a Lifer author. On update, the list of an author's
|
|
35
|
+
# entries would get freshened.
|
|
36
|
+
#
|
|
37
|
+
# @param name [String] The name of the author.
|
|
38
|
+
# @param url [String] A relative or absolute URL to learn more about
|
|
39
|
+
# the author at.
|
|
40
|
+
# @param avatar [String] A relative or absolute URL to an image that
|
|
41
|
+
# represents the author.
|
|
42
|
+
# @return [Lifer::Author] The new or updated author.
|
|
43
|
+
def build_or_update(name:, url: nil, avatar: nil, entries: [])
|
|
44
|
+
update(name:, url:, avatar:, entries:) ||
|
|
45
|
+
build(name:, url:, avatar:, entries:)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
def build(name:, url:, avatar:, entries:)
|
|
51
|
+
if (new_author = new(name:, url:, avatar:, entries:))
|
|
52
|
+
Lifer.author_manifest << new_author
|
|
53
|
+
end
|
|
54
|
+
new_author || false
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def update(name:, url:, avatar:, entries:)
|
|
58
|
+
author_id = Lifer::Utilities.handleize(name)
|
|
59
|
+
|
|
60
|
+
if (author = Lifer.authors.detect { _1.id == author_id })
|
|
61
|
+
author.instance_variable_set :@entries,
|
|
62
|
+
(author.instance_variable_get(:@entries) | entries)
|
|
63
|
+
end
|
|
64
|
+
author || false
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
attr_reader :name, :entries
|
|
69
|
+
|
|
70
|
+
def initialize(name:, url:, avatar:, entries:)
|
|
71
|
+
@name = name
|
|
72
|
+
@url = url
|
|
73
|
+
@avatar = avatar
|
|
74
|
+
@entries = entries
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
# An avatar image URL that represents the author. The URL can either be
|
|
78
|
+
# relative from the website's root or an absolute URL. If the relative or
|
|
79
|
+
# absolute URL is ambiguous, it is sanitized and this method returns nil.
|
|
80
|
+
#
|
|
81
|
+
# @param host [String] The host to prefix to relative URLs. By default,
|
|
82
|
+
# this is the Lifer project's global host.
|
|
83
|
+
# @return [String] The absolute URL to the avatar image.
|
|
84
|
+
def avatar(host: Lifer.setting(:global, :host))
|
|
85
|
+
Lifer::Utilities.uri_from(@avatar, host:, object_type: self.class)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# An identifier built from the author's name. This uses our generic
|
|
89
|
+
# handle-izer function. So a name like "Nat McCartney" becomes
|
|
90
|
+
# "nat-mccartney".
|
|
91
|
+
#
|
|
92
|
+
# @return [String] The identifier for the author.
|
|
93
|
+
def id = (@id ||= Lifer::Utilities.handleize(name))
|
|
94
|
+
|
|
95
|
+
# A URL that provides more info about the author. The URL can either be
|
|
96
|
+
# relative from the website's root or an absolute URL. If the relative or
|
|
97
|
+
# absolute URL is ambiguous, it is sanitized and this method returns nil.
|
|
98
|
+
#
|
|
99
|
+
# @param host [String] The host to prefix to relative URLs. By default,
|
|
100
|
+
# this is the Lifer project's global host.
|
|
101
|
+
# @return [String] The absolute version of the URL.
|
|
102
|
+
def url(host: Lifer.setting(:global, :host))
|
|
103
|
+
Lifer::Utilities.uri_from(@url, host:, object_type: self.class)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
data/lib/lifer/brain.rb
CHANGED
|
@@ -25,6 +25,27 @@ class Lifer::Brain
|
|
|
25
25
|
def init(root: Dir.pwd, config_file: nil) = new(root:, config_file:)
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
+
# The asset manifest tracks the unique assets added to the project as they
|
|
29
|
+
# are added. The writer methods for this instanc evariable is used internally
|
|
30
|
+
# by Lifer when adding new assets.
|
|
31
|
+
#
|
|
32
|
+
# @return [Set<Lifer::Asset>]
|
|
33
|
+
def asset_manifest = (@asset_manifest ||= Set.new)
|
|
34
|
+
|
|
35
|
+
# Given the author manifest, this returns an array of all authors for the
|
|
36
|
+
# current project. This method is preferrable for accessing and querying for
|
|
37
|
+
# authors.
|
|
38
|
+
#
|
|
39
|
+
# @return [Array<Lifer::Author>]
|
|
40
|
+
def authors = author_manifest.to_a
|
|
41
|
+
|
|
42
|
+
# The author manifest tracks the unique authors added to the project as
|
|
43
|
+
# they are added. The writer method for this instance variable is used
|
|
44
|
+
# internally by Lifer when adding new authors.
|
|
45
|
+
#
|
|
46
|
+
# @return [Set<Lifer::Author>]
|
|
47
|
+
def author_manifest = (@author_manifest ||= Set.new)
|
|
48
|
+
|
|
28
49
|
# Destroy any existing build output and then build the Lifer project with all
|
|
29
50
|
# configured `Lifer::Builder`s.
|
|
30
51
|
#
|
|
@@ -73,17 +94,12 @@ class Lifer::Brain
|
|
|
73
94
|
# @return [Lifer::Config] The Lifer configuration object.
|
|
74
95
|
def config = (@config ||= Lifer::Config.build file: config_file_location)
|
|
75
96
|
|
|
76
|
-
#
|
|
77
|
-
#
|
|
97
|
+
# Allows for getting the entry manifest or shovelling new entries to the
|
|
98
|
+
# entry manifest.
|
|
78
99
|
#
|
|
79
100
|
# @return [Set<Lifer::Entry>] All entries that currently exist.
|
|
80
101
|
def entry_manifest = (@entry_manifest ||= Set.new)
|
|
81
102
|
|
|
82
|
-
# A manifest of all Lifer project entries.
|
|
83
|
-
#
|
|
84
|
-
# @return [Set<Lifer::Entry>] A set of all entries.
|
|
85
|
-
def manifest = (@manifest ||= Set.new)
|
|
86
|
-
|
|
87
103
|
# Returns the build directory for the Lifer project's build output.
|
|
88
104
|
#
|
|
89
105
|
# @return [String] The Lifer build directory.
|
|
@@ -11,7 +11,7 @@ class Lifer::Builder::HTML
|
|
|
11
11
|
# </head>
|
|
12
12
|
#
|
|
13
13
|
# <body>
|
|
14
|
-
# <%=
|
|
14
|
+
# <%= render "_layouts/header.html.erb" %>
|
|
15
15
|
#
|
|
16
16
|
# <h1><%= my_collection.name %></h1>
|
|
17
17
|
#
|
|
@@ -23,7 +23,7 @@ class Lifer::Builder::HTML
|
|
|
23
23
|
# </section>
|
|
24
24
|
# <% end %>
|
|
25
25
|
#
|
|
26
|
-
# <%=
|
|
26
|
+
# <%= render "_layouts/footer.html.erb" %>
|
|
27
27
|
# </body>
|
|
28
28
|
# </html>
|
|
29
29
|
#
|
|
@@ -101,16 +101,20 @@ class Lifer::Builder::HTML
|
|
|
101
101
|
collections = collection_context_class.new Lifer.collections
|
|
102
102
|
tags = tag_context_class.new Lifer.tags
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
ERB.new(entry.to_html).result(binding)
|
|
104
|
+
current_binding = binding
|
|
105
|
+
current_binding.local_variable_set :collections, collections
|
|
106
|
+
current_binding.local_variable_set :settings, Lifer.settings
|
|
107
|
+
current_binding.local_variable_set :tags, tags
|
|
109
108
|
|
|
110
109
|
define_singleton_method :render,
|
|
111
110
|
-> (relative_path_to_template, locals = {}) {
|
|
112
|
-
partial_render_method relative_path_to_template,
|
|
111
|
+
partial_render_method relative_path_to_template,
|
|
112
|
+
locals,
|
|
113
|
+
current_binding
|
|
113
114
|
}
|
|
115
|
+
|
|
116
|
+
current_binding.local_variable_set :content,
|
|
117
|
+
ERB.new(entry.to_html).result(binding)
|
|
114
118
|
}
|
|
115
119
|
end
|
|
116
120
|
|
|
@@ -130,21 +134,23 @@ class Lifer::Builder::HTML
|
|
|
130
134
|
# available from entry and layout templates.
|
|
131
135
|
#
|
|
132
136
|
# @example Usage
|
|
133
|
-
# <%=
|
|
137
|
+
# <%= render "_layouts/my_partial.html.erb", id: "123" %>
|
|
134
138
|
# @param relative_path_to_template [String] The path, from the Lifer root,
|
|
135
139
|
# to the partial layout file.
|
|
136
140
|
# @param locals [Hash] Additional data that should be passed along for
|
|
137
141
|
# rendering the partial.
|
|
142
|
+
# @param source_binding [Binding] A binding object carrying context
|
|
143
|
+
# required to render the current partial layout.
|
|
138
144
|
# @return [String] The rendered partial document.
|
|
139
|
-
def partial_render_method(relative_path_to_template, locals)
|
|
145
|
+
def partial_render_method(relative_path_to_template, locals, source_binding)
|
|
140
146
|
template_path = File.join(Lifer.root, relative_path_to_template)
|
|
141
147
|
|
|
142
148
|
partial_binding = binding.tap { |binding|
|
|
143
|
-
|
|
149
|
+
source_binding.local_variables.each do |variable|
|
|
144
150
|
next if variable == :content
|
|
145
151
|
|
|
146
152
|
binding.local_variable_set variable,
|
|
147
|
-
|
|
153
|
+
source_binding.local_variable_get(variable)
|
|
148
154
|
end
|
|
149
155
|
|
|
150
156
|
locals.each do |key, value|
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
module Lifer::Builder::HTML::FromLiquid::Drops
|
|
2
|
+
class AuthorDrop < Liquid::Drop
|
|
3
|
+
attr_accessor :lifer_author
|
|
4
|
+
|
|
5
|
+
def initialize(lifer_author) = (@lifer_author = lifer_author)
|
|
6
|
+
|
|
7
|
+
def avatar = (@avatar ||= lifer_author.avatar)
|
|
8
|
+
|
|
9
|
+
def name = (@name ||= lifer_author.name)
|
|
10
|
+
|
|
11
|
+
def url = (@url ||= lifer_author.url)
|
|
12
|
+
|
|
13
|
+
def entries
|
|
14
|
+
@entries ||= lifer_author.entries.map {
|
|
15
|
+
EntryDrop.new _1, collection: _1.collection, tags: _1.tags
|
|
16
|
+
}
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
class AuthorsDrop < Liquid::Drop
|
|
21
|
+
attr_accessor :authors
|
|
22
|
+
|
|
23
|
+
def initialize(lifer_authors)
|
|
24
|
+
@authors = lifer_authors.map { AuthorDrop.new _1 }
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def each(&block)
|
|
28
|
+
authors.each(&block)
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def to_a = @authors
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -25,7 +25,9 @@ module Lifer::Builder::HTML::FromLiquid::Drops
|
|
|
25
25
|
# The entry authors (or author).
|
|
26
26
|
#
|
|
27
27
|
# @return [String]
|
|
28
|
-
def authors
|
|
28
|
+
def authors
|
|
29
|
+
@authors ||= AuthorsDrop.new(lifer_entry.authors)
|
|
30
|
+
end
|
|
29
31
|
|
|
30
32
|
# The entry content.
|
|
31
33
|
#
|
data/lib/lifer/builder/html.rb
CHANGED
|
@@ -116,7 +116,7 @@ class Lifer::Builder::HTML < Lifer::Builder
|
|
|
116
116
|
# entry cannot be output to HTML. We should not care about this return
|
|
117
117
|
# value.
|
|
118
118
|
def generate_output_file_for(entry)
|
|
119
|
-
return unless entry.
|
|
119
|
+
return unless entry.output_extension == :html
|
|
120
120
|
|
|
121
121
|
relative_path = output_file entry
|
|
122
122
|
absolute_path = File.join(Lifer.output_directory, relative_path)
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
require "fileutils"
|
|
2
|
+
|
|
3
|
+
class Lifer::Builder
|
|
4
|
+
# This builds JSON feed compliant with the JSON Feed 1.1
|
|
5
|
+
# specification[1]. Note that we don't currently support *all* of the features
|
|
6
|
+
# of JSON Feed, but it shouldn't be too hard to add them.
|
|
7
|
+
#
|
|
8
|
+
# The JSON Feed builder can be configured in a number of ways:
|
|
9
|
+
#
|
|
10
|
+
# 1. Boolean
|
|
11
|
+
#
|
|
12
|
+
# Simply set `json_feed: true` or `json_feed: false` to enable or disable
|
|
13
|
+
# a feed for a collection. If `true`, a JSON feed will be build to
|
|
14
|
+
# `/name-of-collection.json` at the root of the Lifer output directory.
|
|
15
|
+
#
|
|
16
|
+
# 2. Simple
|
|
17
|
+
#
|
|
18
|
+
# Simply set `json_feed: name-of-output-file.json` to specify the name of
|
|
19
|
+
# the output JSON Feed file.
|
|
20
|
+
#
|
|
21
|
+
# 3. Fine-grained
|
|
22
|
+
#
|
|
23
|
+
# Provide an object under `json_feed:` for more fine-grained control over
|
|
24
|
+
# configuration. The following sub-settings are supported:
|
|
25
|
+
#
|
|
26
|
+
# - `authors:` A list of authors to fall back to if an entry does not
|
|
27
|
+
# have its own authors data. Each author can include the following fields:
|
|
28
|
+
# - `name:` The author's name.
|
|
29
|
+
# - `url:` A URL that represents the author.
|
|
30
|
+
# - `avatar:` The URL to an avatar that represents the author.
|
|
31
|
+
# - `content_format:` The format of the feed entries for the entire feed.
|
|
32
|
+
# Either `html` or `text`. (Default: `html`.)
|
|
33
|
+
# - `count:` - The limit of JSON Feed items that should be included in the
|
|
34
|
+
# output document. Leave unset or set to `0` to include all entries.
|
|
35
|
+
# - `expired:` Set the expired flag on the feed to broadcast whether
|
|
36
|
+
# the feed will continue to be updated.
|
|
37
|
+
# - `home_page_url:` A URL that represents the home page of the current
|
|
38
|
+
# feed.
|
|
39
|
+
# - `url:` The path to the filename of the output JSON Feed file.
|
|
40
|
+
#
|
|
41
|
+
# [1] https://www.jsonfeed.org/version/1.1/
|
|
42
|
+
#
|
|
43
|
+
class JSONFeed < Lifer::Builder
|
|
44
|
+
# As of this writing, we support the latest version of the JSON Feed
|
|
45
|
+
# specification.
|
|
46
|
+
#
|
|
47
|
+
JSON_FEED_VERSION = "1.1"
|
|
48
|
+
|
|
49
|
+
self.name = :json_feed
|
|
50
|
+
self.settings = [
|
|
51
|
+
json_feed: [
|
|
52
|
+
{authors: [:name, :url, :avatar]},
|
|
53
|
+
:content_format,
|
|
54
|
+
:count,
|
|
55
|
+
:expired,
|
|
56
|
+
:home_page_url,
|
|
57
|
+
:url
|
|
58
|
+
]
|
|
59
|
+
]
|
|
60
|
+
|
|
61
|
+
class << self
|
|
62
|
+
# Traverses and renders a JSON Feed for each JSON Feed-enabled, feedable
|
|
63
|
+
# collection in the configured output directory for the Lifer project.
|
|
64
|
+
#
|
|
65
|
+
# @param root [String] The Lifer root.
|
|
66
|
+
# @return [void]
|
|
67
|
+
def execute(root:)
|
|
68
|
+
Dir.chdir Lifer.output_directory do
|
|
69
|
+
new(root:).execute
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Traverses and renders a JSON Feed for JSON Feed-enabled feedable
|
|
75
|
+
# collections.
|
|
76
|
+
#
|
|
77
|
+
# @return [void]
|
|
78
|
+
def execute
|
|
79
|
+
collections_with_feeds.each do |collection|
|
|
80
|
+
next unless (filename = output_filename(collection))
|
|
81
|
+
|
|
82
|
+
FileUtils.mkdir_p File.dirname(filename)
|
|
83
|
+
|
|
84
|
+
File.open filename, "w" do |file|
|
|
85
|
+
file.puts(
|
|
86
|
+
json_feed_for(collection) do |current_feed|
|
|
87
|
+
max_index = max_feed_items(collection) - 1
|
|
88
|
+
|
|
89
|
+
collection.entries
|
|
90
|
+
.select { |entry| entry.feedable? }[0..max_index]
|
|
91
|
+
.each { |entry| json_feed_entry(current_feed, entry, collection) }
|
|
92
|
+
end
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
private
|
|
99
|
+
|
|
100
|
+
attr_reader :collections_with_feeds, :root
|
|
101
|
+
|
|
102
|
+
def initialize(root:)
|
|
103
|
+
@collections_with_feeds =
|
|
104
|
+
Lifer.collections.select { |collection| collection.setting :json_feed }
|
|
105
|
+
@root = root
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# Builds an entry in the JSON Feed given the current JSON Feed being
|
|
109
|
+
# built, a Lifer entry, and a Lifer collection.
|
|
110
|
+
#
|
|
111
|
+
# @param feed_object [Hash] JSON Feed currently being built.
|
|
112
|
+
# @param lifer_entry [Lifer::Entry] The Lifer entry to build an entry for.
|
|
113
|
+
# @param lifer_collection [Lifer::Collection] The Lifer collection for
|
|
114
|
+
# the entry. This provides additional context that may be required
|
|
115
|
+
# for some parts of JSON Feed entry schema.
|
|
116
|
+
# @return [void] The entry is added to the JSON Feed as a side effect
|
|
117
|
+
# by the end of this procedure.
|
|
118
|
+
def json_feed_entry(feed_object, lifer_entry, lifer_collection)
|
|
119
|
+
feed_item_object = {
|
|
120
|
+
id: lifer_entry.permalink,
|
|
121
|
+
url: lifer_entry.permalink,
|
|
122
|
+
external_url: lifer_entry.frontmatter[:external_url],
|
|
123
|
+
title: lifer_entry.title,
|
|
124
|
+
summary: lifer_entry.summary,
|
|
125
|
+
image: lifer_entry.assets.detect { |asset|
|
|
126
|
+
asset.match?(
|
|
127
|
+
url: lifer_entry.frontmatter[:image] ||
|
|
128
|
+
lifer_entry.frontmatter[:images]&.first
|
|
129
|
+
)
|
|
130
|
+
}&.url,
|
|
131
|
+
banner_image: lifer_entry.assets.detect { |asset|
|
|
132
|
+
asset.match? url: lifer_entry.frontmatter[:banner_image]
|
|
133
|
+
}&.url,
|
|
134
|
+
date_published: lifer_entry.published_at,
|
|
135
|
+
date_modified: lifer_entry.updated_at(fallback: lifer_entry.published_at),
|
|
136
|
+
tags: lifer_entry.tags,
|
|
137
|
+
language: lifer_collection.setting(:language)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
feed_content_format =
|
|
141
|
+
lifer_collection.setting(:json_feed, :content_format)&.to_sym || :html
|
|
142
|
+
case feed_content_format
|
|
143
|
+
when :html
|
|
144
|
+
feed_item_object[:content_html] = lifer_entry.to_html
|
|
145
|
+
when :text
|
|
146
|
+
# Currently, `Entry#to_html` is just the name of the method we use
|
|
147
|
+
# to get the entry content, regardless of whether the entry output is
|
|
148
|
+
# HTML or not.
|
|
149
|
+
feed_item_object[:content_text] = lifer_entry.to_html
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
if (authors = lifer_entry.authors).any?
|
|
153
|
+
feed_item_object[:authors] = authors.map { |author|
|
|
154
|
+
{
|
|
155
|
+
name: author.name,
|
|
156
|
+
avatar: author.avatar,
|
|
157
|
+
url: author.url
|
|
158
|
+
}.reject { |_key, value| value.nil? }
|
|
159
|
+
}
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
feed_object[:items] << feed_item_object
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Provides the entire feed object for serialization.
|
|
166
|
+
#
|
|
167
|
+
# Note that we don't currently support JSON Feed 1.1 *exhaustively*. For
|
|
168
|
+
# example, we don't currently support pagniation or hubs. Here's a list
|
|
169
|
+
# of root-level 1.1 fields we do not currently support:
|
|
170
|
+
#
|
|
171
|
+
# - authors
|
|
172
|
+
# - favicon
|
|
173
|
+
# - hubs
|
|
174
|
+
# - icon
|
|
175
|
+
# - next_url
|
|
176
|
+
# - user_comment
|
|
177
|
+
#
|
|
178
|
+
# @param collection [Lifer::Collection] The current Lifer collection for
|
|
179
|
+
# metadata context.
|
|
180
|
+
# @return [String] The JSON representation of the JSON Feed.
|
|
181
|
+
def json_feed_for(collection, &block)
|
|
182
|
+
feed_object = {
|
|
183
|
+
version: JSON_FEED_VERSION,
|
|
184
|
+
title: collection.setting(:title),
|
|
185
|
+
description: collection.setting(:description) || collection.setting(:site_title),
|
|
186
|
+
expired: collection.setting(:json_feed, :expired, strict: true),
|
|
187
|
+
feed_url: collection.setting(:json_feed, :url, strict: true),
|
|
188
|
+
home_page_url: collection.setting(:json_feed, :home_page_url, strict: true),
|
|
189
|
+
language: collection.setting(:language),
|
|
190
|
+
items: []
|
|
191
|
+
}
|
|
192
|
+
feed_object = feed_object.reject { |_key, value| value.nil? }
|
|
193
|
+
|
|
194
|
+
yield feed_object
|
|
195
|
+
|
|
196
|
+
feed_object.to_json
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def max_feed_items(collection) = collection.setting(:json_feed, :count) || 0
|
|
200
|
+
|
|
201
|
+
def output_filename(collection)
|
|
202
|
+
strict = !collection.root?
|
|
203
|
+
|
|
204
|
+
case collection.setting(:json_feed, strict:)
|
|
205
|
+
when FalseClass, NilClass then nil
|
|
206
|
+
when TrueClass then File.join(Dir.pwd, "#{collection.name}.json")
|
|
207
|
+
when Hash
|
|
208
|
+
File.join Dir.pwd, collection.setting(:json_feed, :url, strict:)
|
|
209
|
+
when String
|
|
210
|
+
File.join Dir.pwd, collection.setting(:json_feed, strict:)
|
|
211
|
+
end
|
|
212
|
+
end
|
|
213
|
+
end
|
|
214
|
+
end
|
data/lib/lifer/builder/rss.rb
CHANGED
|
@@ -15,7 +15,8 @@ require "rss"
|
|
|
15
15
|
# 1. Boolean
|
|
16
16
|
#
|
|
17
17
|
# Simply set `rss: true` or `rss: false` to enable or disable a feed for a
|
|
18
|
-
# collection. If `true`, an RSS feed will be built to
|
|
18
|
+
# collection. If `true`, an RSS feed will be built to
|
|
19
|
+
# `/name-of-collection.xml` at the root of the Lifer output directory.
|
|
19
20
|
#
|
|
20
21
|
# 2. Simple
|
|
21
22
|
#
|
|
@@ -78,8 +79,8 @@ class Lifer::Builder::RSS < Lifer::Builder
|
|
|
78
79
|
]
|
|
79
80
|
|
|
80
81
|
class << self
|
|
81
|
-
# Traverses and renders an RSS feed for each feedable
|
|
82
|
-
# configured output directory for the Lifer project.
|
|
82
|
+
# Traverses and renders an RSS feed for each RSS-enabled feedable
|
|
83
|
+
# collection in the configured output directory for the Lifer project.
|
|
83
84
|
#
|
|
84
85
|
# @param root [String] The Lifer root.
|
|
85
86
|
# @return [void]
|
|
@@ -98,7 +99,7 @@ class Lifer::Builder::RSS < Lifer::Builder
|
|
|
98
99
|
DEFAULT_MAKER_FORMAT_NAME
|
|
99
100
|
end
|
|
100
101
|
|
|
101
|
-
# Traverses and renders an RSS feed for feedable
|
|
102
|
+
# Traverses and renders an RSS feed for RSS-enabled feedable collections.
|
|
102
103
|
#
|
|
103
104
|
# @return [void]
|
|
104
105
|
def execute
|
data/lib/lifer/builder/txt.rb
CHANGED
|
@@ -56,7 +56,7 @@ class Lifer::Builder::TXT < Lifer::Builder
|
|
|
56
56
|
end
|
|
57
57
|
|
|
58
58
|
def generate_output_file_for(entry)
|
|
59
|
-
return unless entry.
|
|
59
|
+
return unless entry.output_extension == :txt
|
|
60
60
|
|
|
61
61
|
relative_path = output_file entry
|
|
62
62
|
absolute_path = File.join(Lifer.output_directory, relative_path)
|
data/lib/lifer/builder.rb
CHANGED
data/lib/lifer/collection.rb
CHANGED
|
@@ -32,12 +32,13 @@ class Lifer::Collection
|
|
|
32
32
|
.select { |candidate| Lifer::Entry.supported? candidate }
|
|
33
33
|
|
|
34
34
|
entries = entry_glob.select { |entry|
|
|
35
|
-
|
|
35
|
+
filenames = Lifer.entry_manifest.map(&:file)
|
|
36
|
+
|
|
37
|
+
if filenames.include? entry
|
|
36
38
|
false
|
|
37
39
|
elsif Lifer.ignoreable? entry.gsub("#{directory}/", "")
|
|
38
40
|
false
|
|
39
41
|
else
|
|
40
|
-
Lifer.manifest << entry
|
|
41
42
|
true
|
|
42
43
|
end
|
|
43
44
|
}.map { |entry| Lifer::Entry.generate file: entry, collection: }
|
|
@@ -89,6 +90,8 @@ class Lifer::Collection
|
|
|
89
90
|
#
|
|
90
91
|
# @param name [*Symbol] A list of symbols that map to a nested Lifer
|
|
91
92
|
# setting (for the current collection).
|
|
93
|
+
# @param strict [boolean] Choose whether to strictly return the collection
|
|
94
|
+
# setting or to fallback to the Lifer root and default settings.
|
|
92
95
|
# @return [String, Nil] The setting as set in the Lifer project's
|
|
93
96
|
# configuration file.
|
|
94
97
|
def setting(*name, strict: false)
|
data/lib/lifer/config.rb
CHANGED
data/lib/lifer/entry/markdown.rb
CHANGED
|
@@ -2,8 +2,6 @@ require "date"
|
|
|
2
2
|
require "kramdown"
|
|
3
3
|
require "time"
|
|
4
4
|
|
|
5
|
-
require_relative "../utilities"
|
|
6
|
-
|
|
7
5
|
# We should initialize each Markdown file in a Lifer project as a
|
|
8
6
|
# `Lifer::Entry::Markdown` object. This class contains convenience methods for
|
|
9
7
|
# parsing a Markdown file with frontmatter as a weblog post or article. Of
|
data/lib/lifer/entry.rb
CHANGED
|
@@ -29,6 +29,11 @@ module Lifer
|
|
|
29
29
|
require_relative "entry/markdown"
|
|
30
30
|
require_relative "entry/txt"
|
|
31
31
|
|
|
32
|
+
# If assets are represented in YAML frontmatter as a string, they're split on
|
|
33
|
+
# commas and/or spaces.
|
|
34
|
+
#
|
|
35
|
+
ASSET_DELIMITER_REGEX = /[,\s]+/
|
|
36
|
+
|
|
32
37
|
# We provide a default date for entries that have no date and entry types that
|
|
33
38
|
# otherwise could not have a date due to no real way of getting that metadata.
|
|
34
39
|
#
|
|
@@ -58,12 +63,15 @@ module Lifer
|
|
|
58
63
|
# @param file [String] The absolute filename of an entry file.
|
|
59
64
|
# @param collection [Lifer::Collection] The collection for the entry.
|
|
60
65
|
# @return [Lifer::Entry] An entry.
|
|
61
|
-
def generate(file:, collection:)
|
|
66
|
+
def generate(file:, collection:, dependencies: [:assets, :authors, :tags])
|
|
62
67
|
error!(file) unless File.exist?(file)
|
|
63
68
|
|
|
64
69
|
if (new_entry = subclass_for(file)&.new(file:, collection:))
|
|
65
70
|
Lifer.entry_manifest << new_entry
|
|
66
|
-
|
|
71
|
+
|
|
72
|
+
dependencies.each do |dependency|
|
|
73
|
+
new_entry.public_send dependency
|
|
74
|
+
end
|
|
67
75
|
end
|
|
68
76
|
|
|
69
77
|
new_entry
|
|
@@ -124,6 +132,14 @@ module Lifer
|
|
|
124
132
|
@collection = collection
|
|
125
133
|
end
|
|
126
134
|
|
|
135
|
+
# Locates and returns all assets defined in the entry.
|
|
136
|
+
#
|
|
137
|
+
# @return [Array<Lifer::Asset>] The entry's assets.
|
|
138
|
+
def assets
|
|
139
|
+
@assets ||= candidate_asset_names
|
|
140
|
+
.map { Lifer::Asset.build_or_update(url: _1, entries: [self]) }
|
|
141
|
+
end
|
|
142
|
+
|
|
127
143
|
# Given the entry's frontmatter, we should be able to get a list of authors.
|
|
128
144
|
# We always prefer authors (as opposed to a singular author) because it makes
|
|
129
145
|
# handling both cases easier in the long run.
|
|
@@ -133,7 +149,20 @@ module Lifer
|
|
|
133
149
|
#
|
|
134
150
|
# @return [Array<String>] An array of authors's names.
|
|
135
151
|
def authors
|
|
136
|
-
Array(frontmatter[:author] || frontmatter[:authors]).compact
|
|
152
|
+
list = Array(frontmatter[:author] || frontmatter[:authors]).compact
|
|
153
|
+
if list.any? && list.all? { _1.is_a? Array }
|
|
154
|
+
list = [list.to_h]
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
list.map {
|
|
158
|
+
attributes = Lifer::Utilities.symbolize_keys(
|
|
159
|
+
case _1
|
|
160
|
+
when Hash then _1
|
|
161
|
+
when String then {name: _1}
|
|
162
|
+
end
|
|
163
|
+
)
|
|
164
|
+
Lifer::Author.build_or_update **attributes.merge(entries: [self])
|
|
165
|
+
}
|
|
137
166
|
end
|
|
138
167
|
|
|
139
168
|
# This method returns the full text of the entry, only removing the
|
|
@@ -176,6 +205,12 @@ module Lifer
|
|
|
176
205
|
@full_text ||= File.readlines(file).join if file
|
|
177
206
|
end
|
|
178
207
|
|
|
208
|
+
# The file extension to be used when at the entry's build time. For
|
|
209
|
+
# example, a Markdown file should be built to HTML.
|
|
210
|
+
#
|
|
211
|
+
# @return [String]
|
|
212
|
+
def output_extension = self.class.output_extension
|
|
213
|
+
|
|
179
214
|
# Using the current Lifer configuration, we can calculate the expected
|
|
180
215
|
# permalink for the entry. For example:
|
|
181
216
|
#
|
|
@@ -284,6 +319,21 @@ module Lifer
|
|
|
284
319
|
|
|
285
320
|
private
|
|
286
321
|
|
|
322
|
+
# Similar to tags, users may represent assets as a string or a list of
|
|
323
|
+
# strings instead of YAML-style arrays. So let's parse for all of them.
|
|
324
|
+
#
|
|
325
|
+
# @return [Array<String>] An array of candidate asset URLs.
|
|
326
|
+
def candidate_asset_names
|
|
327
|
+
candidate_frontmatter_fields = [:banner_image, :image, :images]
|
|
328
|
+
candidate_frontmatter_fields.flat_map {
|
|
329
|
+
case frontmatter[_1]
|
|
330
|
+
when Array then frontmatter[_1].map(&:to_s)
|
|
331
|
+
when String then frontmatter[_1].split(ASSET_DELIMITER_REGEX)
|
|
332
|
+
else []
|
|
333
|
+
end.uniq
|
|
334
|
+
}.compact
|
|
335
|
+
end
|
|
336
|
+
|
|
287
337
|
# It is conventional for users to use spaces or commas to delimit tags in
|
|
288
338
|
# other systems, so let's support that. But let's also support YAML-style
|
|
289
339
|
# arrays.
|
data/lib/lifer/selection.rb
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
# detected Ruby files are dynamically loaded when `Lifer::Brain` is initialized.
|
|
7
7
|
#
|
|
8
8
|
# Implementing a selection is simple. Just implement the `#entries` method and
|
|
9
|
-
#
|
|
9
|
+
# provide a name. The `#entries` method can be used to filter down
|
|
10
10
|
# `Lifer.entry_manifest` in whichever way one needs. To see examples of this,
|
|
11
11
|
# check out the source code of any of the included selections.
|
|
12
12
|
#
|
|
@@ -25,7 +25,9 @@ class Lifer::Selection < Lifer::Collection
|
|
|
25
25
|
#
|
|
26
26
|
# @return [Lifer::Selection]
|
|
27
27
|
def generate
|
|
28
|
-
new(name: name, directory: nil)
|
|
28
|
+
selection = new(name: name, directory: nil)
|
|
29
|
+
Lifer.register_settings name
|
|
30
|
+
selection
|
|
29
31
|
end
|
|
30
32
|
|
|
31
33
|
private
|
data/lib/lifer/tag.rb
CHANGED
|
@@ -43,11 +43,22 @@ module Lifer
|
|
|
43
43
|
|
|
44
44
|
attr_accessor :name
|
|
45
45
|
|
|
46
|
-
attr_reader :entries
|
|
47
|
-
|
|
48
46
|
def initialize(name:, entries:)
|
|
49
47
|
@name = name
|
|
50
48
|
@entries = entries
|
|
51
49
|
end
|
|
50
|
+
|
|
51
|
+
# Returns the tag's associated entries in order.
|
|
52
|
+
#
|
|
53
|
+
# @param order [Symbol] Either :latest (descending) or :oldest (ascending).
|
|
54
|
+
# @return [Array<Lifer::Entry>] The entries for the current tag.
|
|
55
|
+
def entries(order: :latest)
|
|
56
|
+
case order
|
|
57
|
+
when :latest
|
|
58
|
+
@entries.sort_by { |entry| entry.published_at }.reverse
|
|
59
|
+
when :oldest
|
|
60
|
+
@entries.sort_by { |entry| entry.published_at }
|
|
61
|
+
end
|
|
62
|
+
end
|
|
52
63
|
end
|
|
53
64
|
end
|
|
@@ -8,6 +8,7 @@ author: Admin
|
|
|
8
8
|
entries:
|
|
9
9
|
default_title: Untitled Entry
|
|
10
10
|
|
|
11
|
+
json_feed: false
|
|
11
12
|
rss: false
|
|
12
13
|
uri_strategy: simple
|
|
13
14
|
|
|
@@ -33,12 +34,24 @@ uri_strategy: simple
|
|
|
33
34
|
###
|
|
34
35
|
### layout_file: path/to/my_other_layout.html.erb
|
|
35
36
|
###
|
|
36
|
-
###
|
|
37
|
-
###
|
|
37
|
+
### my_collection_with_fine_grained_rss_settings:
|
|
38
|
+
### json_feed:
|
|
39
|
+
### authors:
|
|
40
|
+
### - name: Hal Human
|
|
41
|
+
### avatar: /images/avatars/hal.png
|
|
42
|
+
### url: https://hals-website.local
|
|
43
|
+
### content_format: html
|
|
44
|
+
### count: 99
|
|
45
|
+
### expired: false
|
|
46
|
+
### home_page_url: https://example.com/my-collection
|
|
47
|
+
### url: /location-of-the-feed.json
|
|
48
|
+
###
|
|
49
|
+
### rss:
|
|
38
50
|
### count: 99
|
|
39
51
|
### format: rss
|
|
40
52
|
### managing_editor: editor@example.com (Managing Editor)
|
|
41
53
|
### url: custom.xml
|
|
54
|
+
###
|
|
42
55
|
|
|
43
56
|
# Selections
|
|
44
57
|
#
|
|
@@ -79,6 +92,7 @@ global:
|
|
|
79
92
|
build:
|
|
80
93
|
- html
|
|
81
94
|
- rss
|
|
95
|
+
- json_feed
|
|
82
96
|
- txt
|
|
83
97
|
host: https://example.com
|
|
84
98
|
output_directory: _build
|
data/lib/lifer/uri_strategy.rb
CHANGED
data/lib/lifer/utilities.rb
CHANGED
|
@@ -4,6 +4,8 @@ require "parallel"
|
|
|
4
4
|
# Ensure that these are actually useful globally, though. :-)
|
|
5
5
|
#
|
|
6
6
|
module Lifer::Utilities
|
|
7
|
+
class AmbiguousURIError < StandardError; end
|
|
8
|
+
|
|
7
9
|
class << self
|
|
8
10
|
# Output a string using bold escape sequences to the output TTY text.
|
|
9
11
|
#
|
|
@@ -129,6 +131,23 @@ module Lifer::Utilities
|
|
|
129
131
|
symbolized_hash
|
|
130
132
|
end
|
|
131
133
|
|
|
134
|
+
def uri_from(string, host:, object_type:)
|
|
135
|
+
uri = string && URI.parse(string.strip)
|
|
136
|
+
|
|
137
|
+
if uri && uri.relative? && uri.to_s.start_with?("/")
|
|
138
|
+
"%s%s" % [host, uri]
|
|
139
|
+
elsif uri && uri.relative? && !uri.to_s.start_with?("/")
|
|
140
|
+
raise AmbiguousURIError
|
|
141
|
+
elsif uri&.absolute?
|
|
142
|
+
uri.to_s
|
|
143
|
+
end
|
|
144
|
+
rescue AmbiguousURIError
|
|
145
|
+
Lifer::Message.error "utilities.ambiguous_uri_error",
|
|
146
|
+
object_type: object_type.inspect,
|
|
147
|
+
uri: uri&.to_s
|
|
148
|
+
nil
|
|
149
|
+
end
|
|
150
|
+
|
|
132
151
|
private
|
|
133
152
|
|
|
134
153
|
def camelize(string)
|
data/lib/lifer/version.rb
CHANGED
data/lib/lifer.rb
CHANGED
|
@@ -29,6 +29,22 @@ module Lifer
|
|
|
29
29
|
FRONTMATTER_REGEX = /^---\n(.*?)---\n/m
|
|
30
30
|
|
|
31
31
|
class << self
|
|
32
|
+
# All of the assets represented in Lifer entries for the current project.
|
|
33
|
+
#
|
|
34
|
+
# @return [Array<Lifer::Asset>] The complete list of assets.
|
|
35
|
+
def asset_manifest = brain.asset_manifest
|
|
36
|
+
|
|
37
|
+
# All of the authors represented in Lifer entries for the current project.
|
|
38
|
+
#
|
|
39
|
+
# @return [Array<Lifer::Author>] The complete list of authors.
|
|
40
|
+
def authors = brain.authors
|
|
41
|
+
|
|
42
|
+
# A set of all authors added to the project. Prefer using the `#authors`
|
|
43
|
+
# method for author queries.
|
|
44
|
+
#
|
|
45
|
+
# @return [Set<Lifer::Author>] The complete set of author.
|
|
46
|
+
def author_manifest = brain.author_manifest
|
|
47
|
+
|
|
32
48
|
# The first time `Lifer.brain` is referenced, we build a new `Lifer::Brain`
|
|
33
49
|
# object that is used and reused until the current process has ended.
|
|
34
50
|
#
|
|
@@ -65,9 +81,21 @@ module Lifer
|
|
|
65
81
|
# @return [Pathname] The path to the current Lifer config file.
|
|
66
82
|
def config_file = brain.config.file
|
|
67
83
|
|
|
68
|
-
#
|
|
69
|
-
#
|
|
70
|
-
# @
|
|
84
|
+
# Uses the entry manifest to return entries in the specified order.
|
|
85
|
+
#
|
|
86
|
+
# @param order [Symbol] Either :latest (descending) or :oldest (ascending).
|
|
87
|
+
# @return [Set] All entries in the specified order.
|
|
88
|
+
def entries(order: :latest)
|
|
89
|
+
case order
|
|
90
|
+
when :latest
|
|
91
|
+
entry_manifest.sort_by { |entry| entry.published_at }.reverse
|
|
92
|
+
when :oldest
|
|
93
|
+
entry_manifest.sort_by { |entry| entry.published_at }
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Allows for getting the entry manifest or shovelling new entries to the
|
|
98
|
+
# entry manifest.
|
|
71
99
|
#
|
|
72
100
|
# @return [Set] All entries.
|
|
73
101
|
def entry_manifest = brain.entry_manifest
|
|
@@ -87,13 +115,6 @@ module Lifer
|
|
|
87
115
|
directory_or_file.match?(/#{IGNORE_PATTERNS.join("|")}/)
|
|
88
116
|
end
|
|
89
117
|
|
|
90
|
-
# A set of all entries currently in the project.
|
|
91
|
-
#
|
|
92
|
-
# @fixme Do we need this as well as `Lifer.manifest`?
|
|
93
|
-
#
|
|
94
|
-
# @return [Set] All entries.
|
|
95
|
-
def manifest = brain.manifest
|
|
96
|
-
|
|
97
118
|
# The build directory for the Lifer project.
|
|
98
119
|
#
|
|
99
120
|
# @return [Pathname] The absolute path to the directory where the Lifer
|
|
@@ -173,11 +194,14 @@ require "i18n"
|
|
|
173
194
|
I18n.load_path += Dir["%s/locales/*.yml" % Lifer.gem_root]
|
|
174
195
|
I18n.available_locales = [:en]
|
|
175
196
|
|
|
176
|
-
# `Lifer::Shared`
|
|
177
|
-
#
|
|
197
|
+
# `Lifer::Shared` and `Lifer::Utilities` contain modules and methods that
|
|
198
|
+
# are used by the main models required below.
|
|
178
199
|
#
|
|
179
200
|
require_relative "lifer/shared"
|
|
201
|
+
require_relative "lifer/utilities"
|
|
180
202
|
|
|
203
|
+
require_relative "lifer/asset"
|
|
204
|
+
require_relative "lifer/author"
|
|
181
205
|
require_relative "lifer/brain"
|
|
182
206
|
require_relative "lifer/builder"
|
|
183
207
|
require_relative "lifer/collection"
|
data/locales/en.yml
CHANGED
|
@@ -49,5 +49,6 @@ en:
|
|
|
49
49
|
finder_methods:
|
|
50
50
|
unknown_class: no class with name "%{name}"
|
|
51
51
|
utilities:
|
|
52
|
+
ambiguous_uri_error: "An %{object_type} contains an ambiguous URI: \"%{uri}\". Use an absolute URI or relative from the project root instead."
|
|
52
53
|
classify_error: >
|
|
53
54
|
could not find constant for path "%{string_constant}" (%{camel_cased_string_constant})
|
metadata
CHANGED
|
@@ -1,13 +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.14.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- benjamin wil
|
|
8
|
+
autorequire:
|
|
8
9
|
bindir: bin
|
|
9
10
|
cert_chain: []
|
|
10
|
-
date:
|
|
11
|
+
date: 2026-06-17 00:00:00.000000000 Z
|
|
11
12
|
dependencies:
|
|
12
13
|
- !ruby/object:Gem::Dependency
|
|
13
14
|
name: base64
|
|
@@ -168,7 +169,10 @@ files:
|
|
|
168
169
|
- bin/console
|
|
169
170
|
- bin/lifer
|
|
170
171
|
- bin/setup
|
|
172
|
+
- guix.scm
|
|
171
173
|
- lib/lifer.rb
|
|
174
|
+
- lib/lifer/asset.rb
|
|
175
|
+
- lib/lifer/author.rb
|
|
172
176
|
- lib/lifer/brain.rb
|
|
173
177
|
- lib/lifer/builder.rb
|
|
174
178
|
- lib/lifer/builder/html.rb
|
|
@@ -176,6 +180,7 @@ files:
|
|
|
176
180
|
- lib/lifer/builder/html/from_erb.rb
|
|
177
181
|
- lib/lifer/builder/html/from_liquid.rb
|
|
178
182
|
- lib/lifer/builder/html/from_liquid/drops.rb
|
|
183
|
+
- lib/lifer/builder/html/from_liquid/drops/authors_drop.rb
|
|
179
184
|
- lib/lifer/builder/html/from_liquid/drops/collection_drop.rb
|
|
180
185
|
- lib/lifer/builder/html/from_liquid/drops/collections_drop.rb
|
|
181
186
|
- lib/lifer/builder/html/from_liquid/drops/entry_drop.rb
|
|
@@ -185,6 +190,7 @@ files:
|
|
|
185
190
|
- lib/lifer/builder/html/from_liquid/drops/tags_drop.rb
|
|
186
191
|
- lib/lifer/builder/html/from_liquid/filters.rb
|
|
187
192
|
- lib/lifer/builder/html/from_liquid/liquid_env.rb
|
|
193
|
+
- lib/lifer/builder/json_feed.rb
|
|
188
194
|
- lib/lifer/builder/rss.rb
|
|
189
195
|
- lib/lifer/builder/txt.rb
|
|
190
196
|
- lib/lifer/cli.rb
|
|
@@ -223,9 +229,10 @@ licenses:
|
|
|
223
229
|
- MIT
|
|
224
230
|
metadata:
|
|
225
231
|
allowed_push_host: https://rubygems.org
|
|
226
|
-
homepage_uri: https://github.com/benjaminwil/lifer/blob/v0.
|
|
227
|
-
source_code_uri: https://github.com/benjaminwil/lifer/tree/v0.
|
|
232
|
+
homepage_uri: https://github.com/benjaminwil/lifer/blob/v0.14.0/README.md
|
|
233
|
+
source_code_uri: https://github.com/benjaminwil/lifer/tree/v0.14.0
|
|
228
234
|
changelog_uri: https://github.com/benjaminwil/lifer/blob/main/CHANGELOG.md
|
|
235
|
+
post_install_message:
|
|
229
236
|
rdoc_options: []
|
|
230
237
|
require_paths:
|
|
231
238
|
- lib
|
|
@@ -240,7 +247,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
240
247
|
- !ruby/object:Gem::Version
|
|
241
248
|
version: '0'
|
|
242
249
|
requirements: []
|
|
243
|
-
rubygems_version: 3.
|
|
250
|
+
rubygems_version: 3.5.22
|
|
251
|
+
signing_key:
|
|
244
252
|
specification_version: 4
|
|
245
253
|
summary: Minimal static weblog generator.
|
|
246
254
|
test_files: []
|