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
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 errorSCSS 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 errorA .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) → anyWhether 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.qweOr 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.scssThen, in your program, all of it or only what you call:
use sass.qweruse sass_compile, sass_compile_file from sass // Quillo 0.6: only theseIt uses this crate, which cargo fetches the first time the plugin is built:
grass_compiler = "0.13"Example
// 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 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": [] }), }}Related
css_minify.qwer · 1.0.0CSS made small and ready for the browsers you support (lightningcss): minified with rules merged, vendor prefixes added and nesting and new syntax lowered for a browserslist query, and errors reported by line.8 views · 7 downloadsCSS inliner css_inline.qwer · 1.0.0CSS inlined into HTML for email: style blocks and extra CSS written into each element's style attribute, with nothing ever fetched.7 views · 6 downloadsMinify HTML minify_html.qwer · 1.0.0HTML made smaller for sending: spaces, comments and optional tags out, and the CSS and JavaScript inside minified too.5 views · 9 downloadsMinify JS and CSS js_minify.qwer · 1.0.0JavaScript, CSS and JSON minified: comments and spaces out, for scripts and styles served or embedded in pages.7 views · 8 downloadsThread 0 posts
On Sass: questions, tips, corrections, and code that uses it.
No posts yet. The first one opens the thread.