SassyJSON 1.1.1 → 1.1.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +18 -18
  3. data/README.md +112 -112
  4. data/lib/JsonImporter.rb +228 -228
  5. data/lib/SassyJSON.rb +12 -14
  6. data/stylesheets/SassyJSON.scss +5 -5
  7. data/stylesheets/decode/api/_json.scss +26 -26
  8. data/stylesheets/decode/decode.scss +28 -28
  9. data/stylesheets/decode/helpers/all/_throw.scss +11 -11
  10. data/stylesheets/decode/helpers/all/_value.scss +49 -49
  11. data/stylesheets/decode/helpers/color/_color.scss +50 -50
  12. data/stylesheets/decode/helpers/color/_get-color-value.scss +22 -22
  13. data/stylesheets/decode/helpers/color/_hex-to-dec.scss +19 -19
  14. data/stylesheets/decode/helpers/color/_hex.scss +39 -39
  15. data/stylesheets/decode/helpers/color/_hsl.scss +46 -46
  16. data/stylesheets/decode/helpers/color/_rgb.scss +46 -46
  17. data/stylesheets/decode/helpers/map/_consume.scss +33 -33
  18. data/stylesheets/decode/helpers/number/_find-digits.scss +39 -39
  19. data/stylesheets/decode/helpers/number/_find-exponent.scss +51 -51
  20. data/stylesheets/decode/helpers/number/_find-integer.scss +37 -37
  21. data/stylesheets/decode/helpers/number/_pow.scss +22 -22
  22. data/stylesheets/decode/helpers/string/_find-ending-quote.scss +57 -57
  23. data/stylesheets/decode/helpers/string/_length.scss +42 -42
  24. data/stylesheets/decode/helpers/string/_strip-token.scss +16 -16
  25. data/stylesheets/decode/types/_bool.scss +40 -40
  26. data/stylesheets/decode/types/_list.scss +54 -54
  27. data/stylesheets/decode/types/_map.scss +78 -78
  28. data/stylesheets/decode/types/_null.scss +19 -19
  29. data/stylesheets/decode/types/_number.scss +63 -63
  30. data/stylesheets/decode/types/_string.scss +41 -41
  31. data/stylesheets/encode/api/_json.scss +17 -17
  32. data/stylesheets/encode/encode.scss +17 -17
  33. data/stylesheets/encode/helpers/_quote.scss +9 -9
  34. data/stylesheets/encode/mixins/_json.scss +36 -36
  35. data/stylesheets/encode/types/_bool.scss +9 -9
  36. data/stylesheets/encode/types/_color.scss +9 -9
  37. data/stylesheets/encode/types/_list.scss +13 -13
  38. data/stylesheets/encode/types/_map.scss +13 -13
  39. data/stylesheets/encode/types/_null.scss +9 -9
  40. data/stylesheets/encode/types/_number.scss +9 -9
  41. data/stylesheets/encode/types/_string.scss +9 -9
  42. metadata +8 -10
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8c767cc7d4815295989917dffc2c37a090b40726
4
+ data.tar.gz: dd04982601e4a24085741e479024a7fe0afdff15
5
+ SHA512:
6
+ metadata.gz: 6e98df2f7645642b6d9a650e84bf54a7a8cb6881725e3ab6d46cb5ad88153c64e5eb2614834267609ced30a6d97be6f06173fefc80f1bb1b5d15d00a91e14318
7
+ data.tar.gz: c5b1004ba0f86565ee51c367730d23a8cd7cff70188907de4234bbdb2ee4e1ad606d49195ec923902d5bdd22bb35fd43f51bee11678cd9264db0bba07e977744
data/CHANGELOG.md CHANGED
@@ -1,18 +1,18 @@
1
- # Changelog
2
-
3
- * `1.1.1`: fixing a minor issue with the gem
4
- * `1.1.0`: adding the ability to import a JSON file
5
- * `1.0.11`: fixing an issue with scientific number parsing
6
- * `1.0.10`: improving number and string helpers
7
- * `1.0.9`: fixing a bug in `_find-exponent`
8
- * `1.0.8`: fixing a major issue in Ruby Gem
9
- * `1.0.7`: minor fixes and stable Ruby Gem
10
- * `1.0.6`: released a Ruby Gem
11
- * `1.0.5`: improved the encoding mixin
12
- * `1.0.4`: fixed an error in map parsing
13
- * `1.0.3`: slightly edited the mixin to dump JSON to CSS
14
- * `1.0.2`: fixed an issue with string parsing
15
- * `1.0.1`: fixed an issue with alpha color parsing
16
- * `1.0.0`: Stable API. `json-encode` and `json-decode`
17
- * `0.0.2`: added `json-decode` and test
18
- * `0.0.1`: initial commit
1
+ # Changelog
2
+
3
+ * `1.1.1`: fixing a minor issue with the gem
4
+ * `1.1.0`: adding the ability to import a JSON file
5
+ * `1.0.11`: fixing an issue with scientific number parsing
6
+ * `1.0.10`: improving number and string helpers
7
+ * `1.0.9`: fixing a bug in `_find-exponent`
8
+ * `1.0.8`: fixing a major issue in Ruby Gem
9
+ * `1.0.7`: minor fixes and stable Ruby Gem
10
+ * `1.0.6`: released a Ruby Gem
11
+ * `1.0.5`: improved the encoding mixin
12
+ * `1.0.4`: fixed an error in map parsing
13
+ * `1.0.3`: slightly edited the mixin to dump JSON to CSS
14
+ * `1.0.2`: fixed an issue with string parsing
15
+ * `1.0.1`: fixed an issue with alpha color parsing
16
+ * `1.0.0`: Stable API. `json-encode` and `json-decode`
17
+ * `0.0.2`: added `json-decode` and test
18
+ * `0.0.1`: initial commit
data/README.md CHANGED
@@ -1,112 +1,112 @@
1
- # SassyJSON [![NPM version](https://badge.fury.io/js/sassyjson.png)](http://badge.fury.io/js/sassyjson) [![Gem Version](https://badge.fury.io/rb/SassyJSON.png)](http://badge.fury.io/rb/SassyJSON) [![Build Status](https://travis-ci.org/HugoGiraudel/SassyJSON.png?branch=master)](https://travis-ci.org/HugoGiraudel/SassyJSON)
2
-
3
- SassyJSON is a Sass-powered API for JSON. It provides you the classic `json-encode` and `json-decode` directly from your Sass files. We'll leave you the only judges of the point of this.
4
-
5
- ## Install
6
-
7
- SassyJSON is available on [npm](https://npmjs.org/) or as a [Ruby Gem](http://rubygems.org/gems/SassyJSON).
8
-
9
- ### Git
10
-
11
- ``` git
12
- git clone https://github.com/HugoGiraudel/SassyJSON.git && cd SassyJSON
13
- ```
14
-
15
- ### npm
16
-
17
- ``` bash
18
- npm install sassyjson --save-dev
19
- ```
20
-
21
- ### Compass extension
22
-
23
- 1. `gem install SassyJSON`
24
- 2. Add `require 'SassyJSON'` to your `config.rb`
25
- 3. Import it in your stylesheets with `@import 'SassyJSON'`
26
-
27
- ### Sass
28
-
29
- If you only want to play around the code without cloning the repo or using npm, you can find a [single file](https://github.com/HugoGiraudel/SassyJSON/blob/master/dist/_SassyJSON.scss) containing the whole API in the [dist](https://github.com/HugoGiraudel/SassyJSON/tree/master/dist) folder.
30
-
31
- Also, SassyJSON is available at [Sassmeister](http://sassmeister.com/).
32
-
33
- ## Example
34
-
35
- ### Encoding Sass to JSON
36
-
37
- #### Sass
38
-
39
- ``` scss
40
- $map: ((a: (1 2 ( b : 1 )), b: ( #444444, false, ( a: 1, b: test ) ), c: (2 3 4 string)));
41
-
42
- @include json-encode($map);
43
- ```
44
-
45
- #### CSS
46
-
47
- ``` css
48
- /*! json-encode: '{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}' */
49
-
50
- body::before {
51
- content: '{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}';
52
- }
53
-
54
- head {
55
- font-family: '{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}';
56
- }
57
-
58
- @media -json-encode {
59
- json {
60
- json: '{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}';
61
- }
62
- }
63
- ```
64
-
65
- If you want to restrict the output to only one of the three drivers (comment, media query or regular output) you can pass a flag as the second parameter with one of the four following keywords: `all`, `comment`, `media` or `regular`. Default is `all`.
66
-
67
- ### Decoding JSON to Sass
68
-
69
- ``` scss
70
- $json-decode: json-decode('{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}');
71
- // ("a": 1 2 ("b": 1), "b": #444444 false ("a": 1, "b": "test"), "c": 2 3 4 "string")
72
- ```
73
-
74
- ## Importing and decoding a JSON file
75
-
76
- To importe and decode an external `.json` file directly into a Sass variable:
77
-
78
- ``` scss
79
- @import 'relative/path/to/file.json?variable-name'
80
- // Do something with $variable-name
81
- ```
82
-
83
- **Important:**
84
-
85
- * the path to the JSON file is relative to importing file
86
- * the get parameter is the variable name to use it in Sass
87
-
88
- ## Requirements
89
-
90
- All you need is a clean version of Sass 3.3. Otherwise it's just pure Sass madness.
91
-
92
- ## Development
93
-
94
- ### You need
95
-
96
- * [NodeJS](http://nodejs.org)
97
- * [Ruby](https://www.ruby-lang.org/)
98
- * Sass 3.3 via `gem install sass --pre`
99
- * `grunt-cli` via `npm install -g grunt-cli`
100
-
101
- ### How to
102
-
103
- 1. Fork this repository
104
- 2. Run `npm install`
105
- 3. `grunt dev`
106
- 4. Make your changes + write tests
107
- 5. Commit + Pull request
108
-
109
- ## Credits
110
-
111
- * [Fabrice Weinberg](http://twitter.com/fweinb)
112
- * [Hugo Giraudel](http://twitter.com/hugogiraudel)
1
+ # SassyJSON [![NPM version](https://badge.fury.io/js/sassyjson.png)](http://badge.fury.io/js/sassyjson) [![Gem Version](https://badge.fury.io/rb/SassyJSON.png)](http://badge.fury.io/rb/SassyJSON) [![Build Status](https://travis-ci.org/HugoGiraudel/SassyJSON.png?branch=master)](https://travis-ci.org/HugoGiraudel/SassyJSON)
2
+
3
+ SassyJSON is a Sass-powered API for JSON. It provides you the classic `json-encode` and `json-decode` directly from your Sass files. We'll leave you the only judges of the point of this.
4
+
5
+ ## Install
6
+
7
+ SassyJSON is available on [npm](https://npmjs.org/) or as a [Ruby Gem](http://rubygems.org/gems/SassyJSON).
8
+
9
+ ### Git
10
+
11
+ ``` git
12
+ git clone https://github.com/HugoGiraudel/SassyJSON.git && cd SassyJSON
13
+ ```
14
+
15
+ ### npm
16
+
17
+ ``` bash
18
+ npm install sassyjson --save-dev
19
+ ```
20
+
21
+ ### Compass extension
22
+
23
+ 1. `gem install SassyJSON`
24
+ 2. Add `require 'SassyJSON'` to your `config.rb`
25
+ 3. Import it in your stylesheets with `@import 'SassyJSON'`
26
+
27
+ ### Sass
28
+
29
+ If you only want to play around the code without cloning the repo or using npm, you can find a [single file](https://github.com/HugoGiraudel/SassyJSON/blob/master/dist/_SassyJSON.scss) containing the whole API in the [dist](https://github.com/HugoGiraudel/SassyJSON/tree/master/dist) folder.
30
+
31
+ Also, SassyJSON is available at [Sassmeister](http://sassmeister.com/).
32
+
33
+ ## Example
34
+
35
+ ### Encoding Sass to JSON
36
+
37
+ #### Sass
38
+
39
+ ``` scss
40
+ $map: ((a: (1 2 ( b : 1 )), b: ( #444444, false, ( a: 1, b: test ) ), c: (2 3 4 string)));
41
+
42
+ @include json-encode($map);
43
+ ```
44
+
45
+ #### CSS
46
+
47
+ ``` css
48
+ /*! json-encode: '{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}' */
49
+
50
+ body::before {
51
+ content: '{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}';
52
+ }
53
+
54
+ head {
55
+ font-family: '{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}';
56
+ }
57
+
58
+ @media -json-encode {
59
+ json {
60
+ json: '{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}';
61
+ }
62
+ }
63
+ ```
64
+
65
+ If you want to restrict the output to only one of the three drivers (comment, media query or regular output) you can pass a flag as the second parameter with one of the four following keywords: `all`, `comment`, `media` or `regular`. Default is `all`.
66
+
67
+ ### Decoding JSON to Sass
68
+
69
+ ``` scss
70
+ $json-decode: json-decode('{"a": [1, 2, {"b": 1}], "b": ["#444444", false, {"a": 1, "b": "test"}], "c": [2, 3, 4, "string"]}');
71
+ // ("a": 1 2 ("b": 1), "b": #444444 false ("a": 1, "b": "test"), "c": 2 3 4 "string")
72
+ ```
73
+
74
+ ## Importing and decoding a JSON file
75
+
76
+ To importe and decode an external `.json` file directly into a Sass variable:
77
+
78
+ ``` scss
79
+ @import 'relative/path/to/file.json?variable-name'
80
+ // Do something with $variable-name
81
+ ```
82
+
83
+ **Important:**
84
+
85
+ * the path to the JSON file is relative to importing file
86
+ * the get parameter is the variable name to use it in Sass
87
+
88
+ ## Requirements
89
+
90
+ All you need is a clean version of Sass 3.3. Otherwise it's just pure Sass madness.
91
+
92
+ ## Development
93
+
94
+ ### You need
95
+
96
+ * [NodeJS](http://nodejs.org)
97
+ * [Ruby](https://www.ruby-lang.org/)
98
+ * Sass 3.3 via `gem install sass --pre`
99
+ * `grunt-cli` via `npm install -g grunt-cli`
100
+
101
+ ### How to
102
+
103
+ 1. Fork this repository
104
+ 2. Run `npm install`
105
+ 3. `grunt dev`
106
+ 4. Make your changes + write tests
107
+ 5. Commit + Pull request
108
+
109
+ ## Credits
110
+
111
+ * [Fabrice Weinberg](http://twitter.com/fweinb)
112
+ * [Hugo Giraudel](http://twitter.com/hugogiraudel)
data/lib/JsonImporter.rb CHANGED
@@ -1,229 +1,229 @@
1
- require 'sass'
2
-
3
-
4
- # Monkey Path Sass
5
- # Adapted from: https://github.com/chriseppstein/sass-css-importer
6
- class Sass::Engine
7
- alias initialize_without_json_importer initialize
8
-
9
- def initialize(template, options={})
10
- initialize_without_json_importer(template, options)
11
-
12
- json_importer = self.options[:load_paths].find {|lp| lp.is_a?(Sass::Importers::JsonImporter) }
13
-
14
- unless json_importer
15
- root = File.dirname(options[:filename] || ".")
16
- self.options[:load_paths] << Sass::Importers::JsonImporter.new(root)
17
- end
18
- end
19
- end
20
-
21
- module Sass
22
- module Importers
23
- # The default importer, used for any strings found in the load path.
24
- # Simply loads Sass files from the filesystem using the default logic.
25
- class JsonImporter < Base
26
-
27
- attr_accessor :root
28
-
29
- # Creates a new filesystem importer that imports files relative to a given path.
30
- #
31
- # @param root [String] The root path.
32
- # This importer will import files relative to this path.
33
- def initialize(root)
34
- @root = File.expand_path(root)
35
- @same_name_warnings = Set.new
36
- end
37
-
38
- # Enable watching of json files in Sass 3.3+
39
- def watched_directories
40
- [root]
41
- end
42
-
43
- # Enable watching of json files in Sass 3.3+
44
- def watched_file?(file)
45
- file.start_with?(root+File::SEPARATOR) && File.extname(file) == ".json"
46
- end
47
- # @see Base#find_relative
48
- def find_relative(name, base, options)
49
- _find(File.dirname(base), name, options)
50
- end
51
-
52
- # @see Base#find
53
- def find(name, options)
54
- _find(@root, name, options)
55
- end
56
-
57
- # @see Base#mtime
58
- def mtime(name, options)
59
- name = strip_varname(name);
60
- file, _ = Sass::Util.destructure(find_real_file(@root, name, options))
61
- File.mtime(file) if file
62
- rescue Errno::ENOENT
63
- nil
64
- end
65
-
66
- # @see Base#key
67
- def key(name, options)
68
- name = strip_varname(name);
69
- [self.class.name + ":" + File.dirname(File.expand_path(name)),
70
- File.basename(name)]
71
- end
72
-
73
- # @see Base#to_s
74
- def to_s
75
- @root
76
- end
77
-
78
- def hash
79
- @root.hash
80
- end
81
-
82
- def eql?(other)
83
- root.eql?(other.root)
84
- end
85
-
86
- protected
87
-
88
- # If a full uri is passed, this removes the root from it
89
- # otherwise returns the name unchanged
90
- def remove_root(name)
91
- if name.index(@root + "/") == 0
92
- name[(@root.length + 1)..-1]
93
- else
94
- name
95
- end
96
- end
97
-
98
- # A hash from file extensions to the syntaxes for those extensions.
99
- # The syntaxes must be `:sass` or `:scss`.
100
- #
101
- # This can be overridden by subclasses that want normal filesystem importing
102
- # with unusual extensions.
103
- #
104
- # @return [{String => Symbol}]
105
- def extensions
106
- {'json' => :scss}
107
- end
108
-
109
- # Given an `@import`ed path, returns an array of possible
110
- # on-disk filenames and their corresponding syntaxes for that path.
111
- #
112
- # @param name [String] The filename.
113
- # @return [Array(String, Symbol)] An array of pairs.
114
- # The first element of each pair is a filename to look for;
115
- # the second element is the syntax that file would be in (`:sass` or `:scss`).
116
- def possible_files(name)
117
- name = escape_glob_characters(name)
118
- dirname, basename, extname = split(name)
119
- sorted_exts = extensions.sort
120
- syntax = extensions[extname]
121
-
122
- if syntax
123
- ret = [["#{dirname}/{_,}#{basename}.#{extensions.invert[syntax]}", syntax]]
124
- else
125
- ret = sorted_exts.map {|ext, syn| ["#{dirname}/{_,}#{basename}.#{ext}", syn]}
126
- end
127
-
128
- # JRuby chokes when trying to import files from JARs when the path starts with './'.
129
- ret.map {|f, s| [f.sub(%r{^\./}, ''), s]}
130
- end
131
-
132
- def escape_glob_characters(name)
133
- name.gsub(/[\*\[\]\{\}\?]/) do |char|
134
- "\\#{char}"
135
- end
136
- end
137
-
138
- REDUNDANT_DIRECTORY = %r{#{Regexp.escape(File::SEPARATOR)}\.#{Regexp.escape(File::SEPARATOR)}}
139
- # Given a base directory and an `@import`ed name,
140
- # finds an existant file that matches the name.
141
- #
142
- # @param dir [String] The directory relative to which to search.
143
- # @param name [String] The filename to search for.
144
- # @return [(String, Symbol)] A filename-syntax pair.
145
- def find_real_file(dir, name, options)
146
- # on windows 'dir' can be in native File::ALT_SEPARATOR form
147
- dir = dir.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless File::ALT_SEPARATOR.nil?
148
-
149
- found = possible_files(remove_root(name)).map do |f, s|
150
- path = (dir == "." || Pathname.new(f).absolute?) ? f : "#{escape_glob_characters(dir)}/#{f}"
151
- Dir[path].map do |full_path|
152
- full_path.gsub!(REDUNDANT_DIRECTORY, File::SEPARATOR)
153
- [Pathname.new(full_path).cleanpath.to_s, s]
154
- end
155
- end
156
- found = Sass::Util.flatten(found, 1)
157
- return if found.empty?
158
-
159
- if found.size > 1 && !@same_name_warnings.include?(found.first.first)
160
- found.each {|(f, _)| @same_name_warnings << f}
161
- relative_to = Pathname.new(dir)
162
- if options[:_line]
163
- # If _line exists, we're here due to an actual import in an
164
- # import_node and we want to print a warning for a user writing an
165
- # ambiguous import.
166
- candidates = found.map {|(f, _)| " " + Pathname.new(f).relative_path_from(relative_to).to_s}.join("\n")
167
- Sass::Util.sass_warn <<WARNING
168
- WARNING: On line #{options[:_line]}#{" of #{options[:filename]}" if options[:filename]}:
169
- It's not clear which file to import for '@import "#{name}"'.
170
- Candidates:
171
- #{candidates}
172
- For now I'll choose #{File.basename found.first.first}.
173
- This will be an error in future versions of Sass.
174
- WARNING
175
- else
176
- # Otherwise, we're here via StalenessChecker, and we want to print a
177
- # warning for a user running `sass --watch` with two ambiguous files.
178
- candidates = found.map {|(f, _)| " " + File.basename(f)}.join("\n")
179
- Sass::Util.sass_warn <<WARNING
180
- WARNING: In #{File.dirname(name)}:
181
- There are multiple files that match the name "#{File.basename(name)}":
182
- #{candidates}
183
- WARNING
184
- end
185
- end
186
- found.first
187
- end
188
-
189
- # Splits a filename into three parts, a directory part, a basename, and an extension
190
- # Only the known extensions returned from the extensions method will be recognized as such.
191
- def split(name)
192
- extension = nil
193
- dirname, basename = File.dirname(name), File.basename(name)
194
- if basename =~ /^(.*)\.(#{extensions.keys.map{|e| Regexp.escape(e)}.join('|')})$/
195
- basename = $1
196
- extension = $2
197
- end
198
- [dirname, basename, extension]
199
- end
200
-
201
- private
202
-
203
- def strip_varname(name)
204
- name.include?(".json?") ? name[0..name.rindex("?")-1] : name
205
- end
206
-
207
-
208
- def _find(dir, name, options)
209
- if name.include? ".json?"
210
- quotePos = name.rindex("?");
211
- path = name[0..quotePos-1]
212
- varname = name[quotePos+1..-1]
213
-
214
- full_filename, syntax = Sass::Util.destructure(find_real_file(dir, path, options))
215
- return unless full_filename && File.readable?(full_filename)
216
-
217
- options[:syntax] = syntax
218
- options[:filename] = full_filename
219
- options[:importer] = self
220
-
221
- Sass::Engine.new("$#{varname} : json_decode('" + File.read(full_filename) + "');", options)
222
- else
223
- Sass::Engine.new("$hello:'test';", options)
224
- end
225
- end
226
-
227
- end
228
- end
1
+ require 'sass'
2
+
3
+
4
+ # Monkey Path Sass
5
+ # Adapted from: https://github.com/chriseppstein/sass-css-importer
6
+ class Sass::Engine
7
+ alias initialize_without_json_importer initialize
8
+
9
+ def initialize(template, options={})
10
+ initialize_without_json_importer(template, options)
11
+
12
+ json_importer = self.options[:load_paths].find {|lp| lp.is_a?(Sass::Importers::JsonImporter) }
13
+
14
+ unless json_importer
15
+ root = File.dirname(options[:filename] || ".")
16
+ self.options[:load_paths] << Sass::Importers::JsonImporter.new(root)
17
+ end
18
+ end
19
+ end
20
+
21
+ module Sass
22
+ module Importers
23
+ # The default importer, used for any strings found in the load path.
24
+ # Simply loads Sass files from the filesystem using the default logic.
25
+ class JsonImporter < Base
26
+
27
+ attr_accessor :root
28
+
29
+ # Creates a new filesystem importer that imports files relative to a given path.
30
+ #
31
+ # @param root [String] The root path.
32
+ # This importer will import files relative to this path.
33
+ def initialize(root)
34
+ @root = File.expand_path(root)
35
+ @same_name_warnings = Set.new
36
+ end
37
+
38
+ # Enable watching of json files in Sass 3.3+
39
+ def watched_directories
40
+ [root]
41
+ end
42
+
43
+ # Enable watching of json files in Sass 3.3+
44
+ def watched_file?(file)
45
+ file.start_with?(root+File::SEPARATOR) && File.extname(file) == ".json"
46
+ end
47
+ # @see Base#find_relative
48
+ def find_relative(name, base, options)
49
+ _find(File.dirname(base), name, options)
50
+ end
51
+
52
+ # @see Base#find
53
+ def find(name, options)
54
+ _find(@root, name, options)
55
+ end
56
+
57
+ # @see Base#mtime
58
+ def mtime(name, options)
59
+ name = strip_varname(name);
60
+ file, _ = Sass::Util.destructure(find_real_file(@root, name, options))
61
+ File.mtime(file) if file
62
+ rescue Errno::ENOENT
63
+ nil
64
+ end
65
+
66
+ # @see Base#key
67
+ def key(name, options)
68
+ name = strip_varname(name);
69
+ [self.class.name + ":" + File.dirname(File.expand_path(name)),
70
+ File.basename(name)]
71
+ end
72
+
73
+ # @see Base#to_s
74
+ def to_s
75
+ @root
76
+ end
77
+
78
+ def hash
79
+ @root.hash
80
+ end
81
+
82
+ def eql?(other)
83
+ root.eql?(other.root)
84
+ end
85
+
86
+ protected
87
+
88
+ # If a full uri is passed, this removes the root from it
89
+ # otherwise returns the name unchanged
90
+ def remove_root(name)
91
+ if name.index(@root + "/") == 0
92
+ name[(@root.length + 1)..-1]
93
+ else
94
+ name
95
+ end
96
+ end
97
+
98
+ # A hash from file extensions to the syntaxes for those extensions.
99
+ # The syntaxes must be `:sass` or `:scss`.
100
+ #
101
+ # This can be overridden by subclasses that want normal filesystem importing
102
+ # with unusual extensions.
103
+ #
104
+ # @return [{String => Symbol}]
105
+ def extensions
106
+ {'json' => :scss}
107
+ end
108
+
109
+ # Given an `@import`ed path, returns an array of possible
110
+ # on-disk filenames and their corresponding syntaxes for that path.
111
+ #
112
+ # @param name [String] The filename.
113
+ # @return [Array(String, Symbol)] An array of pairs.
114
+ # The first element of each pair is a filename to look for;
115
+ # the second element is the syntax that file would be in (`:sass` or `:scss`).
116
+ def possible_files(name)
117
+ name = escape_glob_characters(name)
118
+ dirname, basename, extname = split(name)
119
+ sorted_exts = extensions.sort
120
+ syntax = extensions[extname]
121
+
122
+ if syntax
123
+ ret = [["#{dirname}/{_,}#{basename}.#{extensions.invert[syntax]}", syntax]]
124
+ else
125
+ ret = sorted_exts.map {|ext, syn| ["#{dirname}/{_,}#{basename}.#{ext}", syn]}
126
+ end
127
+
128
+ # JRuby chokes when trying to import files from JARs when the path starts with './'.
129
+ ret.map {|f, s| [f.sub(%r{^\./}, ''), s]}
130
+ end
131
+
132
+ def escape_glob_characters(name)
133
+ name.gsub(/[\*\[\]\{\}\?]/) do |char|
134
+ "\\#{char}"
135
+ end
136
+ end
137
+
138
+ REDUNDANT_DIRECTORY = %r{#{Regexp.escape(File::SEPARATOR)}\.#{Regexp.escape(File::SEPARATOR)}}
139
+ # Given a base directory and an `@import`ed name,
140
+ # finds an existant file that matches the name.
141
+ #
142
+ # @param dir [String] The directory relative to which to search.
143
+ # @param name [String] The filename to search for.
144
+ # @return [(String, Symbol)] A filename-syntax pair.
145
+ def find_real_file(dir, name, options)
146
+ # on windows 'dir' can be in native File::ALT_SEPARATOR form
147
+ dir = dir.gsub(File::ALT_SEPARATOR, File::SEPARATOR) unless File::ALT_SEPARATOR.nil?
148
+
149
+ found = possible_files(remove_root(name)).map do |f, s|
150
+ path = (dir == "." || Pathname.new(f).absolute?) ? f : "#{escape_glob_characters(dir)}/#{f}"
151
+ Dir[path].map do |full_path|
152
+ full_path.gsub!(REDUNDANT_DIRECTORY, File::SEPARATOR)
153
+ [Pathname.new(full_path).cleanpath.to_s, s]
154
+ end
155
+ end
156
+ found = Sass::Util.flatten(found, 1)
157
+ return if found.empty?
158
+
159
+ if found.size > 1 && !@same_name_warnings.include?(found.first.first)
160
+ found.each {|(f, _)| @same_name_warnings << f}
161
+ relative_to = Pathname.new(dir)
162
+ if options[:_line]
163
+ # If _line exists, we're here due to an actual import in an
164
+ # import_node and we want to print a warning for a user writing an
165
+ # ambiguous import.
166
+ candidates = found.map {|(f, _)| " " + Pathname.new(f).relative_path_from(relative_to).to_s}.join("\n")
167
+ Sass::Util.sass_warn <<WARNING
168
+ WARNING: On line #{options[:_line]}#{" of #{options[:filename]}" if options[:filename]}:
169
+ It's not clear which file to import for '@import "#{name}"'.
170
+ Candidates:
171
+ #{candidates}
172
+ For now I'll choose #{File.basename found.first.first}.
173
+ This will be an error in future versions of Sass.
174
+ WARNING
175
+ else
176
+ # Otherwise, we're here via StalenessChecker, and we want to print a
177
+ # warning for a user running `sass --watch` with two ambiguous files.
178
+ candidates = found.map {|(f, _)| " " + File.basename(f)}.join("\n")
179
+ Sass::Util.sass_warn <<WARNING
180
+ WARNING: In #{File.dirname(name)}:
181
+ There are multiple files that match the name "#{File.basename(name)}":
182
+ #{candidates}
183
+ WARNING
184
+ end
185
+ end
186
+ found.first
187
+ end
188
+
189
+ # Splits a filename into three parts, a directory part, a basename, and an extension
190
+ # Only the known extensions returned from the extensions method will be recognized as such.
191
+ def split(name)
192
+ extension = nil
193
+ dirname, basename = File.dirname(name), File.basename(name)
194
+ if basename =~ /^(.*)\.(#{extensions.keys.map{|e| Regexp.escape(e)}.join('|')})$/
195
+ basename = $1
196
+ extension = $2
197
+ end
198
+ [dirname, basename, extension]
199
+ end
200
+
201
+ private
202
+
203
+ def strip_varname(name)
204
+ name.include?(".json?") ? name[0..name.rindex("?")-1] : name
205
+ end
206
+
207
+
208
+ def _find(dir, name, options)
209
+ if name.include? ".json?"
210
+ quotePos = name.rindex("?");
211
+ path = name[0..quotePos-1]
212
+ varname = name[quotePos+1..-1]
213
+
214
+ full_filename, syntax = Sass::Util.destructure(find_real_file(dir, path, options))
215
+ return unless full_filename && File.readable?(full_filename)
216
+
217
+ options[:syntax] = syntax
218
+ options[:filename] = full_filename
219
+ options[:importer] = self
220
+
221
+ Sass::Engine.new("$#{varname} : json_decode('" + File.read(full_filename) + "');", options)
222
+ else
223
+ Sass::Engine.new("$hello:'test';", options)
224
+ end
225
+ end
226
+
227
+ end
228
+ end
229
229
  end