module Sass::Script::Functions

Overrides official map functions to support nest keys.

Thanks for Compass Document reference: beta.compass-style.org/reference/compass/helpers/selectors/

Constants

COMMA_SEPARATOR
FONT_TYPES
MIME_TYPES
PATH_REGEX

Public Instance Methods

app_config(name) click to toggle source

Returns the config associated with the given name. Configs are be grouped by ‘SASS_ENV` environment.

Examples: $app-config: (

development: (
  foo: bar
),
production: (
  foo: baz
)

);

$ sass –watch -r base.sass src:dist app-config(foo) => bar

$ SASS_ENV=production sass –watch -r base.sass src:dist app-config(foo) => baz

# File lib/base.sass/env.rb, line 33
def app_config(name)
  assert_type name, :String

  config = environment.global_env.var('app-config')
  return null unless config.is_a? Sass::Script::Value::Map

  map_get(config, *[env(identifier('sass-env')), name])
end
append_selector(selector, to_append) click to toggle source
# File lib/base.sass/selector.rb, line 21
def append_selector(selector, to_append)
  ancestors = selector.value.split(COMMA_SEPARATOR)
  descendants = to_append.value.split(COMMA_SEPARATOR)

  nested = ancestors.map { |a|
    descendants.map { |d|
      "#{a}#{d}"
    }.join(', ')
  }.join(', ')

  identifier(nested)
end
caniuse(cond) click to toggle source

Returns the data in CanIUse which base.sass used.

Examples: caniuse(browsers) caniuse(Chrome versions) caniuse(Chrome all versions)

# File lib/base.sass/caniuse.rb, line 35
def caniuse(cond)
  cond = [sass_to_ruby(cond)].flatten.map { |w| w.downcase }

  output = if cond.first == 'browsers'
    caniuse_browsers
  elsif cond.last == 'versions'
    browser = cond.first
    assert_valid_browser(browser)
    caniuse_versions(browser, cond.include?('all'))
  else
    raise Sass::SyntaxError, "Unknown condition.\nYou can try `caniuse(browsers)` or `caniuse(Chrome versions)`"
  end

  ruby_to_sass(output)
end
enumerate(prefix, from, through, separator = identifier('-')) click to toggle source
# File lib/base.sass/selector.rb, line 34
def enumerate(prefix, from, through, separator = identifier('-'))
  selectors = (from.value..through.value).map { |i|
    "#{prefix.value}#{separator.value}#{i}"
  }.join(', ')

  identifier(selectors)
end
env(name) click to toggle source

Returns the value of environment variable associated with the given name. Returns null if the named variable does not exist.

Examples: env(SASS_ENV) => development env(sass_env) => development env(sass-env) => development

# File lib/base.sass/env.rb, line 10
def env(name)
  assert_type name, :String
  ruby_to_sass(ENV[name.value.gsub('-', '_').upcase])
end
headers(from = nil, to = nil) click to toggle source
# File lib/base.sass/selector.rb, line 42
def headers(from = nil, to = nil)
  if from && !to
    if from.is_a?(Sass::Script::Value::String) && from.value == 'all'
      to, from = number(6), number(1)
    else
      to, from = from, number(1)
    end
  else
    from ||= number(1)
    to ||= number(6)
  end

  list((from.value..to.value).map { |n| identifier("h#{n}") }, :comma)
end
Also aliased as: headings
headings(from = nil, to = nil)
Alias for: headers
map_delete(map, *keys) click to toggle source

Returns a new map with keys removed.

Examples: $map: (a: (b: (c: 1))); map-delete($map, a) => () map-delete($map, a, b) => (a: ()) map-delete($map, a, b, c) => (a: (b: ())) map-delete($map, x) => (a: (b: (c: 1))) map-delete($map, a, x, c) => (a: (b: (c: 1))) map-delete($map, a, b, c, x) => (a: (b: (c: 1)))

# File lib/base.sass/map.rb, line 43
def map_delete(map, *keys)
  return map unless map_has_key(map, *keys).to_bool

  target, hash = keys.pop, get_hash(map, keys)
  hash.delete target

  while keys.size > 0
    target = keys.pop
    _hash, hash = map(hash), get_hash(map, keys)
    hash[target] = _hash
  end

  map(hash)
end
map_get(map, *keys) click to toggle source

Returns the value in a map associated with the given keys.

Examples: $map: (a: (b: (c: 1))); map-get($map, a) => (b: (c: 1)) map-get($map, a, b) => (c: 1) map-get($map, a, b, c) => 1 map-get($map, x) => null map-get($map, a, x, c) => null map-get($map, a, b, c, x) => null map-get((), x) => null

# File lib/base.sass/map.rb, line 15
def map_get(map, *keys)
  assert_type map, :Map
  assert_args_number(keys)

  hash, target = map.to_h, keys.pop

  keys.each do |key|
    # Each parent node must be a map
    unless hash[key].is_a? Sass::Script::Value::Map
      hash = {}
      break
    end
    hash = hash[key].value
  end

  hash[target] || null
end
map_has_key(map, *keys) click to toggle source

Returns whether a map has a value associated with a given keys.

Examples: $map: (a: (b: (c: 1))); map-has-key($map, a) => true map-has-key($map, a, b) => true map-has-key($map, a, c) => false map-has-key($map, a, b, c) => true map-has-key($map, a, x, c) => false map-has-key($map, a, b, c, x) => false

# File lib/base.sass/map.rb, line 68
def map_has_key(map, *keys)
  assert_type map, :Map
  assert_args_number(keys)

  hash = map.to_h

  keys.each do |key|
    # Each parent node must be a map
    return bool(false) unless hash.is_a?(Hash) && hash.key?(key)
    hash = hash[key].value
  end

  bool(true)
end
map_merge(map1, map2, deep = bool(false)) click to toggle source

Merges two maps together into a new map recursively.

Examples: $map1: (a: (b: (c: 1 2, d: foo, e: baz))); $map2: (a: (b: (c: 3 4, d: bar))); map-merge($map1, $map2) => (a: (b: (c: 3 4, d: bar))) map-merge($map1, $map2, true) => (a: (b: (c: 1 2 3 4, d: bar, e: baz)))

# File lib/base.sass/map.rb, line 90
def map_merge(map1, map2, deep = bool(false))
  assert_type map1, :Map
  assert_type map2, :Map

  map1, map2 = map1.to_h.dup, map2.to_h
  return map(map1.merge(map2)) unless deep.to_bool

  map2.each do |k, v|
    orig = map1[k]
    map1[k] = compare_value(orig, v)
  end

  map(map1)
end
nest(*args) click to toggle source
# File lib/base.sass/selector.rb, line 6
def nest(*args)
  nested = args.map { |a| a.value }.inject do |memo, arg|
    ancestors = memo.split(COMMA_SEPARATOR)
    descendants = arg.split(COMMA_SEPARATOR)

    ancestors.map { |a|
      descendants.map { |d|
        "#{a} #{d}"
      }.join(', ')
    }.join(', ')
  end

  identifier(nested)
end
parse_json(path) click to toggle source

Parses a local json file, returns a map, and the result will be cached. If the ‘path` is not a absolute path, relative to current process directory.

