Class: Lifer::Entry

Inherits:
Object
  • Object
show all
Defined in:
lib/lifer/entry.rb

Overview

An entry is a Lifer file that will be built into the output directory. There are more than one subclass of entry: Markdown entries are the most traditional, but HTML and text files are also very valid entries.

This class provides a baseline of the functionality that all entry subclasses should implement. It also provides the entry generator for all entry subclasses.

Direct Known Subclasses

HTML, Markdown, TXT

Defined Under Namespace

Classes: HTML, Markdown, TXT

Constant Summary collapse

ASSET_DELIMITER_REGEX =

If assets are represented in YAML frontmatter as a string, they’re split on commas and/or spaces.

/[,\s]+/
DEFAULT_DATE =

We provide a default date for entries that have no date and entry types that otherwise could not have a date due to no real way of getting that metadata.

Time.new(1900, 01, 01, 0, 0, 0, "+00:00")
FILENAME_DATE_FORMAT =

If a filename contains a date, we should expect it to be in the following format.

/^(\d{4}-\d{1,2}-\d{1,2})-/
TAG_DELIMITER_REGEX =

If tags are represented in YAML frontmatter as a string, they’re split on commas and/or spaces.

/[,\s]+/
TRUNCATION_THRESHOLD =

We truncate anything that needs to be truncated (summaries, meta descriptions) at the following character count.

120

Class Attribute Summary collapse

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(file:, collection:) ⇒ Lifer::Entry

When a new entry is initialized we expect the file to already exist, and we expect to know which ‘Lifer::Collection` it belongs to.

Parameters:

  • file (String)

    An absolute path to a file.

  • collection (Lifer::Collection)

    A collection.



130
131
132
133
# File 'lib/lifer/entry.rb', line 130

def initialize(file:, collection:)
  @file = Pathname file
  @collection = collection
end

Class Attribute Details

.include_in_feedsObject

Returns the value of attribute include_in_feeds.



19
20
21
# File 'lib/lifer/entry.rb', line 19

def include_in_feeds
  @include_in_feeds
end

.input_extensionsObject

Returns the value of attribute input_extensions.



20
21
22
# File 'lib/lifer/entry.rb', line 20

def input_extensions
  @input_extensions
end

.output_extensionObject

Returns the value of attribute output_extension.



21
22
23
# File 'lib/lifer/entry.rb', line 21

def output_extension
  @output_extension
end

Instance Attribute Details

#collectionObject (readonly)

Returns the value of attribute collection.



57
58
59
# File 'lib/lifer/entry.rb', line 57

def collection
  @collection
end

#fileObject (readonly)

Returns the value of attribute file.



57
58
59
# File 'lib/lifer/entry.rb', line 57

def file
  @file
end

Class Method Details

.generate(file:, collection:, dependencies: [:assets, :authors, :tags]) ⇒ Lifer::Entry

The entrypoint for generating entry objects. We should never end up with ‘Lifer::Entry` records: only subclasses.

Parameters:

  • file (String)

    The absolute filename of an entry file.

  • collection (Lifer::Collection)

    The collection for the entry.

Returns:



66
67
68
69
70
71
72
73
74
75
76
77
78
# File 'lib/lifer/entry.rb', line 66

def generate(file:, collection:, dependencies: [:assets, :authors, :tags])
  error!(file) unless File.exist?(file)

  if (new_entry = subclass_for(file)&.new(file:, collection:))
    Lifer.entry_manifest << new_entry

    dependencies.each do |dependency|
      new_entry.public_send dependency
    end
  end

  new_entry
end

.manifestArray<Lifer::Entry>

Whenever an entry is generated it should be added to the entry manifest. This lets us get a list of all generated entries.

Returns:

  • (Array<Lifer::Entry>)

    A list of all entries that currently exist.



84
85
86
87
88
# File 'lib/lifer/entry.rb', line 84

def manifest
  return Lifer.entry_manifest if self == Lifer::Entry

  Lifer.entry_manifest.select { |entry| entry.class == self }
end

.supported?(filename, file_extensions = supported_file_extensions) ⇒ Boolean

Checks whether the given filename is supported entry type (using only its file extension).

Parameters:

  • filename (String)

    The absolute filename to an entry.

  • file_extensions (Array<String>) (defaults to: supported_file_extensions)

    An array of file extensions to check against.

Returns:

  • (Boolean)


97
98
99
# File 'lib/lifer/entry.rb', line 97

def supported?(filename, file_extensions= supported_file_extensions)
  file_extensions.any? { |ext| filename.end_with? ext }
end

Instance Method Details

#assetsArray<Lifer::Asset>

Locates and returns all assets defined in the entry.

Returns:



138
139
140
141
# File 'lib/lifer/entry.rb', line 138

def assets
  @assets ||= candidate_asset_names
    .map { Lifer::Asset.build_or_update(url: _1, entries: [self]) }
end

#authorsArray<String>

Given the entry’s frontmatter, we should be able to get a list of authors. We always prefer authors (as opposed to a singular author) because it makes handling both cases easier in the long run.

The return value here is likely an author’s name. Whether that’s a full name, a first name, or a handle is up to the end user.

Returns:

  • (Array<String>)

    An array of authors’s names.



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
# File 'lib/lifer/entry.rb', line 151

