Web

Sass

Sass and SCSS compiled to CSS in pure Rust (grass): from text or a file, with @use from load paths, the indented syntax, variables set from the program, and errors and @warn messages with their line.

Version 1.0.0 · 3 functions · 1 crate

#css#assets#themes#design

Functions

What use sass.qwer gives a program, with the types Quillo sees. To take only some, or under other names: use sass_compile, sass_compile_file from sass (Quillo 0.6).

sass_compile(source: str, style: str, variables: map, syntax: str, load_paths: list) → stror an error

SCSS text (or the indented sass syntax, or css) compiled to CSS, style "expanded" or "compressed". variables are set before the source ({ brand: "#c62828", radius: "4px" }), so !default values in it give way to them. @use and @import look in load_paths (folders).

sass_compile_file(path: str, style: str, variables: map, load_paths: list) → anyor an error

A .scss, .sass or .css file compiled, its own folder and load_paths searched for what it uses, variables set first. Gives { css, messages }, messages being the @warn and @debug output and deprecation warnings, each with its file and line.

sass_check(source: str, syntax: str) → any

Whether SCSS compiles: { ok, error, messages }, error being the compiler's message with the line it points at.

Get it

$ quillo install sass               # sass.qwer into this folder$ quillo install sass --example     # and its example, sass.qwe

Or with curl:

$ curl -O https://quillolang.org/plugins/files/sass.qwer$ curl -O https://quillolang.org/plugins/files/sass.qwe$ curl -O https://quillolang.org/plugins/files/sass_sample.scss$ curl -O https://quillolang.org/plugins/files/sass_sample_theme.scss

Then, in your program, all of it or only what you call:

main.qwe
use sass.qweruse sass_compile, sass_compile_file from sass      // Quillo 0.6: only these

It uses this crate, which cargo fetches the first time the plugin is built:

[dependencies]
grass_compiler = "0.13"

Example

sass.qwe
// A storefront whose stylesheet is compiled from SCSS at start, with each tenant's brand color// put in as a variable.////   quillo run sass.qwe      then open http://localhost:8080/app.css and http://localhost:8080/tenant/berry.cssuse "sass.qwer"let APP = sass_compile_file("sass_sample.scss", "compressed", {}, [])?route GET /app.css = respond(200, APP.css, { "content-type": "text/css" })let TENANTS = { berry: "#ad1457", ocean: "#0277bd" }// The theme's `$brand: ... !default` gives way to the tenant's color.route GET /tenant/:name {    let color = TENANTS[replace(name, ".css", "")] ?? reply 404 "no such tenant"    let css = sass_compile('@use "sass_sample_theme" with ($brand: ' + color + ');\n.badge { background: sass_sample_theme.$brand; }', "expanded", {}, "scss", ["."])?    respond(200, css, { "content-type": "text/css" })}route GET /demo = {    indented: sass_compile("$pad: 4px\n.tag\n  padding: $pad * 2\n  &:hover\n    opacity: .8\n", "compressed", {}, "sass", [])?,    variables: sass_compile(".hero { color: $ink; margin: $space * 2; }", "compressed", { ink: "#212121", space: "12px" }, "scss", [])?,    warned: sass_check('@warn "the old grid is going away";\n.a { b: c }', "scss"),    broken: sass_check(".a { color: $nope; }", "scss"),}
$ quillo run sass.qwe$ curl 'localhost:8080/app.css'$ curl 'localhost:8080/tenant/berry.css'$ curl 'localhost:8080/demo'

Source

