Class: Jekyll::DistorteD::Invoker

Inherits:
Liquid::Tag
  • Object
show all
Includes:
Floor, StaticState
Defined in:
lib/distorted-jekyll/invoker.rb

Constant Summary collapse

GEM_ROOT =
File.dirname(__FILE__).freeze
MEDIA_MOLECULES =

Enabled media_type drivers. These will be attempted back to front. TODO: Make this configurable.

[
  Jekyll::DistorteD::Molecule::LastResort,
  Jekyll::DistorteD::Molecule::Font,
  Jekyll::DistorteD::Molecule::Text,
  Jekyll::DistorteD::Molecule::PDF,
  Jekyll::DistorteD::Molecule::SVG,
  Jekyll::DistorteD::Molecule::Video,
  Jekyll::DistorteD::Molecule::Image,
]
TYPE_MOLECULES =

Reduce the above to a Hash of Sets of MediaMolecules-per-Type, keyed by Type.

MEDIA_MOLECULES.reduce(
  Hash.new{|hash, key| hash[key] = Set[]}
) { |types, molecule|
  if molecule.const_defined?(:LOWER_WORLD)
    molecule.const_get(:LOWER_WORLD).each { |t|
      types.update(t => Set[molecule]) { |k,o,n| o.merge(n) }
    }
  end
  types
}
ARBITRARY_ATTR_SYMBOL_STRING_LENGTH_BOUNDARY =

Any any attr value will get a to_sym if shorter than this totally arbitrary length, or if the attr key is in the plugged Molecule’s set of attrs that take only a defined set of values. My chosen boundary length fits all of the outer-limit tag names I use, like ‘medium’. It fits the longest value of Vips::Interesting too, though ‘crop` will be symbolized based on the other condition.

13

Constants included from StaticState

StaticState::ATTRIBUTES

Constants included from Floor

Floor::ATTRIBUTES, Floor::CONFIG_ROOT, Floor::DEFAULT_CONFIG_FILE_NAME, Floor::DEFAULT_CONFIG_PATH, Floor::PATH_SEPARATOR, Floor::PP_SEPARATOR

Instance Method Summary collapse

Methods included from StaticState

#destination, #destinations, #modified?, #write, #write?

Methods included from Floor

#changes, config, #lower_world, #outer_limits, #search_keys, set_me_free, symbolic

Constructor Details

#initialize(tag_name, arguments, liquid_options) ⇒ Invoker

𝘏𝘖𝘞 𝘈𝘙𝘌 𝘠𝘖𝘜 𝘎𝘌𝘕𝘛𝘓𝘌𝘔𝘌𝘕 !!



80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
# File 'lib/distorted-jekyll/invoker.rb', line 80

def initialize(tag_name, arguments, liquid_options)
  super
  # Tag name as given to Liquid::Template.register_tag().
  @tag_name = tag_name.to_sym

  # Liquid leaves argument parsing totally up to us.
  # Use the envygeeks/liquid-tag-parser library to wrangle them.
  parsed_arguments = Liquid::Tag::Parser.new(arguments)

  # Filename is the only non-keyword argument our tag should ever get.
  # It's spe-shul and gets its own definition outside the attr loop.
  if parsed_arguments.key?(:src)
    @name = parsed_arguments.delete(:src)
  else
    @name = parsed_arguments.delete(:argv1)
  end
  @liquid_liquid = parsed_arguments.select{ |attr, val|
    not [nil, ''.freeze].include?(val)
  }.transform_keys { |attr|
    attr.length <= ARBITRARY_ATTR_SYMBOL_STRING_LENGTH_BOUNDARY ? attr.to_sym : attr.freeze
  }.transform_values { |val|
    if val.respond_to?(:length)
      val.length <= ARBITRARY_ATTR_SYMBOL_STRING_LENGTH_BOUNDARY ? val.to_sym : val.freeze
    else
      val
    end
  }

  # If we didn't get one of the two above options there is nothing we
  # can do but bail.
  unless @name
    raise "Failed to get a usable filename from #{arguments}"
  end

end

Instance Method Details

#media_moleculeObject

Decides which MediaMolecule is most appropriate for our file and returns it.



138
139
140
141
142
143
144
145
146
147
# File 'lib/distorted-jekyll/invoker.rb', line 138

def media_molecule
  available_molecules = TYPE_MOLECULES.keys.to_set & type_mars
  # TODO: Handle multiple molecules for the same file
  case available_molecules.length
  when 0
    raise MediaTypeNotImplementedError.new(@name)
  when 1
    return TYPE_MOLECULES[available_molecules.first].first
  end
end

#parse_template(site: nil, name: nil) ⇒ Object

Generic Liquid template loader that will be used in every MediaMolecule. Callers will call ‘render(**=> vars)` on the Object returned by this method.



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
# File 'lib/distorted-jekyll/invoker.rb', line 213

