• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

djeedai / weldr / 4505191662

pending completion
4505191662

push

github

GitHub
MPD and DATA extension support (#27)

1073 of 1073 new or added lines in 7 files covered. (100.0%)

1615 of 1752 relevant lines covered (92.18%)

1.54 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

99.49
/lib/src/parse.rs
1
use base64::Engine;
2
use glam::Vec3;
3
use nom::{
4
    branch::alt,
5
    bytes::complete::{tag, tag_no_case, take_while, take_while1, take_while_m_n},
6
    character::{
7
        complete::{digit1, line_ending as eol},
8
        is_digit,
9
    },
10
    combinator::{complete, map, map_res, opt},
11
    error::ErrorKind,
12
    multi::{many0, separated_list1},
13
    number::complete::float,
14
    sequence::{terminated, tuple},
15
    IResult, InputTakeAtPosition,
16
};
17
use std::str;
18

19
use crate::{
20
    Base64DataCmd, CategoryCmd, Color, ColorFinish, ColourCmd, Command, CommentCmd, DataCmd, Error,
21
    FileCmd, GlitterMaterial, GrainSize, KeywordsCmd, LineCmd, MaterialFinish, OptLineCmd,
22
    ParseError, QuadCmd, SpeckleMaterial, SubFileRefCmd, TriangleCmd,
23
};
24

25
// LDraw File Format Specification
26
// https://www.ldraw.org/article/218.html
27

28
pub fn parse_raw(ldr_content: &[u8]) -> Result<Vec<Command>, Error> {
3✔
29
    // "An LDraw file consists of one command per line."
30
    // TODO: What to set for the error message here?
31
    many0(read_line)(ldr_content).map_or_else(
3✔
32
        |e| Err(Error::Parse(ParseError::new_from_nom("", &e))),
×
33
        |(_, cmds)| Ok(cmds),
6✔
34
    )
35
}
36

37
fn nom_error(i: &[u8], kind: ErrorKind) -> nom::Err<nom::error::Error<&[u8]>> {
1✔
38
    nom::Err::Error(nom::error::Error::new(i, kind))
1✔
39
}
40

41
// "Whitespace is defined as one or more spaces (#32), tabs (#9), or combination thereof."
42
fn is_space(chr: u8) -> bool {
3✔
43
    chr == b'\t' || chr == b' '
3✔
44
}
45

46
fn take_spaces(i: &[u8]) -> IResult<&[u8], &[u8]> {
×
47
    take_while(is_space)(i)
×
48
}
49

50
// "All lines in the file must use the standard DOS/Windows line termination of <CR><LF>
51
// (carriage return/line feed). The file is permitted (but not required) to end with a <CR><LF>.
52
// It is recommended that all LDraw-compliant programs also be capable of reading files with the
53
// standard Unix line termination of <LF> (line feed)."
54
fn end_of_line(i: &[u8]) -> IResult<&[u8], &[u8]> {
1✔
55
    if i.is_empty() {
1✔
56
        Ok((i, i))
1✔
57
    } else {
58
        eol(i)
1✔
59
    }
60
}
61

62
// Detect a *potential* end of line <CR><LF> or <LF> by testing for either of <CR>
63
// and <LF>. Note that this doesn't necessarily means a proper end of line if <CR>
64
// is not followed by <LF>, but we assume this doesn't happen.
65
#[inline]
66
fn is_cr_or_lf(chr: u8) -> bool {
3✔
67
    chr == b'\n' || chr == b'\r'
3✔
68
}
69

70
// Parse any character which is not <CR> or <LF>, potentially until the end of input.
71
fn take_not_cr_or_lf(i: &[u8]) -> IResult<&[u8], &[u8]> {
3✔
72
    i.split_at_position_complete(is_cr_or_lf)
3✔
73
}
74

75
// Parse a single comma ',' character.
76
fn single_comma(i: &[u8]) -> IResult<&[u8], &[u8]> {
1✔
77
    if !i.is_empty() && (i[0] == b',') {
2✔
78
        Ok((&i[1..], &i[..1]))
1✔
79
    } else {
80
        // To work with separated_list!(), must return an Err::Error
81
        // when the separator doesn't parse anymore (and therefore
82
        // the list ends).
83
        Err(nom_error(i, nom::error::ErrorKind::Tag))
1✔
84
    }
85
}
86

87
// Parse any character which is not a comma ',' or <CR> or <LF>, potentially until the end of input.
88
// Invalid on empty input.
89
fn take_not_comma_or_eol(i: &[u8]) -> IResult<&[u8], &[u8]> {
1✔
90
    take_while1(|item| item != b',' && !is_cr_or_lf(item))(i)
3✔
91
}
92

93
// Parse any character which is not a space, potentially until the end of input.
94
fn take_not_space(i: &[u8]) -> IResult<&[u8], &[u8]> {
1✔
95
    i.split_at_position_complete(is_space)
1✔
96
}
97

98
// Read the command ID and swallow the following space, if any.
99
fn read_cmd_id_str(i: &[u8]) -> IResult<&[u8], &[u8]> {
3✔
100
    //terminated(take_while1(is_digit), sp)(i) //< This does not work if there's no space (e.g. 4-4cylo.dat)
101
    let (i, o) = i.split_at_position1_complete(|item| !is_digit(item), ErrorKind::Digit)?;
12✔
102
    let (i, _) = space0(i)?;
6✔
103
    Ok((i, o))
3✔
104
}
105

106
fn category(i: &[u8]) -> IResult<&[u8], Command> {
3✔
107
    let (i, _) = tag(b"!CATEGORY")(i)?;
6✔
108
    let (i, _) = sp(i)?;
2✔
109
    let (i, content) = map_res(take_not_cr_or_lf, str::from_utf8)(i)?;
2✔
110

111
    Ok((
1✔
112
        i,
113
        Command::Category(CategoryCmd {
1✔
114
            category: content.to_string(),
1✔
115
        }),
116
    ))
117
}
118

119
fn keywords_list(i: &[u8]) -> IResult<&[u8], Vec<&str>> {
1✔
120
    separated_list1(single_comma, map_res(take_not_comma_or_eol, str::from_utf8))(i)
1✔
121
}
122

123
fn keywords(i: &[u8]) -> IResult<&[u8], Command> {
3✔
124
    let (i, (_, _, keywords)) = tuple((tag(b"!KEYWORDS"), sp, keywords_list))(i)?;
6✔
125
    Ok((
1✔
126
        i,
127
        Command::Keywords(KeywordsCmd {
1✔
128
            keywords: keywords.iter().map(|kw| kw.trim().to_string()).collect(),
4✔
129
        }),
130
    ))
131
}
132

133
fn from_hex(i: &[u8]) -> Result<u8, nom::error::ErrorKind> {
1✔
134
    match std::str::from_utf8(i) {
1✔
135
        Ok(s) => match u8::from_str_radix(s, 16) {
1✔
136
            Ok(val) => Ok(val),
1✔
137
            Err(_) => Err(ErrorKind::AlphaNumeric),
1✔
138
        },
139
        Err(_) => Err(ErrorKind::AlphaNumeric),
1✔
140
    }
141
}
142

143
fn is_hex_digit(c: u8) -> bool {
1✔
144
    (c as char).is_ascii_hexdigit()
1✔
145
}
146

147
fn hex_primary(i: &[u8]) -> IResult<&[u8], u8> {
1✔
148
    map_res(take_while_m_n(2, 2, is_hex_digit), from_hex)(i)
1✔
149
}
150

151
fn hex_color(i: &[u8]) -> IResult<&[u8], Color> {
1✔
152
    let (i, _) = tag(b"#")(i)?;
2✔
153
    let (i, (red, green, blue)) = tuple((hex_primary, hex_primary, hex_primary))(i)?;
3✔
154
    Ok((i, Color { red, green, blue }))
1✔
155
}
156

157
fn digit1_as_u8(i: &[u8]) -> IResult<&[u8], u8> {
1✔
158
    map_res(map_res(digit1, str::from_utf8), str::parse::<u8>)(i)
1✔
159
}
160

161
// ALPHA part of !COLOUR
162
fn colour_alpha(i: &[u8]) -> IResult<&[u8], Option<u8>> {
1✔
163
    opt(complete(|i| {
164
        let (i, _) = sp(i)?;
165
        let (i, _) = tag(b"ALPHA")(i)?;
166
        let (i, _) = sp(i)?;
167
        digit1_as_u8(i)
168
    }))(i)
1✔
169
}
170

171
// LUMINANCE part of !COLOUR
172
fn colour_luminance(i: &[u8]) -> IResult<&[u8], Option<u8>> {
1✔
173
    opt(complete(|i| {
174
        let (i, _) = sp(i)?;
175
        let (i, _) = tag(b"LUMINANCE")(i)?;
176
        let (i, _) = sp(i)?;
177
        digit1_as_u8(i)
178
    }))(i)
1✔
179
}
180

181
fn material_grain_size(i: &[u8]) -> IResult<&[u8], GrainSize> {
1✔
182
    alt((grain_size, grain_min_max_size))(i)
1✔
183
}
184

185
fn grain_size(i: &[u8]) -> IResult<&[u8], GrainSize> {
1✔
186
    // TODO: Create tagged float helper?
187
    let (i, (_, _, size)) = tuple((tag(b"SIZE"), sp, float))(i)?;
2✔
188
    Ok((i, GrainSize::Size(size)))
1✔
189
}
190

191
fn grain_min_max_size(i: &[u8]) -> IResult<&[u8], GrainSize> {
1✔
192
    let (i, (_, _, min_size)) = tuple((tag(b"MINSIZE"), sp, float))(i)?;
2✔
193
    let (i, _) = sp(i)?;
3✔
194
    let (i, (_, _, max_size)) = tuple((tag(b"MAXSIZE"), sp, float))(i)?;
2✔
195
    Ok((i, GrainSize::MinMaxSize((min_size, max_size))))
1✔
196
}
197

198
// GLITTER VALUE v [ALPHA a] [LUMINANCE l] FRACTION f VFRACTION vf (SIZE s | MINSIZE min MAXSIZE max)
199
fn glitter_material(i: &[u8]) -> IResult<&[u8], ColorFinish> {
1✔
200
    let (i, _) = tag_no_case(b"GLITTER")(i)?;
2✔
201
    let (i, _) = sp(i)?;
3✔
202
    let (i, _) = tag_no_case(b"VALUE")(i)?;
2✔
203
    let (i, _) = sp(i)?;
2✔
204
    let (i, value) = hex_color(i)?;
2✔
205
    let (i, alpha) = colour_alpha(i)?;
2✔
206
    let (i, luminance) = colour_luminance(i)?;
2✔
207
    let (i, _) = sp(i)?;
2✔
208
    let (i, _) = tag_no_case(b"FRACTION")(i)?;
2✔
209
    let (i, _) = sp(i)?;
2✔
210
    let (i, surface_fraction) = float(i)?;
2✔
211
    let (i, _) = sp(i)?;
2✔
212
    let (i, _) = tag_no_case(b"VFRACTION")(i)?;
2✔
213
    let (i, _) = sp(i)?;
2✔
214
    let (i, volume_fraction) = float(i)?;
2✔
215
    let (i, _) = sp(i)?;
2✔
216
    let (i, size) = material_grain_size(i)?;
2✔
217

218
    Ok((
1✔
219
        i,
220
        ColorFinish::Material(MaterialFinish::Glitter(GlitterMaterial {
1✔
221
            value,
1✔
222
            alpha,
223
            luminance,
224
            surface_fraction,
225
            volume_fraction,
226
            size,
1✔
227
        })),
228
    ))
229
}
230

231
// SPECKLE VALUE v [ALPHA a] [LUMINANCE l] FRACTION f (SIZE s | MINSIZE min MAXSIZE max)
232
fn speckle_material(i: &[u8]) -> IResult<&[u8], ColorFinish> {
1✔
233
    let (i, _) = tag_no_case(b"SPECKLE")(i)?;
2✔
234
    let (i, _) = sp(i)?;
3✔
235
    let (i, _) = tag_no_case(b"VALUE")(i)?;
2✔
236
    let (i, _) = sp(i)?;
2✔
237
    let (i, value) = hex_color(i)?;
2✔
238
    let (i, alpha) = colour_alpha(i)?;
2✔
239
    let (i, luminance) = colour_luminance(i)?;
2✔
240
    let (i, _) = sp(i)?;
2✔
241
    let (i, _) = tag_no_case(b"FRACTION")(i)?;
2✔
242
    let (i, _) = sp(i)?;
2✔
243
    let (i, surface_fraction) = float(i)?;
2✔
244
    let (i, _) = sp(i)?;
2✔
245
    let (i, size) = material_grain_size(i)?;
2✔
246

247
    Ok((
1✔
248
        i,
249
        ColorFinish::Material(MaterialFinish::Speckle(SpeckleMaterial {
1✔
250
            value,
1✔
251
            alpha,
252
            luminance,
253
            surface_fraction,
254
            size,
1✔
255
        })),
256
    ))
257
}
258

259
// Other unrecognized MATERIAL definition
260
fn other_material(i: &[u8]) -> IResult<&[u8], ColorFinish> {
1✔
261
    let (i, content) = map_res(take_not_cr_or_lf, str::from_utf8)(i)?;
1✔
262
    let finish = content.trim().to_string();
1✔
263
    Ok((i, ColorFinish::Material(MaterialFinish::Other(finish))))
1✔
264
}
265

266
// MATERIAL finish part of !COLOUR
267
fn material_finish(i: &[u8]) -> IResult<&[u8], ColorFinish> {
1✔
268
    let (i, _) = tag_no_case(b"MATERIAL")(i)?;
1✔
269
    let (i, _) = sp(i)?;
2✔
270
    alt((glitter_material, speckle_material, other_material))(i)
1✔
271
}
272

273
// Finish part of !COLOUR
274
// TODO: Avoid having the leading space in each parser?
275
fn color_finish(i: &[u8]) -> IResult<&[u8], Option<ColorFinish>> {
1✔
276
    opt(complete(|i| {
277
        let (i, _) = sp(i)?;
278
        alt((
279
            map(tag_no_case(b"CHROME"), |_| ColorFinish::Chrome),
280
            map(tag_no_case(b"PEARLESCENT"), |_| ColorFinish::Pearlescent),
281
            map(tag_no_case(b"RUBBER"), |_| ColorFinish::Rubber),
282
            map(tag_no_case(b"MATTE_METALLIC"), |_| {
283
                ColorFinish::MatteMetallic
284
            }),
285
            map(tag_no_case(b"METAL"), |_| ColorFinish::Metal),
286
            material_finish,
287
        ))(i)
288
    }))(i)
1✔
289
}
290

291
// !COLOUR extension meta-command
292
fn meta_colour(i: &[u8]) -> IResult<&[u8], Command> {
3✔
293
    let (i, _) = tag(b"!COLOUR")(i)?;
6✔
294
    let (i, _) = sp(i)?;
2✔
295
    let (i, name) = map_res(take_not_space, str::from_utf8)(i)?;
2✔
296
    let (i, _) = sp(i)?;
2✔
297
    let (i, _) = tag(b"CODE")(i)?;
2✔
298
    let (i, _) = sp(i)?;
2✔
299
    let (i, code) = color_id(i)?;
2✔
300
    let (i, _) = sp(i)?;
2✔
301
    let (i, _) = tag(b"VALUE")(i)?;
2✔
302
    let (i, _) = sp(i)?;
2✔
303
    let (i, value) = hex_color(i)?;
2✔
304
    let (i, _) = sp(i)?;
2✔
305
    let (i, _) = tag(b"EDGE")(i)?;
2✔
306
    let (i, _) = sp(i)?;
2✔
307
    let (i, edge) = hex_color(i)?;
2✔
308
    let (i, alpha) = colour_alpha(i)?;
2✔
309
    let (i, luminance) = colour_luminance(i)?;
2✔
310
    let (i, finish) = color_finish(i)?;
2✔
311

312
    Ok((
1✔
313
        i,
314
        Command::Colour(ColourCmd {
1✔
315
            name: name.to_string(),
1✔
316
            code,
317
            value,
1✔
318
            edge,
1✔
319
            alpha,
320
            luminance,
321
            finish,
1✔
322
        }),
323
    ))
324
}
325

326
fn comment(i: &[u8]) -> IResult<&[u8], Command> {
2✔
327
    let (i, comment) = map_res(take_not_cr_or_lf, str::from_utf8)(i)?;
2✔
328
    Ok((i, Command::Comment(CommentCmd::new(comment))))
2✔
329
}
330

331
fn meta_file(i: &[u8]) -> IResult<&[u8], Command> {
3✔
332
    let (i, _) = tag(b"FILE")(i)?;
5✔
333
    let (i, _) = sp(i)?;
4✔
334
    let (i, file) = map_res(take_not_cr_or_lf, str::from_utf8)(i)?;
4✔
335

336
    Ok((
2✔
337
        i,
338
        Command::File(FileCmd {
2✔
339
            file: file.to_string(),
2✔
340
        }),
341
    ))
342
}
343

344
fn meta_data(i: &[u8]) -> IResult<&[u8], Command> {
2✔
345
    let (i, _) = tag(b"!DATA")(i)?;
4✔
346
    let (i, _) = sp(i)?;
2✔
347
    let (i, file) = map_res(take_not_cr_or_lf, str::from_utf8)(i)?;
2✔
348

349
    Ok((
1✔
350
        i,
351
        Command::Data(DataCmd {
1✔
352
            file: file.to_string(),
1✔
353
        }),
354
    ))
355
}
356

357
fn meta_base_64_data(i: &[u8]) -> IResult<&[u8], Command> {
2✔
358
    // TODO: Validate base64 characters?
359
    let (i, _) = tag(b"!:")(i)?;
4✔
360
    let (i, _) = sp(i)?;
2✔
361
    let (i, data) = map_res(take_not_cr_or_lf, |b| {
362
        base64::engine::general_purpose::STANDARD_NO_PAD.decode(b)
363
    })(i)?;
2✔
364

365
    Ok((i, Command::Base64Data(Base64DataCmd { data })))
1✔
366
}
367

368
fn meta_nofile(i: &[u8]) -> IResult<&[u8], Command> {
2✔
369
    let (i, _) = tag(b"NOFILE")(i)?;
4✔
370
    Ok((i, Command::NoFile))
1✔
371
}
372

373
fn meta_cmd(i: &[u8]) -> IResult<&[u8], Command> {
3✔
374
    alt((
375
        complete(category),
376
        complete(keywords),
377
        complete(meta_colour),
378
        complete(meta_file),
379
        complete(meta_nofile),
380
        complete(meta_data),
381
        complete(meta_base_64_data),
382
        comment,
383
    ))(i)
384
}
385

386
fn read_vec3(i: &[u8]) -> IResult<&[u8], Vec3> {
3✔
387
    let (i, (x, _, y, _, z)) = tuple((float, sp, float, sp, float))(i)?;
3✔
388
    Ok((i, Vec3 { x, y, z }))
3✔
389
}
390

391
fn color_id(i: &[u8]) -> IResult<&[u8], u32> {
3✔
392
    map_res(map_res(digit1, str::from_utf8), str::parse::<u32>)(i)
3✔
393
}
394

395
fn filename(i: &[u8]) -> IResult<&[u8], &str> {
2✔
396
    // Assume leading and trailing whitespace isn't part of the filename.
397
    map(map_res(take_not_cr_or_lf, str::from_utf8), |s| s.trim())(i)
6✔
398
}
399

400
fn file_ref_cmd(i: &[u8]) -> IResult<&[u8], Command> {
2✔
401
    let (i, color) = color_id(i)?;
2✔
402
    let (i, _) = sp(i)?;
4✔
403
    let (i, pos) = read_vec3(i)?;
4✔
404
    let (i, _) = sp(i)?;
4✔
405
    let (i, row0) = read_vec3(i)?;
4✔
406
    let (i, _) = sp(i)?;
4✔
407
    let (i, row1) = read_vec3(i)?;
4✔
408
    let (i, _) = sp(i)?;
4✔
409
    let (i, row2) = read_vec3(i)?;
4✔
410
    let (i, _) = sp(i)?;
4✔
411
    let (i, file) = filename(i)?;
4✔
412

413
    Ok((
2✔
414
        i,
415
        Command::SubFileRef(SubFileRefCmd {
2✔
416
            color,
417
            pos,
2✔
418
            row0,
2✔
419
            row1,
2✔
420
            row2,
2✔
421
            file: file.into(),
2✔
422
        }),
423
    ))
424
}
425

426
fn line_cmd(i: &[u8]) -> IResult<&[u8], Command> {
2✔
427
    let (i, color) = color_id(i)?;
2✔
428
    let (i, _) = sp(i)?;
4✔
429
    let (i, v1) = read_vec3(i)?;
4✔
430
    let (i, _) = sp(i)?;
4✔
431
    let (i, v2) = read_vec3(i)?;
4✔
432
    let (i, _) = space0(i)?;
4✔
433

434
    Ok((
2✔
435
        i,
436
        Command::Line(LineCmd {
2✔
437
            color,
438
            vertices: [v1, v2],
2✔
439
        }),
440
    ))
441
}
442

443
fn tri_cmd(i: &[u8]) -> IResult<&[u8], Command> {
2✔
444
    let (i, color) = color_id(i)?;
2✔
445
    let (i, _) = sp(i)?;
4✔
446
    let (i, v1) = read_vec3(i)?;
4✔
447
    let (i, _) = sp(i)?;
4✔
448
    let (i, v2) = read_vec3(i)?;
4✔
449
    let (i, _) = sp(i)?;
4✔
450
    let (i, v3) = read_vec3(i)?;
4✔
451
    let (i, _) = space0(i)?;
4✔
452

453
    Ok((
2✔
454
        i,
455
        Command::Triangle(TriangleCmd {
2✔
456
            color,
457
            vertices: [v1, v2, v3],
2✔
458
        }),
459
    ))
460
}
461

462
fn quad_cmd(i: &[u8]) -> IResult<&[u8], Command> {
3✔
463
    let (i, color) = color_id(i)?;
3✔
464
    let (i, _) = sp(i)?;
6✔
465
    let (i, v1) = read_vec3(i)?;
6✔
466
    let (i, _) = sp(i)?;
6✔
467
    let (i, v2) = read_vec3(i)?;
6✔
468
    let (i, _) = sp(i)?;
6✔
469
    let (i, v3) = read_vec3(i)?;
6✔
470
    let (i, _) = sp(i)?;
6✔
471
    let (i, v4) = read_vec3(i)?;
6✔
472
    let (i, _) = space0(i)?;
6✔
473

474
    Ok((
3✔
475
        i,
476
        Command::Quad(QuadCmd {
3✔
477
            color,
478
            vertices: [v1, v2, v3, v4],
3✔
479
        }),
480
    ))
481
}
482

483
fn opt_line_cmd(i: &[u8]) -> IResult<&[u8], Command> {
2✔
484
    let (i, color) = color_id(i)?;
2✔
485
    let (i, _) = sp(i)?;
4✔
486
    let (i, v1) = read_vec3(i)?;
4✔
487
    let (i, _) = sp(i)?;
4✔
488
    let (i, v2) = read_vec3(i)?;
4✔
489
    let (i, _) = sp(i)?;
4✔
490
    let (i, v3) = read_vec3(i)?;
2✔
491
    let (i, _) = sp(i)?;
2✔
492
    let (i, v4) = read_vec3(i)?;
2✔
493
    let (i, _) = space0(i)?;
2✔
494

495
    Ok((
1✔
496
        i,
497
        Command::OptLine(OptLineCmd {
1✔
498
            color,
499
            vertices: [v1, v2],
1✔
500
            control_points: [v3, v4],
1✔
501
        }),
502
    ))
503
}
504

505
// Zero or more "spaces", as defined in LDraw standard.
506
// Valid even on empty input.
507
fn space0(i: &[u8]) -> IResult<&[u8], &[u8]> {
3✔
508
    i.split_at_position_complete(|item| !is_space(item))
9✔
509
}
510

511
// One or more "spaces", as defined in LDraw standard.
512
// Valid even on empty input.
513
fn sp(i: &[u8]) -> IResult<&[u8], &[u8]> {
3✔
514
    i.split_at_position1_complete(|item| !is_space(item), ErrorKind::Space)
9✔
515
}
516

517
// Zero or more "spaces", as defined in LDraw standard.
518
// Valid even on empty input.
519
fn space_or_eol0(i: &[u8]) -> IResult<&[u8], &[u8]> {
3✔
520
    i.split_at_position_complete(|item| !is_space(item) && !is_cr_or_lf(item))
9✔
521
}
522

523
// An empty line made of optional spaces, and ending with an end-of-line sequence
524
// (either <CR><LF> or <LF> alone) or the end of input.
525
// Valid even on empty input.
526
fn empty_line(i: &[u8]) -> IResult<&[u8], &[u8]> {
1✔
527
    terminated(space0, end_of_line)(i)
1✔
528
}
529

530
// "There is no line length restriction. Each command consists of optional leading
531
// whitespace followed by whitespace-delimited tokens. Some commands also have trailing
532
// arbitrary data which may itself include internal whitespace; such data is not tokenized,
533
// but treated as single unit according to the command."
534
//
535
// "Lines may also be empty or consist only of whitespace. Such lines have no effect."
536
//
537
// "The line type of a line is the first number on the line."
538
// "If the line type of the command is invalid, the line is ignored."
539
fn read_line(i: &[u8]) -> IResult<&[u8], Command> {
3✔
540
    let (i, _) = space_or_eol0(i)?;
3✔
541
    let (i, cmd_id) = read_cmd_id_str(i)?;
9✔
542
    let (i, cmd) = match cmd_id {
7✔
543
        b"0" => meta_cmd(i),
6✔
544
        b"1" => file_ref_cmd(i),
2✔
545
        b"2" => line_cmd(i),
2✔
546
        b"3" => tri_cmd(i),
2✔
547
        b"4" => quad_cmd(i),
3✔
548
        b"5" => opt_line_cmd(i),
2✔
549
        _ => Err(nom_error(i, ErrorKind::Switch)),
×
550
    }?;
551
    Ok((i, cmd))
3✔
552
}
553

554
#[cfg(test)]
555
mod tests {
556
    use nom::error::ErrorKind;
557

558
    use super::*;
559

560
    #[test]
561
    fn test_color_id() {
3✔
562
        assert_eq!(color_id(b""), Err(nom_error(&b""[..], ErrorKind::Digit)));
1✔
563
        assert_eq!(color_id(b"1"), Ok((&b""[..], 1)));
1✔
564
        assert_eq!(color_id(b"16 "), Ok((&b" "[..], 16)));
1✔
565
    }
566

567
    #[test]
568
    fn test_from_hex() {
3✔
569
        assert_eq!(from_hex(b"0"), Ok(0));
1✔
570
        assert_eq!(from_hex(b"1"), Ok(1));
1✔
571
        assert_eq!(from_hex(b"a"), Ok(10));
1✔
572
        assert_eq!(from_hex(b"F"), Ok(15));
1✔
573
        assert_eq!(from_hex(b"G"), Err(ErrorKind::AlphaNumeric));
1✔
574
        assert_eq!(from_hex(b"10"), Ok(16));
1✔
575
        assert_eq!(from_hex(b"FF"), Ok(255));
1✔
576
        assert_eq!(from_hex(b"1G"), Err(ErrorKind::AlphaNumeric));
1✔
577
        assert_eq!(from_hex(b"100"), Err(ErrorKind::AlphaNumeric));
1✔
578
        assert_eq!(from_hex(b"\xFF"), Err(ErrorKind::AlphaNumeric));
1✔
579
    }
580

581
    #[test]
582
    fn test_hex_color() {
3✔
583
        assert_eq!(hex_color(b""), Err(nom_error(&b""[..], ErrorKind::Tag)));
1✔
584
        assert_eq!(
1✔
585
            hex_color(b"#"),
1✔
586
            Err(nom_error(&b""[..], ErrorKind::TakeWhileMN))
1✔
587
        );
588
        assert_eq!(
1✔
589
            hex_color(b"#1"),
1✔
590
            Err(nom_error(&b"1"[..], ErrorKind::TakeWhileMN))
1✔
591
        );
592
        assert_eq!(
1✔
593
            hex_color(b"#12345Z"),
1✔
594
            Err(nom_error(&b"5Z"[..], ErrorKind::TakeWhileMN))
1✔
595
        );
596
        assert_eq!(
1✔
597
            hex_color(b"#123456"),
1✔
598
            Ok((&b""[..], Color::new(0x12, 0x34, 0x56)))
1✔
599
        );
600
        assert_eq!(
1✔
601
            hex_color(b"#ABCDEF"),
1✔
602
            Ok((&b""[..], Color::new(0xAB, 0xCD, 0xEF)))
1✔
603
        );
604
        assert_eq!(
1✔
605
            hex_color(b"#8E5cAf"),
1✔
606
            Ok((&b""[..], Color::new(0x8E, 0x5C, 0xAF)))
1✔
607
        );
608
        assert_eq!(
1✔
609
            hex_color(b"#123456e"),
1✔
610
            Ok((&b"e"[..], Color::new(0x12, 0x34, 0x56)))
1✔
611
        );
612
    }
613

614
    #[test]
615
    fn test_colour_alpha() {
3✔
616
        assert_eq!(colour_alpha(b""), Ok((&b""[..], None)));
1✔
617
        assert_eq!(colour_alpha(b" ALPHA 0"), Ok((&b""[..], Some(0))));
1✔
618
        assert_eq!(colour_alpha(b" ALPHA 1"), Ok((&b""[..], Some(1))));
1✔
619
        assert_eq!(colour_alpha(b" ALPHA 128"), Ok((&b""[..], Some(128))));
1✔
620
        assert_eq!(colour_alpha(b" ALPHA 255"), Ok((&b""[..], Some(255))));
1✔
621
        assert_eq!(colour_alpha(b" ALPHA 34 "), Ok((&b" "[..], Some(34))));
1✔
622
        // TODO - Should fail on partial match, but succeeds because of opt!()
623
        assert_eq!(colour_alpha(b" ALPHA"), Ok((&b" ALPHA"[..], None))); // Err(Err::Incomplete(Needed::Size(1)))
1✔
624
        assert_eq!(colour_alpha(b" ALPHA 256"), Ok((&b" ALPHA 256"[..], None)));
1✔
625
        // Err(Err::Incomplete(Needed::Size(1)))
626
    }
627

628
    #[test]
629
    fn test_colour_luminance() {
3✔
630
        assert_eq!(colour_luminance(b""), Ok((&b""[..], None)));
1✔
631
        assert_eq!(colour_luminance(b" LUMINANCE 0"), Ok((&b""[..], Some(0))));
1✔
632
        assert_eq!(colour_luminance(b" LUMINANCE 1"), Ok((&b""[..], Some(1))));
1✔
633
        assert_eq!(
1✔
634
            colour_luminance(b" LUMINANCE 128"),
1✔
635
            Ok((&b""[..], Some(128)))
1✔
636
        );
637
        assert_eq!(
1✔
638
            colour_luminance(b" LUMINANCE 255"),
1✔
639
            Ok((&b""[..], Some(255)))
1✔
640
        );
641
        assert_eq!(
1✔
642
            colour_luminance(b" LUMINANCE 34 "),
1✔
643
            Ok((&b" "[..], Some(34)))
1✔
644
        );
645
        // TODO - Should fail on partial match, but succeeds because of opt!()
646
        assert_eq!(
1✔
647
            colour_luminance(b" LUMINANCE"),
1✔
648
            Ok((&b" LUMINANCE"[..], None))
1✔
649
        ); // Err(Err::Incomplete(Needed::Size(1)))
650
        assert_eq!(
1✔
651
            colour_luminance(b" LUMINANCE 256"),
1✔
652
            Ok((&b" LUMINANCE 256"[..], None))
1✔
653
        ); // Err(Err::Incomplete(Needed::Size(1)))
654
    }
655

656
    #[test]
657
    fn test_material_grain_size() {
3✔
658
        assert_eq!(
1✔
659
            material_grain_size(b""),
1✔
660
            Err(nom_error(&b""[..], ErrorKind::Tag))
1✔
661
        );
662
        assert_eq!(
1✔
663
            material_grain_size(b"SIZE"),
1✔
664
            Err(nom_error(&b"SIZE"[..], ErrorKind::Tag))
1✔
665
        );
666
        assert_eq!(
1✔
667
            material_grain_size(b"SIZE 1"),
1✔
668
            Ok((&b""[..], GrainSize::Size(1.0)))
1✔
669
        );
670
        assert_eq!(
1✔
671
            material_grain_size(b"SIZE 0.02"),
1✔
672
            Ok((&b""[..], GrainSize::Size(0.02)))
1✔
673
        );
674
        assert_eq!(
1✔
675
            material_grain_size(b"MINSIZE"),
1✔
676
            Err(nom_error(&b""[..], ErrorKind::Space))
1✔
677
        );
678
        assert_eq!(
1✔
679
            material_grain_size(b"MINSIZE 0.02"),
1✔
680
            Err(nom_error(&b""[..], ErrorKind::Space))
1✔
681
        );
682
        assert_eq!(
1✔
683
            material_grain_size(b"MINSIZE 0.02 MAXSIZE 0.04"),
1✔
684
            Ok((&b""[..], GrainSize::MinMaxSize((0.02, 0.04))))
1✔
685
        );
686
    }
687

688
    #[test]
689
    fn test_glitter_material() {
3✔
690
        assert_eq!(
2✔
691
            glitter_material(b""),
1✔
692
            Err(nom_error(&b""[..], ErrorKind::Tag))
2✔
693
        );
694
        assert_eq!(
1✔
695
            glitter_material(b"GLITTER"),
1✔
696
            Err(nom_error(&b""[..], ErrorKind::Space))
2✔
697
        );
698
        assert_eq!(
1✔
699
            glitter_material(b"GLITTER VALUE #123456 FRACTION 1.0 VFRACTION 0.3 SIZE 1"),
1✔
700
            Ok((
1✔
701
                &b""[..],
1✔
702
                ColorFinish::Material(MaterialFinish::Glitter(GlitterMaterial {
1✔
703
                    value: Color::new(0x12, 0x34, 0x56),
1✔
704
                    alpha: None,
1✔
705
                    luminance: None,
1✔
706
                    surface_fraction: 1.0,
707
                    volume_fraction: 0.3,
708
                    size: GrainSize::Size(1.0)
1✔
709
                }))
710
            ))
711
        );
712
        assert_eq!(
1✔
713
            glitter_material(b"GLITTER VALUE #123456 ALPHA 128 FRACTION 1.0 VFRACTION 0.3 SIZE 1"),
1✔
714
            Ok((
1✔
715
                &b""[..],
1✔
716
                ColorFinish::Material(MaterialFinish::Glitter(GlitterMaterial {
1✔
717
                    value: Color::new(0x12, 0x34, 0x56),
1✔
718
                    alpha: Some(128),
1✔
719
                    luminance: None,
1✔
720
                    surface_fraction: 1.0,
721
                    volume_fraction: 0.3,
722
                    size: GrainSize::Size(1.0)
1✔
723
                }))
724
            ))
725
        );
726
        assert_eq!(
1✔
727
            glitter_material(
1✔
728
                b"GLITTER VALUE #123456 LUMINANCE 32 FRACTION 1.0 VFRACTION 0.3 SIZE 1"
729
            ),
730
            Ok((
1✔
731
                &b""[..],
1✔
732
                ColorFinish::Material(MaterialFinish::Glitter(GlitterMaterial {
1✔
733
                    value: Color::new(0x12, 0x34, 0x56),
1✔
734
                    alpha: None,
1✔
735
                    luminance: Some(32),
1✔
736
                    surface_fraction: 1.0,
737
                    volume_fraction: 0.3,
738
                    size: GrainSize::Size(1.0)
1✔
739
                }))
740
            ))
741
        );
742
        assert_eq!(
1✔
743
            glitter_material(
1✔
744
                b"GLITTER VALUE #123456 FRACTION 1.0 VFRACTION 0.3 MINSIZE 0.02 MAXSIZE 0.04"
745
            ),
746
            Ok((
1✔
747
                &b""[..],
1✔
748
                ColorFinish::Material(MaterialFinish::Glitter(GlitterMaterial {
1✔
749
                    value: Color::new(0x12, 0x34, 0x56),
1✔
750
                    alpha: None,
1✔
751
                    luminance: None,
1✔
752
                    surface_fraction: 1.0,
753
                    volume_fraction: 0.3,
754
                    size: GrainSize::MinMaxSize((0.02, 0.04))
1✔
755
                }))
756
            ))
757
        );
758
    }
759

760
    #[test]
761
    fn test_speckle_material() {
3✔
762
        assert_eq!(
2✔
763
            speckle_material(b""),
1✔
764
            Err(nom_error(&b""[..], ErrorKind::Tag))
2✔
765
        );
766
        assert_eq!(
1✔
767
            speckle_material(b"SPECKLE"),
1✔
768
            Err(nom_error(&b""[..], ErrorKind::Space))
2✔
769
        );
770
        assert_eq!(
1✔
771
            speckle_material(b"SPECKLE VALUE #123456 FRACTION 1.0 SIZE 1"),
1✔
772
            Ok((
1✔
773
                &b""[..],
1✔
774
                ColorFinish::Material(MaterialFinish::Speckle(SpeckleMaterial {
1✔
775
                    value: Color::new(0x12, 0x34, 0x56),
1✔
776
                    alpha: None,
1✔
777
                    luminance: None,
1✔
778
                    surface_fraction: 1.0,
779
                    size: GrainSize::Size(1.0)
1✔
780
                }))
781
            ))
782
        );
783
        assert_eq!(
1✔
784
            speckle_material(b"SPECKLE VALUE #123456 ALPHA 128 FRACTION 1.0 SIZE 1"),
1✔
785
            Ok((
1✔
786
                &b""[..],
1✔
787
                ColorFinish::Material(MaterialFinish::Speckle(SpeckleMaterial {
1✔
788
                    value: Color::new(0x12, 0x34, 0x56),
1✔
789
                    alpha: Some(128),
1✔
790
                    luminance: None,
1✔
791
                    surface_fraction: 1.0,
792
                    size: GrainSize::Size(1.0)
1✔
793
                }))
794
            ))
795
        );
796
        assert_eq!(
1✔
797
            speckle_material(b"SPECKLE VALUE #123456 LUMINANCE 32 FRACTION 1.0 SIZE 1"),
1✔
798
            Ok((
1✔
799
                &b""[..],
1✔
800
                ColorFinish::Material(MaterialFinish::Speckle(SpeckleMaterial {
1✔
801
                    value: Color::new(0x12, 0x34, 0x56),
1✔
802
                    alpha: None,
1✔
803
                    luminance: Some(32),
1✔
804
                    surface_fraction: 1.0,
805
                    size: GrainSize::Size(1.0)
1✔
806
                }))
807
            ))
808
        );
809
        assert_eq!(
1✔
810
            speckle_material(b"SPECKLE VALUE #123456 FRACTION 1.0 MINSIZE 0.02 MAXSIZE 0.04"),
1✔
811
            Ok((
1✔
812
                &b""[..],
1✔
813
                ColorFinish::Material(MaterialFinish::Speckle(SpeckleMaterial {
1✔
814
                    value: Color::new(0x12, 0x34, 0x56),
1✔
815
                    alpha: None,
1✔
816
                    luminance: None,
1✔
817
                    surface_fraction: 1.0,
818
                    size: GrainSize::MinMaxSize((0.02, 0.04))
1✔
819
                }))
820
            ))
821
        );
822
    }
823

824
    #[test]
825
    fn test_color_finish() {
3✔
826
        assert_eq!(color_finish(b""), Ok((&b""[..], None)));
2✔
827
        assert_eq!(color_finish(b"CHROME"), Ok((&b"CHROME"[..], None)));
1✔
828
        assert_eq!(
1✔
829
            color_finish(b" CHROME"),
1✔
830
            Ok((&b""[..], Some(ColorFinish::Chrome)))
2✔
831
        );
832
        assert_eq!(
1✔
833
            color_finish(b" PEARLESCENT"),
1✔
834
            Ok((&b""[..], Some(ColorFinish::Pearlescent)))
2✔
835
        );
836
        assert_eq!(
1✔
837
            color_finish(b" RUBBER"),
1✔
838
            Ok((&b""[..], Some(ColorFinish::Rubber)))
2✔
839
        );
840
        assert_eq!(
1✔
841
            color_finish(b" MATTE_METALLIC"),
1✔
842
            Ok((&b""[..], Some(ColorFinish::MatteMetallic)))
2✔
843
        );
844
        assert_eq!(
1✔
845
            color_finish(b" METAL"),
1✔
846
            Ok((&b""[..], Some(ColorFinish::Metal)))
2✔
847
        );
848
        // TODO - Should probably ensure <SPACE> or <EOF> after keyword, not *anything*
849
        assert_eq!(
1✔
850
            color_finish(b" CHROMEas"),
1✔
851
            Ok((&b"as"[..], Some(ColorFinish::Chrome)))
2✔
852
        );
853
        assert_eq!(
1✔
854
            color_finish(b" MATERIAL custom values"),
1✔
855
            Ok((
1✔
856
                &b""[..],
1✔
857
                Some(ColorFinish::Material(MaterialFinish::Other(
1✔
858
                    "custom values".to_string()
1✔
859
                )))
860
            ))
861
        );
862
    }
863

864
    #[test]
865
    fn test_digit1_as_u8() {
3✔
866
        assert_eq!(
1✔
867
            digit1_as_u8(b""),
1✔
868
            Err(nom_error(&b""[..], ErrorKind::Digit))
1✔
869
        );
870
        assert_eq!(digit1_as_u8(b"0"), Ok((&b""[..], 0u8)));
1✔
871
        assert_eq!(digit1_as_u8(b"1"), Ok((&b""[..], 1u8)));
1✔
872
        assert_eq!(digit1_as_u8(b"255"), Ok((&b""[..], 255u8)));
1✔
873
        assert_eq!(
1✔
874
            digit1_as_u8(b"256"),
1✔
875
            Err(nom_error(&b"256"[..], ErrorKind::MapRes))
1✔
876
        );
877
        assert_eq!(digit1_as_u8(b"32 "), Ok((&b" "[..], 32u8)));
1✔
878
    }
879

880
    #[test]
881
    fn test_meta_colour() {
3✔
882
        assert_eq!(meta_colour(b""), Err(nom_error(&b""[..], ErrorKind::Tag)));
2✔
883
        // Test one color of each type from LDCfgalt.ldr
884
        // The formatting is similar in LDConfig.ldr.
885
        assert_eq!(
1✔
886
            meta_colour(b"!COLOUR Black                              CODE   0   VALUE #1B2A34   EDGE #2B4354"),
1✔
887
            Ok((
1✔
888
                &b""[..],
1✔
889
                Command::Colour(ColourCmd {
1✔
890
                    name: "Black".to_string(),
1✔
891
                    code: 0,
892
                    value: Color::new(0x1B, 0x2A, 0x34),
2✔
893
                    edge: Color::new(0x2B, 0x43, 0x54),
1✔
894
                    alpha: None,
1✔
895
                    luminance: None,
1✔
896
                    finish: None
1✔
897
                })
898
            ))
899
        );
900
        assert_eq!(
1✔
901
            meta_colour(b"!COLOUR Trans_Dark_Blue                    CODE  33   VALUE #0020A0   EDGE #000B38   ALPHA 128"),
1✔
902
            Ok((
1✔
903
                &b""[..],
1✔
904
                Command::Colour(ColourCmd {
1✔
905
                    name: "Trans_Dark_Blue".to_string(),
1✔
906
                    code: 33,
907
                    value: Color::new(0x00, 0x20, 0xA0),
2✔
908
                    edge: Color::new(0x00, 0x0B, 0x38),
1✔
909
                    alpha: Some(128),
1✔
910
                    luminance: None,
1✔
911
                    finish: None
1✔
912
                })
913
            ))
914
        );
915
        assert_eq!(
1✔
916
            meta_colour(b"!COLOUR Chrome_Antique_Brass               CODE  60   VALUE #645A4C   EDGE #665B4D                               CHROME"),
1✔
917
            Ok((
1✔
918
                &b""[..],
1✔
919
                Command::Colour(ColourCmd {
1✔
920
                    name: "Chrome_Antique_Brass".to_string(),
1✔
921
                    code: 60,
922
                    value: Color::new(0x64, 0x5A, 0x4C),
2✔
923
                    edge: Color::new(0x66, 0x5B, 0x4D),
1✔
924
                    alpha: None,
1✔
925
                    luminance: None,
1✔
926
                    finish: Some(ColorFinish::Chrome)
1✔
927
                })
928
            ))
929
        );
930
        assert_eq!(
1✔
931
            meta_colour(b"!COLOUR Pearl_Gold                         CODE 297   VALUE #AA7F2E   EDGE #805F23                               PEARLESCENT"),
1✔
932
            Ok((
1✔
933
                &b""[..],
1✔
934
                Command::Colour(ColourCmd {
1✔
935
                    name: "Pearl_Gold".to_string(),
1✔
936
                    code: 297,
937
                    value: Color::new(0xAA, 0x7F, 0x2E),
2✔
938
                    edge: Color::new(0x80, 0x5F, 0x23),
1✔
939
                    alpha: None,
1✔
940
                    luminance: None,
1✔
941
                    finish: Some(ColorFinish::Pearlescent)
1✔
942
                })
943
            ))
944
        );
945
        assert_eq!(
1✔
946
            meta_colour(b"!COLOUR Metallic_Silver                    CODE  80   VALUE #767676   EDGE #8E8E8E                               METAL"),
1✔
947
            Ok((
1✔
948
                &b""[..],
1✔
949
                Command::Colour(ColourCmd {
1✔
950
                    name: "Metallic_Silver".to_string(),
1✔
951
                    code: 80,
952
                    value: Color::new(0x76, 0x76, 0x76),
2✔
953
                    edge: Color::new(0x8E, 0x8E, 0x8E),
1✔
954
                    alpha: None,
1✔
955
                    luminance: None,
1✔
956
                    finish: Some(ColorFinish::Metal)
1✔
957
                })
958
            ))
959
        );
960
        assert_eq!(
1✔
961
            meta_colour(b"!COLOUR Glow_In_Dark_White                 CODE 329   VALUE #F5F3D7   EDGE #E0DA85   ALPHA 240   LUMINANCE 15"),
1✔
962
            Ok((
1✔
963
                &b""[..],
1✔
964
                Command::Colour(ColourCmd {
1✔
965
                    name: "Glow_In_Dark_White".to_string(),
1✔
966
                    code: 329,
967
                    value: Color::new(0xF5, 0xF3, 0xD7),
2✔
968
                    edge: Color::new(0xE0, 0xDA, 0x85),
1✔
969
                    alpha: Some(240),
1✔
970
                    luminance: Some(15),
1✔
971
                    finish: None
1✔
972
                })
973
            ))
974
        );
975
        assert_eq!(
1✔
976
            meta_colour(b"!COLOUR Opal_Trans_Dark_Blue               CODE 10366 VALUE #0020A0   EDGE #000B38   ALPHA 200   LUMINANCE  5    MATERIAL GLITTER VALUE #001D38 FRACTION 0.8 VFRACTION 0.6 MINSIZE 0.02 MAXSIZE 0.1"),
1✔
977
            Ok((
1✔
978
                &b""[..],
1✔
979
                Command::Colour(ColourCmd {
1✔
980
                    name: "Opal_Trans_Dark_Blue".to_string(),
1✔
981
                    code: 10366,
982
                    value: Color::new(0x00, 0x20, 0xA0),
2✔
983
                    edge: Color::new(0x00, 0x0B, 0x38),
1✔
984
                    alpha: Some(200),
1✔
985
                    luminance: Some(5),
1✔
986
                    finish: Some(ColorFinish::Material(MaterialFinish::Glitter(
1✔
987
                        GlitterMaterial {
1✔
988
                            value: Color::new(0x00, 0x1D, 0x38),
1✔
989
                            alpha: None,
1✔
990
                            luminance: None,
1✔
991
                            surface_fraction: 0.8,
992
                            volume_fraction: 0.6,
993
                            size: GrainSize::MinMaxSize((0.02, 0.1)),
1✔
994
                        },
995
                    ))),
996
                })
997
            ))
998
        );
999
        assert_eq!(
1✔
1000
            meta_colour(b"!COLOUR Speckle_Black_Silver               CODE 132   VALUE #000000   EDGE #898788                               MATERIAL SPECKLE VALUE #898788 FRACTION 0.4 MINSIZE 1 MAXSIZE 3"),
1✔
1001
            Ok((
1✔
1002
                &b""[..],
1✔
1003
                Command::Colour(ColourCmd {
1✔
1004
                    name: "Speckle_Black_Silver".to_string(),
1✔
1005
                    code: 132,
1006
                    value: Color::new(0x00, 0x00, 0x00),
2✔
1007
                    edge: Color::new(0x89, 0x87, 0x88),
1✔
1008
                    alpha: None,
1✔
1009
                    luminance: None,
1✔
1010
                    finish: Some(ColorFinish::Material(MaterialFinish::Speckle(
1✔
1011
                        SpeckleMaterial {
1✔
1012
                            value: Color::new(0x89, 0x87, 0x88),
1✔
1013
                            alpha: None,
1✔
1014
                            luminance: None,
1✔
1015
                            surface_fraction: 0.4,
1016
                            size: GrainSize::MinMaxSize((1.0, 3.0)),
1✔
1017
                        },
1018
                    ))),
1019
                })
1020
            ))
1021
        );
1022
        assert_eq!(
1✔
1023
            meta_colour(b"!COLOUR Rubber_Yellow                      CODE  65   VALUE #FAC80A   EDGE #9A7C03                               RUBBER"),
1✔
1024
            Ok((
1✔
1025
                &b""[..],
1✔
1026
                Command::Colour(ColourCmd {
1✔
1027
                    name: "Rubber_Yellow".to_string(),
1✔
1028
                    code: 65,
1029
                    value: Color::new(0xFA, 0xC8, 0x0A),
2✔
1030
                    edge: Color::new(0x9A, 0x7C, 0x03),
1✔
1031
                    alpha: None,
1✔
1032
                    luminance: None,
1✔
1033
                    finish: Some(ColorFinish::Rubber),
1✔
1034
                })
1035
            ))
1036
        );
1037
    }
1038

1039
    #[test]
1040
    fn test_vec3() {
3✔
1041
        assert_eq!(
1✔
1042
            read_vec3(b"0 0 0"),
1✔
1043
            Ok((&b""[..], Vec3::new(0.0, 0.0, 0.0)))
2✔
1044
        );
1045
        assert_eq!(
1✔
1046
            read_vec3(b"0 0 0 1"),
1✔
1047
            Ok((&b" 1"[..], Vec3::new(0.0, 0.0, 0.0)))
2✔
1048
        );
1049
        assert_eq!(
1✔
1050
            read_vec3(b"2 5 -7"),
1✔
1051
            Ok((&b""[..], Vec3::new(2.0, 5.0, -7.0)))
2✔
1052
        );
1053
        assert_eq!(
1✔
1054
            read_vec3(b"2.3 5 -7.4"),
1✔
1055
            Ok((&b""[..], Vec3::new(2.3, 5.0, -7.4)))
2✔
1056
        );
1057
    }
1058

1059
    #[test]
1060
    fn test_read_cmd_id_str() {
3✔
1061
        assert_eq!(read_cmd_id_str(b"0"), Ok((&b""[..], &b"0"[..])));
1✔
1062
        assert_eq!(read_cmd_id_str(b"0 "), Ok((&b""[..], &b"0"[..])));
1✔
1063
        assert_eq!(read_cmd_id_str(b"0   "), Ok((&b""[..], &b"0"[..])));
1✔
1064
        assert_eq!(read_cmd_id_str(b"0   e"), Ok((&b"e"[..], &b"0"[..])));
1✔
1065
        assert_eq!(
1✔
1066
            read_cmd_id_str(b"4547    ssd"),
1✔
1067
            Ok((&b"ssd"[..], &b"4547"[..]))
1✔
1068
        );
1069
    }
1070

1071
    #[test]
1072
    fn test_end_of_line() {
3✔
1073
        assert_eq!(end_of_line(b""), Ok((&b""[..], &b""[..])));
1✔
1074
        assert_eq!(end_of_line(b"\n"), Ok((&b""[..], &b"\n"[..])));
1✔
1075
        assert_eq!(end_of_line(b"\r\n"), Ok((&b""[..], &b"\r\n"[..])));
1✔
1076
    }
1077

1078
    #[test]
1079
    fn test_take_not_cr_or_lf() {
3✔
1080
        assert_eq!(take_not_cr_or_lf(b""), Ok((&b""[..], &b""[..])));
1✔
1081
        assert_eq!(take_not_cr_or_lf(b"\n"), Ok((&b"\n"[..], &b""[..])));
1✔
1082
        assert_eq!(take_not_cr_or_lf(b"\r\n"), Ok((&b"\r\n"[..], &b""[..])));
1✔
1083
        assert_eq!(take_not_cr_or_lf(b"\n\n\n"), Ok((&b"\n\n\n"[..], &b""[..])));
1✔
1084
        assert_eq!(
1✔
1085
            take_not_cr_or_lf(b"\r\n\r\n\r\n"),
1✔
1086
            Ok((&b"\r\n\r\n\r\n"[..], &b""[..]))
1✔
1087
        );
1088
        assert_eq!(take_not_cr_or_lf(b" a \n"), Ok((&b"\n"[..], &b" a "[..])));
1✔
1089
        assert_eq!(take_not_cr_or_lf(b"test"), Ok((&b""[..], &b"test"[..])));
1✔
1090
    }
1091

1092
    #[test]
1093
    fn test_single_comma() {
3✔
1094
        assert_eq!(single_comma(b""), Err(nom_error(&b""[..], ErrorKind::Tag)));
1✔
1095
        assert_eq!(single_comma(b","), Ok((&b""[..], &b","[..])));
1✔
1096
        assert_eq!(single_comma(b",s"), Ok((&b"s"[..], &b","[..])));
1✔
1097
        assert_eq!(
1✔
1098
            single_comma(b"w,s"),
1✔
1099
            Err(nom_error(&b"w,s"[..], ErrorKind::Tag))
1✔
1100
        );
1101
    }
1102

1103
    #[test]
1104
    fn test_keywords_list() {
3✔
1105
        assert_eq!(
2✔
1106
            keywords_list(b""),
1✔
1107
            Err(nom_error(&b""[..], ErrorKind::TakeWhile1))
2✔
1108
        );
1109
        assert_eq!(keywords_list(b"a"), Ok((&b""[..], vec!["a"])));
1✔
1110
        assert_eq!(keywords_list(b"a,b,c"), Ok((&b""[..], vec!["a", "b", "c"])));
1✔
1111
    }
1112

1113
    #[test]
1114
    fn test_filename() {
3✔
1115
        assert_eq!(filename(b"asd\\kw/l.ldr"), Ok((&b""[..], "asd\\kw/l.ldr")));
1✔
1116
        assert_eq!(filename(b"asdkwl.ldr"), Ok((&b""[..], "asdkwl.ldr")));
1✔
1117
        assert_eq!(
1✔
1118
            filename(b"asd\\kw/l.ldr\n"),
1✔
1119
            Ok((&b"\n"[..], "asd\\kw/l.ldr"))
1✔
1120
        );
1121
        assert_eq!(filename(b"asdkwl.ldr\n"), Ok((&b"\n"[..], "asdkwl.ldr")));
1✔
1122
        assert_eq!(
1✔
1123
            filename(b"asd\\kw/l.ldr\r\n"),
1✔
1124
            Ok((&b"\r\n"[..], "asd\\kw/l.ldr"))
1✔
1125
        );
1126
        assert_eq!(
1✔
1127
            filename(b"asdkwl.ldr\r\n"),
1✔
1128
            Ok((&b"\r\n"[..], "asdkwl.ldr"))
1✔
1129
        );
1130
        assert_eq!(
1✔
1131
            filename(b"  asdkwl.ldr   \r\n"),
1✔
1132
            Ok((&b"\r\n"[..], "asdkwl.ldr"))
1✔
1133
        );
1134
    }
1135

1136
    #[test]
1137
    fn test_category_cmd() {
3✔
1138
        let res = Command::Category(CategoryCmd {
2✔
1139
            category: "Figure Accessory".to_string(),
1✔
1140
        });
1141
        assert_eq!(category(b"!CATEGORY Figure Accessory"), Ok((&b""[..], res)));
3✔
1142
    }
1143

1144
    #[test]
1145
    fn test_keywords_cmd() {
3✔
1146
        let res = Command::Keywords(KeywordsCmd {
2✔
1147
            keywords: vec![
1✔
1148
                "western".to_string(),
1✔
1149
                "wild west".to_string(),
1✔
1150
                "spaghetti western".to_string(),
1✔
1151
                "horse opera".to_string(),
1✔
1152
                "cowboy".to_string(),
1✔
1153
            ],
1154
        });
1155
        assert_eq!(
1✔
1156
            keywords(b"!KEYWORDS western, wild west, spaghetti western, horse opera, cowboy"),
1✔
1157
            Ok((&b""[..], res))
2✔
1158
        );
1159
    }
1160

1161
    #[test]
1162
    fn test_comment_cmd() {
3✔
1163
        let comment = b"test of comment, with \"weird\" characters";
1✔
1164
        let res = Command::Comment(CommentCmd::new(std::str::from_utf8(comment).unwrap()));
1✔
1165
        assert_eq!(meta_cmd(comment), Ok((&b""[..], res)));
3✔
1166
        // Match empty comment too (e.g. "0" line without anything else, or "0   " with only spaces)
1167
        assert_eq!(
1✔
1168
            meta_cmd(b""),
1✔
1169
            Ok((&b""[..], Command::Comment(CommentCmd::new(""))))
2✔
1170
        );
1171
    }
1172

1173
    #[test]
1174
    fn test_file_ref_cmd() {
3✔
1175
        let res = Command::SubFileRef(SubFileRefCmd {
2✔
1176
            color: 16,
1177
            pos: Vec3::new(0.0, 0.0, 0.0),
1178
            row0: Vec3::new(1.0, 0.0, 0.0),
1179
            row1: Vec3::new(0.0, 1.0, 0.0),
1180
            row2: Vec3::new(0.0, 0.0, 1.0),
1181
            file: "aaaaaaddd".to_string(),
1✔
1182
        });
1183
        assert_eq!(
2✔
1184
            file_ref_cmd(b"16 0 0 0 1 0 0 0 1 0 0 0 1 aaaaaaddd"),
1✔
1185
            Ok((&b""[..], res))
2✔
1186
        );
1187
    }
1188

1189
    #[test]
1190
    fn test_space0() {
3✔
1191
        assert_eq!(space0(b""), Ok((&b""[..], &b""[..])));
1✔
1192
        assert_eq!(space0(b" "), Ok((&b""[..], &b" "[..])));
1✔
1193
        assert_eq!(space0(b"   "), Ok((&b""[..], &b"   "[..])));
1✔
1194
        assert_eq!(space0(b"  a"), Ok((&b"a"[..], &b"  "[..])));
1✔
1195
        assert_eq!(space0(b"a  "), Ok((&b"a  "[..], &b""[..])));
1✔
1196
    }
1197

1198
    #[test]
1199
    fn test_space_or_eol0() {
3✔
1200
        assert_eq!(space_or_eol0(b""), Ok((&b""[..], &b""[..])));
1✔
1201
        assert_eq!(space_or_eol0(b" "), Ok((&b""[..], &b" "[..])));
1✔
1202
        assert_eq!(space_or_eol0(b"   "), Ok((&b""[..], &b"   "[..])));
1✔
1203
        assert_eq!(space_or_eol0(b"  a"), Ok((&b"a"[..], &b"  "[..])));
1✔
1204
        assert_eq!(space_or_eol0(b"a  "), Ok((&b"a  "[..], &b""[..])));
1✔
1205
        assert_eq!(space_or_eol0(b"\n"), Ok((&b""[..], &b"\n"[..])));
1✔
1206
        assert_eq!(space_or_eol0(b"\n\n\n"), Ok((&b""[..], &b"\n\n\n"[..])));
1✔
1207
        assert_eq!(space_or_eol0(b"\n\r\n"), Ok((&b""[..], &b"\n\r\n"[..])));
1✔
1208
        // Unfortunately <LF> alone is not handled well, but we assume this needs to be ignored too
1209
        assert_eq!(
1✔
1210
            space_or_eol0(b"\n\r\r\r\n"),
1✔
1211
            Ok((&b""[..], &b"\n\r\r\r\n"[..]))
1✔
1212
        );
1213
        assert_eq!(space_or_eol0(b"  \n"), Ok((&b""[..], &b"  \n"[..])));
1✔
1214
        assert_eq!(space_or_eol0(b"  \n   "), Ok((&b""[..], &b"  \n   "[..])));
1✔
1215
        assert_eq!(
1✔
1216
            space_or_eol0(b"  \n   \r\n"),
1✔
1217
            Ok((&b""[..], &b"  \n   \r\n"[..]))
1✔
1218
        );
1219
        assert_eq!(
1✔
1220
            space_or_eol0(b"  \n   \r\n "),
1✔
1221
            Ok((&b""[..], &b"  \n   \r\n "[..]))
1✔
1222
        );
1223
        assert_eq!(space_or_eol0(b"  \nsa"), Ok((&b"sa"[..], &b"  \n"[..])));
1✔
1224
        assert_eq!(
1✔
1225
            space_or_eol0(b"  \n  \r\nsa"),
1✔
1226
            Ok((&b"sa"[..], &b"  \n  \r\n"[..]))
1✔
1227
        );
1228
    }
1229

1230
    #[test]
1231
    fn test_empty_line() {
3✔
1232
        assert_eq!(empty_line(b""), Ok((&b""[..], &b""[..])));
1✔
1233
        assert_eq!(empty_line(b" "), Ok((&b""[..], &b" "[..])));
1✔
1234
        assert_eq!(empty_line(b"   "), Ok((&b""[..], &b"   "[..])));
1✔
1235
        assert_eq!(
1✔
1236
            empty_line(b"  a"),
1✔
1237
            Err(nom_error(&b"a"[..], ErrorKind::CrLf))
1✔
1238
        );
1239
        assert_eq!(
1✔
1240
            empty_line(b"a  "),
1✔
1241
            Err(nom_error(&b"a  "[..], ErrorKind::CrLf))
1✔
1242
        );
1243
    }
1244

1245
    #[test]
1246
    fn test_read_cmd() {
3✔
1247
        let res = Command::Comment(CommentCmd::new("this doesn't matter"));
1✔
1248
        assert_eq!(read_line(b"0 this doesn't matter"), Ok((&b""[..], res)));
3✔
1249
    }
1250

1251
    #[test]
1252
    fn test_read_line_cmd() {
3✔
1253
        let res = Command::Line(LineCmd {
2✔
1254
            color: 16,
1255
            vertices: [Vec3::new(1.0, 1.0, 0.0), Vec3::new(0.9239, 1.0, 0.3827)],
1✔
1256
        });
1257
        assert_eq!(
2✔
1258
            read_line(b"2 16 1 1 0 0.9239 1 0.3827"),
1✔
1259
            Ok((&b""[..], res))
2✔
1260
        );
1261
    }
1262

1263
    #[test]
1264
    fn test_read_tri_cmd() {
3✔
1265
        let res = Command::Triangle(TriangleCmd {
2✔
1266
            color: 16,
1267
            vertices: [
1✔
1268
                Vec3::new(1.0, 1.0, 0.0),
1269
                Vec3::new(0.9239, 1.0, 0.3827),
1270
                Vec3::new(0.9239, 0.0, 0.3827),
1271
            ],
1272
        });
1273
        assert_eq!(
2✔
1274
            read_line(b"3 16 1 1 0 0.9239 1 0.3827 0.9239 0 0.3827"),
1✔
1275
            Ok((&b""[..], res))
2✔
1276
        );
1277
        let res = Command::Triangle(TriangleCmd {
1✔
1278
            color: 16,
1279
            vertices: [
1✔
1280
                Vec3::new(1.0, 1.0, 0.0),
1281
                Vec3::new(0.9239, 1.0, 0.3827),
1282
                Vec3::new(0.9239, 0.0, 0.3827),
1283
            ],
1284
        });
1285
        assert_eq!(
1✔
1286
            // Note: extra spaces at end
1287
            read_line(b"3 16 1 1 0 0.9239 1 0.3827 0.9239 0 0.3827  "),
1✔
1288
            Ok((&b""[..], res))
2✔
1289
        );
1290
    }
1291

1292
    #[test]
1293
    fn test_read_quad_cmd() {
3✔
1294
        let res = Command::Quad(QuadCmd {
2✔
1295
            color: 16,
1296
            vertices: [
1✔
1297
                Vec3::new(1.0, 1.0, 0.0),
1298
                Vec3::new(0.9239, 1.0, 0.3827),
1299
                Vec3::new(0.9239, 0.0, 0.3827),
1300
                Vec3::new(1.0, 0.0, 0.0),
1301
            ],
1302
        });
1303
        assert_eq!(
2✔
1304
            read_line(b"4 16 1 1 0 0.9239 1 0.3827 0.9239 0 0.3827 1 0 0"),
1✔
1305
            Ok((&b""[..], res))
2✔
1306
        );
1307
    }
1308

1309
    #[test]
1310
    fn test_read_opt_line_cmd() {
3✔
1311
        let res = Command::OptLine(OptLineCmd {
2✔
1312
            color: 16,
1313
            vertices: [Vec3::new(1.0, 1.0, 0.0), Vec3::new(0.9239, 1.0, 0.3827)],
1✔
1314
            control_points: [Vec3::new(0.9239, 0.0, 0.3827), Vec3::new(1.0, 0.0, 0.0)],
1✔
1315
        });
1316
        assert_eq!(
2✔
1317
            read_line(b"5 16 1 1 0 0.9239 1 0.3827 0.9239 0 0.3827 1 0 0"),
1✔
1318
            Ok((&b""[..], res))
2✔
1319
        );
1320
    }
1321

1322
    #[test]
1323
    fn test_read_line_subfileref() {
3✔
1324
        let res = Command::SubFileRef(SubFileRefCmd {
2✔
1325
            color: 16,
1326
            pos: Vec3::new(0.0, 0.0, 0.0),
1327
            row0: Vec3::new(1.0, 0.0, 0.0),
1328
            row1: Vec3::new(0.0, 1.0, 0.0),
1329
            row2: Vec3::new(0.0, 0.0, 1.0),
1330
            file: "aa/aaaaddd".to_string(),
1✔
1331
        });
1332
        assert_eq!(
2✔
1333
            read_line(b"1 16 0 0 0 1 0 0 0 1 0 0 0 1 aa/aaaaddd"),
1✔
1334
            Ok((&b""[..], res))
2✔
1335
        );
1336
    }
1337

1338
    #[test]
1339
    fn test_meta_data() {
3✔
1340
        let res = Command::Data(DataCmd {
2✔
1341
            file: "data.bin".to_string(),
1✔
1342
        });
1343
        assert_eq!(read_line(b"0 !DATA data.bin"), Ok((&b""[..], res)));
3✔
1344
    }
1345

1346
    #[test]
1347
    fn test_base64_data() {
3✔
1348
        let res = Command::Base64Data(Base64DataCmd {
2✔
1349
            data: b"Hello World!".to_vec(),
1✔
1350
        });
1351
        assert_eq!(read_line(b"0 !: SGVsbG8gV29ybGQh"), Ok((&b""[..], res)));
3✔
1352
    }
1353

1354
    #[test]
1355
    fn test_file_cmd() {
3✔
1356
        let res = Command::File(FileCmd {
2✔
1357
            file: "submodel".to_string(),
1✔
1358
        });
1359
        assert_eq!(meta_cmd(b"FILE submodel"), Ok((&b""[..], res)));
3✔
1360
    }
1361

1362
    #[test]
1363
    fn test_nofile_cmd() {
3✔
1364
        let res = Command::NoFile;
1✔
1365
        assert_eq!(meta_cmd(b"NOFILE"), Ok((&b""[..], res)));
3✔
1366
    }
1367
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc