lib/jekyll/assets/tag.rb
# Frozen-string-literal: true
# rubocop:disable Style/AccessModifierDeclarations
# Copyright: 2012 - 2020 - ISC License
# Encoding: utf-8
require 'fastimage'
require 'liquid/tag/parser'
require 'nokogiri'
module Jekyll
module Assets
class Tag < Liquid::Tag
class << self
public :new
end
class MixedArg < StandardError
def initialize(arg, mixed)
super "cannot use #{arg} w/ #{mixed}"
end
end
class InvalidExternal < StandardError
def initialize(arg)
super "cannot use `#{arg}' with external url's"
end
end
attr_reader :name
attr_reader :tokens
attr_reader :args
attr_reader :tag
#
# initialize a new instance
# @param tag [String,Symbol] the current tag ('asset')
# @param args [Hash, String, Liquid::Tag::Parser] the arguments
# @param tokens [Liquid::ParseContext] the tokens
# @return [void]
#
def initialize(tag, args, tokens)
@tag = tag.to_sym
@tokens = tokens
@args = args
super
end
#
# parse, or return the args
# @note you can pass in parsed args
# @return [Liquid::Tag::Parser]
#
def parse_args(args)
return args if args.is_a?(Liquid::Tag::Parser) || args.is_a?(Hash)
Liquid::Tag::Parser.new(
@args
)
end
def render_raw(ctx)
args = parse_args(@args)
env = ctx.registers[:site].sprockets
args = env.parse_liquid(args, ctx: ctx)
raise_unfound_asset_on(ctx: ctx, with: args) unless args.key?(:argv1)
asset = external(ctx, args: args) if env.external?(args)
asset ||= internal(ctx, args: args)
[
args, asset
]
end
#
# @return [String]
# Render the tag, run the proxies, set the defaults.
# @note Defaults are ran twice just incase the content type
# changes, at that point there might be something that
# has to change in the new content.
#
def render(ctx)
env = ctx.registers[:site].sprockets
args, asset = render_raw(
ctx
)
env.logger.debug args.to_h(html: false).inspect
return_or_build(ctx, args: args, asset: asset) do
HTML.build(
args: args,
asset: asset,
ctx: ctx,
)
end
#
rescue Sprockets::FileNotFound => e
e_not_found(e,
ctx: ctx
)
#
rescue ExecJS::RuntimeError => e
e_exjs(e,
args: args,
ctx: ctx
)
#
# @note you can --trace to get this same info
# Handle errors that Sass ships because Jekyll finds
# error handling hard, and makes it even harder, so we
# need to ship debug info to the user, or they'll
# never get it. That's not very good.
#
rescue SassC::SyntaxError => e
e_sass(e,
args: args,
ctx: ctx,
)
end
def return_or_build(ctx, args:, asset:)
methods.grep(%r@^on_(?!or_build$)@).each do |m|
out = send(m, args, ctx: ctx, asset: asset)
if out
regenerate(asset,
ctx: ctx
)
return out
end
end
regenerate(asset,
ctx: ctx
)
yield
end
def regenerate(asset, ctx:)
r = ctx.registers[:site].regenerator
r.add(asset.filename)
Array(asset.metadata[:dependencies]).each do |d|
next unless d.start_with?('file-digest:')
p = URI.parse(d).path
r.add(p)
end
end
#
# Returns the path to the asset.
# @example {% asset img.png @path %}
# @return [String]
#
def on_path(args, ctx:, asset:)
env = ctx.registers[:site].sprockets
return unless args[:path]
raise InvalidExternal, '@path' if env.external?(args)
env.prefix_url(asset.digest_path)
end
#
# Returns the data uri of an object.
# @example {% asset img.png @data-url %}
# @example {% asset img.png @data_uri %}
# @return [String]
#
def on_data(args, ctx:, asset:)
env = ctx.registers[:site].sprockets
return unless args[:data]
raise InvalidExternal '@data' if env.external?(args)
asset.data_uri
end
#
# @param [Liquid::Context] ctx
# Set's up an external url using `Url`
# @return [Url]
#
def external(ctx, args:)
env = ctx.registers[:site].sprockets
out = env.external_asset(args[:argv1], args: args)
Default.set(args, ctx: ctx, asset: out)
out
end
#
# @param [Liquid::Context] ctx
# Set's up an internal asset using `Sprockets::Asset`
# @return [Sprockets::Asset]
#
def internal(ctx, args:)
env = ctx.registers[:site].sprockets
original = env.find_asset!(args[:argv1])
Default.set(args, ctx: ctx, asset: original)
out = Proxy.proxy(original, args: args, ctx: ctx)
env.assets_to_write |= [out.logical_path]
Default.set(args,
ctx: ctx, asset: out
)
out
end
private
def raise_unfound_asset_on(ctx:, with:)
raise Sprockets::FileNotFound, "Unknown asset `#{with[:argv1]}'" \
" in #{ctx.registers[:page]['relative_path']}"
end
private
def e_not_found(e, ctx:)
lines = e.message.each_line.to_a
page = ctx.registers[:page]&.[]('relative_path') || ctx.registers[:page]&.[]('path')
lines[0] = lines[0].strip + " in `#{page || 'Untraceable'}'\n\n"
raise e.class, lines.join
end
private
def e_exjs(e, ctx:, args:)
env = ctx.registers[:site].sprockets
env.logger.error e.message
env.logger.err_file args[:argv1]
raise e.class, 'JS Error'
end
private
def e_sass(e, ctx:, args:)
env = ctx.registers[:site].sprockets
env.logger.error e.message
env.logger.err_file env.strip_paths(e.backtrace.first)
env.logger.error "error from file #{args[:argv1]}" if args
raise e.class, 'Sass Error'
end
#
# Register the tag
# @see `jekyll/assets.rb`
# @return [nil]
#
public
def self.register
Liquid::Template.register_tag(
'asset', self
)
end
end
end
end