Examples: parse-json(‘~/Desktop/example.json’) parse-json(‘package.json’)

# File lib/base.sass/parse-json.rb, line 13
def parse_json(path)
  assert_type path, :String
  path = File.expand_path(path.value)

  if $cached_files.key? path
    Sass.logger.debug "Reading file from cache: #{path}"
    $cached_files[path]
  else
    $cached_files[path] = ruby_to_sass(load_json(path))
  end
end
parse_rules(*rules) click to toggle source

Returns the specified browsers and versions associated with the given rules.

Rules:

  1. last 1 version is last versions for each browser.

  2. last 2 Chrome versions is last versions of the specified browser.

  3. IE > 8 is ie versions newer than 8.

  4. IE >= 8 is ie version 8 or newer.

  5. iOS 7 to set browser version directly.

# File lib/base.sass/parse-rules.rb, line 11
def parse_rules(*rules)
  rules = rules.map { |r| sass_to_ruby(r) }.flatten.uniq

  # Parse
  selected = rules.map { |r| rules_parser(r.downcase) }.compact
  # Merge
  selected = selected.inject { |memo, p|
    memo.merge(p) { |k, orig, added| orig + added }
  }
  # Uniq & Sort
  selected.each { |k, v|
    v.uniq!
    v.sort!
  }

  ruby_to_sass(selected)
