/src/libsass/src/context.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | // sass.hpp must go before all system headers to get the |
2 | | // __EXTENSIONS__ fix on Solaris. |
3 | | #include "sass.hpp" |
4 | | #include "ast.hpp" |
5 | | |
6 | | #include "remove_placeholders.hpp" |
7 | | #include "sass_functions.hpp" |
8 | | #include "check_nesting.hpp" |
9 | | #include "fn_selectors.hpp" |
10 | | #include "fn_strings.hpp" |
11 | | #include "fn_numbers.hpp" |
12 | | #include "fn_colors.hpp" |
13 | | #include "fn_miscs.hpp" |
14 | | #include "fn_lists.hpp" |
15 | | #include "fn_maps.hpp" |
16 | | #include "context.hpp" |
17 | | #include "expand.hpp" |
18 | | #include "parser.hpp" |
19 | | #include "cssize.hpp" |
20 | | #include "source.hpp" |
21 | | |
22 | | namespace Sass { |
23 | | using namespace Constants; |
24 | | using namespace File; |
25 | | using namespace Sass; |
26 | | |
27 | | inline bool sort_importers (const Sass_Importer_Entry& i, const Sass_Importer_Entry& j) |
28 | 0 | { return sass_importer_get_priority(i) > sass_importer_get_priority(j); } |
29 | | |
30 | | static sass::string safe_input(const char* in_path) |
31 | 20 | { |
32 | 20 | if (in_path == nullptr || in_path[0] == '\0') return "stdin"; |
33 | 0 | return in_path; |
34 | 20 | } |
35 | | |
36 | | static sass::string safe_output(const char* out_path, sass::string input_path) |
37 | 20 | { |
38 | 20 | if (out_path == nullptr || out_path[0] == '\0') { |
39 | 20 | if (input_path.empty()) return "stdout"; |
40 | 20 | return input_path.substr(0, input_path.find_last_of(".")) + ".css"; |
41 | 20 | } |
42 | 0 | return out_path; |
43 | 20 | } |
44 | | |
45 | | Context::Context(struct Sass_Context& c_ctx) |
46 | | : CWD(File::get_cwd()), |
47 | | c_options(c_ctx), |
48 | | entry_path(""), |
49 | | head_imports(0), |
50 | | plugins(), |
51 | | emitter(c_options), |
52 | | |
53 | | ast_gc(), |
54 | | strings(), |
55 | | resources(), |
56 | | sheets(), |
57 | | import_stack(), |
58 | | callee_stack(), |
59 | | traces(), |
60 | | extender(Extender::NORMAL, traces), |
61 | | c_compiler(NULL), |
62 | | |
63 | | c_headers (sass::vector<Sass_Importer_Entry>()), |
64 | | c_importers (sass::vector<Sass_Importer_Entry>()), |
65 | | c_functions (sass::vector<Sass_Function_Entry>()), |
66 | | |
67 | | indent (safe_str(c_options.indent, " ")), |
68 | | linefeed (safe_str(c_options.linefeed, "\n")), |
69 | | |
70 | | input_path (make_canonical_path(safe_input(c_options.input_path))), |
71 | | output_path (make_canonical_path(safe_output(c_options.output_path, input_path))), |
72 | | source_map_file (make_canonical_path(safe_str(c_options.source_map_file, ""))), |
73 | | source_map_root (make_canonical_path(safe_str(c_options.source_map_root, ""))) |
74 | | |
75 | 20 | { |
76 | | |
77 | | // Sass 3.4: The current working directory will no longer be placed onto the Sass load path by default. |
78 | | // If you need the current working directory to be available, set SASS_PATH=. in your shell's environment. |
79 | | // include_paths.push_back(CWD); |
80 | | |
81 | | // collect more paths from different options |
82 | 20 | collect_include_paths(c_options.include_path); |
83 | 20 | collect_include_paths(c_options.include_paths); |
84 | 20 | collect_plugin_paths(c_options.plugin_path); |
85 | 20 | collect_plugin_paths(c_options.plugin_paths); |
86 | | |
87 | | // load plugins and register custom behaviors |
88 | 20 | for(auto plug : plugin_paths) plugins.load_plugins(plug); |
89 | 20 | for(auto fn : plugins.get_headers()) c_headers.push_back(fn); |
90 | 20 | for(auto fn : plugins.get_importers()) c_importers.push_back(fn); |
91 | 20 | for(auto fn : plugins.get_functions()) c_functions.push_back(fn); |
92 | | |
93 | | // sort the items by priority (lowest first) |
94 | 20 | sort (c_headers.begin(), c_headers.end(), sort_importers); |
95 | 20 | sort (c_importers.begin(), c_importers.end(), sort_importers); |
96 | | |
97 | 20 | emitter.set_filename(abs2rel(output_path, source_map_file, CWD)); |
98 | | |
99 | 20 | } |
100 | | |
101 | | void Context::add_c_function(Sass_Function_Entry function) |
102 | 0 | { |
103 | 0 | c_functions.push_back(function); |
104 | 0 | } |
105 | | void Context::add_c_header(Sass_Importer_Entry header) |
106 | 0 | { |
107 | 0 | c_headers.push_back(header); |
108 | | // need to sort the array afterwards (no big deal) |
109 | 0 | sort (c_headers.begin(), c_headers.end(), sort_importers); |
110 | 0 | } |
111 | | void Context::add_c_importer(Sass_Importer_Entry importer) |
112 | 0 | { |
113 | 0 | c_importers.push_back(importer); |
114 | | // need to sort the array afterwards (no big deal) |
115 | 0 | sort (c_importers.begin(), c_importers.end(), sort_importers); |
116 | 0 | } |
117 | | |
118 | | Context::~Context() |
119 | 20 | { |
120 | | // resources were allocated by malloc |
121 | 40 | for (size_t i = 0; i < resources.size(); ++i) { |
122 | 20 | free(resources[i].contents); |
123 | 20 | free(resources[i].srcmap); |
124 | 20 | } |
125 | | // free all strings we kept alive during compiler execution |
126 | 40 | for (size_t n = 0; n < strings.size(); ++n) free(strings[n]); |
127 | | // everything that gets put into sources will be freed by us |
128 | | // this shouldn't have anything in it anyway!? |
129 | 56 | for (size_t m = 0; m < import_stack.size(); ++m) { |
130 | 36 | sass_import_take_source(import_stack[m]); |
131 | 36 | sass_import_take_srcmap(import_stack[m]); |
132 | 36 | sass_delete_import(import_stack[m]); |
133 | 36 | } |
134 | | // clear inner structures (vectors) and input source |
135 | 20 | resources.clear(); import_stack.clear(); |
136 | 20 | sheets.clear(); |
137 | 20 | } |
138 | | |
139 | | Data_Context::~Data_Context() |
140 | | { |
141 | | // --> this will be freed by resources |
142 | | // make sure we free the source even if not processed! |
143 | | // if (resources.size() == 0 && source_c_str) free(source_c_str); |
144 | | // if (resources.size() == 0 && srcmap_c_str) free(srcmap_c_str); |
145 | | // source_c_str = 0; srcmap_c_str = 0; |
146 | | } |
147 | | |
148 | | File_Context::~File_Context() |
149 | | { |
150 | | } |
151 | | |
152 | | void Context::collect_include_paths(const char* paths_str) |
153 | 20 | { |
154 | 20 | if (paths_str) { |
155 | 0 | const char* beg = paths_str; |
156 | 0 | const char* end = Prelexer::find_first<PATH_SEP>(beg); |
157 | |
|
158 | 0 | while (end) { |
159 | 0 | sass::string path(beg, end - beg); |
160 | 0 | if (!path.empty()) { |
161 | 0 | if (*path.rbegin() != '/') path += '/'; |
162 | 0 | include_paths.push_back(path); |
163 | 0 | } |
164 | 0 | beg = end + 1; |
165 | 0 | end = Prelexer::find_first<PATH_SEP>(beg); |
166 | 0 | } |
167 | |
|
168 | 0 | sass::string path(beg); |
169 | 0 | if (!path.empty()) { |
170 | 0 | if (*path.rbegin() != '/') path += '/'; |
171 | 0 | include_paths.push_back(path); |
172 | 0 | } |
173 | 0 | } |
174 | 20 | } |
175 | | |
176 | | void Context::collect_include_paths(string_list* paths_array) |
177 | 20 | { |
178 | 20 | while (paths_array) |
179 | 0 | { |
180 | 0 | collect_include_paths(paths_array->string); |
181 | 0 | paths_array = paths_array->next; |
182 | 0 | } |
183 | 20 | } |
184 | | |
185 | | void Context::collect_plugin_paths(const char* paths_str) |
186 | 20 | { |
187 | 20 | if (paths_str) { |
188 | 0 | const char* beg = paths_str; |
189 | 0 | const char* end = Prelexer::find_first<PATH_SEP>(beg); |
190 | |
|
191 | 0 | while (end) { |
192 | 0 | sass::string path(beg, end - beg); |
193 | 0 | if (!path.empty()) { |
194 | 0 | if (*path.rbegin() != '/') path += '/'; |
195 | 0 | plugin_paths.push_back(path); |
196 | 0 | } |
197 | 0 | beg = end + 1; |
198 | 0 | end = Prelexer::find_first<PATH_SEP>(beg); |
199 | 0 | } |
200 | |
|
201 | 0 | sass::string path(beg); |
202 | 0 | if (!path.empty()) { |
203 | 0 | if (*path.rbegin() != '/') path += '/'; |
204 | 0 | plugin_paths.push_back(path); |
205 | 0 | } |
206 | 0 | } |
207 | 20 | } |
208 | | |
209 | | void Context::collect_plugin_paths(string_list* paths_array) |
210 | 20 | { |
211 | 20 | while (paths_array) |
212 | 0 | { |
213 | 0 | collect_plugin_paths(paths_array->string); |
214 | 0 | paths_array = paths_array->next; |
215 | 0 | } |
216 | 20 | } |
217 | | |
218 | | // resolve the imp_path in base_path or include_paths |
219 | | // looks for alternatives and returns a list from one directory |
220 | | sass::vector<Include> Context::find_includes(const Importer& import) |
221 | 0 | { |
222 | | // make sure we resolve against an absolute path |
223 | 0 | sass::string base_path(rel2abs(import.base_path)); |
224 | | // first try to resolve the load path relative to the base path |
225 | 0 | sass::vector<Include> vec(resolve_includes(base_path, import.imp_path)); |
226 | | // then search in every include path (but only if nothing found yet) |
227 | 0 | for (size_t i = 0, S = include_paths.size(); vec.size() == 0 && i < S; ++i) |
228 | 0 | { |
229 | | // call resolve_includes and individual base path and append all results |
230 | 0 | sass::vector<Include> resolved(resolve_includes(include_paths[i], import.imp_path)); |
231 | 0 | if (resolved.size()) vec.insert(vec.end(), resolved.begin(), resolved.end()); |
232 | 0 | } |
233 | | // return vector |
234 | 0 | return vec; |
235 | 0 | } |
236 | | |
237 | | // register include with resolved path and its content |
238 | | // memory of the resources will be freed by us on exit |
239 | | void Context::register_resource(const Include& inc, const Resource& res) |
240 | 20 | { |
241 | | |
242 | | // do not parse same resource twice |
243 | | // maybe raise an error in this case |
244 | | // if (sheets.count(inc.abs_path)) { |
245 | | // free(res.contents); free(res.srcmap); |
246 | | // throw std::runtime_error("duplicate resource registered"); |
247 | | // return; |
248 | | // } |
249 | | |
250 | | // get index for this resource |
251 | 20 | size_t idx = resources.size(); |
252 | | |
253 | | // tell emitter about new resource |
254 | 20 | emitter.add_source_index(idx); |
255 | | |
256 | | // put resources under our control |
257 | | // the memory will be freed later |
258 | 20 | resources.push_back(res); |
259 | | |
260 | | // add a relative link to the working directory |
261 | 20 | included_files.push_back(inc.abs_path); |
262 | | // add a relative link to the source map output file |
263 | 20 | srcmap_links.push_back(abs2rel(inc.abs_path, source_map_file, CWD)); |
264 | | |
265 | | // get pointer to the loaded content |
266 | 20 | Sass_Import_Entry import = sass_make_import( |
267 | 20 | inc.imp_path.c_str(), |
268 | 20 | inc.abs_path.c_str(), |
269 | 20 | res.contents, |
270 | 20 | res.srcmap |
271 | 20 | ); |
272 | | // add the entry to the stack |
273 | 20 | import_stack.push_back(import); |
274 | | |
275 | | // get pointer to the loaded content |
276 | 20 | const char* contents = resources[idx].contents; |
277 | 20 | SourceFileObj source = SASS_MEMORY_NEW(SourceFile, |
278 | 20 | inc.abs_path.c_str(), contents, idx); |
279 | | |
280 | | // create the initial parser state from resource |
281 | 20 | SourceSpan pstate(source); |
282 | | |
283 | | // check existing import stack for possible recursion |
284 | 20 | for (size_t i = 0; i < import_stack.size() - 2; ++i) { |
285 | 0 | auto parent = import_stack[i]; |
286 | 0 | if (std::strcmp(parent->abs_path, import->abs_path) == 0) { |
287 | 0 | sass::string cwd(File::get_cwd()); |
288 | | // make path relative to the current directory |
289 | 0 | sass::string stack("An @import loop has been found:"); |
290 | 0 | for (size_t n = 1; n < i + 2; ++n) { |
291 | 0 | stack += "\n " + sass::string(File::abs2rel(import_stack[n]->abs_path, cwd, cwd)) + |
292 | 0 | " imports " + sass::string(File::abs2rel(import_stack[n+1]->abs_path, cwd, cwd)); |
293 | 0 | } |
294 | | // implement error throw directly until we |
295 | | // decided how to handle full stack traces |
296 | 0 | throw Exception::InvalidSyntax(pstate, traces, stack); |
297 | | // error(stack, prstate ? *prstate : pstate, import_stack); |
298 | 0 | } |
299 | 0 | } |
300 | | |
301 | | // create a parser instance from the given c_str buffer |
302 | 20 | Parser p(source, *this, traces); |
303 | | // do not yet dispose these buffers |
304 | 20 | sass_import_take_source(import); |
305 | 20 | sass_import_take_srcmap(import); |
306 | | // then parse the root block |
307 | 20 | Block_Obj root = p.parse(); |
308 | | // delete memory of current stack frame |
309 | 20 | sass_delete_import(import_stack.back()); |
310 | | // remove current stack frame |
311 | 20 | import_stack.pop_back(); |
312 | | // create key/value pair for ast node |
313 | 20 | std::pair<const sass::string, StyleSheet> |
314 | 20 | ast_pair(inc.abs_path, { res, root }); |
315 | | // register resulting resource |
316 | 20 | sheets.insert(ast_pair); |
317 | 20 | } |
318 | | |
319 | | // register include with resolved path and its content |
320 | | // memory of the resources will be freed by us on exit |
321 | | void Context::register_resource(const Include& inc, const Resource& res, SourceSpan& prstate) |
322 | 0 | { |
323 | 0 | traces.push_back(Backtrace(prstate)); |
324 | 0 | register_resource(inc, res); |
325 | 0 | traces.pop_back(); |
326 | 0 | } |
327 | | |
328 | | // Add a new import to the context (called from `import_url`) |
329 | | Include Context::load_import(const Importer& imp, SourceSpan pstate) |
330 | 0 | { |
331 | | |
332 | | // search for valid imports (ie. partials) on the filesystem |
333 | | // this may return more than one valid result (ambiguous imp_path) |
334 | 0 | const sass::vector<Include> resolved(find_includes(imp)); |
335 | | |
336 | | // error nicely on ambiguous imp_path |
337 | 0 | if (resolved.size() > 1) { |
338 | 0 | sass::ostream msg_stream; |
339 | 0 | msg_stream << "It's not clear which file to import for "; |
340 | 0 | msg_stream << "'@import \"" << imp.imp_path << "\"'." << "\n"; |
341 | 0 | msg_stream << "Candidates:" << "\n"; |
342 | 0 | for (size_t i = 0, L = resolved.size(); i < L; ++i) |
343 | 0 | { msg_stream << " " << resolved[i].imp_path << "\n"; } |
344 | 0 | msg_stream << "Please delete or rename all but one of these files." << "\n"; |
345 | 0 | error(msg_stream.str(), pstate, traces); |
346 | 0 | } |
347 | | |
348 | | // process the resolved entry |
349 | 0 | else if (resolved.size() == 1) { |
350 | 0 | bool use_cache = c_importers.size() == 0; |
351 | | // use cache for the resource loading |
352 | 0 | if (use_cache && sheets.count(resolved[0].abs_path)) return resolved[0]; |
353 | | // try to read the content of the resolved file entry |
354 | | // the memory buffer returned must be freed by us! |
355 | 0 | if (char* contents = read_file(resolved[0].abs_path)) { |
356 | | // register the newly resolved file resource |
357 | 0 | register_resource(resolved[0], { contents, 0 }, pstate); |
358 | | // return resolved entry |
359 | 0 | return resolved[0]; |
360 | 0 | } |
361 | 0 | } |
362 | | |
363 | | // nothing found |
364 | 0 | return { imp, "" }; |
365 | |
|
366 | 0 | } |
367 | | |
368 | 0 | void Context::import_url (Import* imp, sass::string load_path, const sass::string& ctx_path) { |
369 | |
|
370 | 0 | SourceSpan pstate(imp->pstate()); |
371 | 0 | sass::string imp_path(unquote(load_path)); |
372 | 0 | sass::string protocol("file"); |
373 | |
|
374 | 0 | using namespace Prelexer; |
375 | 0 | if (const char* proto = sequence< identifier, exactly<':'>, exactly<'/'>, exactly<'/'> >(imp_path.c_str())) { |
376 | |
|
377 | 0 | protocol = sass::string(imp_path.c_str(), proto - 3); |
378 | | // if (protocol.compare("file") && true) { } |
379 | 0 | } |
380 | | |
381 | | // add urls (protocol other than file) and urls without protocol to `urls` member |
382 | | // ToDo: if ctx_path is already a file resource, we should not add it here? |
383 | 0 | if (imp->import_queries() || protocol != "file" || imp_path.substr(0, 2) == "//") { |
384 | 0 | imp->urls().push_back(SASS_MEMORY_NEW(String_Quoted, imp->pstate(), load_path)); |
385 | 0 | } |
386 | 0 | else if (imp_path.length() > 4 && imp_path.substr(imp_path.length() - 4, 4) == ".css") { |
387 | 0 | String_Constant* loc = SASS_MEMORY_NEW(String_Constant, pstate, unquote(load_path)); |
388 | 0 | Argument_Obj loc_arg = SASS_MEMORY_NEW(Argument, pstate, loc); |
389 | 0 | Arguments_Obj loc_args = SASS_MEMORY_NEW(Arguments, pstate); |
390 | 0 | loc_args->append(loc_arg); |
391 | 0 | Function_Call* new_url = SASS_MEMORY_NEW(Function_Call, pstate, sass::string("url"), loc_args); |
392 | 0 | imp->urls().push_back(new_url); |
393 | 0 | } |
394 | 0 | else { |
395 | 0 | const Importer importer(imp_path, ctx_path); |
396 | 0 | Include include(load_import(importer, pstate)); |
397 | 0 | if (include.abs_path.empty()) { |
398 | 0 | error("File to import not found or unreadable: " + imp_path + ".", pstate, traces); |
399 | 0 | } |
400 | 0 | imp->incs().push_back(include); |
401 | 0 | } |
402 | |
|
403 | 0 | } |
404 | | |
405 | | |
406 | | // call custom importers on the given (unquoted) load_path and eventually parse the resulting style_sheet |
407 | | bool Context::call_loader(const sass::string& load_path, const char* ctx_path, SourceSpan& pstate, Import* imp, sass::vector<Sass_Importer_Entry> importers, bool only_one) |
408 | 18 | { |
409 | | // unique counter |
410 | 18 | size_t count = 0; |
411 | | // need one correct import |
412 | 18 | bool has_import = false; |
413 | | // process all custom importers (or custom headers) |
414 | 18 | for (Sass_Importer_Entry& importer_ent : importers) { |
415 | | // int priority = sass_importer_get_priority(importer); |
416 | 0 | Sass_Importer_Fn fn = sass_importer_get_function(importer_ent); |
417 | | // skip importer if it returns NULL |
418 | 0 | if (Sass_Import_List includes = |
419 | 0 | fn(load_path.c_str(), importer_ent, c_compiler) |
420 | 0 | ) { |
421 | | // get c pointer copy to iterate over |
422 | 0 | Sass_Import_List it_includes = includes; |
423 | 0 | while (*it_includes) { ++count; |
424 | | // create unique path to use as key |
425 | 0 | sass::string uniq_path = load_path; |
426 | 0 | if (!only_one && count) { |
427 | 0 | sass::ostream path_strm; |
428 | 0 | path_strm << uniq_path << ":" << count; |
429 | 0 | uniq_path = path_strm.str(); |
430 | 0 | } |
431 | | // create the importer struct |
432 | 0 | Importer importer(uniq_path, ctx_path); |
433 | | // query data from the current include |
434 | 0 | Sass_Import_Entry include_ent = *it_includes; |
435 | 0 | char* source = sass_import_take_source(include_ent); |
436 | 0 | char* srcmap = sass_import_take_srcmap(include_ent); |
437 | 0 | size_t line = sass_import_get_error_line(include_ent); |
438 | 0 | size_t column = sass_import_get_error_column(include_ent); |
439 | 0 | const char *abs_path = sass_import_get_abs_path(include_ent); |
440 | | // handle error message passed back from custom importer |
441 | | // it may (or may not) override the line and column info |
442 | 0 | if (const char* err_message = sass_import_get_error_message(include_ent)) { |
443 | 0 | if (source || srcmap) register_resource({ importer, uniq_path }, { source, srcmap }, pstate); |
444 | 0 | if (line == sass::string::npos && column == sass::string::npos) error(err_message, pstate, traces); |
445 | 0 | else { error(err_message, { pstate.source, { line, column } }, traces); } |
446 | 0 | } |
447 | | // content for import was set |
448 | 0 | else if (source) { |
449 | | // resolved abs_path should be set by custom importer |
450 | | // use the created uniq_path as fallback (maybe enforce) |
451 | 0 | sass::string path_key(abs_path ? abs_path : uniq_path); |
452 | | // create the importer struct |
453 | 0 | Include include(importer, path_key); |
454 | | // attach information to AST node |
455 | 0 | imp->incs().push_back(include); |
456 | | // register the resource buffers |
457 | 0 | register_resource(include, { source, srcmap }, pstate); |
458 | 0 | } |
459 | | // only a path was retuned |
460 | | // try to load it like normal |
461 | 0 | else if(abs_path) { |
462 | | // checks some urls to preserve |
463 | | // `http://`, `https://` and `//` |
464 | | // or dispatchs to `import_file` |
465 | | // which will check for a `.css` extension |
466 | | // or resolves the file on the filesystem |
467 | | // added and resolved via `add_file` |
468 | | // finally stores everything on `imp` |
469 | 0 | import_url(imp, abs_path, ctx_path); |
470 | 0 | } |
471 | | // move to next |
472 | 0 | ++it_includes; |
473 | 0 | } |
474 | | // deallocate the returned memory |
475 | 0 | sass_delete_import_list(includes); |
476 | | // set success flag |
477 | 0 | has_import = true; |
478 | | // break out of loop |
479 | 0 | if (only_one) break; |
480 | 0 | } |
481 | 0 | } |
482 | | // return result |
483 | 18 | return has_import; |
484 | 18 | } |
485 | | |
486 | | void register_function(Context&, Signature sig, Native_Function f, Env* env); |
487 | | void register_function(Context&, Signature sig, Native_Function f, size_t arity, Env* env); |
488 | | void register_overload_stub(Context&, sass::string name, Env* env); |
489 | | void register_built_in_functions(Context&, Env* env); |
490 | | void register_c_functions(Context&, Env* env, Sass_Function_List); |
491 | | void register_c_function(Context&, Env* env, Sass_Function_Entry); |
492 | | |
493 | | char* Context::render(Block_Obj root) |
494 | 4 | { |
495 | | // check for valid block |
496 | 4 | if (!root) return 0; |
497 | | // start the render process |
498 | 4 | root->perform(&emitter); |
499 | | // finish emitter stream |
500 | 4 | emitter.finalize(); |
501 | | // get the resulting buffer from stream |
502 | 4 | OutputBuffer emitted = emitter.get_buffer(); |
503 | | // should we append a source map url? |
504 | 4 | if (!c_options.omit_source_map_url) { |
505 | | // generate an embedded source map |
506 | 4 | if (c_options.source_map_embed) { |
507 | 0 | emitted.buffer += linefeed; |
508 | 0 | emitted.buffer += format_embedded_source_map(); |
509 | 0 | } |
510 | | // or just link the generated one |
511 | 4 | else if (source_map_file != "") { |
512 | 0 | emitted.buffer += linefeed; |
513 | 0 | emitted.buffer += format_source_mapping_url(source_map_file); |
514 | 0 | } |
515 | 4 | } |
516 | | // create a copy of the resulting buffer string |
517 | | // this must be freed or taken over by implementor |
518 | 4 | return sass_copy_c_string(emitted.buffer.c_str()); |
519 | 4 | } |
520 | | |
521 | | void Context::apply_custom_headers(Block_Obj root, const char* ctx_path, SourceSpan pstate) |
522 | 18 | { |
523 | | // create a custom import to resolve headers |
524 | 18 | Import_Obj imp = SASS_MEMORY_NEW(Import, pstate); |
525 | | // dispatch headers which will add custom functions |
526 | | // custom headers are added to the import instance |
527 | 18 | call_headers(entry_path, ctx_path, pstate, imp); |
528 | | // increase head count to skip later |
529 | 18 | head_imports += resources.size() - 1; |
530 | | // add the statement if we have urls |
531 | 18 | if (!imp->urls().empty()) root->append(imp); |
532 | | // process all other resources (add Import_Stub nodes) |
533 | 18 | for (size_t i = 0, S = imp->incs().size(); i < S; ++i) { |
534 | 0 | root->append(SASS_MEMORY_NEW(Import_Stub, pstate, imp->incs()[i])); |
535 | 0 | } |
536 | 18 | } |
537 | | |
538 | | Block_Obj File_Context::parse() |
539 | 0 | { |
540 | | |
541 | | // check if entry file is given |
542 | 0 | if (input_path.empty()) return {}; |
543 | | |
544 | | // create absolute path from input filename |
545 | | // ToDo: this should be resolved via custom importers |
546 | 0 | sass::string abs_path(rel2abs(input_path, CWD)); |
547 | | |
548 | | // try to load the entry file |
549 | 0 | char* contents = read_file(abs_path); |
550 | | |
551 | | // alternatively also look inside each include path folder |
552 | | // I think this differs from ruby sass (IMO too late to remove) |
553 | 0 | for (size_t i = 0, S = include_paths.size(); contents == 0 && i < S; ++i) { |
554 | | // build absolute path for this include path entry |
555 | 0 | abs_path = rel2abs(input_path, include_paths[i]); |
556 | | // try to load the resulting path |
557 | 0 | contents = read_file(abs_path); |
558 | 0 | } |
559 | | |
560 | | // abort early if no content could be loaded (various reasons) |
561 | 0 | if (!contents) throw std::runtime_error( |
562 | 0 | "File to read not found or unreadable: " |
563 | 0 | + std::string(input_path.c_str())); |
564 | | |
565 | | // store entry path |
566 | 0 | entry_path = abs_path; |
567 | | |
568 | | // create entry only for import stack |
569 | 0 | Sass_Import_Entry import = sass_make_import( |
570 | 0 | input_path.c_str(), |
571 | 0 | entry_path.c_str(), |
572 | 0 | contents, |
573 | 0 | 0 |
574 | 0 | ); |
575 | | // add the entry to the stack |
576 | 0 | import_stack.push_back(import); |
577 | | |
578 | | // create the source entry for file entry |
579 | 0 | register_resource({{ input_path, "." }, abs_path }, { contents, 0 }); |
580 | | |
581 | | // create root ast tree node |
582 | 0 | return compile(); |
583 | |
|
584 | 0 | } |
585 | | |
586 | | Block_Obj Data_Context::parse() |
587 | 20 | { |
588 | | |
589 | | // check if source string is given |
590 | 20 | if (!source_c_str) return {}; |
591 | | |
592 | | // convert indented sass syntax |
593 | 20 | if(c_options.is_indented_syntax_src) { |
594 | | // call sass2scss to convert the string |
595 | 0 | char * converted = sass2scss(source_c_str, |
596 | | // preserve the structure as much as possible |
597 | 0 | SASS2SCSS_PRETTIFY_1 | SASS2SCSS_KEEP_COMMENT); |
598 | | // replace old source_c_str with converted |
599 | 0 | free(source_c_str); source_c_str = converted; |
600 | 0 | } |
601 | | |
602 | | // remember entry path (defaults to stdin for string) |
603 | 20 | entry_path = input_path.empty() ? "stdin" : input_path; |
604 | | |
605 | | // ToDo: this may be resolved via custom importers |
606 | 20 | sass::string abs_path(rel2abs(entry_path)); |
607 | 20 | char* abs_path_c_str = sass_copy_c_string(abs_path.c_str()); |
608 | 20 | strings.push_back(abs_path_c_str); |
609 | | |
610 | | // create entry only for the import stack |
611 | 20 | Sass_Import_Entry import = sass_make_import( |
612 | 20 | entry_path.c_str(), |
613 | 20 | abs_path_c_str, |
614 | 20 | source_c_str, |
615 | 20 | srcmap_c_str |
616 | 20 | ); |
617 | | // add the entry to the stack |
618 | 20 | import_stack.push_back(import); |
619 | | |
620 | | // register a synthetic resource (path does not really exist, skip in includes) |
621 | 20 | register_resource({{ input_path, "." }, input_path }, { source_c_str, srcmap_c_str }); |
622 | | |
623 | | // create root ast tree node |
624 | 20 | return compile(); |
625 | 20 | } |
626 | | |
627 | | // parse root block from includes |
628 | | Block_Obj Context::compile() |
629 | 4 | { |
630 | | // abort if there is no data |
631 | 4 | if (resources.size() == 0) return {}; |
632 | | // get root block from the first style sheet |
633 | 4 | Block_Obj root = sheets.at(entry_path).root; |
634 | | // abort on invalid root |
635 | 4 | if (root.isNull()) return {}; |
636 | 4 | Env global; // create root environment |
637 | | // register built-in functions on env |
638 | 4 | register_built_in_functions(*this, &global); |
639 | | // register custom functions (defined via C-API) |
640 | 4 | for (size_t i = 0, S = c_functions.size(); i < S; ++i) |
641 | 0 | { register_c_function(*this, &global, c_functions[i]); } |
642 | | // create initial backtrace entry |
643 | | // create crtp visitor objects |
644 | 4 | Expand expand(*this, &global); |
645 | 4 | Cssize cssize(*this); |
646 | 4 | CheckNesting check_nesting; |
647 | | // check nesting in all files |
648 | 4 | for (auto sheet : sheets) { |
649 | 4 | auto styles = sheet.second; |
650 | 4 | check_nesting(styles.root); |
651 | 4 | } |
652 | | // expand and eval the tree |
653 | 4 | root = expand(root); |
654 | | |
655 | 4 | Extension unsatisfied; |
656 | | // check that all extends were used |
657 | 4 | if (extender.checkForUnsatisfiedExtends(unsatisfied)) { |
658 | 0 | throw Exception::UnsatisfiedExtend(traces, unsatisfied); |
659 | 0 | } |
660 | | |
661 | | // check nesting |
662 | 4 | check_nesting(root); |
663 | | // merge and bubble certain rules |
664 | 4 | root = cssize(root); |
665 | | |
666 | | // clean up by removing empty placeholders |
667 | | // ToDo: maybe we can do this somewhere else? |
668 | 4 | Remove_Placeholders remove_placeholders; |
669 | 4 | root->perform(&remove_placeholders); |
670 | | |
671 | | // return processed tree |
672 | 4 | return root; |
673 | 4 | } |
674 | | // EO compile |
675 | | |
676 | | sass::string Context::format_embedded_source_map() |
677 | 0 | { |
678 | 0 | sass::string map = emitter.render_srcmap(*this); |
679 | 0 | sass::istream is( map.c_str() ); |
680 | 0 | sass::ostream buffer; |
681 | 0 | base64::encoder E; |
682 | 0 | E.encode(is, buffer); |
683 | 0 | sass::string url = "data:application/json;base64," + buffer.str(); |
684 | 0 | url.erase(url.size() - 1); |
685 | 0 | return "/*# sourceMappingURL=" + url + " */"; |
686 | 0 | } |
687 | | |
688 | | sass::string Context::format_source_mapping_url(const sass::string& file) |
689 | 0 | { |
690 | 0 | sass::string url = abs2rel(file, output_path, CWD); |
691 | 0 | return "/*# sourceMappingURL=" + url + " */"; |
692 | 0 | } |
693 | | |
694 | | char* Context::render_srcmap() |
695 | 4 | { |
696 | 4 | if (source_map_file == "") return 0; |
697 | 0 | sass::string map = emitter.render_srcmap(*this); |
698 | 0 | return sass_copy_c_string(map.c_str()); |
699 | 4 | } |
700 | | |
701 | | |
702 | | // for data context we want to start after "stdin" |
703 | | // we probably always want to skip the header includes? |
704 | | sass::vector<sass::string> Context::get_included_files(bool skip, size_t headers) |
705 | 4 | { |
706 | | // create a copy of the vector for manipulations |
707 | 4 | sass::vector<sass::string> includes = included_files; |
708 | 4 | if (includes.size() == 0) return includes; |
709 | 4 | if (skip) { includes.erase( includes.begin(), includes.begin() + 1 + headers); } |
710 | 0 | else { includes.erase( includes.begin() + 1, includes.begin() + 1 + headers); } |
711 | 4 | includes.erase( std::unique( includes.begin(), includes.end() ), includes.end() ); |
712 | 4 | std::sort( includes.begin() + (skip ? 0 : 1), includes.end() ); |
713 | 4 | return includes; |
714 | 4 | } |
715 | | |
716 | | void register_function(Context& ctx, Signature sig, Native_Function f, Env* env) |
717 | 336 | { |
718 | 336 | Definition* def = make_native_function(sig, f, ctx); |
719 | 336 | def->environment(env); |
720 | 336 | (*env)[def->name() + "[f]"] = def; |
721 | 336 | } |
722 | | |
723 | | void register_function(Context& ctx, Signature sig, Native_Function f, size_t arity, Env* env) |
724 | 8 | { |
725 | 8 | Definition* def = make_native_function(sig, f, ctx); |
726 | 8 | sass::ostream ss; |
727 | 8 | ss << def->name() << "[f]" << arity; |
728 | 8 | def->environment(env); |
729 | 8 | (*env)[ss.str()] = def; |
730 | 8 | } |
731 | | |
732 | | void register_overload_stub(Context& ctx, sass::string name, Env* env) |
733 | 4 | { |
734 | 4 | Definition* stub = SASS_MEMORY_NEW(Definition, |
735 | 4 | SourceSpan{ "[built-in function]" }, |
736 | 4 | nullptr, |
737 | 4 | name, |
738 | 4 | Parameters_Obj{}, |
739 | 4 | nullptr, |
740 | 4 | true); |
741 | 4 | (*env)[name + "[f]"] = stub; |
742 | 4 | } |
743 | | |
744 | | |
745 | | void register_built_in_functions(Context& ctx, Env* env) |
746 | 4 | { |
747 | 4 | using namespace Functions; |
748 | | // RGB Functions |
749 | 4 | register_function(ctx, rgb_sig, rgb, env); |
750 | 4 | register_overload_stub(ctx, "rgba", env); |
751 | 4 | register_function(ctx, rgba_4_sig, rgba_4, 4, env); |
752 | 4 | register_function(ctx, rgba_2_sig, rgba_2, 2, env); |
753 | 4 | register_function(ctx, red_sig, red, env); |
754 | 4 | register_function(ctx, green_sig, green, env); |
755 | 4 | register_function(ctx, blue_sig, blue, env); |
756 | 4 | register_function(ctx, mix_sig, mix, env); |
757 | | // HSL Functions |
758 | 4 | register_function(ctx, hsl_sig, hsl, env); |
759 | 4 | register_function(ctx, hsla_sig, hsla, env); |
760 | 4 | register_function(ctx, hue_sig, hue, env); |
761 | 4 | register_function(ctx, saturation_sig, saturation, env); |
762 | 4 | register_function(ctx, lightness_sig, lightness, env); |
763 | 4 | register_function(ctx, adjust_hue_sig, adjust_hue, env); |
764 | 4 | register_function(ctx, lighten_sig, lighten, env); |
765 | 4 | register_function(ctx, darken_sig, darken, env); |
766 | 4 | register_function(ctx, saturate_sig, saturate, env); |
767 | 4 | register_function(ctx, desaturate_sig, desaturate, env); |
768 | 4 | register_function(ctx, grayscale_sig, grayscale, env); |
769 | 4 | register_function(ctx, complement_sig, complement, env); |
770 | 4 | register_function(ctx, invert_sig, invert, env); |
771 | | // Opacity Functions |
772 | 4 | register_function(ctx, alpha_sig, alpha, env); |
773 | 4 | register_function(ctx, opacity_sig, alpha, env); |
774 | 4 | register_function(ctx, opacify_sig, opacify, env); |
775 | 4 | register_function(ctx, fade_in_sig, opacify, env); |
776 | 4 | register_function(ctx, transparentize_sig, transparentize, env); |
777 | 4 | register_function(ctx, fade_out_sig, transparentize, env); |
778 | | // Other Color Functions |
779 | 4 | register_function(ctx, adjust_color_sig, adjust_color, env); |
780 | 4 | register_function(ctx, scale_color_sig, scale_color, env); |
781 | 4 | register_function(ctx, change_color_sig, change_color, env); |
782 | 4 | register_function(ctx, ie_hex_str_sig, ie_hex_str, env); |
783 | | // String Functions |
784 | 4 | register_function(ctx, unquote_sig, sass_unquote, env); |
785 | 4 | register_function(ctx, quote_sig, sass_quote, env); |
786 | 4 | register_function(ctx, str_length_sig, str_length, env); |
787 | 4 | register_function(ctx, str_insert_sig, str_insert, env); |
788 | 4 | register_function(ctx, str_index_sig, str_index, env); |
789 | 4 | register_function(ctx, str_slice_sig, str_slice, env); |
790 | 4 | register_function(ctx, to_upper_case_sig, to_upper_case, env); |
791 | 4 | register_function(ctx, to_lower_case_sig, to_lower_case, env); |
792 | | // Number Functions |
793 | 4 | register_function(ctx, percentage_sig, percentage, env); |
794 | 4 | register_function(ctx, round_sig, round, env); |
795 | 4 | register_function(ctx, ceil_sig, ceil, env); |
796 | 4 | register_function(ctx, floor_sig, floor, env); |
797 | 4 | register_function(ctx, abs_sig, abs, env); |
798 | 4 | register_function(ctx, min_sig, min, env); |
799 | 4 | register_function(ctx, max_sig, max, env); |
800 | 4 | register_function(ctx, random_sig, random, env); |
801 | | // List Functions |
802 | 4 | register_function(ctx, length_sig, length, env); |
803 | 4 | register_function(ctx, nth_sig, nth, env); |
804 | 4 | register_function(ctx, set_nth_sig, set_nth, env); |
805 | 4 | register_function(ctx, index_sig, index, env); |
806 | 4 | register_function(ctx, join_sig, join, env); |
807 | 4 | register_function(ctx, append_sig, append, env); |
808 | 4 | register_function(ctx, zip_sig, zip, env); |
809 | 4 | register_function(ctx, list_separator_sig, list_separator, env); |
810 | 4 | register_function(ctx, is_bracketed_sig, is_bracketed, env); |
811 | | // Map Functions |
812 | 4 | register_function(ctx, map_get_sig, map_get, env); |
813 | 4 | register_function(ctx, map_merge_sig, map_merge, env); |
814 | 4 | register_function(ctx, map_remove_sig, map_remove, env); |
815 | 4 | register_function(ctx, map_keys_sig, map_keys, env); |
816 | 4 | register_function(ctx, map_values_sig, map_values, env); |
817 | 4 | register_function(ctx, map_has_key_sig, map_has_key, env); |
818 | 4 | register_function(ctx, keywords_sig, keywords, env); |
819 | | // Introspection Functions |
820 | 4 | register_function(ctx, type_of_sig, type_of, env); |
821 | 4 | register_function(ctx, unit_sig, unit, env); |
822 | 4 | register_function(ctx, unitless_sig, unitless, env); |
823 | 4 | register_function(ctx, comparable_sig, comparable, env); |
824 | 4 | register_function(ctx, variable_exists_sig, variable_exists, env); |
825 | 4 | register_function(ctx, global_variable_exists_sig, global_variable_exists, env); |
826 | 4 | register_function(ctx, function_exists_sig, function_exists, env); |
827 | 4 | register_function(ctx, mixin_exists_sig, mixin_exists, env); |
828 | 4 | register_function(ctx, feature_exists_sig, feature_exists, env); |
829 | 4 | register_function(ctx, call_sig, call, env); |
830 | 4 | register_function(ctx, content_exists_sig, content_exists, env); |
831 | 4 | register_function(ctx, get_function_sig, get_function, env); |
832 | | // Boolean Functions |
833 | 4 | register_function(ctx, not_sig, sass_not, env); |
834 | 4 | register_function(ctx, if_sig, sass_if, env); |
835 | | // Misc Functions |
836 | 4 | register_function(ctx, inspect_sig, inspect, env); |
837 | 4 | register_function(ctx, unique_id_sig, unique_id, env); |
838 | | // Selector functions |
839 | 4 | register_function(ctx, selector_nest_sig, selector_nest, env); |
840 | 4 | register_function(ctx, selector_append_sig, selector_append, env); |
841 | 4 | register_function(ctx, selector_extend_sig, selector_extend, env); |
842 | 4 | register_function(ctx, selector_replace_sig, selector_replace, env); |
843 | 4 | register_function(ctx, selector_unify_sig, selector_unify, env); |
844 | 4 | register_function(ctx, is_superselector_sig, is_superselector, env); |
845 | 4 | register_function(ctx, simple_selectors_sig, simple_selectors, env); |
846 | 4 | register_function(ctx, selector_parse_sig, selector_parse, env); |
847 | 4 | } |
848 | | |
849 | | void register_c_functions(Context& ctx, Env* env, Sass_Function_List descrs) |
850 | 0 | { |
851 | 0 | while (descrs && *descrs) { |
852 | 0 | register_c_function(ctx, env, *descrs); |
853 | 0 | ++descrs; |
854 | 0 | } |
855 | 0 | } |
856 | | void register_c_function(Context& ctx, Env* env, Sass_Function_Entry descr) |
857 | 0 | { |
858 | 0 | Definition* def = make_c_function(descr, ctx); |
859 | 0 | def->environment(env); |
860 | 0 | (*env)[def->name() + "[f]"] = def; |
861 | 0 | } |
862 | | |
863 | | } |