1b2e7d2016-11-23Pontus Östlund /*
a0a7212018-04-10Pontus Östlund || This file is part of Pike. For copyright information see COPYRIGHT. || Pike is distributed under GPL, LGPL and MPL. See the file COPYING || for more information. */ /* Author: Pontus Östlund <https://github.com/poppa/>
1b2e7d2016-11-23Pontus Östlund */
8384a22016-12-03Pontus Östlund 
b9018d2016-11-29Pontus Östlund //! Sass is a scripting language that is interpreted into Cascading Style //! Sheets (CSS). This module is a glue for @tt{libsass@}. //! //! @seealso //! SASS @url{http://sass-lang.com/@}
1b2e7d2016-11-23Pontus Östlund  #pike __REAL_VERSION__
b9018d2016-11-29Pontus Östlund #require constant(Tools@module@)
1b2e7d2016-11-23Pontus Östlund 
e1370d2016-12-01Pontus Östlund //! @ignore
1b2e7d2016-11-23Pontus Östlund inherit Tools@module@;
e1370d2016-12-01Pontus Östlund //! @endignore
1b2e7d2016-11-23Pontus Östlund 
a122412018-03-23Pontus Östlund //! Shorthand for @tt{string(8bit)@} typedef string(8bit) s8;
e8f2512018-03-23Pontus Östlund 
7ec6492018-03-26Pontus Östlund class Compiler
148c002016-12-04Pontus Östlund //! Sass/SCSS compiler. //!
b9018d2016-11-29Pontus Östlund //! @example //! @code
8384a22016-12-03Pontus Östlund //! Tools.Sass.Compiler compiler = Tools.Sass.Compiler();
148c002016-12-04Pontus Östlund //! //! // Allow for HTTP imports, disallowed by default. //! compiler->http_import = Tools.Sass.HTTP_IMPORT_ANY; //!
b9018d2016-11-29Pontus Östlund //! // Minify the output and create a source map file. //! compiler->set_options(([
3480d62018-03-07Pontus Östlund //! "output_style" : Tools.Sass.STYLE_COMPRESSED,
b9018d2016-11-29Pontus Östlund //! "source_map_file" : "path/to/write/source.map" //! ])); //! //! if (mixed e = catch(compiler->compile_file("input.scss", "output.css"))) { //! werror("Failed compiling input.scss to output.css\n"); //! } //! @endcode
4e4d002018-03-08Pontus Östlund {
8384a22016-12-03Pontus Östlund  //! @ignore
b9018d2016-11-29Pontus Östlund  inherit Tools@module@.Api;
8384a22016-12-03Pontus Östlund  //! @endignore
1b2e7d2016-11-23Pontus Östlund 
7ec6492018-03-26Pontus Östlund 
72603a2016-12-03Pontus Östlund  //! If a Sass file is importing an external URI this flag determines if //! thats allowed at all, or if the content type of the imported file has
4dedae2018-03-05Pontus Östlund  //! to be in @[http_import_allow_ct], or if anything goes. //! Default is @[HTTP_IMPORT_NONE].
72603a2016-12-03Pontus Östlund  //! //! @seealso
8384a22016-12-03Pontus Östlund  //! @[HTTP_IMPORT_NONE], @[HTTP_IMPORT_GREEDY] and //! @[HTTP_IMPORT_ANY].
148c002016-12-04Pontus Östlund  public int(0..2) http_import = HTTP_IMPORT_NONE;
7ec6492018-03-26Pontus Östlund 
4dedae2018-03-05Pontus Östlund  //! List of allowed content types if @[http_import] is set to //! @[HTTP_IMPORT_GREEDY]. The default is to allow @tt{text/scss@} and //! @tt{text/sass@}.
e8f2512018-03-23Pontus Östlund  public multiset(s8) http_import_allow_ct =
4dedae2018-03-05Pontus Östlund  (< "text/scss", "text/sass" >);
7ec6492018-03-26Pontus Östlund 
148c002016-12-04Pontus Östlund  //! Should file access be tested right away when paths are set or should that //! be left to Sass to handle? The default value is @tt{true@}. public bool check_file_access = true;
72603a2016-12-03Pontus Östlund 
7ec6492018-03-26Pontus Östlund  //! @decl protected string|array(string(8bit)) handle_sass_import(@ //! string(8bit) path, @ //! void|string(8bit) absolute_path, @ //! void|string(8bit) relative_path) //! //! Resolve imports in sass/scss files. //! //! @note //! In general this method doesn't need to overloaded. In principle it's //! only necessary if the Sass files reside in a non-standard filesystem.
a122412018-03-23Pontus Östlund  //! //! @note //! If overloaded @[abs_path] and @[rel_path] is the absolute and relaive //! paths of the file containing the import statement @[path]. //! If the Sass/SCSS files are located in a normal filesystem this method //! can return the contents of @[path] as a string and @tt{libsass@} will //! resolve the paths to the imports by itself. //! //! However, if the files are not located in a normal filesystem this //! function should return an array of two indices, where the first index //! should be the contents of @[path] and the second the calculated absolute //! path of @[path].
7ec6492018-03-26Pontus Östlund  //! //! @param path //! This is the value of `path` in @tt{@@import 'path'@}. //! @param absolute_path //! This is the absolute path of the file containing the @tt{@@import@} //! statement. //! @param relative_path //! The relative path of @[absolute_path] in relation to the prevoius //! @[absolute_path] //! //! @returns //! @mixed //! @type int(0..0) //! If undefined is returned the import resolution is given back to //! @tt{libsass@}. //! @type string(8bit) //! The contents of @[path] //! @type array(string(8bit)) //! if an array is returned it should contain two indices, where the first //! if the contents of @[path] and the second should be the absolute path //! @[path]. This is only useful (needed) if the Sass files doesn't //! reside in a normal filesystem that @tt{libsass@} can read. //! @endmixed protected s8|array(s8) handle_sass_import(s8 path, s8 abs_path, s8 rel_path)
72603a2016-12-03Pontus Östlund  { Standards.URI uri;
148c002016-12-04Pontus Östlund  // If it's not an URI we assume it's a local import and we let Sass handle // it. This could of course be a, by mistake, malformed URI, but then Sass // will eventually throw.
72603a2016-12-03Pontus Östlund  if (catch (uri = Standards.URI(path))) { return UNDEFINED; }
148c002016-12-04Pontus Östlund  if (http_import == HTTP_IMPORT_NONE) {
8384a22016-12-03Pontus Östlund  error("Imports over HTTP not allowed!\n"); }
72603a2016-12-03Pontus Östlund  Protocols.HTTP.Query q = Protocols.HTTP.get_url(uri);
4dedae2018-03-05Pontus Östlund  if (q->status / 100 != 2) {
72603a2016-12-03Pontus Östlund  error("Bad HTTP status (%d) for @import %q!\n", q->status, (string) uri); } array(string) ct_parts = map(q->headers["content-type"]/";", String.trim_all_whites);
148c002016-12-04Pontus Östlund  if (http_import == HTTP_IMPORT_GREEDY) {
4dedae2018-03-05Pontus Östlund  if (!http_import_allow_ct[ct_parts[0]]) {
72603a2016-12-03Pontus Östlund  error("Returned content type from import (%s) was %q. "
4dedae2018-03-05Pontus Östlund  "Expected %s!\n", uri, ct_parts[0], String.implode_nicely((array)http_import_allow_ct, "or"));
72603a2016-12-03Pontus Östlund  } }
e8f2512018-03-23Pontus Östlund  s8 data = q->data();
72603a2016-12-03Pontus Östlund 
7ec6492018-03-26Pontus Östlund #if 0 // FIXME: Decode/encode properly to UTF-8
72603a2016-12-03Pontus Östlund  if (sizeof(ct_parts) > 1) { sscanf(ct_parts[1], "%*s=%s", string charset);
4f0a5f2016-12-05Pontus Östlund  // In case of charset="utf-8" or charset='utf-8', remove the "fnutts"
72603a2016-12-03Pontus Östlund  if (charset && charset[0] < 65) { charset = charset[1..<1]; } }
7ec6492018-03-26Pontus Östlund #endif
72603a2016-12-03Pontus Östlund  return data; }
4f0a5f2016-12-05Pontus Östlund  // Documented in the CMOD
e8f2512018-03-23Pontus Östlund  void `include_path=(s8 path)
148c002016-12-04Pontus Östlund  {
3480d62018-03-07Pontus Östlund  if (check_file_access && stringp(path) && !Stdio.exist(path)) {
4f0a5f2016-12-05Pontus Östlund  error("Include path %q does not exist!\n", path);
148c002016-12-04Pontus Östlund  }
4f0a5f2016-12-05Pontus Östlund  ::include_path = path;
148c002016-12-04Pontus Östlund  }
4f0a5f2016-12-05Pontus Östlund  // Documented in the CMOD
0ead192018-03-26Pontus Östlund  // In Pike 8.1 this is needed or else the compiler will bitch about // "No getter for variable" when the setter above is defined! s8 `include_path() { return ::include_path; }
7ec6492018-03-26Pontus Östlund 
dd97562016-12-04Pontus Östlund 
acafa02016-12-01Pontus Östlund  //! Compile the file @[input_file] and return the result //! //! @param input_file //! The SCSS file to compile //! //! @returns //! A mapping with the generated CSS and source mapping file if such is //! set to be generated //! //! @mapping
d178732018-03-24Pontus Östlund  //! @member string(8bit) "css"
acafa02016-12-01Pontus Östlund  //! The generated CSS
d178732018-03-24Pontus Östlund  //! @member string(8bit) "map"
acafa02016-12-01Pontus Östlund  //! The generated source mapping data //! @endmapping
e8f2512018-03-23Pontus Östlund  mapping(s8:s8) compile_file(s8 input_file)
acafa02016-12-01Pontus Östlund  {
148c002016-12-04Pontus Östlund  if (check_file_access && !Stdio.exist(input_file)) { error("Input file %q does not exist or isn't accessible!\n", input_file); }
ee3dae2017-01-20Pontus Östlund  return ::compile_file(input_file);
acafa02016-12-01Pontus Östlund  }
7ec6492018-03-26Pontus Östlund 
acafa02016-12-01Pontus Östlund  //! Compile the file @[input_file] and write the result to @[output_file]. //! If a source mapping file is set to be generated either via
c40b7c2016-12-06Pontus Östlund  //! @[set_options()] or @[source_map_file] it will be written as per
acafa02016-12-01Pontus Östlund  //! the value set in the option. //! //! @param input_file //! The SCSS file to compile //! @param output_file //! The name of the CSS file to save the result in.
e8f2512018-03-23Pontus Östlund  variant void compile_file(s8 input_file, s8 output_file)
acafa02016-12-01Pontus Östlund  {
148c002016-12-04Pontus Östlund  if (check_file_access && !Stdio.exist(input_file)) { error("Input file %q does not exist or isn't accessible!\n", input_file); }
e8f2512018-03-23Pontus Östlund  mapping(s8:s8) val = ::compile_file(input_file);
acafa02016-12-01Pontus Östlund  Stdio.write_file(output_file, val->css);
4f0a5f2016-12-05Pontus Östlund  if (val->map && source_map_file) { Stdio.write_file(source_map_file, val->map);
acafa02016-12-01Pontus Östlund  } }
7ec6492018-03-26Pontus Östlund 
c40b7c2016-12-06Pontus Östlund  //! Set options to the SASS compiler.
1b2e7d2016-11-23Pontus Östlund  //! //! @param opts //! @mapping //! @member int "output_style" //! Any of the @[STYLE_NESTED], @[STYLE_EXPANDED], @[STYLE_COMPACT]
4f0a5f2016-12-05Pontus Östlund  //! or @[STYLE_COMPRESSED] constants. See also @[output_style].
e1370d2016-12-01Pontus Östlund  //!
d178732018-03-24Pontus Östlund  //! @member string(8bit) "include_path"
4f0a5f2016-12-05Pontus Östlund  //! Path to root of incude files. See also @[include_path].
e1370d2016-12-01Pontus Östlund  //!
d178732018-03-24Pontus Östlund  //! @member string(8bit) "source_map_file"
4f0a5f2016-12-05Pontus Östlund  //! File to write source map file to. //! See also @[source_map_file].
e1370d2016-12-01Pontus Östlund  //!
1b2e7d2016-11-23Pontus Östlund  //! @member bool "source_comments" //! Turn on/off comments in the output containing info about the source
ef03332016-11-26Pontus Östlund  //! file - line numbers and such. Default of @tt{false@}. See also
4f0a5f2016-12-05Pontus Östlund  //! @[source_comments].
e1370d2016-12-01Pontus Östlund  //!
1b2e7d2016-11-23Pontus Östlund  //! @member bool "source_map_embed" //! Turn on/off if a source map should be embedded in the output or not.
4f0a5f2016-12-05Pontus Östlund  //! Default is @tt{false@}. See also @[source_map_embed].
e1370d2016-12-01Pontus Östlund  //!
d178732018-03-24Pontus Östlund  //! @member string(8bit) "source_map_root"
e1370d2016-12-01Pontus Östlund  //! Set the root path of the source files, relative to where the //! source.map file is written.
4f0a5f2016-12-05Pontus Östlund  //! See also @[source_map_root]
e1370d2016-12-01Pontus Östlund  //!
4f0a5f2016-12-05Pontus Östlund  //! @member bool "omit_source_map_url"
e1370d2016-12-01Pontus Östlund  //! Omit the #sourceMappingURL or not.
4f0a5f2016-12-05Pontus Östlund  //! See also @[omit_source_map_url]
4e4d002018-03-08Pontus Östlund  //! //! @member int "precision" //! Floating point precision. See also @[precision].
1b2e7d2016-11-23Pontus Östlund  //! @endmapping
e8f2512018-03-23Pontus Östlund  void set_options(mapping(s8:s8|int) opts)
1b2e7d2016-11-23Pontus Östlund  { foreach (opts; string opt; string|int val) { switch (opt) { case "output_style":
2294332017-07-26Pontus Östlund  if (!(< STYLE_NESTED, STYLE_COMPRESSED, STYLE_COMPACT, STYLE_EXPANDED >)[val]) { error("Unrecognized output style value!\n"); }
4f0a5f2016-12-05Pontus Östlund  output_style = val;
1b2e7d2016-11-23Pontus Östlund  break; case "include_path":
4f0a5f2016-12-05Pontus Östlund  include_path = val;
1b2e7d2016-11-23Pontus Östlund  break; case "source_map_file":
4f0a5f2016-12-05Pontus Östlund  source_map_file = val;
1b2e7d2016-11-23Pontus Östlund  break; case "source_map_embed":
4f0a5f2016-12-05Pontus Östlund  source_map_embed = val;
1b2e7d2016-11-23Pontus Östlund  break;
18ad792016-11-24Pontus Östlund  case "source_map_root":
4f0a5f2016-12-05Pontus Östlund  source_map_root = val;
18ad792016-11-24Pontus Östlund  break;
4f0a5f2016-12-05Pontus Östlund  case "omit_source_map_url": omit_source_map_url = val;
18ad792016-11-24Pontus Östlund  break;
1b2e7d2016-11-23Pontus Östlund  case "source_comments":
4f0a5f2016-12-05Pontus Östlund  source_comments = val;
1b2e7d2016-11-23Pontus Östlund  break;
18ad792016-11-24Pontus Östlund 
3480d62018-03-07Pontus Östlund  case "precision": precision = val; break;
18ad792016-11-24Pontus Östlund  default: error("Unknown option %O!\n", opt);
1b2e7d2016-11-23Pontus Östlund  } } } }