end
strftime(format = nil) click to toggle source

Formats time according to the directives in the given format string. Read more: www.ruby-doc.org/core-2.1.1/Time.html#method-i-strftime

Examples: strftime() => 1399392214 strftime(‘%FT%T%:z’) => 2014-05-07T00:03:34+08:00 strftime(‘at %I:%M%p’) => at 12:03AM

# File lib/base.sass/strftime.rb, line 10
def strftime(format = nil)
  time = Time.now.localtime

  if format
    assert_type format, :String
    identifier(time.strftime(format.value))
  else
    identifier(time.to_i.to_s)
  end
end
url(*paths) click to toggle source

Reinforce the official ‘url()` in CSS to support multi url and data url. Activates only when all paths are wrapped with quotes.

Examples: url() => url() # Did nothing url(‘’) => url(a.com/b.png?1399394203) url(‘a.png’, ‘b.png’) => url(a.png?1399394203), url(b.png?1399394203) url(‘a.eot#iefix’, ‘b.woff’) => url(a.eot?1399394203#iefix) format(‘embedded-opentype’), url(b.woff?1399394203) format(‘woff’)

url(‘a.png’, $timestamp: false) => url(a.png) url(‘a.png’, $timestamp: ‘1.0.0’) => url(a.png?1.0.0)

$app-config: (timestamp: ‘1.0.0’); url(‘a.png’) => url(a.png?1.0.0)

$app-config: (timestamp: ‘p1’); url(‘a.png’, $timestamp: ‘p0’) => url(a.png?p0)

url(‘a.png’, $base64: true) => url(data:image/png;base64,iVBORw…)

# File lib/base.sass/url.rb, line 42
def url(*paths)
  kwargs = paths.last.is_a?(Hash) ? paths.pop : {}
  raise Sass::SyntaxError, 'url() needs one path at least' if paths.empty?

  encode = kwargs['base64'] == bool(true)
  ts = timestamp(kwargs['timestamp'])

  paths = paths.map { |path| sass_to_ruby(path) }.flatten
               .map { |path| to_url(path, encode, ts) }

  list(paths, :comma)
end

Protected Instance Methods

assert_valid_browser(browser, version = nil) click to toggle source
# File lib/base.sass/parse-rules.rb, line 42
def assert_valid_browser(browser, version = nil)
  unless caniuse_browsers.include? browser
    raise Sass::SyntaxError, "Unknown browser name: #{browser}\nYou can find all valid names according to `caniuse(browsers)`"
  end

  unless version.nil? || caniuse_versions(browser, true).include?(version)
    raise Sass::SyntaxError, "Unknown version for #{browser}: #{version}\nYou can find all valid versions according to `caniuse(#{browser} all versions)`"
  end
end
caniuse_browsers() click to toggle source
# File lib/base.sass/parse-rules.rb, line 32
def caniuse_browsers
  @browsers ||= CanIUse.instance.browsers
end
caniuse_versions(browser, include_future) click to toggle source
# File lib/base.sass/parse-rules.rb, line 36
def caniuse_versions(browser, include_future)
  @versions ||= {}
  k = browser.to_s + include_future.to_s
  @versions[k] ||= CanIUse.instance.versions(browser, include_future)
end
load_json(path) click to toggle source
# File lib/base.sass/parse-json.rb, line 28
def load_json(path)
  JSON.load(
    read_file(File.expand_path(path)).to_s.gsub(/(\\r|\\n)/, '')
  )
end
read_file(path) click to toggle source
# File lib/base.sass/parse-json.rb, line 34
def read_file(path)
  raise Sass::SyntaxError, "File not found or cannot be read: #{path}" unless File.readable? path

  Sass.logger.debug "Reading file: #{path}"
  File.open(path, 'rb') { |f| f.read }
end
ruby_to_sass(obj) click to toggle source
# File lib/base.sass/ruby-to-sass.rb, line 5
def ruby_to_sass(obj)
  return bool(obj) if obj.is_a?(TrueClass) || obj.is_a?(FalseClass)
  return null if obj.nil?
  return number(obj) if obj.is_a? Numeric
  return to_sass_list(obj) if obj.is_a? Array
  return to_sass_map(obj) if obj.is_a? Hash
  identifier(obj.to_s)
end
sass_to_ruby(obj) click to toggle source
# File lib/base.sass/sass-to-ruby.rb, line 5
def sass_to_ruby(obj)
  return to_ruby_hash(obj) if obj.is_a? Sass::Script::Value::Map
  return to_ruby_array(obj) if obj.is_a? Sass::Script::Value::List
  return obj.inspect if obj.is_a? Sass::Script::Value::Color
  obj.value
end
to_ruby_array(sass_list) click to toggle source
# File lib/base.sass/sass-to-ruby.rb, line 19
def to_ruby_array(sass_list)
  sass_list.to_a.map do |item|
    sass_to_ruby(item)
  end
end
to_ruby_hash(sass_map) click to toggle source
# File lib/base.sass/sass-to-ruby.rb, line 12
def to_ruby_hash(sass_map)
  sass_map.to_h.inject({}) do |memo, (k, v)|
    memo[k.to_s] = sass_to_ruby(v)
    memo
  end
end
to_sass_list(ruby_array) click to toggle source
# File lib/base.sass/ruby-to-sass.rb, line 27
def to_sass_list(ruby_array)
  list(ruby_array.map { |item|
    ruby_to_sass(item)
  }, :comma)
end
to_sass_map(ruby_hash) click to toggle source
# File lib/base.sass/ruby-to-sass.rb, line 14
def to_sass_map(ruby_hash)
  sass_map = map({})

  ruby_hash.each do |k, v|
    sass_map = map_merge(
      sass_map,
      map(Hash[identifier(k.to_s), ruby_to_sass(v)])
    )
  end

  sass_map
end

Private Instance Methods

assert_args_number(keys) click to toggle source
# File lib/base.sass/map.rb, line 108
def assert_args_number(keys)
  raise ArgumentError.new('wrong number of arguments (1 for 2+)') if keys.empty?
end
compare_value(oldVal, newVal) click to toggle source
# File lib/base.sass/map.rb, line 116
def compare_value(oldVal, newVal)
  if oldVal.is_a?(Sass::Script::Value::Map) && newVal.is_a?(Sass::Script::Value::Map)
    map_merge(oldVal, newVal, bool(true))
  elsif oldVal.is_a?(Sass::Script::Value::List) && newVal.is_a?(Sass::Script::Value::List)
    join(oldVal, newVal)
  else
    newVal
  end
end
direct_parser(browser, version) click to toggle source
# File lib/base.sass/parse-rules.rb, line 104
def direct_parser(browser, version)
  version = to_if(version)
  assert_valid_browser(browser, version)
  Hash[browser, [version]]
end
get_hash(map, keys) click to toggle source
# File lib/base.sass/map.rb, line 112
def get_hash(map, keys)
  (keys.empty? ? map : map_get(map, *keys)).to_h.dup
end
last_browser_versions_parser(num, browser, assert) click to toggle source
# File lib/base.sass/parse-rules.rb, line 85
def last_browser_versions_parser(num, browser, assert)
  assert_valid_browser(browser) if assert
  Hash[browser, caniuse_versions(browser, false).last(num.to_i)]
end
last_versions_parser(num) click to toggle source
# File lib/base.sass/parse-rules.rb, line 79
def last_versions_parser(num)
  caniuse_browsers.map { |browser|
    last_browser_versions_parser(num, browser, false)
  }.inject :merge
end
newer_then_parser(browser, sign, version) click to toggle source
# File lib/base.sass/parse-rules.rb, line 90
def newer_then_parser(browser, sign, version)
  assert_valid_browser(browser)

  versions = caniuse_versions(browser, false)
  versions = case sign
    when '>='
      versions.select { |n| n >= version.to_f }
    when '>'
      versions.select { |n| n > version.to_f }
    end

  versions.empty? ? nil : Hash[browser, versions]
end
output_data(path, ext) click to toggle source
# File lib/base.sass/url.rb, line 106
def output_data(path, ext)
  data = [read_file(File.expand_path(path))].pack('m').gsub(/\s/, '')
  "url(data:#{MIME_TYPES[ext]};base64,#{data})"
end
output_path(path, ext, query, anchor, ts) click to toggle source
# File lib/base.sass/url.rb, line 111
def output_path(path, ext, query, anchor, ts)
  query += sign(query) + ts unless ts.nil?

  output = "url(#{path}#{query}#{anchor})"
  return output unless FONT_TYPES.key? ext

  [identifier(output), identifier("format('#{FONT_TYPES[ext]}')")]
end
rules_parser(rule) click to toggle source
# File lib/base.sass/parse-rules.rb, line 55
def rules_parser(rule)
  case rule

  # match `last 1 version`
  when /^last (\d+) versions?$/
    last_versions_parser($1)

  # match `last 3 chrome versions`
  when /^last (\d+) (\w+) versions?$/
    last_browser_versions_parser($1, $2, true)

  # match `ie > 9`
  when /^(\w+) (>=?) ([\d\.]+)$/
    newer_then_parser($1, $2, $3)

  # match `ios 7`
  when /^(\w+) ([\d\.]+)$/
    direct_parser($1, $2)

  else
    raise Sass::SyntaxError, "Unknown rule: `#{rule}`"
  end
end
sign(query) click to toggle source
# File lib/base.sass/url.rb, line 71
def sign(query)
  case query.size
  when 0
    '?'
  when 1
    ''
  else
    '&'
  end
end
timestamp(ts) click to toggle source
# File lib/base.sass/url.rb, line 59
def timestamp(ts)
  # no kwargs
  if ts.nil?
    cfg = app_config(identifier('timestamp'))
    ts = cfg == null ? bool(true) : cfg
  end

  return nil unless ts.to_bool
  return strftime.value if ts.is_a? Sass::Script::Value::Bool
  ts.value.to_s
end
to_if(s) click to toggle source
# File lib/base.sass/parse-rules.rb, line 110
def to_if(s)
  s.include?('.') ? s.to_f : s.to_i
end
to_url(path, encode, ts) click to toggle source
# File lib/base.sass/url.rb, line 82
def to_url(path, encode, ts)
  output = "url(#{path})"

  if path.is_a?(String) && path =~ PATH_REGEX

    path, ext, query, anchor = $1 + $2, $2[1..-1].downcase.to_sym, $3, $4

    if MIME_TYPES.key? ext
      output = if encode
        output_data(path, ext)
      else
        output_path(path, ext, query, anchor, ts)
      end
    end

  end

  if output.is_a? Array
    list(output, :space)
  else
    identifier(output)
  end
end