These functions provide forward-compatible map (hash) data-type functionality for libsass and ruby-sass 3.2.x using the SassScript
list data-type. They are a polyfill for the
map data-type introduced in ruby-sass 3.3.x. They feature-match all the current native (ruby-sass)
map functions, and add nested (or 'chained')
merge() functions and inspection / debugging functions as well.
- 0.9.9 -- added
map-pretty()function to inspect/debug list-maps in pretty-printed form
- 0.9.6-0.9.8 -- argument-handling enhancements; typo fixes
- 0.9.3-0.9.5 -- handling single-pair lists automatically. This means no more need for
zip()functions, which are now deprecated.
- 0.9.2 -- improved merge performance with new
set()aliases by default
- 0.9.1 -- now listed at the sache.in directory of Sass & Compass Extensions
You can test-drive these functions at Sassmeister, in this pre-loaded gist—but note that the libsass version at Sassmeister might be a couple of point-releases behind this repo.
'Sass List-Maps' can be installed as a Bower component for non-ruby setups (see node-sass options if you are using libsass via node) or as a gem-based Compass extension for ruby setups:
# installation with bowerbower install sass-list-maps# if using grunt-sass, you need to set 'includePaths' optionoptions:# installation with rubygems, for compassgem install sass-list-maps# Add to your Sass@import "sass-list-maps";
You can of course also just fork or download this repo or copy-and-paste, as the functions are all in one file.
Maps (known in programming as hashes, dictionaries, or objects1) allow dynamic creating, setting, merging and retrieving of data. They are native to ruby-sass as of version 3.3.x, but for earlier ruby-sass versions, and for the libsass C-based compiler (until the point at which maps are integrated there natively), this is an alternative solution which feature-matches ruby-sass' 3.3.x map functionality using the
list data-type. Additional functions are also provided to allow nested (chained) getting and merging/setting, and inspection for debugging.
Compared to ruby-sass' native maps, 'list-maps' are lists like any other list in Sass, but they are lists of pairs, formatted in such a way as to be interpreted like a map. To this purpose, the first item in each pair is interpreted as the 'key' (usually a string), while the second is interpreted as the correspondent 'value'. This 'value' can be any Sass-script data-type, including a list—which means list-maps can contain other list-maps, allowing them to form nested data structures.
The formatting used here keeps as close as possible to the syntax of native maps, with the difference that there no colons (
:) used, and the placement of commas is more critical (e.g. a comma after the last item is not allowed):
/* a single-line list-map -- compatible with anyversion/compiler of sass including 3.3+ */;/* a single-line ruby-sass native map-- would cause an error in any version/compilerother than ruby-sass 3.3+ */;/* a multi-line list-map */;/* a mutli-line ruby-sass native map */s;
It should be clear that these 'list-maps' and ruby-sass' native maps are very similar—in fact they are in principle the same (native maps are a special type of list). For this reason it was possible to reverse engineer the map functions of ruby-sass' 3.3+ to use the SassScript 'list' data-type.
These functions have the same names as the map functions in ruby-sass >= 3.3.x, which means that if they were used in ruby-sass 3.3.x or higher they would conflict. Therefore, the following code assume a sass environment of either ruby-sass < 3.3.x or libsass. Also, as with native maps in ruby-sass, native list functions (e.g.
index()) can also be used on list-maps since they are still lists.
Core (matching the ruby-sass 3.3.x native map functions)
map-merge($list1, $list2), map-remove($list, $key)
;;;// -> $new-map = ( alpha 1, beta 2, gamma 4 );// -> $short-map = ( beta 2, gamma 3)
NB: you might notice in the second example above, that the second argument to map-merge isn't really a 'list-map' it's just a list of two items. This is the so-called "single item" list conundrum in Sass which is a bit tricky, but these functions as of 0.9.5 handle this case automatically.
Advanced (beyond the ruby-sass 3.3.x native map functions)
In addition to ruby-sass' native map functionality, this library also provides nested (deep / chained) 'get' and 'set'/'merge' and debugging functions.
Nesting / Chaining
map-get-z() function will retrieve values from a list-map according to a chain of keys (similar to the way nested array/hash/object values are accessed in other languages):
map-merge-z() function takes a chain of keys to indicate where (at what depth) to merge, and takes its final argument as the value to be merged. This value can be of any type including another list/list-map. Note that if only one key/value argument is passed and it is not a list, it is interpreted as the key, and an empty list is merged in as the value:
;;;// -> ( alpha ( beta ( gamma 3 ) ), ( delta ( ) ) );// -> ( alpha ( beta ( gamma 3 ) ), ( delta epsilon ) );// -> ( alpha ( beta ( gamma 3 ) ), ( delta epsilon ) );// -> ( alpha ( beta ( gamma 3 ) ), ( delta 4 ), ( epsilon 5 ) );// -> ( alpha ( beta ( gamma 3 ) ), ( delta ( epsilon 5 ) ) )
Inspection / Debugging
To aid in development, list-map inspection functions are provided.
map-inspect() will format a list-map as a string on one line, while
map-pretty() will format the same string on multiple lines with indentation.
One Syntax to Rule them All
Since the 'advanced' nested/chained
map-merge-z() take a variable number of
map-merge-z() can accept argument patterns consistent with both merge- and set-style operations, the following aliases can provide unified function syntaxes to replace the 'core'
merge() functions while adding a
;// get($list, $key[s...])// accepts 1 or more key args as target, returns value// merge($list1, [$keys...,] $list2)// accepts 0 or more key args as target, merges list at target// set($list, $key[s...], $value)// accepts 1 or more key args as target, sets value at target
There are a few points that bear mentioning/repeating:
- operating on global variables in libsass and in ruby-sass 3.2.x or earlier, works differently than in 3.3.x+: You can make changes to global variables from inside a mixin scope but you can't create them from there. There is no
!globalflag. This has implications for mixins that operate on global list-maps.
- as noted, the 'list-map' syntax is less forgiving than that of native maps (watch your commas). Also, it lacks any error-checking (e.g. native maps will produce a warning if you have duplicate keys). And obviously fancy features of native maps such as passing a map to a function in the form
my-function($map...)whereupon you can reference the key/value elements inside the function as if they were named variables, doesn't work with list-maps.
- as of this writing, this code contains no test-suites or inline error-catches or warnings of any kind. I've been using it in my own work but there are surely edge-cases I haven't seen. I welcome reports and contributions!
- Make a depth-based version of
- Push a native maps version of the 'advanced' functions above
First and foremost, gratitude to the core Sass devs (@nex3 and @chriseppstein) for their tireless advancement of the gold-standard of CSS pre-processing, and secondly to @jedfoster and @anotheruiguy for Sassmeister, which makes developing complex functions and mixins relatively painless.
Also acknowledgements to @HugoGiraudel for SassyLists, from which I adapted some early functions, and especially for his list
debug() function, without which I would not have been able to figure out what was going on (and going wrong) in ruby-sass 3.2.x and libsass.