lifer 0.9.0 → 0.10.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b6414082e8f6f1d9b18034f2f227890b85ac73b22ab50c25cb2e350812c1a90
4
- data.tar.gz: 5891afc57d102fd67422bf581edc4de839b05efbe10d104529a817391c0e7cad
3
+ metadata.gz: ae7d44054aac227a4d75940cf8c6d30b46ba403780f5d8583d0db22dfd8cf36e
4
+ data.tar.gz: 7c8ac4e684afa95e23f22dc1b6ad0472391c3fc946a6b7bbbdf9acfbf46ac31d
5
5
  SHA512:
6
- metadata.gz: 7adb1041c558a2d60ef525bf1d12af4dd3d587a26f8ae0ef4e2f4c8fb9e04e14c648cf4bd72717aba143e747ccabe970ec1c859cd0c69b5259847887c0f99361
7
- data.tar.gz: a9ec79fce82554f69ba1634ce414b7b63014c8545f8f8c5a6201c194d375d57d09a36128db4f6b242c279bfe0fe419f3b333034268415379c1b9138d372d352f
6
+ metadata.gz: 464566f240f05e2c5e199582555ccecd9bc6449ad477d53adb24905d76e07b9f93c13d050f69cd6c5923f9f9434edca1d14b4d01d674c5b4148bb5d9c85723df
7
+ data.tar.gz: 270f4d8c20bc24b180ae474a6d62037b2f2347a4d9e25f7a4af207d0e85d6df66fec0f92a82f57248e05ac34966ba69ff542a30c9e36d4f90e99fe41a46eeba6
data/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  ## Next
2
2
 
3
+ ## v0.10.1
4
+
5
+ This release resolves a bug with `Entry#summary`. When I originally implemented
6
+ summaries as first paragraphs, I didn't realize that using the Kramdown
7
+ representation of the document would result in special characters being
8
+ transformed badly:
9
+
10
+ In the ldquorealmrdquo where dreams dance
11
+
12
+ This change ensures that we don't end up with trash like that, or other HTML
13
+ trash.
14
+
15
+ The summary is meant to be ideal for things like `<meta>` description tags,
16
+ which should only contain plain text.
17
+
18
+
19
+ ## v0.10.0
20
+
21
+ This release lets all layout files (either Liquid or ERB files) provide a
22
+ reference to parent, or "root", layout files that should wrap the via
23
+ frontmatter.
24
+
25
+ Previously, we did this for Liquid layouts using the custom `layout` tag. But
26
+ because there was no equivalent tag for ERB files, I realized it would be less
27
+ work to just provide the value via frontmatter. The same way for every type of
28
+ layout file.
29
+
30
+ End users (just me?) must update their Lifer project Liquid layouts accordingly:
31
+
32
+ ```diff
33
+ - {% layout "layouts/root_layout.html.liquid" %}
34
+ + ---
35
+ + layout: layouts/root_layout.html.liquid
36
+ + ---
37
+
38
+ Layout content.
39
+ ```
40
+
3
41
  ## v0.9.0
4
42
 
5
43
  Atom feeds now support entries with both `#published_at` and `#updated_at`
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- lifer (0.9.0)
4
+ lifer (0.10.1)
5
5
  i18n (< 2)
6
6
  kramdown (~> 2.4)
7
7
  liquid (~> 5.6, < 6)
@@ -32,9 +32,9 @@ GEM
32
32
  irb (~> 1.10)
33
33
  reline (>= 0.3.8)
34
34
  diff-lcs (1.5.1)
35
- ffi (1.17.1-arm64-darwin)
36
- ffi (1.17.1-x86_64-darwin)
37
- ffi (1.17.1-x86_64-linux-gnu)
35
+ ffi (1.17.2-arm64-darwin)
36
+ ffi (1.17.2-x86_64-darwin)
37
+ ffi (1.17.2-x86_64-linux-gnu)
38
38
  i18n (1.14.7)
39
39
  concurrent-ruby (~> 1.0)
40
40
  io-console (0.7.2)
@@ -44,7 +44,7 @@ GEM
44
44
  kramdown (2.5.1)
45
45
  rexml (>= 3.3.9)
46
46
  language_server-protocol (3.17.0.3)
47
- liquid (5.8.2)
47
+ liquid (5.8.6)
48
48
  bigdecimal
49
49
  strscan (>= 3.1.1)
50
50
  listen (3.9.0)
@@ -107,7 +107,7 @@ GEM
107
107
  sorbet-runtime (>= 0.5.10782)
108
108
  sorbet-runtime (0.5.11558)
109
109
  stringio (3.1.1)