def parse_template(site: nil, name: nil)
  site = site || @site || Jekyll.sites.first
  begin
    # Use a given filename, or detect one based on media-type.
    if name.nil?
      # e.g. Jekyll::DistorteD::Molecule::Image -> 'image.liquid'
      name = "#{self.singleton_class.instance_variable_get(:@media_molecule).name.gsub(/^.*::/, '').downcase}.liquid".freeze
    elsif not name.include?('.liquid'.freeze)
      # Support filename arguments with and without file extension.
      # The given String might already be frozen, so concatenating
      # the extension might fail. Just set a new version.
      name = "#{name}.liquid"
    end
    template = File.join(
      self.singleton_class.const_get(:GEM_ROOT),
      'template'.freeze,
      name,
    )

    # Jekyll's Liquid renderer caches in 4.0+.
    if Jekyll::DistorteD::Floor::config(
        Jekyll::DistorteD::Floor::CONFIG_ROOT,
        :cache_templates,
    )
      # file(path) is the caching function, with path as the cache key.
      # The `template` here will be the full path, so no versions of this
      # gem should ever conflict. For example, right now during dev it's:
      # `/home/okeeblow/Works/DistorteD/lib/image.liquid`
      Jekyll.logger.debug('DistorteD', "Parsing #{template} with caching renderer.")
      site.liquid_renderer.file(template).parse(File.read(template))
    else
      # Re-read the template just for this piece of media.
      Jekyll.logger.debug('DistorteD', "Parsing #{template} with fresh (uncached) renderer.")
      Liquid::Template.parse(File.read(template))
    end

  rescue Liquid::SyntaxError => l
    # This shouldn't ever happen unless a new version of Liquid
    # breaks syntax compatibility with our templates somehow.
    l.message
  end
end

#plugObject



149
150
151
152
153
154
155
# File 'lib/distorted-jekyll/invoker.rb', line 149

def plug
  unless self.singleton_class.instance_variable_defined?(:@media_molecule)
    self.singleton_class.instance_variable_set(:@media_molecule, media_molecule)
    self.singleton_class.prepend(media_molecule)
    Jekyll.logger.info(@name, "Plugging #{media_molecule}")
  end
end

#render(context) ⇒ Object



160
161
162
163
# File 'lib/distorted-jekyll/invoker.rb', line 160

def render(context)
  plug
  render_to_output_buffer(context, '')
end

#render_to_output_buffer(context, output) ⇒ Object

A future Liquid version (5.0?) will call this function directly instead of calling render()



167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
# File 'lib/distorted-jekyll/invoker.rb', line 167

def render_to_output_buffer(context, output)
  plug
  # Get Jekyll Site object back from tag rendering context registers so we
  # can get configuration data and path information from it and
  # then pass it along to our StaticFile subclass.
  @site = context.registers[:site]

  # The rendering context's `first` page will be the one that invoked us.
  page_data = context.environments.first['page'.freeze]

  #
  # Our subclass' additional args:
  # dest - The String path to the generated `url` folder of the page HTML output
  @base = @site.source

  # `relative_path` doesn't seem to always exist, but `path` does? idk.
  # I was testing with `relative_path` only with `_posts`, but it broke
  # when I invoked DD on a _page. Both have `path`.
  @dir = File.dirname(page_data['path'.freeze])

  # Every one of Ruby's `File.directory?` / `Pathname.directory?` /
  # `FileTest.directory?` methods actually tests that path on the
  # real filesystem, but we shouldn't look at the FS here because
  # this function gets called when the Site.dest directory does
  # not exist yet!
  # Hackily look at the last character to see if the URL is a
  # directory (like configured on cooltrainer) or a `.html`
  # (or other extension) like the default Jekyll config.
  # Get the dirname if the url is not a dir itself.
  @relative_dest = page_data['url'.freeze]
  unless @relative_dest[-1] == Jekyll::DistorteD::Floor::PATH_SEPARATOR
    @relative_dest = File.dirname(@relative_dest)
    # Append the trailing slash so we don't have to do it
    # in the Liquid templates.
    @relative_dest << Jekyll::DistorteD::Floor::PATH_SEPARATOR
  end

  # Add our new file to the list that will be handled
  # by Jekyll's built-in StaticFile generator.
  @site.static_files << self
  output
end

#type_marsObject

Returns a Set of DD MIME::Types descriving our file, optionally falling through to a plain file copy.



118
119
120
121
122
123
124
125
126
127
128
# File 'lib/distorted-jekyll/invoker.rb', line 118

def type_mars
  @type_mars ||= begin
    mime = CHECKING::YOU::OUT(@name)
    if mime.empty?
      if Jekyll::DistorteD::Floor::config(Jekyll::DistorteD::Floor::CONFIG_ROOT, :last_resort)
        mime = Jekyll::DistorteD::Molecule::LastResort::LOWER_WORLD
      end
    end
    mime
  end
end

#user_argumentsObject

Return any arguments given by the user to our Liquid tag. This method name is generic across all DD entrypoints so it can be referenced from lower layers in the pile.



133
134
135
# File 'lib/distorted-jekyll/invoker.rb', line 133

def user_arguments
  @liquid_liquid || Hash[]
end