Skip to content

Commit afe45eb

Browse files
authored
Implement !CATEGORY and !KEYWORDS metacmd (#5)
- Remove the generic `Cmd`, and replace with `CommentCmd` for all comments and all unrecognized meta-commands - Parse `!CATEGORY` meta-command into `CategoryCmd` - Parse `!KEYWORDS` meta-command into `KeywordsCmd`
1 parent f2a304c commit afe45eb

File tree

1 file changed

+115
-19
lines changed

1 file changed

+115
-19
lines changed

src/lib.rs

Lines changed: 115 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -131,19 +131,67 @@ fn take_not_cr_or_lf(input: &[u8]) -> IResult<&[u8], &[u8]> {
131131
input.split_at_position_complete(|item| is_cr_or_lf(item))
132132
}
133133

134+
// Parse a single comma ',' character.
135+
fn single_comma(input: &[u8]) -> IResult<&[u8], &[u8]> {
136+
if !input.is_empty() && (input[0] == b',') {
137+
Ok((&input[1..], &input[..1]))
138+
} else {
139+
// To work with separated_list!(), must return an Err::Error
140+
// when the separator doesn't parse anymore (and therefore
141+
// the list ends).
142+
Err(nom::Err::Error((input, nom::error::ErrorKind::Tag)))
143+
}
144+
}
145+
146+
// Parse any character which is not a comma ',' or <CR> or <LF>, potentially until the end of input.
147+
fn take_not_comma_or_eol(input: &[u8]) -> IResult<&[u8], &[u8]> {
148+
input.split_at_position_complete(|item| item == b',' || is_cr_or_lf(item))
149+
}
150+
134151
// Read the command ID and swallow the following space
135152
fn read_cmd_id_str(input: &[u8]) -> IResult<&[u8], &[u8]> {
136153
terminated(take_while1(is_digit), sp)(input)
137154
}
138155

139-
named!(meta_cmd<CommandType>,
156+
named!(category<CommandType>,
157+
do_parse!(
158+
tag!(b"!CATEGORY") >>
159+
sp >>
160+
content: take_not_cr_or_lf >> (
161+
CommandType::Category(CategoryCmd{ category: std::str::from_utf8(content).unwrap().to_string() })
162+
)
163+
)
164+
);
165+
166+
named!(keywords_list<Vec<&[u8]>>,
167+
separated_nonempty_list!(
168+
single_comma,
169+
take_not_comma_or_eol
170+
)
171+
);
172+
173+
named!(keywords<CommandType>,
174+
do_parse!(
175+
tag!(b"!KEYWORDS") >>
176+
sp >>
177+
keywords: keywords_list >> (
178+
CommandType::Keywords(KeywordsCmd{ keywords: keywords.iter().map(|&kw| std::str::from_utf8(kw).unwrap().trim().to_string()).collect() })
179+
)
180+
)
181+
);
182+
183+
named!(comment<CommandType>,
140184
do_parse!(
141185
content: take_not_cr_or_lf >> (
142-
CommandType::Command(Cmd{ content: std::str::from_utf8(content).unwrap().to_string() })
186+
CommandType::Comment(CommentCmd{ text: std::str::from_utf8(content).unwrap().to_string() })
143187
)
144188
)
145189
);
146190

191+
named!(meta_cmd<CommandType>,
192+
alt!(category | keywords | comment)
193+
);
194+
147195
named!(pub sp<char>, char!(' '));
148196

149197
named!(read_vec3<Vec3>,
@@ -489,19 +537,26 @@ pub fn parse(filename: &str, resolver: &dyn FileRefResolver, source_map: &mut Ha
489537
root_file
490538
}
491539

492-
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) comment or META command,
493-
/// when not otherwise interpreted as a meta-command.
540+
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) META command:
541+
/// [!CATEGORY language extension](https://www.ldraw.org/article/340.html#category).
494542
#[derive(Debug, PartialEq)]
495-
pub struct Cmd {
496-
/// Verbatim content of the command, excluding the initial `0` identifier.
497-
content: String
543+
pub struct CategoryCmd {
544+
/// Category name.
545+
category: String
546+
}
547+
548+
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) META command:
549+
/// [!KEYWORDS language extension](https://www.ldraw.org/article/340.html#keywords).
550+
#[derive(Debug, PartialEq)]
551+
pub struct KeywordsCmd {
552+
/// List of keywords.
553+
keywords: Vec<String>
498554
}
499555

500556
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) comment.
501557
#[derive(Debug, PartialEq)]
502-
pub struct Comment {
503-
/// Comment content, excluding the command identififer `0` and the optional comment
504-
/// marker `//`.
558+
pub struct CommentCmd {
559+
/// Comment content, excluding the command identififer `0` and the optional comment marker `//`.
505560
text: String
506561
}
507562

@@ -593,8 +648,15 @@ pub struct OptLineCmd {
593648
/// Types of commands contained in a LDraw file.
594649
#[derive(Debug, PartialEq)]
595650
pub enum CommandType {
596-
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) comment or META command.
597-
Command(Cmd),
651+
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) META command:
652+
/// [!CATEGORY language extension](https://www.ldraw.org/article/340.html#category).
653+
Category(CategoryCmd),
654+
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) META command:
655+
/// [!KEYWORDS language extension](https://www.ldraw.org/article/340.html#keywords).
656+
Keywords(KeywordsCmd),
657+
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) comment.
658+
/// Note: any line type 0 not otherwise parsed as a known meta-command is parsed as a generic comment.
659+
Comment(CommentCmd),
598660
/// [Line Type 1](https://www.ldraw.org/article/218.html#lt1) sub-file reference.
599661
SubFileRef(SubFileRefCmd),
600662
/// [Line Type 2](https://www.ldraw.org/article/218.html#lt2) segment.
@@ -663,7 +725,22 @@ mod tests {
663725
}
664726