110
- strscan (3.1.2)
110
+ strscan (3.1.5)
111
111
  xpath (3.2.0)
112
112
  nokogiri (~> 1.8)
113
113
  yard (0.9.37)
data/README.md CHANGED
@@ -107,6 +107,10 @@ Then, build and push the gem to RubyGems:
107
107
  $ gem build
108
108
  $ gem push lifer-<new_version_without_the_v_prefix>.gem
109
109
 
110
+ And ensure that the release commit(s) are on the `main` branch:
111
+
112
+ $ git push origin main
113
+
110
114
  ## Contributing
111
115
 
112
116
  I'm not currently accepting unsolicited contributions to Lifer. I'm still
@@ -0,0 +1,66 @@
1
+ class Lifer::Builder
2
+ class HTML
3
+ # A base class for all HTML builder adapters. The methods provided by this
4
+ # class are either required or reusable by builder subclasses. See the
5
+ # committed HTML builder adapter classes for example implementations.
6
+ class FromAny
7
+ class << self
8
+ # Build and render an entry.
9
+ #
10
+ # @param entry [Lifer::Entry] The entry to be rendered.
11
+ # @return [String] The rendered entry.
12
+ def build(entry:)
13
+ new(entry: entry).render
14
+ end
15
+ end
16
+
17
+ # The base class does not provide a render method, but any subclass
18
+ # should be expected to.
19
+ #
20
+ # @raise [NotImplementedError]
21
+ def render
22
+ raise NotImplementedError,
23
+ "subclasses must implement a custom `#render` method"
24
+ end
25
+
26
+ private
27
+
28
+ # The frontmatter provided by the layout file.
29
+ #
30
+ # @return [Hash] The frontmatter represented as a hash.
31
+ def frontmatter
32
+ return {} unless frontmatter?
33
+
34
+ Lifer::Utilities.symbolize_keys(
35
+ YAML.load layout_file_contents(raw: true)[Lifer::FRONTMATTER_REGEX, 1],
36
+ permitted_classes: [Time]
37
+ )
38
+ end
39
+
40
+ # Checks whether frontmatter is present in the layout file.
41
+ #
42
+ # @return [boolean]
43
+ def frontmatter?
44
+ @frontmatter ||=
45
+ layout_file_contents(raw: true).match?(Lifer::FRONTMATTER_REGEX)
46
+ end
47
+
48
+ # The contents of the layout file.
49
+ #
50
+ # @param raw [boolean] Whether to include or exclude frontmatter from
51
+ # the contents.
52
+ # @return [String] The contents of the layout file.
53
+ def layout_file_contents(raw: false)
54
+ cache_variable = "@layout_file_contents_#{raw}"
55
+ cached_value = instance_variable_get cache_variable
56
+
57
+ return cached_value if cached_value
58
+
59
+ contents = File.read layout_file
60
+ contents = contents.gsub(Lifer::FRONTMATTER_REGEX, "") unless raw
61
+
62
+ instance_variable_set cache_variable, contents
63
+ end
64
+ end
65
+ end
66
+ end
@@ -23,23 +23,21 @@ class Lifer::Builder::HTML
23
23
  # </body>
24
24
  # </html>
25
25
  #
26
- class FromERB
27
- class << self
28
- # Build and render an entry.
29
- #
30
- # @param entry [Lifer::Entry] The entry to be rendered.
31
- # @return [String] The rendered entry.
32
- def build(entry:)
33
- new(entry: entry).render
34
- end
35
- end
36
-
26
+ class FromERB < FromAny
37
27
  # Reads the entry as ERB, given our renderer context (see the documentation
38
28
  # for `#build_binding_context`) and renders the production-ready entry.
39
29
  #
40
30
  # @return [String] The rendered entry.
41
31
  def render
42
- ERB.new(File.read layout_file).result context
32
+ document = ERB.new(layout_file_contents).result context
33
+
34
+ return document unless (relative_layout_path = frontmatter[:layout])
35
+
36
+ document_binding = binding.tap { |binding|
37
+ binding.local_variable_set :content, document
38
+ }
39
+ layout_path = "%s/%s" % [Lifer.root, relative_layout_path]
40
+ ERB.new(File.read layout_path).result(document_binding)
43
41
  end
44
42
 
45
43
  private
@@ -22,8 +22,6 @@ class Lifer::Builder::HTML::FromLiquid
22
22
  Liquid::LocalFileSystem.new(Lifer.root, "%s.html.liquid")
23
23
 
24
24
  environment.register_filter Lifer::Builder::HTML::FromLiquid::Filters
