@@ -131,19 +131,67 @@ fn take_not_cr_or_lf(input: &[u8]) -> IResult<&[u8], &[u8]> {
131
131
input. split_at_position_complete ( |item| is_cr_or_lf ( item) )
132
132
}
133
133
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
+
134
151
// Read the command ID and swallow the following space
135
152
fn read_cmd_id_str ( input : & [ u8 ] ) -> IResult < & [ u8 ] , & [ u8 ] > {
136
153
terminated ( take_while1 ( is_digit) , sp) ( input)
137
154
}
138
155
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 >,
140
184
do_parse!(
141
185
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( ) } )
143
187
)
144
188
)
145
189
) ;
146
190
191
+ named ! ( meta_cmd<CommandType >,
192
+ alt!( category | keywords | comment)
193
+ ) ;
194
+
147
195
named ! ( pub sp<char >, char !( ' ' ) ) ;
148
196
149
197
named ! ( read_vec3<Vec3 >,
@@ -489,19 +537,26 @@ pub fn parse(filename: &str, resolver: &dyn FileRefResolver, source_map: &mut Ha
489
537
root_file
490
538
}
491
539
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) .
494
542
#[ 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 >
498
554
}
499
555
500
556
/// [Line Type 0](https://www.ldraw.org/article/218.html#lt0) comment.
501
557
#[ 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 `//`.
505
560
text : String
506
561
}
507
562
@@ -593,8 +648,15 @@ pub struct OptLineCmd {
593
648
/// Types of commands contained in a LDraw file.
594
649
#[ derive( Debug , PartialEq ) ]
595
650
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 ) ,
598
660
/// [Line Type 1](https://www.ldraw.org/article/218.html#lt1) sub-file reference.
599
661
SubFileRef ( SubFileRefCmd ) ,
600
662
/// [Line Type 2](https://www.ldraw.org/article/218.html#lt2) segment.
@@ -663,7 +725,22 @@ mod tests {
663
725
}
664
726
665
727
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
+ }
667
744
668
745
#[ test]
669
746
fn test_filename_char ( ) {
@@ -684,9 +761,28 @@ mod tests {
684
761
}
685
762
686
763
#[ 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) ) ) ;
690
786
}
691
787
692
788
#[ test]
@@ -742,7 +838,7 @@ mod tests {
742
838
743
839
#[ test]
744
840
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 ( ) } ) ;
746
842
assert_eq ! ( read_line( b"0 this doesn't matter" ) , Ok ( ( & b"" [ ..] , res) ) ) ;
747
843
}
748
844
@@ -803,7 +899,7 @@ mod tests {
803
899
804
900
#[ test]
805
901
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 ( ) } ) ;
807
903
let cmd1 = CommandType :: SubFileRef ( SubFileRefCmd {
808
904
color : 16 ,
809
905
pos : Vec3 { x : 0.0 , y : 0.0 , z : 0.0 } ,
@@ -814,7 +910,7 @@ mod tests {
814
910
} ) ;
815
911
assert_eq ! ( read_lines( b"\n 0 this doesn't matter\n \n 1 16 0 0 0 1 0 0 0 1 0 0 0 1 aa/aaaaddd" ) , Ok ( ( & b"" [ ..] , vec![ cmd0, cmd1] ) ) ) ;
816
912
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 ( ) } ) ;
818
914
let cmd1 = CommandType :: SubFileRef ( SubFileRefCmd {
819
915
color : 16 ,
820
916
pos : Vec3 { x : 0.0 , y : 0.0 , z : 0.0 } ,
0 commit comments