cargo_zng/l10n/
pseudo.rs

1use std::{borrow::Cow, fmt::Write as _, fs, path::Path};
2
3use fluent_syntax::ast::*;
4
5use crate::util;
6
7pub fn pseudo(dir: &str, check: bool, verbose: bool) {
8    fluent_pseudo_impl(dir, "pseudo", false, false, check, verbose)
9}
10
11pub fn pseudo_mirr(dir: &str, check: bool, verbose: bool) {
12    fluent_pseudo_impl(dir, "pseudo-mirr", true, false, check, verbose)
13}
14
15pub fn pseudo_wide(dir: &str, check: bool, verbose: bool) {
16    fluent_pseudo_impl(dir, "pseudo-wide", false, true, check, verbose)
17}
18
19fn fluent_pseudo_impl(dir: &str, to_name: &str, flipped: bool, elongate: bool, check: bool, verbose: bool) {
20    pseudo_impl(dir, to_name, &|s| fluent_pseudo::transform(s, flipped, elongate), check, verbose)
21}
22
23fn pseudo_impl(dir: &str, to_name: &str, transform: &impl Fn(&str) -> Cow<str>, check: bool, verbose: bool) {
24    let dir = Path::new(dir);
25    let to_dir = dir.with_file_name(to_name);
26
27    for entry in fs::read_dir(dir).unwrap_or_else(|e| fatal!("cannot read `{}`, {e}", dir.display())) {
28        let entry = entry.unwrap_or_else(|e| fatal!("cannot read `{}` entry, {e}", dir.display()));
29        let path = entry.path();
30        if path.is_file() && path.extension().map(|e| e == "ftl").unwrap_or(false) {
31            let _ = util::check_or_create_dir(check, &to_dir);
32            generate(&path, &to_dir.join(path.file_name().unwrap()), transform, check, verbose);
33        }
34    }
35
36    let unnamed = dir.with_extension("ftl");
37    if unnamed.exists() {
38        generate(&unnamed, &to_dir.with_extension("ftl"), transform, check, verbose);
39    }
40}
41
42fn generate(from: &Path, to: &Path, transform: &impl Fn(&str) -> Cow<str>, check: bool, verbose: bool) {
43    let source = match fs::read_to_string(from) {
44        Ok(s) => s,
45        Err(e) => {
46            error!("cannot read `{}`, {e}", from.display());
47            return;
48        }
49    };
50
51    let source = match fluent_syntax::parser::parse(source) {
52        Ok(s) => s,
53        Err((s, e)) => {
54            error!(
55                "cannot parse `{}`\n{}",
56                from.display(),
57                e.into_iter().map(|e| format!("    {e}")).collect::<Vec<_>>().join("\n")
58            );
59            s
60        }
61    };
62
63    let mut output = "# Test locale, generated by cargo zng l10n".to_owned();
64
65    for entry in source.body {
66        match entry {
67            Entry::Message(m) => write_entry(&mut output, &m.id, m.value.as_ref(), &m.attributes, transform),
68            Entry::Term(t) => write_entry(&mut output, &t.id, Some(&t.value), &t.attributes, transform),
69            Entry::Comment(_) | Entry::GroupComment(_) | Entry::ResourceComment(_) | Entry::Junk { .. } => {}
70        }
71    }
72
73    if let Err(e) = util::check_or_write(check, to, output.as_bytes(), verbose) {
74        error!("cannot write `{}`, {e}", to.display());
75    } else {
76        println!("  generated {}", to.display());
77    }
78}
79
80fn write_entry(
81    output: &mut String,
82    id: &Identifier<String>,
83    value: Option<&Pattern<String>>,
84    attributes: &[Attribute<String>],
85    transform: &impl Fn(&str) -> Cow<str>,
86) {
87    write!(output, "\n\n{} = ", id.name).unwrap();
88    if let Some(value) = value {
89        write_pattern(output, value, transform);
90    }
91    for attr in attributes {
92        write!(output, "\n    .{} = ", attr.id.name).unwrap();
93        write_pattern(output, &attr.value, transform);
94    }
95}
96
97fn write_pattern(output: &mut String, pattern: &Pattern<String>, transform: &impl Fn(&str) -> Cow<str>) {
98    for el in &pattern.elements {
99        match el {
100            PatternElement::TextElement { value } => {
101                let mut prefix = "";
102                for line in value.lines() {
103                    write!(output, "{prefix}{}", transform(line)).unwrap();
104                    prefix = "    ";
105                }
106            }
107            PatternElement::Placeable { expression } => write_expression(output, expression, transform),
108        }
109    }
110}
111
112fn write_expression(output: &mut String, expr: &Expression<String>, transform: &impl Fn(&str) -> Cow<str>) {
113    match expr {
114        Expression::Select { selector, variants } => {
115            write!(output, "{{").unwrap();
116            write_inline_expression(output, selector, transform);
117            writeln!(output, " ->").unwrap();
118
119            for v in variants {
120                write!(output, "    ").unwrap();
121                if v.default {
122                    write!(output, "*").unwrap();
123                }
124                let key = match &v.key {
125                    VariantKey::Identifier { name } => name,
126                    VariantKey::NumberLiteral { value } => value,
127                };
128                write!(output, "[{key}] ").unwrap();
129
130                write_pattern(output, &v.value, transform);
131            }
132
133            writeln!(output, "}}").unwrap();
134        }
135        Expression::Inline(e) => write_inline_expression(output, e, transform),
136    }
137}
138fn write_inline_expression(output: &mut String, expr: &InlineExpression<String>, transform: &impl Fn(&str) -> Cow<str>) {
139    match expr {
140        InlineExpression::StringLiteral { value } => write!(output, "{{ \"{value}\" }}").unwrap(),
141        InlineExpression::NumberLiteral { value } => write!(output, "{{ {value} }}").unwrap(),
142        InlineExpression::FunctionReference { id, arguments } => {
143            write!(output, "{{ {}", id.name).unwrap();
144            write_arguments(output, arguments, transform);
145            write!(output, " }}").unwrap()
146        }
147        InlineExpression::MessageReference { id, attribute } => {
148            write!(output, "{{ {}", id.name).unwrap();
149            if let Some(a) = attribute {
150                write!(output, ".{}", a.name).unwrap();
151            }
152            write!(output, " }}").unwrap()
153        }
154        InlineExpression::TermReference { id, attribute, arguments } => {
155            write!(output, "{{ -{}", id.name).unwrap();
156            if let Some(a) = attribute {
157                write!(output, ".{}", a.name).unwrap();
158            }
159            if let Some(args) = arguments {
160                write_arguments(output, args, transform);
161            }
162            write!(output, " }}").unwrap()
163        }
164        InlineExpression::VariableReference { id } => write!(output, "{{ ${} }}", id.name).unwrap(),
165        InlineExpression::Placeable { expression } => {
166            write!(output, "{{ ").unwrap();
167            write_expression(output, expression, transform);
168            write!(output, " }}").unwrap();
169        }
170    }
171}
172
173fn write_arguments(output: &mut String, arguments: &CallArguments<String>, transform: &impl Fn(&str) -> Cow<str>) {
174    write!(output, "(").unwrap();
175    let mut sep = "";
176    for a in &arguments.positional {
177        write!(output, "{sep}").unwrap();
178        write_inline_expression(output, a, transform);
179        sep = ", ";
180    }
181    for a in &arguments.named {
182        write!(output, "{sep}{}", a.name.name).unwrap();
183        write_inline_expression(output, &a.value, transform);
184        sep = ", ";
185    }
186    write!(output, ")").unwrap();
187}