def authors
  list = Array(frontmatter[:author] || frontmatter[:authors]).compact
  if list.any? && list.all? { _1.is_a? Array }
    list = [list.to_h]
  end

  list.map {
    attributes = Lifer::Utilities.symbolize_keys(
      case _1
      when Hash then _1
      when String then {name: _1}
      end
     )
    Lifer::Author.build_or_update **attributes.merge(entries: [self])
  }
end

#bodyString

This method returns the full text of the entry, only removing the frontmatter. It should not parse anything other than frontmatter.

Returns:

  • (String)

    The body of the entry.



172
173
174
175
176
# File 'lib/lifer/entry.rb', line 172

def body
  return full_text.strip unless frontmatter?

  full_text.sub(Lifer::FRONTMATTER_REGEX, "").strip
end

#feedable?Boolean

Returns:

  • (Boolean)


178
179
180
181
182
183
184
185
# File 'lib/lifer/entry.rb', line 178

def feedable?
  if (setting = self.class.include_in_feeds).nil?
    raise NotImplementedError,
      I18n.t("entry.feedable_error", entry_class: self.class)
  end

  setting
end

#frontmatterHash

Frontmatter is a widely supported YAML metadata block found at the top of text–often Markdown–files. We attempt to parse all entries for frontmatter.

Returns:

  • (Hash)

    A hash representation of the entry frontmatter.



192
193
194
195
196
197
198
199
# File 'lib/lifer/entry.rb', line 192

def frontmatter
  return {} unless frontmatter?

  Lifer::Utilities.symbolize_keys(
    YAML.load full_text[Lifer::FRONTMATTER_REGEX, 1],
      permitted_classes: [Time]
  )
end

#full_textString

The full text of the entry.

Returns:

  • (String)


204
205
206
# File 'lib/lifer/entry.rb', line 204

def full_text
  @full_text ||= File.readlines(file).join if file
end

#pathString

The expected, absolute URI path to the entry. For example:

/index.html
/blog/my-trip-to-toronto.html

Returns:

  • (String)

    The absolute URI path to the entry.



240
# File 'lib/lifer/entry.rb', line 240

def path = permalink(host: "/")

Using the current Lifer configuration, we can calculate the expected permalink for the entry. For example:

https://example.com/index.html
https://example.com/blog/my-trip-to-toronto.html

This would be useful for indexes and feeds and so on.

Returns:

  • (String)

    A permalink to the current entry.



217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
# File 'lib/lifer/entry.rb', line 217

def permalink(host: Lifer.setting(:global, :host))
  cached_permalink_variable =
    "@entry_permalink_" + Digest::SHA1.hexdigest(host)

  instance_variable_get(cached_permalink_variable) ||
    instance_variable_set(
      cached_permalink_variable,
      File.join(
        host,
        Lifer::URIStrategy
          .find(collection.setting :uri_strategy)
          .new(root: Lifer.root)
          .permalink(self)
      )
    )
end

#published_atTime

The entry’s publication date. The published date can be inferred in a few ways. The priority is:

1. the frontmatter's `published_at` field
2. the frontmatter's `published` field
3. the frontamtter's `date` field
4. The date in the filename.

Since text files would only store dates as simple strings, it’s nice to attempt to convert those into Ruby date or datetime objects.

Returns:

  • (Time)

    A Ruby representation of the date and time provided by the entry frontmatter or filename.



255
256
257
258
259
260
261
# File 'lib/lifer/entry.rb', line 255

def published_at
  date_for frontmatter[:published_at],
    frontmatter[:published],
    frontmatter[:date],
    filename_date,
    missing_metadata_translation_key: "entry.no_published_at_metadata"
end

#summaryString

If given a summary in the frontmatter of the entry, we can use this to provide a summary.

Since subclasses may have more sophisticated access to the document, they may override this method with their own distinct implementations.

Returns:

  • (String)

    A summary of the entry.



270
271
272
# File 'lib/lifer/entry.rb', line 270

def summary
  return frontmatter[:summary] if frontmatter[:summary]
end

#tagsArray<Lifer::Tag>

Locates and returns all tags defined in the entry.

Returns:



277
278
279
280
# File 'lib/lifer/entry.rb', line 277

def tags
  @tags ||= candidate_tag_names
    .map { Lifer::Tag.build_or_update(name: _1, entries: [self]) }
end

#titleString

Returns the title of the entry. Every entry subclass must implement this method so that builders have access to some kind of title for each entry.

Returns:

  • (String)

Raises:

  • (NotImplementedError)


286
287
288
# File 'lib/lifer/entry.rb', line 286

def title
  raise NotImplementedError, I18n.t("shared.not_implemented_method")
end

#to_htmlObject

Raises:

  • (NotImplementedError)


290
291
292
# File 'lib/lifer/entry.rb', line 290

def to_html
  raise NotImplementedError, I18n.t("shared.not_implemented_method")
end

#updated_at(fallback: nil) ⇒ Time

The entry’s last updated date. In the frontmatter, the last updated date can be specified using one of two fields. In priority order:

1. the `updated_at` field
2. the `updated` field

The developer could set a fallback value as a fallback. For example, when building RSS feeds one might want the value of ‘#published_at` if there is no last updated date.

Parameters:

  • fallback (Time, String, NilClass) (defaults to: nil)

    Provide datetime data, a string that parses to a datetime object, or nil.

Returns:

  • (Time)

    A Ruby representation of the date and time provided by the entry frontmatter.



308
309
310
311
312
# File 'lib/lifer/entry.rb', line 308

def updated_at(fallback: nil)
  date_for frontmatter[:updated_at],
    frontmatter[:updated],
    default_date: fallback
end