pretty_format/
lib.rs

1mod error;
2mod types;
3
4use std::rc::Rc;
5
6use wasm_bindgen::{JsCast, JsValue};
7
8pub use error::PrettyFormatError;
9pub use types::{Config, Plugin, PrettyFormatOptions, Printer, Refs};
10
11use types::{Colors, Plugins};
12use web_sys::js_sys::{BigInt, Number, Object};
13
14fn print_number(val: &Number) -> String {
15    if Object::is(val, &JsValue::from_f64(-0.0)) {
16        "-0".into()
17    } else {
18        val.to_string(10)
19            .expect("Number should be formatted as string.")
20            .into()
21    }
22}
23
24fn print_big_int(val: &BigInt) -> String {
25    format!(
26        "{}n",
27        String::from(
28            val.to_string(10)
29                .expect("Number should be formatted as string.")
30        )
31    )
32}
33
34pub fn print_basic_value(
35    val: &JsValue,
36    _print_function_name: bool,
37    _escape_regex: bool,
38    escape_string: bool,
39) -> Option<String> {
40    if *val == JsValue::TRUE {
41        return Some("true".into());
42    }
43    if *val == JsValue::FALSE {
44        return Some("false".into());
45    }
46    if val.is_undefined() {
47        return Some("undefined".into());
48    }
49    if val.is_null() {
50        return Some("null".into());
51    }
52
53    let type_of = val.js_typeof();
54
55    if type_of == "number" {
56        return Some(print_number(val.unchecked_ref::<Number>()));
57    }
58    if type_of == "bigint" {
59        return Some(print_big_int(val.unchecked_ref::<BigInt>()));
60    }
61    if type_of == "string" {
62        if escape_string {
63            return Some(
64                val.as_string()
65                    .expect("Value should be a string.")
66                    .replace('"', "\\\"")
67                    .replace('\\', "\\\\"),
68            );
69        }
70        return Some(format!(
71            "\"{}\"",
72            val.as_string().expect("Value should be a string.")
73        ));
74    }
75
76    todo!("print basic value {:?}", val)
77}
78
79pub fn print_complex_value(
80    val: &JsValue,
81    _config: &Config,
82    _indentation: String,
83    _depth: usize,
84    _refs: Refs,
85    _has_called_to_json: Option<bool>,
86) -> String {
87    todo!("print complex value {:?}", val)
88}
89
90fn print_plugin(
91    plugin: Rc<dyn Plugin>,
92    val: &JsValue,
93    config: &Config,
94    indentation: String,
95    depth: usize,
96    refs: Refs,
97) -> String {
98    plugin.serialize(val, config, indentation, depth, refs, &printer)
99}
100
101fn find_plugin(plugins: &Plugins, val: &JsValue) -> Option<Rc<dyn Plugin>> {
102    plugins.iter().find(|plugin| plugin.test(val)).cloned()
103}
104
105fn printer(
106    val: &JsValue,
107    config: &Config,
108    indentation: String,
109    depth: usize,
110    refs: Refs,
111    has_called_to_json: Option<bool>,
112) -> String {
113    if let Some(plugin) = find_plugin(&config.plugins, val) {
114        return print_plugin(plugin, val, config, indentation, depth, refs);
115    }
116
117    if let Some(basic_result) = print_basic_value(
118        val,
119        config.print_function_name,
120        config.escape_regex,
121        config.escape_string,
122    ) {
123        return basic_result;
124    }
125
126    print_complex_value(val, config, indentation, depth, refs, has_called_to_json)
127}
128
129fn validate_options(options: &PrettyFormatOptions) -> Result<(), PrettyFormatError> {
130    if options.min.is_some() && options.indent.is_some_and(|indent| indent != 0) {
131        Err(PrettyFormatError::Configuration(
132            "Options `min` and `indent` cannot be used togther.".into(),
133        ))
134    } else {
135        Ok(())
136    }
137}
138
139fn get_colors_highlight(options: &PrettyFormatOptions) -> Colors {
140    let theme = options.theme.clone().unwrap_or_default();
141
142    Colors {
143        comment: theme.comment,
144        content: theme.content,
145        prop: theme.prop,
146        tag: theme.tag,
147        value: theme.value,
148    }
149}
150
151fn get_colors_empty() -> Colors {
152    Colors::default()
153}
154
155fn get_print_function_name(options: &PrettyFormatOptions) -> bool {
156    options.print_function_name.unwrap_or(true)
157}
158
159fn get_escape_regex(options: &PrettyFormatOptions) -> bool {
160    options.escape_regex.unwrap_or(false)
161}
162
163fn get_escape_string(options: &PrettyFormatOptions) -> bool {
164    options.escape_string.unwrap_or(true)
165}
166
167fn get_config(options: PrettyFormatOptions) -> Config {
168    Config {
169        call_to_json: options.call_to_json.unwrap_or(true),
170        compare_keys: options.compare_keys.clone(),
171        colors: match options.highlight {
172            Some(true) => get_colors_highlight(&options),
173            _ => get_colors_empty(),
174        },
175        escape_regex: options.escape_regex.unwrap_or(false),
176        escape_string: options.escape_string.unwrap_or(true),
177        indent: match options.min {
178            Some(true) => "".into(),
179            _ => create_indent(options.indent.unwrap_or(2)),
180        },
181        max_depth: options.max_depth.unwrap_or(usize::MAX),
182        max_width: options.max_width.unwrap_or(usize::MAX),
183        min: options.min.unwrap_or(false),
184        plugins: options.plugins.unwrap_or_default(),
185        print_function_name: options.print_function_name.unwrap_or(true),
186        spacing_inner: match options.min {
187            Some(true) => " ",
188            _ => "\n",
189        }
190        .into(),
191        spacing_outer: match options.min {
192            Some(true) => "",
193            _ => "\n",
194        }
195        .into(),
196    }
197}
198
199fn create_indent(indent: usize) -> String {
200    " ".repeat(indent)
201}
202
203pub fn format(val: &JsValue, options: PrettyFormatOptions) -> Result<String, PrettyFormatError> {
204    validate_options(&options)?;
205
206    if let Some(plugins) = &options.plugins {
207        if let Some(plugin) = find_plugin(plugins, val) {
208            return Ok(print_plugin(
209                plugin,
210                val,
211                &get_config(options),
212                "".into(),
213                0,
214                vec![],
215            ));
216        }
217    }
218
219    let basic_result = print_basic_value(
220        val,
221        get_print_function_name(&options),
222        get_escape_regex(&options),
223        get_escape_string(&options),
224    );
225    if let Some(basic_result) = basic_result {
226        Ok(basic_result)
227    } else {
228        Ok(print_complex_value(
229            val,
230            &get_config(options),
231            "".into(),
232            0,
233            vec![],
234            None,
235        ))
236    }
237}