mas_i18n/sprintf/
mod.rs

1// Copyright 2024 New Vector Ltd.
2// Copyright 2023, 2024 The Matrix.org Foundation C.I.C.
3//
4// SPDX-License-Identifier: AGPL-3.0-only
5// Please see LICENSE in the repository root for full details.
6
7#![allow(unused_macros)]
8
9mod argument;
10mod formatter;
11mod message;
12mod parser;
13
14pub use self::{
15    argument::{Argument, List as ArgumentList},
16    formatter::{FormatError, FormattedMessage, FormattedMessagePart},
17    message::Message,
18};
19
20macro_rules! arg_list_inner {
21    ($var:ident |) => { };
22    ($var:ident | $name:ident = $($arg:expr)*, $($rest:tt)*) => {{
23        $var.push($crate::sprintf::Argument::from((stringify!($name), ::serde_json::json!($($arg)*))));
24        $crate::sprintf::arg_list_inner!($var | $($rest)* );
25    }};
26    ($var:ident | $name:ident = $($arg:expr)*) => {{
27        $var.push($crate::sprintf::Argument::from((stringify!($name), ::serde_json::json!($($arg)*))));
28    }};
29    ($var:ident | $($arg:expr)*, $($rest:tt)*) => {{
30        $var.push($crate::sprintf::Argument::from(::serde_json::json!($($arg)*)));
31        $crate::sprintf::arg_list_inner!($var | $($rest)* );
32    }};
33    ($var:ident | $($arg:expr)*) => {{
34        $var.push($crate::sprintf::Argument::from(::serde_json::json!($($arg)*)));
35    }};
36}
37
38macro_rules! arg_list {
39    ($($args:tt)*) => {{
40        let mut __args = Vec::<$crate::sprintf::Argument>::new();
41        $crate::sprintf::arg_list_inner!(__args | $($args)* );
42        $crate::sprintf::ArgumentList::from_iter(__args)
43    }}
44}
45
46macro_rules! sprintf {
47    ($message:literal) => {{
48        <$crate::sprintf::Message as ::std::str::FromStr>::from_str($message)
49            .map_err($crate::sprintf::Error::from)
50            .and_then(|message| {
51                let __args = $crate::sprintf::ArgumentList::default();
52                message.format(&__args).map_err($crate::sprintf::Error::from)
53            })
54    }};
55
56    ($message:literal, $($args:tt)*) => {{
57        <$crate::sprintf::Message as ::std::str::FromStr>::from_str($message)
58            .map_err($crate::sprintf::Error::from)
59            .and_then(|message| {
60                let __args = $crate::sprintf::arg_list!($($args)*);
61                message.format(&__args).map_err($crate::sprintf::Error::from)
62            })
63    }};
64}
65
66#[allow(unused_imports)]
67pub(crate) use arg_list;
68#[allow(unused_imports)]
69pub(crate) use arg_list_inner;
70#[allow(unused_imports)]
71pub(crate) use sprintf;
72
73#[derive(Debug, thiserror::Error)]
74#[error(transparent)]
75enum Error {
76    Format(#[from] self::formatter::FormatError),
77    Parse(Box<self::parser::Error>),
78}
79
80impl From<self::parser::Error> for Error {
81    fn from(err: self::parser::Error) -> Self {
82        Self::Parse(Box::new(err))
83    }
84}
85
86#[cfg(test)]
87mod tests {
88    use std::f64::consts::PI;
89
90    #[test]
91    fn test_sprintf() {
92        let res = sprintf!("Hello, %(name)s!", name = "world").unwrap();
93        assert_eq!(res, "Hello, world!");
94        assert_eq!("%", sprintf!("%%").unwrap());
95        assert_eq!("10", sprintf!("%b", 2).unwrap());
96        assert_eq!("A", sprintf!("%c", 65).unwrap());
97        assert_eq!("2", sprintf!("%d", 2).unwrap());
98        assert_eq!("2", sprintf!("%i", 2).unwrap());
99        //assert_eq!("2", sprintf!("%d", "2").unwrap()); -- We don't convert on the fly
100        //assert_eq!("2", sprintf!("%i", "2").unwrap()); -- We don't convert on the fly
101        assert_eq!(
102            r#"{"foo":"bar"}"#,
103            sprintf!("%j", serde_json::json!({"foo": "bar"})).unwrap()
104        );
105        assert_eq!(r#"["foo","bar"]"#, sprintf!("%j", ["foo", "bar"]).unwrap());
106        assert_eq!("2e0", sprintf!("%e", 2).unwrap()); // sprintf-js returns 2e+0
107        assert_eq!("2", sprintf!("%u", 2).unwrap());
108        assert_eq!("4294967294", sprintf!("%u", -2).unwrap());
109        assert_eq!("2.2", sprintf!("%f", 2.2).unwrap());
110        assert_eq!("3.141592653589793", sprintf!("%g", PI).unwrap());
111        assert_eq!("10", sprintf!("%o", 8).unwrap());
112        assert_eq!("37777777770", sprintf!("%o", -8).unwrap());
113        assert_eq!("%s", sprintf!("%s", "%s").unwrap());
114        assert_eq!("ff", sprintf!("%x", 255).unwrap());
115        assert_eq!("ffffff01", sprintf!("%x", -255).unwrap());
116        assert_eq!("FF", sprintf!("%X", 255).unwrap());
117        assert_eq!("FFFFFF01", sprintf!("%X", -255).unwrap());
118        assert_eq!(
119            "Polly wants a cracker",
120            sprintf!("%2$s %3$s a %1$s", "cracker", "Polly", "wants").unwrap()
121        );
122        assert_eq!(
123            "Hello world!",
124            sprintf!("Hello %(who)s!", who = "world").unwrap()
125        );
126
127        assert_eq!("true", sprintf!("%t", true).unwrap());
128        assert_eq!("t", sprintf!("%.1t", true).unwrap());
129        // We don't implement truthiness
130        //assert_eq!("true", sprintf!("%t", "true").unwrap());
131        //assert_eq!("true", sprintf!("%t", 1).unwrap());
132        assert_eq!("false", sprintf!("%t", false).unwrap());
133        assert_eq!("f", sprintf!("%.1t", false).unwrap());
134        //assert_eq!("false", sprintf!("%t", "").unwrap());
135        //assert_eq!("false", sprintf!("%t", 0).unwrap());
136
137        assert_eq!("null", sprintf!("%T", serde_json::json!(null)).unwrap());
138        assert_eq!("boolean", sprintf!("%T", true).unwrap());
139        assert_eq!("number", sprintf!("%T", 42).unwrap());
140        assert_eq!("string", sprintf!("%T", "This is a string").unwrap());
141        assert_eq!("array", sprintf!("%T", [1, 2, 3]).unwrap());
142        assert_eq!(
143            "object",
144            sprintf!("%T", serde_json::json!({"foo": "bar"})).unwrap()
145        );
146    }
147
148    #[test]
149    fn test_complex() {
150        // sign
151        assert_eq!("2", sprintf!("%d", 2).unwrap());
152        assert_eq!("-2", sprintf!("%d", -2).unwrap());
153        assert_eq!("+2", sprintf!("%+d", 2).unwrap());
154        assert_eq!("-2", sprintf!("%+d", -2).unwrap());
155        assert_eq!("2", sprintf!("%i", 2).unwrap());
156        assert_eq!("-2", sprintf!("%i", -2).unwrap());
157        assert_eq!("+2", sprintf!("%+i", 2).unwrap());
158        assert_eq!("-2", sprintf!("%+i", -2).unwrap());
159        assert_eq!("2.2", sprintf!("%f", 2.2).unwrap());
160        assert_eq!("-2.2", sprintf!("%f", -2.2).unwrap());
161        assert_eq!("+2.2", sprintf!("%+f", 2.2).unwrap());
162        assert_eq!("-2.2", sprintf!("%+f", -2.2).unwrap());
163        assert_eq!("-2.3", sprintf!("%+.1f", -2.34).unwrap());
164        assert_eq!("-0.0", sprintf!("%+.1f", -0.01).unwrap());
165
166        assert_eq!("3.14159", sprintf!("%.6g", PI).unwrap());
167        assert_eq!("3.14", sprintf!("%.3g", PI).unwrap());
168        assert_eq!("3", sprintf!("%.1g", PI).unwrap());
169        assert_eq!("3e5", sprintf!("%.1g", 300_000.0).unwrap());
170        assert_eq!("300", sprintf!("%.3g", 300).unwrap());
171
172        assert_eq!("-000000123", sprintf!("%+010d", -123).unwrap());
173        assert_eq!("______-123", sprintf!("%+'_10d", -123).unwrap());
174        assert_eq!("-234.34 123.2", sprintf!("%f %f", -234.34, 123.2).unwrap());
175
176        // padding
177        assert_eq!("-0002", sprintf!("%05d", -2).unwrap());
178        assert_eq!("-0002", sprintf!("%05i", -2).unwrap());
179        assert_eq!("    <", sprintf!("%5s", "<").unwrap());
180        assert_eq!("0000<", sprintf!("%05s", "<").unwrap());
181        assert_eq!("____<", sprintf!("%'_5s", "<").unwrap());
182        assert_eq!(">    ", sprintf!("%-5s", ">").unwrap());
183        assert_eq!(">0000", sprintf!("%0-5s", ">").unwrap());
184        assert_eq!(">____", sprintf!("%'_-5s", ">").unwrap());
185        assert_eq!("xxxxxx", sprintf!("%5s", "xxxxxx").unwrap());
186        assert_eq!("1234", sprintf!("%02u", 1234).unwrap());
187        assert_eq!(" -10.235", sprintf!("%8.3f", -10.23456).unwrap());
188        assert_eq!("-12.34 xxx", sprintf!("%f %s", -12.34, "xxx").unwrap());
189        assert_eq!(
190            r#"{
191  "foo": "bar"
192}"#,
193            sprintf!("%2j", serde_json::json!({"foo": "bar"})).unwrap()
194        );
195        assert_eq!(
196            r#"[
197  "foo",
198  "bar"
199]"#,
200            sprintf!("%2j", ["foo", "bar"]).unwrap()
201        );
202
203        // precision
204        assert_eq!("2.3", sprintf!("%.1f", 2.345).unwrap());
205        assert_eq!("xxxxx", sprintf!("%5.5s", "xxxxxx").unwrap());
206        assert_eq!("    x", sprintf!("%5.1s", "xxxxxx").unwrap());
207    }
208}