25
- environment.register_tag "layout",
26
- Lifer::Builder::HTML::FromLiquid::LayoutTag
27
25
  end
28
26
  end
29
27
  end
@@ -1,10 +1,5 @@
1
1
  require "liquid"
2
2
 
3
- require_relative "from_liquid/drops"
4
- require_relative "from_liquid/filters"
5
- require_relative "from_liquid/layout_tag"
6
- require_relative "from_liquid/liquid_env"
7
-
8
3
  class Lifer::Builder::HTML
9
4
  # If the HTML builder is given a Liquid template, it uses this class to parse
10
5
  # the Liquid into HTML. Lifer project metadata is provided as context. For
@@ -28,16 +23,10 @@ class Lifer::Builder::HTML
28
23
  # </body>
29
24
  # </html>
30
25
  #
31
- class FromLiquid
32
- class << self
33
- # Render and build a Lifer entry.
34
- #
35
- # @param entry [Lifer::Entry] The entry to render.
36
- # @return [String] The rendered entry, ready for output.
37
- def build(entry:) = new(entry:).render
38
- end
39
-
40
- attr_accessor :entry, :layout_file
26
+ class FromLiquid < FromAny
27
+ require_relative "from_liquid/drops"
28
+ require_relative "from_liquid/filters"
29
+ require_relative "from_liquid/liquid_env"
41
30
 
42
31
  # Reads the entry as Liquid, given our document context, and renders
43
32
  # an entry.
@@ -49,13 +38,23 @@ class Lifer::Builder::HTML
49
38
  .parse(entry.to_html, **parse_options)
50
39
  .render(context, **render_options)
51
40
  )
41
+ document = Liquid::Template
42
+ .parse(layout_file_contents, **parse_options)
43
+ .render(document_context, **render_options)
44
+
45
+ return document unless (relative_layout_path = frontmatter[:layout])
46
+
47
+ layout_path = "%s/%s" % [Lifer.root, relative_layout_path]
48
+ document_context = context.merge! "content" => document
52
49
  Liquid::Template
53
- .parse(layout, **parse_options)
50
+ .parse(File.read layout_path, **parse_options)
54
51
  .render(document_context, **render_options)
55
52
  end
56
53
 
57
54
  private
58
55
 
56
+ attr_accessor :entry, :layout_file
57
+
59
58
  def initialize(entry:)
60
59
  @entry = entry
61
60
  @layout_file = entry.collection.layout_file
@@ -79,19 +78,6 @@ class Lifer::Builder::HTML
79
78
  }
80
79
  end
81
80
 
