1b2e7d2016-11-23Pontus Östlund /* Author: Pontus Östlund <https://profiles.google.com/poppanator> */
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 
e8f2512018-03-23Pontus Östlund protected typedef string(8bit) s8;
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
8384a22016-12-03Pontus Östlund class Compiler
1b2e7d2016-11-23Pontus Östlund {
4e4d002018-03-08Pontus Östlund  // NOTE: The only reason for this wrapper class is to be able to explicily // destruct the real compiler. There's a cyclic reference between // the compiler and the import callback in the real compiler so it // won't be destructed when it runs out of scope. If it can be solved // internally in the Low_Compiler this class no longer has a purpose //! @ignore
d2455d2018-03-13Pontus Östlund  //! //! Ignore the rest of this class. The methods and members are documented in //! @[Low_Compiler]
4e4d002018-03-08Pontus Östlund  protected Low_Compiler compiler; protected void create() { compiler = Low_Compiler(); }
d2455d2018-03-13Pontus Östlund // SETTER(bool, prop) -> // public void `prop=(bool value) { compiler->prop = value; }
4e4d002018-03-08Pontus Östlund #define SETTER(T,P) \ public void ` ## P ## =(T value) { compiler-> ## P = value; }
d2455d2018-03-13Pontus Östlund // GETTER(bool, prop) -> // public bool `prop() { return compiler->prop; }
4e4d002018-03-08Pontus Östlund #define GETTER(T,P) \ public T ` ## P ## () { return compiler-> ## P; } #define GETTER_SETTER(T,P) \ GETTER(T,P) \ SETTER(T,P)
e8f2512018-03-23Pontus Östlund  GETTER_SETTER(int(0..2), http_import) GETTER_SETTER(multiset(s8), http_import_allow_ct) GETTER_SETTER(bool, check_file_access) GETTER_SETTER(s8, include_path) GETTER_SETTER(int(0..3), output_style) GETTER_SETTER(s8, source_map_file) GETTER_SETTER(s8, source_map_root) GETTER_SETTER(int(0..), precision) GETTER_SETTER(int(0..1), source_comments) GETTER_SETTER(int(0..1), source_map_embed) GETTER_SETTER(int(0..1), omit_source_map_url) mapping(s8:s8) compile_file(s8 input_file)
4e4d002018-03-08Pontus Östlund  { return compiler->compile_file(input_file); }
e8f2512018-03-23Pontus Östlund  variant void compile_file(s8 input_file, s8 output_file)
4e4d002018-03-08Pontus Östlund  { compiler->compile_file(input_file, output_file); }
e8f2512018-03-23Pontus Östlund  mapping(s8:s8) compile_string(s8 source)
4e4d002018-03-08Pontus Östlund  { return compiler->compile_string(source); }
e8f2512018-03-23Pontus Östlund  void set_options(mapping(s8:s8|int) opts)
4e4d002018-03-08Pontus Östlund  { compiler->set_options(opts); } protected void _destruct() { if (compiler) { destruct(compiler); } } //! @endignore } class Low_Compiler {
8384a22016-12-03Pontus Östlund  //! @ignore
b9018d2016-11-29Pontus Östlund  inherit Tools@module@.Api;
8384a22016-12-03Pontus Östlund  //! @endignore
1b2e7d2016-11-23Pontus Östlund 
4e4d002018-03-08Pontus Östlund  //! @appears Tools.Sass.Compiler.http_import //!
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;
4e4d002018-03-08Pontus Östlund  //! @appears Tools.Sass.Compiler.http_import_allow_ct //!
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" >);
4e4d002018-03-08Pontus Östlund  //! @appears Tools.Sass.Compiler.check_file_access //!
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  //! @ignore protected void create() {
4e4d002018-03-08Pontus Östlund  // __set_importer_callback(__resolve_import); this::__importer_cb = __resolve_import;
72603a2016-12-03Pontus Östlund  } //! @endignore
4f0a5f2016-12-05Pontus Östlund 
72603a2016-12-03Pontus Östlund  //! @ignore
4f0a5f2016-12-05Pontus Östlund  //! Resolve external imports in sass/scss files.
e8f2512018-03-23Pontus Östlund  protected s8 __resolve_import(s8 path, s8 abs_path, s8 prev_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  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]; } } return data; } //! @endignore
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
e8f2512018-03-23Pontus Östlund  s8 `include_path()
4f0a5f2016-12-05Pontus Östlund  { return ::include_path;
dd97562016-12-04Pontus Östlund  }
4e4d002018-03-08Pontus Östlund  //! @appears Tools.Sass.Compiler.compile_file //!
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
e8f2512018-03-23Pontus Östlund  //! @member s8 "css"
acafa02016-12-01Pontus Östlund  //! The generated CSS
e8f2512018-03-23Pontus Östlund  //! @member s8 "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  }
4e4d002018-03-08Pontus Östlund  //! @appears Tools.Sass.Compiler.compile_file //!
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  } }
4e4d002018-03-08Pontus Östlund  //! @appears Tools.Sass.Compiler.set_options
148c002016-12-04Pontus Ö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  //!
e8f2512018-03-23Pontus Östlund  //! @member s8 "include_path"
4f0a5f2016-12-05Pontus Östlund  //! Path to root of incude files. See also @[include_path].
e1370d2016-12-01Pontus Östlund  //!
e8f2512018-03-23Pontus Östlund  //! @member s8 "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  //!
e8f2512018-03-23Pontus Östlund  //! @member s8 "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  } } } }