///|
pub struct Theme {
name : String
tokens : Array[Token]
} derive(Debug, Eq)
///|
pub fn Theme::new(name~ : String, tokens~ : Array[Token]) -> Theme {
{ name, tokens }
}
///|
pub fn Theme::name(self : Theme) -> String {
self.name
}
///|
pub fn Theme::tokens(self : Theme) -> Array[Token] {
self.tokens
}
///|
pub fn Theme::get(self : Theme, name : String) -> Token? {
find_token(self.tokens, name)
}
///|
pub fn Theme::merge(self : Theme, overlay : Theme) -> Theme {
{ name: overlay.name, tokens: merge_tokens(self.tokens, overlay.tokens) }
}
///|
pub fn Theme::diff(self : Theme, other : Theme) -> Array[TokenChange] {
diff_tokens(self.tokens, other.tokens)
}
///|
pub fn theme(name~ : String, tokens~ : Array[Token]) -> Theme {
Theme::new(name~, tokens~)
}
///|
pub fn theme_family(
base : Array[Token],
light_overrides : Array[Token],
dark_overrides : Array[Token],
) -> Array[Theme] {
[
Theme::new(name="light", tokens=merge_tokens(base, light_overrides)),
Theme::new(name="dark", tokens=merge_tokens(base, dark_overrides)),
]
}
///|
pub fn theme_css(theme : Theme, selector : String) -> String {
to_css_scoped(theme.tokens, selector)
}
///|
pub fn theme_switcher(themes : Array[Theme], attribute : String) -> String {
let blocks : Array[String] = []
for item in themes {
blocks.push(to_css_scoped(item.tokens, "[\{attribute}=\"\{item.name}\"]"))
}
blocks.join("\n")
}
///|
pub struct ThemeReport {
theme : String
errors : Array[String]
warnings : Array[String]
} derive(Debug, Eq)
///|
pub fn ThemeReport::ok(self : ThemeReport) -> Bool {
self.errors.length() == 0
}
///|
pub fn ThemeReport::summary(self : ThemeReport) -> String {
"theme=\{self.theme}, errors=\{self.errors.length()}, warnings=\{self.warnings.length()}"
}
///|
pub fn inspect_theme(item : Theme) -> ThemeReport {
let errors : Array[String] = []
let warnings : Array[String] = []
for problem in validate_names(item.tokens) {
warnings.push(problem)
}
for problem in validate_unique_names(item.tokens) {
errors.push(problem)
}
for problem in validate_all(item.tokens) {
errors.push(problem.message())
}
{ theme: item.name, errors, warnings }
}
///|
pub fn validate_themes(themes : Array[Theme]) -> Array[ThemeReport] {
[
for item in themes => inspect_theme(item)
]
}
///|
pub fn theme_token_matrix(
themes : Array[Theme],
names : Array[String],
) -> Array[Array[String]] {
[
for name in names => {
[
for item in themes => {
match item.get(name) {
None => ""
Some(token) => token.value
}
}
]
}
]
}
///|
pub fn fallback_value(
themes : Array[Theme],
name : String,
default : String,
) -> String {
for item in themes {
match item.get(name) {
Some(token) => return token.value
None => ()
}
}
default
}
///|
pub fn resolve_alias(value : String, tokens : Array[Token]) -> String {
resolve_alias_with_seen(value, tokens, [])
}
///|
pub fn resolve_aliases(tokens : Array[Token]) -> Array[Token] {
[
for token in tokens => {
{ ..token, value: resolve_alias(token.value, tokens) }
}
]
}
///|
fn resolve_alias_with_seen(
value : String,
tokens : Array[Token],
seen : Array[String],
) -> String {
if value.has_prefix("{") && value.has_suffix("}") {
let name = value[1:value.length() - 1].to_owned()
if contains_string(seen, name) {
value
} else {
match find_token(tokens, name) {
None => value
Some(token) =>
resolve_alias_with_seen(token.value, tokens, [..seen, name])
}
}
} else {
value
}
}
///|
pub fn unresolved_aliases(tokens : Array[Token]) -> Array[String] {
let names = token_names(tokens)
let result : Array[String] = []
for token in tokens {
if token.value.has_prefix("{") && token.value.has_suffix("}") {
let wanted = token.value[1:token.value.length() - 1].to_owned()
if !contains_string(names, wanted) {
result.push("\{token.name} references missing token \{wanted}")
}
}
}
result
}
///|
pub fn alias_cycles(tokens : Array[Token]) -> Array[String] {
let cycles : Array[String] = []
for token in tokens {
let seen : Array[String] = []
let mut current = token
while current.value.has_prefix("{") && current.value.has_suffix("}") {
if contains_string(seen, current.name) {
cycles.push("alias cycle at \{current.name}")
break
}
seen.push(current.name)
let wanted = current.value[1:current.value.length() - 1].to_owned()
match find_token(tokens, wanted) {
None => break
Some(next) => current = next
}
}
}
unique_strings(cycles)
}
///|
fn unique_strings(values : Array[String]) -> Array[String] {
let result : Array[String] = []
for value in values {
if !contains_string(result, value) {
result.push(value)
}
}
result
}