Skip to content

Commit 075e6ed

Browse files
committed
fix function, add tests
1 parent 480c24c commit 075e6ed

File tree

2 files changed

+213
-42
lines changed

2 files changed

+213
-42
lines changed

dsc_lib/locales/en-us.toml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -200,6 +200,12 @@ notFound = "Environment variable not found"
200200
[functions.if]
201201
conditionNotBoolean = "Condition is not a boolean"
202202

203+
[functions.format]
204+
formatInvalid = "First `format()` argument must be a string"
205+
numberTooLarge = "Number is too large"
206+
invalidArgType = "Unsupported argument type"
207+
invalidFormatString = "Invalid format string"
208+
203209
[functions.int]
204210
invalidInput = "invalid input string"
205211
parseStringError = "unable to parse string to int"

dsc_lib/src/functions/format.rs

Lines changed: 207 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -4,54 +4,76 @@
44
use crate::DscError;
55
use crate::configure::context::Context;
66
use crate::functions::{AcceptedArgKind, Function};
7-
use rt_format::{Format as RtFormat, FormatArgument, ParsedFormat, Specifier};
7+
use rt_format::{Format as RtFormat, FormatArgument, ParsedFormat, argument::NoNamedArguments};
88
use rust_i18n::t;
99
use serde_json::Value;
10-
use std::fmt;
11-
use std::fmt::{Error, Formatter, Write};
1210

