///|
/// 编译门面:持有若干可插拔引擎
///
/// 门面只负责「识别格式 → 路由到引擎 → 统一错误」,不绑定任何具体预处理引擎。
pub struct Compiler {
engines : Array[Engine]
}
///|
/// 用一组引擎构造门面
pub fn Compiler::new(engines : Array[Engine]) -> Compiler {
{ engines, }
}
///|
/// 自动识别格式并编译
pub fn Compiler::compile(
self : Compiler,
source : String,
) -> String raise CompileError {
let fmt = Format::detect(source)
self.compile_with_format(source, fmt)
}
///|
/// 以显式格式编译
pub fn Compiler::compile_with_format(
self : Compiler,
source : String,
fmt : Format,
) -> String raise CompileError {
let engine = self.pick(fmt)
engine.compile(source)
}
///|
/// 依据输入形态编译
pub fn Compiler::compile_input(
self : Compiler,
input : Input,
) -> String raise CompileError {
match input {
Source(source) => self.compile(source)
SourceWithFormat(source, fmt) => self.compile_with_format(source, fmt)
File(_) =>
raise CompileError::EngineFailed(
engine="compile_input",
message="File input requires compile_many with a reader",
)
}
}
///|
/// 编译多个输入(文件/字符串可混用),逐个编译并拼接
///
/// 每个输入先归一化成源码(`File` 用 `read` 读取,`Source` 直接用),
/// 再交给 SCSS 引擎做 `@import` 内联编译。
pub fn Compiler::compile_many(
self : Compiler,
inputs : Array[Input],
read : (String) -> String raise CompileError,
) -> String raise CompileError {
let parts : Array[String] = []
for input in inputs {
// 每段输入按自己的格式选引擎:SourceWithFormat 用显式格式,
// File 按扩展名,Source 走内容识别。不能统一挑一个引擎——LESS 源码
// 交给 SCSS 引擎会产出无意义的结果且不报错。
let (src, fmt) = match input {
Source(s) => (s, Format::detect(s))
SourceWithFormat(s, f) => (s, f)
File(p) => {
let s = read(p)
let f = match Format::from_path(p) {
Some(f) => f
None => Format::detect(s)
}
(s, f)
}
}
let piece = self.pick(fmt).compile_imports(src, read)
// 透传的 CSS 段不带结尾换行,编译出来的段带;统一补齐后再拼接,
// 否则拼接结果的段间空行时有时无。
parts.push(
if piece.length() > 0 && !piece.has_suffix("\n") {
piece + "\n"
} else {
piece
},
)
}
parts.join("\n")
}
///|
/// 编译文件
///
/// 读取函数由调用方注入,核心不耦合具体 IO 后端,跨 target 可用。
pub fn Compiler::compile_file(
self : Compiler,
path : String,
read : (String) -> String raise CompileError,
) -> String raise CompileError {
let source = read(path)
// 路径优先:按扩展名定格式,认不出来才回退到内容启发式。
// 用内容判定会让无变量的 `.scss`(全是 @import 很常见)被当成 CSS 透传,
// @import 就不内联了。
// 也必须走 compile_imports 而不是 compile:后者不携带 reader。
let fmt = match Format::from_path(path) {
Some(f) => f
None => Format::detect(source)
}
self.pick(fmt).compile_imports(source, read)
}
///|
/// 按格式挑选引擎
fn Compiler::pick(self : Compiler, fmt : Format) -> Engine raise CompileError {
for e in self.engines {
if e.supports(fmt) {
return e
}
}
raise CompileError::NoEngine(fmt)
}