use once_cell::sync::Lazy;
use regex::Regex;
use std::hash::Hash;
#[derive(Debug, Hash)]
pub struct TSEnum {
pub name: String,
pub generics: Option<String>,
pub variants: Vec<(String, String)>,
pub export: bool,
}
static RE_ENUM: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"\n(?P<export>export\s+)?type[\s]+(?P<name>\w+)(?:<(?P<generics>[\w\s,]+)>)?\s*=\s*Enum<\{(?P<variants>[\s\S]+?)\n\}>").unwrap()
});
static RE_VARIANTS_INDENT: Lazy<Regex> = Lazy::new(|| Regex::new(r"^\n([\t ]+)").unwrap());
static RE_VARIANT: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"\n(?P<name>\w+):\s*(?P<contents>[^\n;,]+(?:\n[ \t]+[\s\S]+?\n[\]\}>]+)?)").unwrap()
});
#[derive(Debug, Hash)]
pub struct Parsed {
pub enums: Vec<TSEnum>,
pub indent: String,
}
pub fn parse(source: &str) -> Parsed {
let mut enums = Vec::new();
let mut indent = String::new();
for cap in RE_ENUM.captures_iter(&source) {
let variants: &str = &cap["variants"];
let indent_match: &str = &RE_VARIANTS_INDENT
.captures(&variants)
.expect("at least one variant + indented")[1];
let unindented_variants: String = variants
.lines()
.into_iter()
.map(|line| {
let unindented = line.replacen(&indent_match, "", 1);
assert!(
line.is_empty() || unindented != line,
"line indentation is irregular:>>>{}<<<",
line
);
unindented
})
.collect::<Vec<String>>()
.join("\n");
indent = indent_match.to_string();
enums.push(TSEnum {
name: cap["name"].to_string(),
generics: cap.name("generics").map(|val| val.as_str().to_string()),
export: cap.name("export").is_some(),
variants: RE_VARIANT
.captures_iter(&unindented_variants)
.map(|cap| (cap["name"].to_string(), cap["contents"].to_string()))
.collect(),
});
}
Parsed { indent, enums }
}
#[cfg(test)]
mod tests {
use super::*;
use insta::assert_debug_snapshot;
#[test]
fn parse_result_with_generics() {
assert_debug_snapshot!(parse(
r###"
// enum: factory, match
type Result<Ok, Err> = Enum<{
Ok: Ok;
Err: Err;
}>;
// enum: factory, match
type Stoplight = Enum<{
Green: 0;
Yellow: 0;
Red: 0;
}>;
"###,
), @r###"
Parsed {
enums: [
TSEnum {
name: "Result",
generics: Some(
"Ok, Err",
),
variants: [
(
"Ok",
"Ok",
),
(
"Err",
"Err",
),
],
export: false,
},
TSEnum {
name: "Stoplight",
generics: None,
variants: [
(
"Green",
"0",
),
(
"Yellow",
"0",
),
(
"Red",
"0",
),
],
export: false,
},
],
indent: " ",
}
"###)
}
}