rf-web/vendor/bundle/gems/jekyll-sass-converter-2.0.1/lib/jekyll/converters/scss.rb
2019-10-21 10:18:17 +02:00

289 lines
8.8 KiB
Ruby

# frozen_string_literal: true
require "sassc"
require "jekyll/utils"
require "jekyll/source_map_page"
module Jekyll
module Converters
class Scss < Converter
BYTE_ORDER_MARK = %r!^\xEF\xBB\xBF!.freeze
EXTENSION_PATTERN = %r!^\.scss$!i.freeze
SyntaxError = Class.new(ArgumentError)
safe true
priority :low
# This hook is triggered just before the method {#convert(content)} is executed, it
# associates the Scss (and Sass) converters with their respective sass_page objects.
Jekyll::Hooks.register :pages, :pre_render do |page|
next unless page.is_a?(Jekyll::Page)
page.converters.each do |converter|
converter.associate_page(page) if converter.is_a?(Jekyll::Converters::Scss)
end
end
# This hook is triggered just after the method {#convert(content)} has been executed, it
# dissociates the Scss (and Sass) converters with their respective sass_page objects.
Jekyll::Hooks.register :pages, :post_render do |page|
next unless page.is_a?(Jekyll::Page)
page.converters.each do |converter|
converter.dissociate_page(page) if converter.is_a?(Jekyll::Converters::Scss)
end
end
ALLOWED_STYLES = %w(nested expanded compact compressed).freeze
# Associate this Converter with the "page" object that manages input and output files for
# this converter.
#
# Note: changing the associated sass_page during the live time of this Converter instance
# may result in inconsistent results.
#
# @param [Jekyll:Page] page The sass_page for which this object acts as converter.
def associate_page(page)
if @sass_page
Jekyll.logger.debug "Sass Converter:",
"sass_page re-assigned: #{@sass_page.name} to #{page.name}"
dissociate_page(page)
return
end
@sass_page = page
end
# Dissociate this Converter with the "page" object.
#
# @param [Jekyll:Page] page The sass_page for which this object has acted as a converter.
def dissociate_page(page)
unless page.equal?(@sass_page)
Jekyll.logger.debug "Sass Converter:",
"dissociating a page that was never associated #{page.name}"
end
@source_map_page = nil
@sass_page = nil
@site = nil
end
def matches(ext)
ext =~ self.class::EXTENSION_PATTERN
end
def output_ext(_ext)
".css"
end
def safe?
!!@config["safe"]
end
def jekyll_sass_configuration
@jekyll_sass_configuration ||= begin
options = @config["sass"] || {}
unless options["style"].nil?
options["style"] = options["style"].to_s.gsub(%r!\A:!, "").to_sym
end
options
end
end
def sass_build_configuration_options(overrides)
if safe?
overrides
else
Jekyll::Utils.symbolize_hash_keys(
Jekyll::Utils.deep_merge_hashes(
jekyll_sass_configuration,
overrides
)
)
end
end
def syntax
:scss
end
def sass_dir
return "_sass" if jekyll_sass_configuration["sass_dir"].to_s.empty?
jekyll_sass_configuration["sass_dir"]
end
def sass_style
style = jekyll_sass_configuration.fetch("style", :compact)
ALLOWED_STYLES.include?(style.to_s) ? style.to_sym : :compact
end
def user_sass_load_paths
Array(jekyll_sass_configuration["load_paths"])
end
def sass_dir_relative_to_site_source
Jekyll.sanitized_path(site_source, sass_dir)
end
# rubocop:disable Metrics/AbcSize
def sass_load_paths
paths = user_sass_load_paths + [sass_dir_relative_to_site_source]
if safe?
# Sanitize paths to prevent any attack vectors (.e.g. `/**/*`)
paths.map! { |path| Jekyll.sanitized_path(site_source, path) }
end
# Expand file globs (e.g. `node_modules/*/node_modules` )
Dir.chdir(site_source) do
paths = paths.flat_map { |path| Dir.glob(path) }.uniq
paths.map! do |path|
if safe?
# Sanitize again in case globbing was able to do something crazy.
Jekyll.sanitized_path(site_source, path)
else
File.expand_path(path)
end
end
end
paths << site.theme.sass_path if site.theme&.sass_path
paths.select { |path| File.directory?(path) }
end
# rubocop:enable Metrics/AbcSize
def allow_caching?
!safe?
end
def add_charset?
!!jekyll_sass_configuration["add_charset"]
end
def sass_configs
sass_build_configuration_options(
:style => sass_style,
:syntax => syntax,
:filename => filename,
:output_path => output_path,
:source_map_file => source_map_file,
:load_paths => sass_load_paths,
:omit_source_map_url => !sourcemap_required?,
:source_map_contents => true,
:line_comments_option => line_comments_option
)
end
def convert(content)
config = sass_configs
engine = SassC::Engine.new(content.dup, config)
output = engine.render
generate_source_map(engine) if sourcemap_required?
replacement = add_charset? ? '@charset "UTF-8";' : ""
output.sub(BYTE_ORDER_MARK, replacement)
rescue SassC::SyntaxError => e
raise SyntaxError, e.to_s
end
private
# The Page instance for which this object acts as a converter.
attr_reader :sass_page
def associate_page_failed?
!sass_page
end
# The name of the input scss (or sass) file. This information will be used for error
# reporting and will written into the source map file as main source.
#
# Returns the name of the input file or "stdin" if #associate_page failed
def filename
return "stdin" if associate_page_failed?
sass_page.name
end
# The value of the `line_comments` option.
# When set to `true` causes the line number and filename of the source be emitted into the
# compiled CSS-file. Useful for debugging when the source-map is not available.
#
# Returns the value of the `line_comments`-option chosen by the user or 'false' by default.
def line_comments_option
jekyll_sass_configuration.fetch("line_comments", false)
end
# The value of the `sourcemap` option chosen by the user.
#
# This option controls when sourcemaps shall be generated or not.
#
# Returns the value of the `sourcemap`-option chosen by the user or ':always' by default.
def sourcemap_option
jekyll_sass_configuration.fetch("sourcemap", :always).to_sym
end
# Determines whether a sourcemap shall be generated or not.
#
# Returns `true` if a sourcemap shall be generated, `false` otherwise.
def sourcemap_required?
return false if associate_page_failed? || sourcemap_option == :never
return true if sourcemap_option == :always
!(sourcemap_option == :development && Jekyll.env != "development")
end
# The name of the generated css file. This information will be written into the source map
# file as a backward reference to the input.
#
# Returns the name of the css file or "stdin.css" if #associate_page failed
def output_path
return "stdin.css" if associate_page_failed?
sass_page.basename + ".css"
end
# The name of the generated source map file. This information will be written into the
# css file to reference to the source map.
#
# Returns the name of the css file or "" if #associate_page failed
def source_map_file
return "" if associate_page_failed?
sass_page.basename + ".css.map"
end
def source_map_page
return if associate_page_failed?
@source_map_page ||= SourceMapPage.new(sass_page)
end
# Reads the source-map from the engine and adds it to the source-map-page.
#
# @param [::SassC::Engine] engine The sass Compiler engine.
def generate_source_map(engine)
return if associate_page_failed?
source_map_page.source_map(engine.source_map)
site.pages << source_map_page
rescue ::SassC::NotRenderedError => e
Jekyll.logger.warn "Could not generate source map #{e.message} => #{e.cause}"
end
def site
if associate_page_failed?
Jekyll.sites.last
else
sass_page.site
end
end
def site_source
site.source
end
end
end
end