lifer 0.2.0 → 0.3.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/.github/workflows/main.yml +1 -1
- data/.gitignore +1 -0
- data/CHANGELOG.md +26 -0
- data/Gemfile +9 -0
- data/Gemfile.lock +110 -25
- data/LICENSE +18 -0
- data/README.md +79 -14
- data/Rakefile +2 -4
- data/bin/lifer +4 -2
- data/lib/lifer/brain.rb +171 -21
- data/lib/lifer/builder/html/from_erb.rb +92 -0
- data/lib/lifer/builder/html/from_liquid/drops/collection_drop.rb +40 -0
- data/lib/lifer/builder/html/from_liquid/drops/collections_drop.rb +40 -0
- data/lib/lifer/builder/html/from_liquid/drops/entry_drop.rb +63 -0
- data/lib/lifer/builder/html/from_liquid/drops/frontmatter_drop.rb +45 -0
- data/lib/lifer/builder/html/from_liquid/drops/settings_drop.rb +42 -0
- data/lib/lifer/builder/html/from_liquid/drops.rb +15 -0
- data/lib/lifer/builder/html/from_liquid/filters.rb +27 -0
- data/lib/lifer/builder/html/from_liquid/layout_tag.rb +67 -0
- data/lib/lifer/builder/html/from_liquid.rb +116 -0
- data/lib/lifer/builder/html.rb +107 -51
- data/lib/lifer/builder/rss.rb +113 -0
- data/lib/lifer/builder/txt.rb +60 -0
- data/lib/lifer/builder.rb +100 -1
- data/lib/lifer/cli.rb +105 -0
- data/lib/lifer/collection.rb +87 -8
- data/lib/lifer/config.rb +159 -31
- data/lib/lifer/dev/response.rb +61 -0
- data/lib/lifer/dev/router.rb +44 -0
- data/lib/lifer/dev/server.rb +97 -0
- data/lib/lifer/entry/html.rb +39 -0
- data/lib/lifer/entry/markdown.rb +162 -0
- data/lib/lifer/entry/txt.rb +41 -0
- data/lib/lifer/entry.rb +142 -41
- data/lib/lifer/message.rb +58 -0
- data/lib/lifer/selection/all_markdown.rb +16 -0
- data/lib/lifer/selection/included_in_feeds.rb +15 -0
- data/lib/lifer/selection.rb +79 -0
- data/lib/lifer/shared/finder_methods.rb +35 -0
- data/lib/lifer/shared.rb +6 -0
- data/lib/lifer/templates/cli.txt.erb +10 -0
- data/lib/lifer/templates/config.yaml +77 -0
- data/lib/lifer/templates/its-a-living.png +0 -0
- data/lib/lifer/templates/layout.html.erb +1 -1
- data/lib/lifer/uri_strategy/pretty.rb +14 -6
- data/lib/lifer/uri_strategy/pretty_root.rb +24 -0
- data/lib/lifer/uri_strategy/pretty_yyyy_mm_dd.rb +32 -0
- data/lib/lifer/uri_strategy/root.rb +17 -0
- data/lib/lifer/uri_strategy/simple.rb +10 -6
- data/lib/lifer/uri_strategy.rb +46 -6
- data/lib/lifer/utilities.rb +117 -0
- data/lib/lifer/version.rb +3 -0
- data/lib/lifer.rb +130 -23
- data/lifer.gemspec +12 -6
- data/locales/en.yml +54 -0
- metadata +142 -9
- data/lib/lifer/layout.rb +0 -25
- data/lib/lifer/templates/config +0 -4
- data/lib/lifer/uri_strategy/base.rb +0 -15
data/lib/lifer/entry.rb
CHANGED
@@ -1,65 +1,166 @@
|
|
1
|
-
require "
|
2
|
-
|
3
|
-
|
1
|
+
require "digest/sha1"
|
2
|
+
|
3
|
+
# An entry is a Lifer file that will be built into the output directory.
|
4
|
+
# There are more than one subclass of entry: Markdown entries are the most
|
5
|
+
# traditional, but HTML and text files are also very valid entries.
|
6
|
+
#
|
7
|
+
# This class provides a baseline of the functionality that all entry subclasses
|
8
|
+
# should implement. It also provides the entry generator for *all* entry
|
9
|
+
# subclasses.
|
10
|
+
#
|
11
|
+
# FIXME: Markdown entries are able to provide metadata via frontmatter, but
|
12
|
+
# other entry types do not currently support frontmatter. Should they? Or is
|
13
|
+
# there some nicer way to provide entry metadata for non-Markdown files in
|
14
|
+
# 2024?
|
15
|
+
#
|
16
|
+
class Lifer::Entry
|
17
|
+
class << self
|
18
|
+
attr_accessor :include_in_feeds
|
19
|
+
attr_accessor :input_extensions
|
20
|
+
attr_accessor :output_extension
|
21
|
+
end
|
4
22
|
|
5
|
-
|
23
|
+
self.include_in_feeds = false
|
24
|
+
self.input_extensions = []
|
25
|
+
self.output_extension = nil
|
26
|
+
|
27
|
+
require_relative "entry/html"
|
28
|
+
require_relative "entry/markdown"
|
29
|
+
require_relative "entry/txt"
|
30
|
+
|
31
|
+
# We provide a default date for entries that have no date and entry types that
|
32
|
+
# otherwise could not have a date due to no real way of getting that metadata.
|
33
|
+
#
|
34
|
+
DEFAULT_DATE = Time.new(1900, 01, 01, 0, 0, 0, "+00:00")
|
35
|
+
|
36
|
+
attr_reader :file, :collection
|
37
|
+
|
38
|
+
class << self
|
39
|
+
# The entrypoint for generating entry objects. We should never end up with
|
40
|
+
# `Lifer::Entry` records: only subclasses.
|
41
|
+
#
|
42
|
+
# @param file [String] The absolute filename of an entry file.
|
43
|
+
# @param collection [Lifer::Collection] The collection for the entry.
|
44
|
+
# @return [Lifer::Entry::HTML, Lifer::Entry::Markdown]
|
45
|
+
def generate(file:, collection:)
|
46
|
+
error!(file) unless File.exist?(file)
|
47
|
+
|
48
|
+
if (new_entry = subclass_for(file)&.new(file:, collection:))
|
49
|
+
Lifer.entry_manifest << new_entry
|
50
|
+
end
|
51
|
+
new_entry
|
52
|
+
end
|
6
53
|
|
7
|
-
|
8
|
-
|
9
|
-
|
54
|
+
# Whenever an entry is generated it should be added to the entry manifest.
|
55
|
+
# This lets us get a list of all generated entries.
|
56
|
+
#
|
57
|
+
# @return [Array<Lifer::Entry>] A list of all entries that currently exist.
|
58
|
+
def manifest
|
59
|
+
return Lifer.entry_manifest if self == Lifer::Entry
|
10
60
|
|
11
|
-
|
61
|
+
Lifer.entry_manifest.select { |entry| entry.class == self }
|
62
|
+
end
|
12
63
|
|
13
|
-
|
14
|
-
|
15
|
-
|
64
|
+
# Checks whether the given filename is supported entry type (using only its
|
65
|
+
# file extension).
|
66
|
+
#
|
67
|
+
# @param filename [String] The absolute filename to an entry.
|
68
|
+
# @param file_extensions [Array<String>] An array of file extensions to
|
69
|
+
# check against.
|
70
|
+
# @return [Boolean]
|
71
|
+
def supported?(filename, file_extensions= supported_file_extensions)
|
72
|
+
file_extensions.any? { |ext| filename.end_with? ext }
|
73
|
+
end
|
16
74
|
|
17
|
-
|
18
|
-
return full_text.strip unless frontmatter?
|
75
|
+
private
|
19
76
|
|
20
|
-
|
21
|
-
|
77
|
+
def supported_file_extensions
|
78
|
+
@supported_file_extensions ||= subclasses.flat_map(&:input_extensions)
|
79
|
+
end
|
22
80
|
|
23
|
-
|
24
|
-
|
81
|
+
# @private
|
82
|
+
# Retrieve the entry subclass based on the current filename.
|
83
|
+
#
|
84
|
+
# @param filename [String] The current entry's filename.
|
85
|
+
# @return [Class] The entry subclass for the current entry.
|
86
|
+
def subclass_for(filename)
|
87
|
+
Lifer::Entry.subclasses.detect { |klass|
|
88
|
+
klass.input_extensions.any? { |ext| filename.end_with? ext }
|
89
|
+
}
|
90
|
+
end
|
25
91
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
else
|
30
|
-
puts "[%s]: no date metadata" % [file]
|
31
|
-
nil
|
92
|
+
# @private
|
93
|
+
def error!(file)
|
94
|
+
raise StandardError, I18n.t("entry.not_found", file:)
|
32
95
|
end
|
33
|
-
rescue ArgumentError => error
|
34
|
-
puts "[%s]: %s" % [file, error]
|
35
|
-
nil
|
36
96
|
end
|
37
97
|
|
38
|
-
|
39
|
-
|
98
|
+
# When a new Markdown entry is initialized we expect the file to already
|
99
|
+
# exist, and we expect to know which `Lifer::Collection` it belongs to.
|
100
|
+
#
|
101
|
+
# @param file [String] An absolute path to a file.
|
102
|
+
# @param collection [Lifer::Collection] A collection.
|
103
|
+
# @return [Lifer::Entry]
|
104
|
+
def initialize(file:, collection:)
|
105
|
+
@file = Pathname file
|
106
|
+
@collection = collection
|
107
|
+
end
|
108
|
+
|
109
|
+
def feedable?
|
110
|
+
if (setting = self.class.include_in_feeds).nil?
|
111
|
+
raise NotImplementedError,
|
112
|
+
I18n.t("entry.feedable_error", entry_class: self.class)
|
113
|
+
end
|
40
114
|
|
41
|
-
|
42
|
-
YAML.load(full_text[FRONTMATTER_REGEX, 1], permitted_classes: [Time])
|
43
|
-
)
|
115
|
+
setting
|
44
116
|
end
|
45
117
|
|
118
|
+
# The full text of the entry.
|
119
|
+
#
|
120
|
+
# @return [String]
|
46
121
|
def full_text
|
47
|
-
File.readlines(file).join if file
|
122
|
+
@full_text ||= File.readlines(file).join if file
|
48
123
|
end
|
49
124
|
|
50
|
-
|
51
|
-
|
125
|
+
# Using the current Lifer configuration, we can calculate the expected
|
126
|
+
# permalink for the entry. For example:
|
127
|
+
#
|
128
|
+
# https://example.com/index.html
|
129
|
+
# https://example.com/blog/my-trip-to-toronto.html
|
130
|
+
#
|
131
|
+
# This would be useful for indexes and feeds and so on.
|
132
|
+
#
|
133
|
+
# @return [String] A permalink to the current entry.
|
134
|
+
def permalink(host: Lifer.setting(:global, :host))
|
135
|
+
cached_permalink_variable =
|
136
|
+
"@entry_permalink_" + Digest::SHA1.hexdigest(host)
|
137
|
+
|
138
|
+
instance_variable_get(cached_permalink_variable) ||
|
139
|
+
instance_variable_set(
|
140
|
+
cached_permalink_variable,
|
141
|
+
File.join(
|
142
|
+
host,
|
143
|
+
Lifer::URIStrategy.find(collection.setting :uri_strategy)
|
144
|
+
.new(root: Lifer.root)
|
145
|
+
.output_file(self)
|
146
|
+
)
|
147
|
+
)
|
52
148
|
end
|
53
149
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
150
|
+
# The expected, absolute URI path to the entry. For example:
|
151
|
+
#
|
152
|
+
# /index.html
|
153
|
+
# /blog/my-trip-to-toronto.html
|
154
|
+
#
|
155
|
+
# @return [String] The absolute URI path to the entry.
|
156
|
+
def path = permalink(host: "/")
|
58
157
|
|
59
|
-
|
158
|
+
def title
|
159
|
+
raise NotImplementedError, I18n.t("shared.not_implemented_method")
|
60
160
|
end
|
61
161
|
|
62
|
-
def
|
63
|
-
|
162
|
+
def to_html
|
163
|
+
raise NotImplementedError, I18n.t("shared.not_implemented_method")
|
64
164
|
end
|
65
165
|
end
|
166
|
+
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# This message class lets us output rich messages to STDOUT without muddying up
|
2
|
+
# the Lifer source code with ad hoc `puts` statements. Using this interface
|
3
|
+
# helps us ensure that translations are accounted for, and it lets us format
|
4
|
+
# errors and log messages in a consistent way.
|
5
|
+
#
|
6
|
+
# If the program is in test mode, this means the message should not be output
|
7
|
+
# to STDOUT.
|
8
|
+
#
|
9
|
+
class Lifer::Message
|
10
|
+
# ANSI colour codes for colours used by different messages.
|
11
|
+
#
|
12
|
+
ANSI_COLOURS = {red: "31"}
|
13
|
+
|
14
|
+
class << self
|
15
|
+
# Outputs a red error message into STDOUT for higher visibility. Note that
|
16
|
+
# this is still just a message, and the program might not exit due to an
|
17
|
+
# exception having been raised.
|
18
|
+
#
|
19
|
+
# @param translation_key [String] A translation key that can be read by the
|
20
|
+
# `I18n` library.
|
21
|
+
# @param test_mode [boolean] Whether the message should be output as if the
|
22
|
+
# program were in test mode.
|
23
|
+
# @param args [**Hash] A catch-all keyword arguments to be passed on to
|
24
|
+
# `I18n.t!`.
|
25
|
+
# @return [void] The message is pushed to STDOUT.
|
26
|
+
def error(translation_key, test_mode: test_mode?, **args)
|
27
|
+
return if test_mode
|
28
|
+
|
29
|
+
prefix = I18n.t("message.prefix.error")
|
30
|
+
error_message = I18n.t!(translation_key, **args)
|
31
|
+
|
32
|
+
puts colorize(("%s: %s" % [prefix, error_message]), :red)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Outputs a log message into STDOUT.
|
36
|
+
#
|
37
|
+
# @param translation_key [String] A translation key that can be read by the
|
38
|
+
# `I18n` library.
|
39
|
+
# @param test_mode [boolean] Whether the message should be output as if the
|
40
|
+
# program were in test mode.
|
41
|
+
# @param args [**Hash] A catch-all keyword arguments to be passed on to
|
42
|
+
# `I18n.t!`.
|
43
|
+
# @return [void] The message is pushed to STDOUT.
|
44
|
+
def log(translation_key, test_mode: test_mode?, **args)
|
45
|
+
return if test_mode
|
46
|
+
|
47
|
+
puts I18n.t!(translation_key, **args)
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def colorize(text, ansi_colour_name)
|
53
|
+
"\e[#{ANSI_COLOURS[ansi_colour_name]}m#{text}\e[0m"
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_mode? = (ENV["LIFER_ENV"] == "test")
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This selection provides a list of all Markdown entries from all
|
2
|
+
# collections.
|
3
|
+
#
|
4
|
+
# To provide this to Lifer, configure it in your selections:
|
5
|
+
#
|
6
|
+
# selections:
|
7
|
+
# - lifer/selection/all_markdown
|
8
|
+
#
|
9
|
+
class Lifer::Selection::AllMarkdown < Lifer::Selection
|
10
|
+
self.name = :all_markdown
|
11
|
+
|
12
|
+
# @!visibility private
|
13
|
+
def entries
|
14
|
+
Lifer::Entry::Markdown.manifest
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# This selection provides a list of all entries that can be included in feeds.
|
2
|
+
#
|
3
|
+
# To provide this to Lifer, configure it in your selections:
|
4
|
+
#
|
5
|
+
# selections:
|
6
|
+
# - lifer/selection/included_in_feeds
|
7
|
+
#
|
8
|
+
class Lifer::Selection::IncludedInFeeds < Lifer::Selection
|
9
|
+
self.name = :included_in_feeds
|
10
|
+
|
11
|
+
# @!visibility private
|
12
|
+
def entries
|
13
|
+
Lifer::Entry.manifest.select { |entry| entry.class.include_in_feeds }
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# A selection is a group of entries that belong to any collection.
|
2
|
+
# Lifer includes some selection classes by default, but the intent here is to
|
3
|
+
# let users bring their own selections.
|
4
|
+
#
|
5
|
+
# A selection subclass can be added to the Lifer project as a Ruby file. Any
|
6
|
+
# detected Ruby files are dynamically loaded when `Lifer::Brain` is initialized.
|
7
|
+
#
|
8
|
+
# Implementing a selection is simple. Just implement the `#entries` method and
|
9
|
+
# rovide a name. The `#entries` method can be used to filter down
|
10
|
+
# `Lifer.entry_manifest` in whichever way one needs. To see examples of this,
|
11
|
+
# check out the source code of any of the included selections.
|
12
|
+
#
|
13
|
+
class Lifer::Selection < Lifer::Collection
|
14
|
+
class << self
|
15
|
+
attr_accessor :name
|
16
|
+
|
17
|
+
# The constructor method for selections. Unlike collections:
|
18
|
+
#
|
19
|
+
# 1. Selections never have a unique instance name. (There is only one
|
20
|
+
# instance of each selection, and it's inherited from the class.)
|
21
|
+
# 2. Selections never have a directory. Selections are virtual,
|
22
|
+
# pseudo-collections that paste together entries across collections.
|
23
|
+
#
|
24
|
+
# Thus, the generator method here takes no arguments.
|
25
|
+
#
|
26
|
+
# @return [Lifer::Selection]
|
27
|
+
def generate
|
28
|
+
new(name: name, directory: nil)
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
# @private
|
34
|
+
# This callback is invoked whenever a subclass of the current class is
|
35
|
+
# created. It ensures that each subclass, at least, as a default name that
|
36
|
+
# isn't `nil`.
|
37
|
+
#
|
38
|
+
# @return [void]
|
39
|
+
def inherited(klass)
|
40
|
+
klass.name ||= :unnamed_selection
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# The `#entries` method should be implemented on every selection subclass.
|
45
|
+
#
|
46
|
+
# @raise [NotImplementedError]
|
47
|
+
def entries
|
48
|
+
raise NotImplementedError, I18n.t("selection.entries_not_implemented")
|
49
|
+
end
|
50
|
+
|
51
|
+
# FIXME:
|
52
|
+
# Getting selection settings may actually need to be different than getting
|
53
|
+
# collection settings. But for now let's just inherit the superclass method.
|
54
|
+
#
|
55
|
+
# A getter for selection settings. See `Lifer::Collection#setting` for more
|
56
|
+
# information.
|
57
|
+
#
|
58
|
+
# @return [String, Symbol, NilClass] The setting for the collection (or a
|
59
|
+
# fallback setting, or a default setting).
|
60
|
+
def setting(...)
|
61
|
+
super(...)
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
# @private
|
67
|
+
# Selections do not support layout files. Entries only have permalinks and
|
68
|
+
# layouts via their collections. If something were ever to ask for the layout
|
69
|
+
# file of a selection, it would be wrong to. So we would raise an error.
|
70
|
+
#
|
71
|
+
def layout_file
|
72
|
+
raise I18n.t("selection.layouts_not_allowed")
|
73
|
+
end
|
74
|
+
|
75
|
+
self.name = :selection
|
76
|
+
end
|
77
|
+
|
78
|
+
require_relative "selection/all_markdown"
|
79
|
+
require_relative "selection/included_in_feeds"
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# This module provides simple finder methods to classes that need to keep track
|
2
|
+
# of their descendant classes.
|
3
|
+
#
|
4
|
+
# Example usage:
|
5
|
+
#
|
6
|
+
# class MyClass
|
7
|
+
# included Lifer::Shared::FinderMethods
|
8
|
+
# # ...
|
9
|
+
# end
|
10
|
+
#
|
11
|
+
module Lifer::Shared::FinderMethods
|
12
|
+
# @!visibility private
|
13
|
+
def self.included(klass)
|
14
|
+
klass.extend ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
# This module contains the class methods to be included in other classes.
|
18
|
+
#
|
19
|
+
module ClassMethods
|
20
|
+
# A simple finder.
|
21
|
+
#
|
22
|
+
# @param name [string] The configured name of the builder you want to find.
|
23
|
+
# @return [Class] A builder class.
|
24
|
+
def find(name)
|
25
|
+
result = subclasses.detect { |klass| klass.name == name.to_sym }
|
26
|
+
|
27
|
+
if result.nil?
|
28
|
+
raise StandardError, I18n.t("shared.finder_methods.unknown_class", name:)
|
29
|
+
return
|
30
|
+
end
|
31
|
+
|
32
|
+
result
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
data/lib/lifer/shared.rb
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
<%= I18n.t "cli.banner.description", program_name: "Lifer" %>
|
2
|
+
<%= I18n.t "cli.banner.usage" %>:
|
3
|
+
lifer [subcommand] [options]
|
4
|
+
|
5
|
+
<%= I18n.t "cli.banner.subcommands" %>:
|
6
|
+
<%= Lifer::CLI::SUBCOMMANDS
|
7
|
+
.map { [Lifer::Utilities.bold_text(_1), _2].join(": ") }
|
8
|
+
.join("\n ") %>
|
9
|
+
|
10
|
+
<%= I18n.t "cli.banner.options" %>:
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# This file provides the default configuration settings for Lifer.
|
2
|
+
|
3
|
+
title: My Lifer Weblog
|
4
|
+
description: Just another Lifer weblog, lol...
|
5
|
+
language: en
|
6
|
+
author: Admin
|
7
|
+
|
8
|
+
entries:
|
9
|
+
default_title: Untitled Entry
|
10
|
+
|
11
|
+
rss: false
|
12
|
+
uri_strategy: simple
|
13
|
+
|
14
|
+
# You can optionally set a path to the layout file. If this setting is left
|
15
|
+
# unset, Lifer will use its built-in, simple-but-usable layout file instead.
|
16
|
+
#
|
17
|
+
### layout_file: path/to/my_layout.html.erb
|
18
|
+
|
19
|
+
# Collections
|
20
|
+
#
|
21
|
+
# In addition to the root collection configured above, your configuration file
|
22
|
+
# can include any number of collections. Collections use the root collections
|
23
|
+
# configuration by default, or they can have their own values.
|
24
|
+
#
|
25
|
+
### my_collection:
|
26
|
+
### title: My Collection
|
27
|
+
### description: A collection separate from the root collection.
|
28
|
+
### language: fr
|
29
|
+
### author: Benjamin
|
30
|
+
###
|
31
|
+
### rss: my-collection.xml
|
32
|
+
### uri_strategy: pretty
|
33
|
+
###
|
34
|
+
### layout_file: path/to/my_other_layout.html.erb
|
35
|
+
|
36
|
+
# Selections
|
37
|
+
#
|
38
|
+
# Selections are pseudo-collections of entries. You may want to group disparate
|
39
|
+
# entries together across many collections. Selections is a way to do that.
|
40
|
+
# Lifer includes some basic selections but you can also create your own by
|
41
|
+
# writing a simple Ruby class. See the `Lifer::Selection` class documentation for
|
42
|
+
# more information.
|
43
|
+
#
|
44
|
+
selections:
|
45
|
+
- lifer/selection/all_markdown
|
46
|
+
- lifer/selection/included_in_feeds
|
47
|
+
|
48
|
+
# Global settings
|
49
|
+
#
|
50
|
+
# These settings are special, and they're used for all of your collections and
|
51
|
+
# cannot be set per collection.
|
52
|
+
#
|
53
|
+
# Note that the `build:` and `prebuild:` keys can be configured to work
|
54
|
+
# differently per environment (`build` or `serve`):
|
55
|
+
#
|
56
|
+
# Valid:
|
57
|
+
#
|
58
|
+
# # global:
|
59
|
+
# # prebuild:
|
60
|
+
# # serve:
|
61
|
+
# # - watch_and_rebuild_assets_command
|
62
|
+
# # build:
|
63
|
+
# # - final_minified_build_assets_command
|
64
|
+
#
|
65
|
+
# Also valid:
|
66
|
+
#
|
67
|
+
# # global:
|
68
|
+
# # prebuild:
|
69
|
+
# # - final_minified_build_assets_command
|
70
|
+
#
|
71
|
+
global:
|
72
|
+
build:
|
73
|
+
- html
|
74
|
+
- rss
|
75
|
+
- txt
|
76
|
+
host: https://example.com
|
77
|
+
output_directory: _build
|
Binary file
|
@@ -1,13 +1,21 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
# The pretty URI strategy ensures that all entries are indexified, making their
|
2
|
+
# URLs "pretty" in the browser. "Pretty" because browsers often do not show
|
3
|
+
# "index.html" at the end of the URL because it's implicit.
|
4
|
+
#
|
5
|
+
# For example:
|
6
|
+
#
|
7
|
+
# entry.md ---> entry/index.html
|
8
|
+
#
|
9
|
+
class Lifer::URIStrategy::Pretty < Lifer::URIStrategy
|
10
|
+
self.name = :pretty
|
5
11
|
|
12
|
+
# @see Lifer::URIStrategy#output_file
|
6
13
|
def output_file(entry)
|
7
|
-
basename = File.basename
|
14
|
+
basename = File.basename entry.file,
|
15
|
+
Lifer::Utilities.file_extension(entry.file)
|
8
16
|
|
9
17
|
Pathname entry.file.to_s
|
10
18
|
.gsub(/#{root}[\/]{0,1}/, "")
|
11
|
-
.gsub(/#{basename}(\..+)/, "#{basename}/index
|
19
|
+
.gsub(/#{basename}(\..+)/, "#{basename}/index.#{file_extension(entry)}")
|
12
20
|
end
|
13
21
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Lifer::URIStrategy
|
2
|
+
# This URI strategy follows the "pretty" strategy of indexifying all entries
|
3
|
+
# (i.e. `entry.md` outputs to `entry/index.html`) and ensuring that, no matter
|
4
|
+
# what collection the entry is in, the entry is output to the root of the Lifer
|
5
|
+
# build directory. For example:
|
6
|
+
#
|
7
|
+
# subdir/entry.md ---> entry/index.html
|
8
|
+
#
|
9
|
+
class PrettyRoot < Lifer::URIStrategy
|
10
|
+
self.name = :pretty_root
|
11
|
+
|
12
|
+
# @see Lifer::URIStrategy#output_file
|
13
|
+
def output_file(entry)
|
14
|
+
basename = File.basename entry.file,
|
15
|
+
Lifer::Utilities.file_extension(entry.file)
|
16
|
+
|
17
|
+
if basename == "index"
|
18
|
+
Pathname "index.html"
|
19
|
+
else
|
20
|
+
Pathname "#{basename}/index.#{file_extension(entry)}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# This URI strategy ensures that entries with dates in the filename (i.e.
|
2
|
+
# `1990-12-12-my-trip-to-hamilton.md`) will always be have output filenames
|
3
|
+
# without the date included. It also follows the "pretty" URI strategy of
|
4
|
+
# outputting each file to an `index` file in a subdirectory.
|
5
|
+
#
|
6
|
+
# For example:
|
7
|
+
#
|
8
|
+
# 1990-12-12-my-trip-to-hamilton.md ---> my-trip-to-hamilton/index.html
|
9
|
+
#
|
10
|
+
class Lifer::URIStrategy::PrettyYYYYMMDD < Lifer::URIStrategy
|
11
|
+
self.name = :pretty_yyyy_mm_dd
|
12
|
+
|
13
|
+
# We expect date separators to fall into this regular expression.
|
14
|
+
#
|
15
|
+
DATE_SEPARATORS = "[\-\._]{1}"
|
16
|
+
|
17
|
+
# The date regular expression we expect entry filenames to follow.
|
18
|
+
#
|
19
|
+
DATE_REGEXP =
|
20
|
+
/\d{4}#{DATE_SEPARATORS}\d{1,2}#{DATE_SEPARATORS}\d{1,2}#{DATE_SEPARATORS}/
|
21
|
+
|
22
|
+
# @see Lifer::URIStrategy#output_file
|
23
|
+
def output_file(entry)
|
24
|
+
basename = File.basename entry.file,
|
25
|
+
Lifer::Utilities.file_extension(entry.file)
|
26
|
+
|
27
|
+
Pathname entry.file.to_s
|
28
|
+
.gsub(/#{root}[\/]{0,1}/, "")
|
29
|
+
.gsub(/#{basename}(\..+)/, "#{basename}/index.#{file_extension(entry)}")
|
30
|
+
.gsub(DATE_REGEXP, "")
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
class Lifer::URIStrategy
|
2
|
+
# No matter what collection an entry is in, this URI strategy ensures that the
|
3
|
+
# entries are output to the root of the output directory (for example:
|
4
|
+
# `_build/<your-entry>.html`, never `_build/collection_name/<your-entry>.html`.
|
5
|
+
#
|
6
|
+
class Root < Lifer::URIStrategy
|
7
|
+
self.name = :root
|
8
|
+
|
9
|
+
# @see Lifer::URIStrategy#output_file
|
10
|
+
def output_file(entry)
|
11
|
+
basename = File.basename entry.file,
|
12
|
+
Lifer::Utilities.file_extension(entry.file)
|
13
|
+
|
14
|
+
Pathname "#{basename}.#{file_extension(entry)}"
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -1,13 +1,17 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
1
|
+
# The default URI strategy. It simply takes an input filename (i.e. "entry.md")
|
2
|
+
# and outputs a mirrorring output filename with the correct output format (i.e.
|
3
|
+
# "entry.html").
|
4
|
+
#
|
5
|
+
class Lifer::URIStrategy::Simple < Lifer::URIStrategy
|
6
|
+
self.name = :simple
|
5
7
|
|
8
|
+
# @see Lifer::URIStrategy#output_file
|
6
9
|
def output_file(entry)
|
7
|
-
basename = File.basename
|
10
|
+
basename = File.basename entry.file,
|
11
|
+
Lifer::Utilities.file_extension(entry.file)
|
8
12
|
|
9
13
|
Pathname entry.file.to_s
|
10
14
|
.gsub(/#{root}[\/]{0,1}/, "")
|
11
|
-
.gsub(/#{basename}(\..+)/, "#{basename}
|
15
|
+
.gsub(/#{basename}(\..+)/, "#{basename}.#{file_extension(entry)}")
|
12
16
|
end
|
13
17
|
end
|