82
- # @private
83
- # It's possible for the provided layout to request a parent layout, which
84
- # makes this method a bit complicated.
85
- #
86
- # @return [String] A Liquid layout document, ready for parsing.
87
- def layout
88
- contents = File.read layout_file
89
-
90
- return contents unless contents.match?(/\{%\s*#{LayoutTag::NAME}.*%\}/)
91
-
92
- contents + "\n{% #{LayoutTag::ENDNAME} %}"
93
- end
94
-
95
81
  def liquid_environment = (@liquid_environment ||= LiquidEnv.global)
96
82
 
97
83
  def parse_options
@@ -103,6 +89,7 @@ class Lifer::Builder::HTML
103
89
 
104
90
  def render_options
105
91
  {
92
+ registers: {file_system: liquid_environment.file_system},
106
93
  strict_variables: true,
107
94
  strict_filters: true
108
95
  }
@@ -33,6 +33,7 @@ require "fileutils"
33
33
  class Lifer::Builder::HTML < Lifer::Builder
34
34
  self.name = :html
35
35
 
36
+ require_relative "html/from_any"
36
37
  require_relative "html/from_erb"
37
38
  require_relative "html/from_liquid"
38
39
 
@@ -30,14 +30,16 @@ class Lifer::Entry::Markdown < Lifer::Entry
30
30
  def summary
31
31
  return super if super
32
32
 
33
- return if first_paragraph.nil?
34
- return first_paragraph if first_paragraph.length <= TRUNCATION_THRESHOLD
33
+ return if raw_first_paragraph_text.nil?
35
34
 
36
- truncated_paragraph = first_paragraph[0..TRUNCATION_THRESHOLD]
37
- if (index_of_final_fullstop = truncated_paragraph.rindex ". ")
38
- truncated_paragraph[0..index_of_final_fullstop]
35
+ text = raw_first_paragraph_text
36
+ return text if text.length <= TRUNCATION_THRESHOLD
37
+
38
+ truncated_text = text[0..TRUNCATION_THRESHOLD]
39
+ if (index_of_final_fullstop = text.rindex ". ")
40
+ truncated_text[0..index_of_final_fullstop]
39
41
  else
40
- "%s..." % truncated_paragraph
42
+ "%s..." % truncated_text
41
43
  end
42
44
  end
43
45
 
@@ -57,7 +59,7 @@ class Lifer::Entry::Markdown < Lifer::Entry
57
59
  #
58
60
  # @return [String] The HTML for the body of the entry.
59
61
  def to_html
60
- Kramdown::Document.new(body).to_html
62
+ @to_html ||= Kramdown::Document.new(body).to_html
61
63
  end
62
64
 
63
65
  private
@@ -75,25 +77,20 @@ class Lifer::Entry::Markdown < Lifer::Entry
75
77
  end.uniq
76
78
  end
77
79
 
78
- # Using Kramdown we can detect the first paragraph of the entry.
80
+ # Detects the raw paragraph text from the entry.
79
81
  #
82
+ # @fixme It would be easier and less error prone to do this with Nokogiri. But
83
+ # we currently don't need the dependency for any other reason, so let's
84
+ # defer adding it until then.
80
85
  # @private
81
- def first_paragraph
82
- @first_paragraph ||=
83
- kramdown_paragraph_text(
84
- Kramdown::Document.new(body).root
85
- .children
86
- .detect { |child| child.type == :p }
87
- )
88
- end
86
+ def raw_first_paragraph_text
87
+ paragraphs = to_html.match %r{<p[^>]*>(.*?)</p>}im
88
+ paragraph = paragraphs ? paragraphs[1].strip : nil
89
89
 
90
- # @private
91
- def kramdown_paragraph_text(kramdown_element)
92
- return if kramdown_element.nil?
90
+ return unless paragraph
93
91
 
94
- kramdown_element.children
95
- .flat_map { |child| child.value || kramdown_paragraph_text(child) }
96
- .join
97
- .gsub(/\n/, " ")
92
+ paragraph = paragraph.gsub /<\/?[^>]*>/, ""
93
+ paragraph = CGI.unescapeHTML paragraph
94
+ paragraph.gsub(/[\s\n\t]+/, " ").strip
98
95
  end
99
96
  end
data/lib/lifer/entry.rb CHANGED
@@ -39,10 +39,6 @@ module Lifer
39
39
  #
40
40
  FILENAME_DATE_FORMAT = /^(\d{4}-\d{1,2}-\d{1,2})-/
41
41
 
42
- # We expect frontmatter to be provided in the following format.
43
- #
44
- FRONTMATTER_REGEX = /^---\n(.*)---\n/m
45
-
46
42
  # If tags are represented in YAML frontmatter as a string, they're split on
47
43
  # commas and/or spaces.
48
44
  #
@@ -147,7 +143,7 @@ module Lifer
147
143
  def body
148
144
  return full_text.strip unless frontmatter?
149
145
 
150
- full_text.gsub(FRONTMATTER_REGEX, "").strip
146
+ full_text.gsub(Lifer::FRONTMATTER_REGEX, "").strip
151
147
  end
152
148
 
153
149
  def feedable?
@@ -168,7 +164,8 @@ module Lifer
168
164
  return {} unless frontmatter?
169
165
 
170
166
  Lifer::Utilities.symbolize_keys(
171
- YAML.load(full_text[FRONTMATTER_REGEX, 1], permitted_classes: [Time])
167
+ YAML.load full_text[Lifer::FRONTMATTER_REGEX, 1],
168
+ permitted_classes: [Time]
172
169
  )
173
170
  end
174
171
 
@@ -326,6 +323,6 @@ module Lifer
326
323
  File.basename(file).match(FILENAME_DATE_FORMAT)[1]
327
324
  end
328
325
 
329
- def frontmatter? = (full_text && full_text.match?(FRONTMATTER_REGEX))
326
+ def frontmatter? = (full_text && full_text.match?(Lifer::FRONTMATTER_REGEX))
330
327
  end
331
328
  end
data/lib/lifer/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Lifer
2
- VERSION = "0.9.0"
2
+ VERSION = "0.10.1"
3
3
  end
data/lib/lifer.rb CHANGED
@@ -24,6 +24,10 @@ module Lifer
24
24
  "(\\/\\.)+" # Contains a dot directory.
25
25
  ] | IGNORE_DIRECTORIES.map { |d| "^(#{d})" }
26
26
 
27
+ # We expect frontmatter in any file to be provided in the following format.
28
+ #
29
+ FRONTMATTER_REGEX = /^---\n(.*)---\n/m
30
+
27
31
  class << self
28
32
  # The first time `Lifer.brain` is referenced, we build a new `Lifer::Brain`
29
33
  # object that is used and reused until the current process has ended.
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lifer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.0
4
+ version: 0.10.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - benjamin wil
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-03-30 00:00:00.000000000 Z
11
+ date: 2025-05-10 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: i18n
@@ -159,6 +159,7 @@ files:
159
159
  - lib/lifer/brain.rb
160
160
  - lib/lifer/builder.rb
161
161
  - lib/lifer/builder/html.rb
162
+ - lib/lifer/builder/html/from_any.rb
162
163
  - lib/lifer/builder/html/from_erb.rb
163
164
  - lib/lifer/builder/html/from_liquid.rb
164
165
  - lib/lifer/builder/html/from_liquid/drops.rb
@@ -170,7 +171,6 @@ files:
170
171
  - lib/lifer/builder/html/from_liquid/drops/tag_drop.rb
171
172
  - lib/lifer/builder/html/from_liquid/drops/tags_drop.rb
172
173
  - lib/lifer/builder/html/from_liquid/filters.rb
173
- - lib/lifer/builder/html/from_liquid/layout_tag.rb
174
174
  - lib/lifer/builder/html/from_liquid/liquid_env.rb
175
175
  - lib/lifer/builder/rss.rb
176
176
  - lib/lifer/builder/txt.rb
@@ -210,8 +210,8 @@ licenses:
210
210
  - MIT
211
211
  metadata:
212
212
  allowed_push_host: https://rubygems.org
213
- homepage_uri: https://github.com/benjaminwil/lifer/blob/v0.9.0/README.md
214
- source_code_uri: https://github.com/benjaminwil/lifer/tree/v0.9.0
213
+ homepage_uri: https://github.com/benjaminwil/lifer/blob/v0.10.1/README.md
214
+ source_code_uri: https://github.com/benjaminwil/lifer/tree/v0.10.1
215
215
  changelog_uri: https://github.com/benjaminwil/lifer/blob/main/CHANGELOG.md
216
216
  post_install_message:
217
217
  rdoc_options: []
@@ -1,66 +0,0 @@
1
- class Lifer::Builder::HTML::FromLiquid
2
- # Note that if you want to learn more about the shape of this class, check out
3
- # `Liquid::Block` in the `liquid` gem.
4
- #
5
- # The layout tag is a bit magic. The idea here is to emulate how Jekyll
6
- # handles `layout:` YAML frontmatter within entries to change the normal
7
- # parent layout to an override parent layout--but without the need for
8
- # frontmatter.
9
- #
10
- # The reason we took this strategy was to avoid pre-processing every entry for
11
- # frontmatter when we didn't need to. Maybe in the long run this was a bad
12
- # call? I don't know.
13
- #
14
- # @example Usage from a Liquid template.
15
- # {% layout "path/to/my_liquid_layout_template" %}
16
- #
17
- # (The required `endlayout` tag will be appended to the end of the file
18
- # on render if you do not insert it yourself.
19
- #
20
- class LayoutTag < Liquid::Block
21
- # The name of the tag in Liquid templates, `layout`.
22
- #
23
- NAME = :layout
24
-
25
- # The end name of the tag in Liquid templates, `endlayout`.
26
- #
27
- ENDNAME = ("end%s" % NAME).to_sym
28
-
29
- def initialize(layout, path, options)
30
- @path = path.delete("\"").strip
31
- super
32
- end
33
-
34
- # A layout tag wraps an entire document and outputs it inside of whatever
35
- # the `@layout` is. This lets a child document specify a parernt layout!
36
- # Very confusing stuff.
37
- #
38
- # @param context [Liquid::Context] All of the context of the Liquid
39
- # document that would be rendered.
40
- # @return [String] A rendered document.
41
- def render(context)
42
- document_context = context.environments.first
43
- parse_options = document_context["parse_options"]
44
- liquid_file_system = parse_options[:environment].file_system
45
- render_options = document_context["render_options"]
46
-
47
- current_layout_file = File
48
- .read(document_context["entry"]["collection"]["layout_file"])
49
- .gsub(/\{%\s*#{tag_name}.+%\}/, "")
50
-
51
- content_with_layout = Liquid::Template
52
- .parse(current_layout_file, **parse_options)
53
- .render(document_context, **render_options)
54
-
55
- Liquid::Template
56
- .parse(
57
- liquid_file_system.read_template_file(@path),
58
- **parse_options
59
- )
60
- .render(
61
- document_context.merge({"content" => content_with_layout}),
62
- **render_options
63
- )
64
- end
65
- end
66
- end