sass.qwer
//! Sass and SCSS compiled to CSS in pure Rust (grass, dart-sass's behavior): from text or a file, with @use and @import from load paths, the indented syntax too, variables set from the program (a tenant's brand color), and errors and @warn messages with their line.//! version: 1.0.0//!//! [dependencies]//! grass_compiler = "0.13"use grass_compiler::codemap::SpanLoc;use grass_compiler::{InputSyntax, Logger, Options, OutputStyle};use serde_json::{json, Value};use std::sync::Mutex;#[derive(Debug, Default)]struct Collect(Mutex<Vec<Value>>);impl Logger for Collect {    fn debug(&self, loc: SpanLoc, message: &str) {        self.0.lock().unwrap().push(json!({ "kind": "debug", "message": message, "line": loc.begin.line + 1, "file": loc.file.name() }));    }    fn warn(&self, loc: SpanLoc, message: &str) {        self.0.lock().unwrap().push(json!({ "kind": "warning", "message": message, "line": loc.begin.line + 1, "file": loc.file.name() }));    }}fn style(s: &str) -> Result<OutputStyle, String> {    match s {        "" | "expanded" => Ok(OutputStyle::Expanded),        "compressed" => Ok(OutputStyle::Compressed),        other => Err(format!("style `{}` is expanded or compressed", other)),    }}fn syntax(s: &str) -> Result<InputSyntax, String> {    match s {        "" | "scss" => Ok(InputSyntax::Scss),        "sass" => Ok(InputSyntax::Sass),        "css" => Ok(InputSyntax::Css),        other => Err(format!("syntax `{}` is scss, sass or css", other)),    }}/// `$name: value;` lines for each variable, values written as Sass reads them (strings that are not colors, numbers or lengths quoted).fn prelude(vars: &std::collections::HashMap<String, Value>, indented: bool) -> Result<String, String> {    let mut names: Vec<&String> = vars.keys().collect();    names.sort();    let mut out = String::new();    for n in names {        if !n.chars().all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_') {            return Err(format!("`{}` is not a Sass variable name", n));        }        let v = match &vars[n] {            Value::String(s) if s.chars().all(|c| c.is_ascii_alphanumeric() || "#.%-() ,".contains(c)) => s.clone(),            Value::String(s) => format!("\"{}\"", s.replace('\\', "\\\\").replace('"', "\\\"")),            Value::Number(x) => x.to_string(),            Value::Bool(b) => b.to_string(),            Value::Null => "null".into(),            other => return Err(format!("${}: {} cannot be a Sass value", n, other)),        };        out.push_str(&format!("${}: {}{}\n", n, v, if indented { "" } else { ";" }));    }    Ok(out)}fn run(source: &str, opts: Options) -> Result<(String, Vec<Value>), String> {    let log = Collect::default();    let css = grass_compiler::from_string(source.to_string(), &opts.logger(&log)).map_err(|e| e.to_string())?;    let logs = log.0.into_inner().unwrap_or_default();    Ok((css, logs))}/// SCSS text (or the indented `sass` syntax, or `css`) compiled to CSS, `style` "expanded" or "compressed". `variables` are/// set before the source (`{ brand: "#c62828", radius: "4px" }`), so `!default` values in it give way to them. `@use` and/// `@import` look in `load_paths` (folders).pub fn sass_compile(source: &str, style: &str, variables: std::collections::HashMap<String, serde_json::Value>, syntax: &str, load_paths: Vec<String>) -> Result<String, String> {    let syn = self::syntax(syntax)?;    let full = format!("{}{}", prelude(&variables, syn == InputSyntax::Sass)?, source);    let opts = Options::default().style(self::style(style)?).input_syntax(syn).load_paths(&load_paths);    Ok(run(&full, opts)?.0)}/// A `.scss`, `.sass` or `.css` file compiled, its own folder and `load_paths` searched for what it uses, `variables` set first./// Gives `{ css, messages }`, `messages` being the `@warn` and `@debug` output and deprecation warnings, each with its file and line.pub fn sass_compile_file(path: &str, style: &str, variables: std::collections::HashMap<String, serde_json::Value>, load_paths: Vec<String>) -> Result<Value, String> {    let p = std::path::Path::new(path);    let source = std::fs::read_to_string(p).map_err(|e| format!("{}: {}", path, e))?;    let syn = InputSyntax::for_ext(p);    let dir = p.parent().map(|d| if d.as_os_str().is_empty() { std::path::Path::new(".") } else { d }).unwrap_or(std::path::Path::new("."));    let mut paths = vec![dir.to_string_lossy().to_string()];    paths.extend(load_paths);    let full = format!("{}{}", prelude(&variables, syn == InputSyntax::Sass)?, source);    let opts = Options::default().style(self::style(style)?).input_syntax(syn).load_paths(&paths);    let (css, messages) = run(&full, opts)?;    Ok(json!({ "css": css, "messages": messages }))}trait ForExt {    fn for_ext(p: &std::path::Path) -> InputSyntax;}impl ForExt for InputSyntax {    fn for_ext(p: &std::path::Path) -> InputSyntax {        match p.extension().and_then(|e| e.to_str()).map(|e| e.to_ascii_lowercase()).as_deref() {            Some("sass") => InputSyntax::Sass,            Some("css") => InputSyntax::Css,            _ => InputSyntax::Scss,        }    }}/// Whether SCSS compiles: `{ ok, error, messages }`, `error` being the compiler's message with the line it points at.pub fn sass_check(source: &str, syntax: &str) -> Value {    let opts = match self::syntax(syntax) {        Ok(s) => Options::default().input_syntax(s),        Err(e) => return json!({ "ok": false, "error": e, "messages": [] }),    };    match run(source, opts) {        Ok((_, messages)) => json!({ "ok": true, "error": null, "messages": messages }),        Err(e) => json!({ "ok": false, "error": e, "messages": [] }),    }}
Thread
67Useful? Like it, or add to its thread below.

Thread 0 posts

On Sass: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.