lifer 0.2.0 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|