665727
use nom::{Err, Needed};
666-
use nom::error::ErrorKind::AlphaNumeric;
728+
use nom::error::ErrorKind;
729+
730+
#[test]
731+
fn test_single_comma() {
732+
assert_eq!(single_comma(b""), Err(Err::Error((&b""[..], ErrorKind::Tag))));
733+
assert_eq!(single_comma(b","), Ok((&b""[..], &b","[..])));
734+
assert_eq!(single_comma(b",s"), Ok((&b"s"[..], &b","[..])));
735+
assert_eq!(single_comma(b"w,s"), Err(Err::Error((&b"w,s"[..], ErrorKind::Tag))));
736+
}
737+
738+
#[test]
739+
fn test_keywords_list() {
740+
assert_eq!(keywords_list(b""), Err(Err::Error((&b""[..], ErrorKind::SeparatedList))));
741+
assert_eq!(keywords_list(b"a"), Ok((&b""[..], vec![&b"a"[..]])));
742+
assert_eq!(keywords_list(b"a,b,c"), Ok((&b""[..], vec![&b"a"[..], &b"b"[..], &b"c"[..]])));
743+
}
667744

668745
#[test]
669746
fn test_filename_char() {
@@ -684,9 +761,28 @@ mod tests {
684761
}
685762

686763
#[test]
687-
fn test_meta_cmd() {
688-
let res = CommandType::Command(Cmd{ content: "test of metacommand".to_string() });
689-
assert_eq!(meta_cmd(b"test of metacommand"), Ok((&b""[..], res)));
764+
fn test_category_cmd() {
765+
let res = CommandType::Category(CategoryCmd{ category: "Figure Accessory".to_string() });
766+
assert_eq!(category(b"!CATEGORY Figure Accessory"), Ok((&b""[..], res)));
767+
}
768+
769+
#[test]
770+
fn test_keywords_cmd() {
771+
let res = CommandType::Keywords(KeywordsCmd{ keywords: vec![
772+
"western".to_string(),
773+
"wild west".to_string(),
774+
"spaghetti western".to_string(),
775+
"horse opera".to_string(),
776+
"cowboy".to_string()
777+
] });
778+
assert_eq!(keywords(b"!KEYWORDS western, wild west, spaghetti western, horse opera, cowboy"), Ok((&b""[..], res)));
779+
}
780+
781+
#[test]
782+
fn test_comment_cmd() {
783+
let comment = b"test of comment, with \"weird\" characters";
784+
let res = CommandType::Comment(CommentCmd{ text: std::str::from_utf8(comment).unwrap().to_string() });
785+
assert_eq!(meta_cmd(comment), Ok((&b""[..], res)));
690786
}
691787

692788
#[test]
@@ -742,7 +838,7 @@ mod tests {
742838

743839
#[test]
744840
fn test_read_cmd() {
745-
let res = CommandType::Command(Cmd{ content: "this doesn't matter".to_string() });
841+
let res = CommandType::Comment(CommentCmd{ text: "this doesn't matter".to_string() });
746842
assert_eq!(read_line(b"0 this doesn't matter"), Ok((&b""[..], res)));
747843
}
748844

@@ -803,7 +899,7 @@ mod tests {
803899

804900
#[test]
805901
fn test_read_lines() {
806-
let cmd0 = CommandType::Command(Cmd{ content: "this doesn't matter".to_string() });
902+
let cmd0 = CommandType::Comment(CommentCmd{ text: "this doesn't matter".to_string() });
807903
let cmd1 = CommandType::SubFileRef(SubFileRefCmd{
808904
color: 16,
809905
pos: Vec3{ x: 0.0, y: 0.0, z: 0.0 },
@@ -814,7 +910,7 @@ mod tests {
814910
});
815911
assert_eq!(read_lines(b"\n0 this doesn't matter\n\n1 16 0 0 0 1 0 0 0 1 0 0 0 1 aa/aaaaddd"), Ok((&b""[..], vec![cmd0, cmd1])));
816912

817-
let cmd0 = CommandType::Command(Cmd{ content: "this doesn't \"matter\"".to_string() });
913+
let cmd0 = CommandType::Comment(CommentCmd{ text: "this doesn't \"matter\"".to_string() });
818914
let cmd1 = CommandType::SubFileRef(SubFileRefCmd{
819915
color: 16,
820916
pos: Vec3{ x: 0.0, y: 0.0, z: 0.0 },

0 commit comments

Comments
 (0)