///|
pub struct TokenSet {
name : String
tokens : Array[Token]
} derive(Debug, Eq)
///|
pub fn TokenSet::new(name~ : String, tokens~ : Array[Token]) -> TokenSet {
{ name, tokens }
}
///|
pub fn TokenSet::size(self : TokenSet) -> Int {
self.tokens.length()
}
///|
pub fn TokenSet::name(self : TokenSet) -> String {
self.name
}
///|
pub fn TokenSet::all(self : TokenSet) -> Array[Token] {
self.tokens
}
///|
pub fn TokenSet::get(self : TokenSet, name : String) -> Token? {
find_token(self.tokens, name)
}
///|
pub fn TokenSet::canonical(self : TokenSet) -> TokenSet {
{ ..self, tokens: canonical_tokens(self.tokens) }
}
///|
pub fn TokenSet::merge(self : TokenSet, overlay : TokenSet) -> TokenSet {
{ name: overlay.name, tokens: merge_tokens(self.tokens, overlay.tokens) }
}
///|
pub fn TokenSet::diff(self : TokenSet, other : TokenSet) -> Array[TokenChange] {
diff_tokens(self.tokens, other.tokens)
}
///|
pub fn TokenSet::with_prefix(self : TokenSet, prefix : String) -> TokenSet {
{ ..self, tokens: prefix_tokens(self.tokens, prefix) }
}
///|
pub fn TokenSet::render(self : TokenSet, format : OutputFormat) -> String {
render_output(self.tokens, format)
}
///|
pub fn TokenSet::valid(self : TokenSet) -> Bool {
validate_all(self.tokens).length() == 0
}
///|
pub fn TokenSet::validate(self : TokenSet) -> Array[TokenProblem] {
validate_all(self.tokens)
}
///|
pub fn token_set(name~ : String, tokens~ : Array[Token]) -> TokenSet {
TokenSet::new(name~, tokens~)
}
///|
pub fn token_names(tokens : Array[Token]) -> Array[String] {
[
for token in tokens => token.name
]
}
///|
pub fn find_token(tokens : Array[Token], name : String) -> Token? {
for token in tokens {
if token.name == name {
return Some(token)
}
}
None
}
///|
pub fn tokens_of_kind(tokens : Array[Token], kind : TokenKind) -> Array[Token] {
[
for token in tokens if token.kind == kind => token
]
}
///|
pub fn tokens_with_prefix(
tokens : Array[Token],
prefix : String,
) -> Array[Token] {
[
for token in tokens if token.name.has_prefix(prefix) => token
]
}
///|
pub fn token_names_with_prefix(
tokens : Array[Token],
prefix : String,
) -> Array[String] {
[
for token in tokens if token.name.has_prefix(prefix) => token.name
]
}
///|
pub fn token_count_by_kind(tokens : Array[Token], kind : TokenKind) -> Int {
tokens_of_kind(tokens, kind).length()
}
///|
pub fn has_duplicate_names(tokens : Array[Token]) -> Bool {
for i in 0.. Array[Token] {
let result : Array[Token] = []
for token in tokens {
if find_token(result, token.name) is None {
result.push(token)
}
}
result
}
///|
pub fn merge_tokens(
base : Array[Token],
overlay : Array[Token],
) -> Array[Token] {
let result = base.copy()
for incoming in overlay {
let mut replaced = false
for i in 0.. Array[Token] {
[
for token in tokens if !contains_string(names, token.name) => token
]
}
///|
fn contains_string(values : Array[String], wanted : String) -> Bool {
for value in values {
if value == wanted {
return true
}
}
false
}
///|
pub enum TokenChange {
Added(Token)
Removed(Token)
Changed(Token, Token)
} derive(Debug, Eq)
///|
pub fn diff_tokens(
before : Array[Token],
after : Array[Token],
) -> Array[TokenChange] {
let changes : Array[TokenChange] = []
for old in before {
match find_token(after, old.name) {
None => changes.push(Removed(old))
Some(new) => if old != new { changes.push(Changed(old, new)) }
}
}
for new in after {
if find_token(before, new.name) is None {
changes.push(Added(new))
}
}
changes
}
///|
pub fn TokenChange::kind(self : TokenChange) -> String {
match self {
Added(_) => "added"
Removed(_) => "removed"
Changed(_, _) => "changed"
}
}
///|
pub fn TokenChange::name(self : TokenChange) -> String {
match self {
Added(token) => token.name
Removed(token) => token.name
Changed(_, token) => token.name
}
}
///|
pub fn diff_summary(changes : Array[TokenChange]) -> String {
let mut added = 0
let mut removed = 0
let mut changed = 0
for change in changes {
match change {
Added(_) => added += 1
Removed(_) => removed += 1
Changed(_, _) => changed += 1
}
}
"added=\{added}, removed=\{removed}, changed=\{changed}"
}
///|
pub fn prefix_tokens(tokens : Array[Token], prefix : String) -> Array[Token] {
[
for token in tokens => { ..token, name: "\{prefix}\{token.name}" }
]
}
///|
pub fn rename_token(
tokens : Array[Token],
old_name : String,
new_name : String,
) -> Array[Token] {
[
for token in tokens => {
if token.name == old_name {
{ ..token, name: new_name }
} else {
token
}
}
]
}
///|
pub fn map_descriptions(tokens : Array[Token], prefix : String) -> Array[Token] {
[
for token in tokens => {
{ ..token, description: "\{prefix}\{token.description}" }
}
]
}
///|
pub fn normalize_name(name : String) -> String {
let output : Array[String] = []
let mut previous_separator = false
for character in name.to_lower() {
if is_name_character(character) {
output.push("\{character}")
previous_separator = false
} else if !previous_separator && output.length() > 0 {
output.push("-")
previous_separator = true
}
}
let value = output.join("")
if value.has_suffix("-") && value.length() > 0 {
value[0:value.length() - 1].to_owned()
} else {
value
}
}
///|
fn is_name_character(character : Char) -> Bool {
(character >= 'a' && character <= 'z') ||
(character >= '0' && character <= '9')
}
///|
pub fn normalize_tokens(tokens : Array[Token]) -> Array[Token] {
[
for token in tokens => { ..token, name: normalize_name(token.name) }
]
}
///|
pub fn sort_tokens_by_name(tokens : Array[Token]) -> Array[Token] {
let result = tokens.copy()
for i in 1.. 0 && result[j - 1].name > current.name {
result[j] = result[j - 1]
j -= 1
}
result[j] = current
}
result
}
///|
pub fn canonical_tokens(tokens : Array[Token]) -> Array[Token] {
sort_tokens_by_name(unique_tokens(normalize_tokens(tokens)))
}
///|
pub(all) struct TokenStats {
total : Int
valid : Int
aliases : Int
duplicate_names : Int
empty_values : Int
longest_name : Int
longest_value : Int
} derive(Debug, Eq)
///|
pub fn TokenStats::total(self : TokenStats) -> Int {
self.total
}
///|
pub fn TokenStats::valid(self : TokenStats) -> Int {
self.valid
}
///|
pub fn TokenStats::aliases(self : TokenStats) -> Int {
self.aliases
}
///|
pub fn TokenStats::duplicate_names(self : TokenStats) -> Int {
self.duplicate_names
}
///|
pub fn TokenStats::empty_values(self : TokenStats) -> Int {
self.empty_values
}
///|
pub fn TokenStats::longest_name(self : TokenStats) -> Int {
self.longest_name
}
///|
pub fn TokenStats::longest_value(self : TokenStats) -> Int {
self.longest_value
}
///|
pub fn TokenStats::summary(self : TokenStats) -> String {
"total=\{self.total}, valid=\{self.valid}, aliases=\{self.aliases}, duplicates=\{self.duplicate_names}, empty=\{self.empty_values}, longest-name=\{self.longest_name}, longest-value=\{self.longest_value}"
}
///|
pub fn analyze_tokens(tokens : Array[Token]) -> TokenStats {
let mut valid = 0
let mut aliases = 0
let mut empty_values = 0
let mut longest_name = 0
let mut longest_value = 0
for token in tokens {
if token.validate().length() == 0 {
valid += 1
}
if token.value.has_prefix("{") && token.value.has_suffix("}") {
aliases += 1
}
if token.value == "" {
empty_values += 1
}
if token.name.length() > longest_name {
longest_name = token.name.length()
}
if token.value.length() > longest_value {
longest_value = token.value.length()
}
}
{
total: tokens.length(),
valid,
aliases,
duplicate_names: duplicate_name_count(tokens),
empty_values,
longest_name,
longest_value,
}
}
///|
fn duplicate_name_count(tokens : Array[Token]) -> Int {
let names : Array[String] = []
let mut duplicates = 0
for token in tokens {
if contains_string(names, token.name) {
duplicates += 1
} else {
names.push(token.name)
}
}
duplicates
}
///|
pub fn tokens_referencing(tokens : Array[Token], name : String) -> Array[Token] {
let reference = "{" + name + "}"
[
for token in tokens if token.value == reference => token
]
}
///|
pub fn replace_token_reference(
tokens : Array[Token],
old_name : String,
new_name : String,
) -> Array[Token] {
let old_reference = "{" + old_name + "}"
let new_reference = "{" + new_name + "}"
[
for token in tokens => {
if token.value == old_reference {
{ ..token, value: new_reference }
} else {
token
}
}
]
}
///|
pub fn validate_unique_names(tokens : Array[Token]) -> Array[String] {
let problems : Array[String] = []
for i in 0.. Array[String] {
let problems : Array[String] = []
for token in tokens {
if normalize_name(token.name) != token.name {
problems.push("token name `\{token.name}` is not canonical")
}
if token.name.has_prefix("-") || token.name.has_suffix("-") {
problems.push("token name `\{token.name}` has a boundary separator")
}
}
problems
}
///|
pub fn Token::css_name(self : Token) -> String {
"--\{normalize_name(self.name)}"
}
///|
pub fn Token::css_declaration(self : Token) -> String {
"\{self.css_name()}: \{self.value};"
}
///|
pub fn to_css_minified(tokens : Array[Token]) -> String {
let declarations = [
for token in canonical_tokens(tokens) => "\{token.css_declaration()}"
]
":root{\{declarations.join("")}}"
}
///|
pub fn to_css_scoped(tokens : Array[Token], selector : String) -> String {
let rows = [
for token in canonical_tokens(tokens) => " \{token.css_declaration()}"
]
"\{selector} {\n\{rows.join("\n")}\n}\n"
}
///|
pub fn to_css_layer(tokens : Array[Token], layer : String) -> String {
"@layer \{layer} {\n\{to_css(tokens)}"
}
///|
pub fn to_json_lines(tokens : Array[Token]) -> String {
let rows = [ for token in canonical_tokens(tokens) => token.json_object() ]
"\{rows.join("\n")}\n"
}
///|
pub fn to_markdown_sorted(tokens : Array[Token]) -> String {
to_markdown_table(canonical_tokens(tokens))
}
///|
pub fn render_all(tokens : Array[Token]) -> String {
"CSS\n\{to_css(tokens)}\nJSON\n\{to_json(tokens)}\nMoonBit\n\{to_moonbit_constants(tokens)}\nMarkdown\n\{to_markdown_table(tokens)}"
}