13-
impl FormatArgument for Value {
14-
fn supports_format(&self, spec: &Specifier) -> bool {
11+
#[derive(Debug, PartialEq)]
12+
enum Variant {
13+
Boolean(bool),
14+
Number(i64),
15+
String(String),
16+
}
17+
18+
impl FormatArgument for Variant {
19+
fn supports_format(&self, specifier: &rt_format::Specifier) -> bool {
20+
match self {
21+
Variant::Boolean(_) | Variant::String(_)=> matches!(specifier.format, RtFormat::Display),
22+
Variant::Number(_) => matches!(specifier.format, RtFormat::Display | RtFormat::Binary | RtFormat::Octal | RtFormat::LowerHex | RtFormat::UpperHex | RtFormat::Debug | RtFormat::LowerExp | RtFormat::UpperExp),
23+
}
24+
}
25+
26+
fn fmt_display(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
1527
match self {
16-
Value::Boolean(_) | Value::String(_) | Value::Number(_) => true,
17-
_ => false,
28+
Variant::Boolean(value) => write!(f, "{value}"),
29+
Variant::Number(value) => write!(f, "{value}"),
30+
Variant::String(value) => write!(f, "{value}"),
1831
}
1932
}
2033

21-
fn fmt_display(&self, f: &mut Formatter) -> std::fmt::Result {
34+
fn fmt_octal(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2235
match self {
23-
Value::Boolean(b) => write!(f, "{}", b),
24-
Value::String(s) => write!(f, "{}", s),
25-
Value::Number(n) => write!(f, "{}", n),
26-
_ => Err(fmt::Error),
36+
Variant::Number(value) => write!(f, "{value:o}"),
37+
_ => Err(std::fmt::Error),
2738
}
2839
}
2940

30-
fn fmt_lower_hex(&self, f: &mut Formatter) -> std::fmt::Result {
41+
fn fmt_lower_hex(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
3142
match self {
32-
Value::Number(n) => write!(f, "{:x}", n.as_i64().unwrap_or_default()),
33-
_ => Err(fmt::Error),
43+
Variant::Number(value) => write!(f, "{value:x}"),
44+
_ => Err(std::fmt::Error),
3445
}
3546
}
3647

37-
fn fmt_upper_hex(&self, f: &mut Formatter) -> std::fmt::Result {
48+
fn fmt_upper_hex(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
3849
match self {
39-
Value::Number(n) => write!(f, "{:X}", n.as_i64().unwrap_or_default()),
40-
_ => Err(fmt::Error),
50+
Variant::Number(value) => write!(f, "{value:X}"),
51+
_ => Err(std::fmt::Error),
4152
}
4253
}
4354

44-
fn fmt_binary(&self, f: &mut Formatter) -> std::fmt::Result {
55+
fn fmt_binary(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
56+
match self {
57+
Variant::Number(value) => write!(f, "{value:b}"),
58+
_ => Err(std::fmt::Error),
59+
}
60+
}
61+
62+
fn fmt_debug(&self, _f: &mut std::fmt::Formatter) -> std::fmt::Result {
63+
Err(std::fmt::Error)
64+
}
65+
66+
fn fmt_lower_exp(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
4567
match self {
46-
Value::Number(n) => write!(f, "{:b}", n.as_i64().unwrap_or_default()),
47-
_ => Err(fmt::Error),
68+
Variant::Number(value) => write!(f, "{value:e}"),
69+
_ => Err(std::fmt::Error),
4870
}
4971
}
5072

51-
fn fmt_octal(&self, f: &mut Formatter) -> std::fmt::Result {
73+
fn fmt_upper_exp(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
5274
match self {
53-
Value::Number(n) => write!(f, "{:o}", n.as_i64().unwrap_or_default()),
54-
_ => Err(fmt::Error),
75+
Variant::Number(value) => write!(f, "{value:E}"),
76+
_ => Err(std::fmt::Error),
5577
}
5678
}
5779
}
@@ -69,29 +91,33 @@ impl Function for Format {
6991
}
7092

7193
fn accepted_arg_types(&self) -> Vec<AcceptedArgKind> {
72-
vec![AcceptedArgKind::Boolean, AcceptedArgKind::String, AcceptedArgKind::Number]
94+
vec![AcceptedArgKind::Boolean, AcceptedArgKind::Number, AcceptedArgKind::String]
7395
}
7496

7597
fn invoke(&self, args: &[Value], _context: &Context) -> Result<Value, DscError> {
76-
let mut string_result = String::new();
77-
let Ok(format_string) = args[0].as_str() else {
78-
return Err(DscError::Parser("First `format()` argument must be a string".to_string()));
98+
let Some(format_string) = args[0].as_str() else {
99+
return Err(DscError::Parser(t!("functions.format.formatInvalid").to_string()));
79100
};
101+
let mut position_args = Vec::new();
80102
for value in &args[1..] {
81-
if let Some(parsed_format) = ParsedFormat::parse(format_string) {
82-
let mut formatted_string = String::new();
83-
for specifier in parsed_format.specifiers() {
84-
if let Some(arg) = args.get(specifier.index()) {
85-
formatted_string.push_str(&arg.to_string());
103+
let arg = match value {
104+
Value::Bool(b) => Variant::Boolean(*b),
105+
Value::Number(n) => {
106+
if let Some(i) = n.as_i64() {
107+
Variant::Number(i)
86108
} else {
87-
return Err(DscError::Parser("Invalid format specifier".to_string()));
109+
return Err(DscError::Parser(t!("functions.format.numberTooLarge").to_string()));
88110
}
89111
}
90-
string_result.push_str(&formatted_string);
91-
} else {
92-
return Err(DscError::Parser("Invalid format string".to_string()));
93-
}
112+
Value::String(s) => Variant::String(s.clone()),
113+
_ => return Err(DscError::Parser(t!("functions.format.invalidArgType").to_string())),
114+
};
115+
position_args.push(arg);
94116
}
117+
let string_result = match ParsedFormat::parse(format_string, &position_args, &NoNamedArguments) {
118+
Ok(parsed_format) => format!("{parsed_format}"),
119+
Err(_e) => return Err(DscError::Parser(t!("functions.format.invalidFormatString").to_string())),
120+
};
95121
Ok(Value::String(string_result))
96122
}
97123
}
@@ -104,15 +130,154 @@ mod tests {
104130
#[test]
105131
fn position() {
106132
let mut parser = Statement::new().unwrap();
107-
let result = parser.parse_and_execute("[format('{0} - {1}', 'hello', 2)]", &Context::new()).unwrap();
108-
assert_eq!(result, "hello - 2");
133+
let result = parser.parse_and_execute("[format('world {0} - {1}', 'hello', 2)]", &Context::new()).unwrap();
134+
assert_eq!(result, "world hello - 2");
135+
}
136+
137+
#[test]
138+
fn reverse_position() {
139+
let mut parser = Statement::new().unwrap();
140+
let result = parser.parse_and_execute("[format('two{1} - {0}world', 'hello', 2)]", &Context::new()).unwrap();
141+
assert_eq!(result, "two2 - helloworld");
142+
}
143+
144+
#[test]
145+
fn repeated_position() {
146+
let mut parser = Statement::new().unwrap();
147+
let result = parser.parse_and_execute("[format('{0} - {0}{1}', 'hello', 2)]", &Context::new()).unwrap();
148+
assert_eq!(result, "hello - hello2");
109149
}
110150

111151
#[test]
112152
fn numbers_as_hex() {
113153
let mut parser = Statement::new().unwrap();
114-
let result = parser.parse_and_execute("[format('{0:x} {0:X}', 12, 13)]", &Context::new()).unwrap();
115-
assert_eq!(result, "c D");
154+
let result = parser.parse_and_execute("[format('{0:x} = {1:X}', 12, 13)]", &Context::new()).unwrap();
155+
assert_eq!(result, "c = D");
156+
}
157+
158+
#[test]
159+
fn numbers_as_octal() {
160+
let mut parser = Statement::new().unwrap();
161+
let result = parser.parse_and_execute("[format('{0:o} == {1:o}', 12, 13)]", &Context::new()).unwrap();
162+
assert_eq!(result, "14 == 15");
163+
}
164+
165+
#[test]
166+
fn numbers_as_binary() {
167+
let mut parser = Statement::new().unwrap();
168+
let result = parser.parse_and_execute("[format('{0:b} = {1:b}', 12, 13)]", &Context::new()).unwrap();
169+
assert_eq!(result, "1100 = 1101");
170+
}
171+
172+
#[test]
173+
fn numbers_as_exp() {
174+
let mut parser = Statement::new().unwrap();
175+
let result = parser.parse_and_execute("[format('{0:e} = {1:E}', 12, 13)]", &Context::new()).unwrap();
176+
assert_eq!(result, "1.2e1 = 1.3E1");
177+
}
178+
179+
#[test]
180+
fn numbers_as_display_just_one() {
181+
let mut parser = Statement::new().unwrap();
182+
let result = parser.parse_and_execute("[format('hello {0} there', 12, 13)]", &Context::new()).unwrap();
183+
assert_eq!(result, "hello 12 there");
184+
}
185+
186+
#[test]
187+
fn string_as_octal() {
188+
let mut parser = Statement::new().unwrap();
189+
let result = parser.parse_and_execute("[format('{0:o} = {1:O}', 'hello', 13)]", &Context::new());
190+
assert!(result.is_err());
191+
}
192+
193+
#[test]
194+
fn bool_as_octal() {
195+
let mut parser = Statement::new().unwrap();
196+
let result = parser.parse_and_execute("[format('{0:o} = {1:O}', true, 13)]", &Context::new());
197+
assert!(result.is_err());
198+
}
199+
200+
#[test]
201+
fn string_as_hex() {
202+
let mut parser = Statement::new().unwrap();
203+
let result = parser.parse_and_execute("[format('{0:x} = {1:X}', 'hello', 13)]", &Context::new());
204+
assert!(result.is_err());
205+
}
206+
207+
#[test]
208+
fn bool_as_hex() {
209+
let mut parser = Statement::new().unwrap();
210+
let result = parser.parse_and_execute("[format('{0:x} = {1:X}', true, 13)]", &Context::new());
211+
assert!(result.is_err());
212+
}
213+
214+
#[test]
215+
fn string_as_binary() {
216+
let mut parser = Statement::new().unwrap();
217+
let result = parser.parse_and_execute("[format('{1:b} = {0:B}', 'hello', 13)]", &Context::new());
218+
assert!(result.is_err());
219+
}
220+
221+
#[test]
222+
fn bool_as_binary() {
223+
let mut parser = Statement::new().unwrap();
224+
let result = parser.parse_and_execute("[format('{1:b} = {0:B}', true, 13)]", &Context::new());
225+
assert!(result.is_err());
226+
}
227+
228+
#[test]
229+
fn string_as_exp() {
230+
let mut parser = Statement::new().unwrap();
231+
let result = parser.parse_and_execute("[format('{1:e} = {0:E}', 'hello', 13)]", &Context::new());
232+
assert!(result.is_err());
233+
}
234+
235+
#[test]
236+
fn bool_as_exp() {
237+
let mut parser = Statement::new().unwrap();
238+
let result = parser.parse_and_execute("[format('{1:e} = {0:E}', true, 13)]", &Context::new());
239+
assert!(result.is_err());
116240
}
117241

242+
#[test]
243+
fn args_out_of_bounds() {
244+
let mut parser = Statement::new().unwrap();
245+
let result = parser.parse_and_execute("[format('hello {1} {2} there', 12, 13)]", &Context::new());
246+
assert!(result.is_err());
247+
}
248+
249+
#[test]
250+
fn missing_closing_brace() {
251+
let mut parser = Statement::new().unwrap();
252+
let result = parser.parse_and_execute("[format('hello {0 there', 12, 13)]", &Context::new());
253+
assert!(result.is_err());
254+
}
255+
256+
#[test]
257+
fn missing_opening_brace() {
258+
let mut parser = Statement::new().unwrap();
259+
let result = parser.parse_and_execute("[format('hello 0} there', 12, 13)]", &Context::new());
260+
assert!(result.is_err());
261+
}
262+
263+
#[test]
264+
fn invalid_format_option() {
265+
let mut parser = Statement::new().unwrap();
266+
let result = parser.parse_and_execute("[format('hello {0:invalid} there', 12, 13)]", &Context::new());
267+
assert!(result.is_err());
268+
}
269+
270+
#[test]
271+
fn invalid_index_syntax() {
272+
let mut parser = Statement::new().unwrap();
273+
let result = parser.parse_and_execute("[format('hello {0;x} there', 12, 13)]", &Context::new());
274+
assert!(result.is_err());
275+
}
276+
277+
#[test]
278+
fn missing_format_type() {
279+
let mut parser = Statement::new().unwrap();
280+
let result = parser.parse_and_execute("[format('hello {0:} there', 12, 13)]", &Context::new()).unwrap();
281+
assert_eq!(result, "hello 12 there");
282+
}
118283
}

0 commit comments

Comments
 (0)