@@ -107,6 +107,7 @@ impl ParserStateMachine {
107107
108108#[ derive( Debug , Clone ) ]
109109pub struct Article {
110+ pub order : i16 ,
110111 pub topic : String ,
111112 pub content : String ,
112113 pub path : String ,
@@ -150,6 +151,27 @@ enum Keyword {
150151 * ```
151152 */
152153 Article ,
154+ /**
155+ * @Article Syntax
156+ * `@Order <Order of the Article>` is for controlling the order of the article sections.
157+ *
158+ * Example:
159+ *
160+ * ```rust
161+ * /**
162+ * * @Article Usage example
163+ * * @Order 2
164+ * * ## Header2
165+ * */
166+ *
167+ * /**
168+ * * @Article Usage example
169+ * * @Order 1
170+ * * # Header1
171+ * */
172+ * ```
173+ */
174+ Order ,
153175 /**
154176 * @Article Syntax
155177 * `@FileArticle` allows you to mark a whole file is a source of documentation for a specified
@@ -229,6 +251,7 @@ enum Keyword {
229251impl Keyword {
230252 fn as_str ( & self ) -> & ' static str {
231253 match * self {
254+ Keyword :: Order => "@Order" ,
232255 Keyword :: Article => "@Article" ,
233256 Keyword :: FileArticle => "@FileArticle" ,
234257 Keyword :: Ignore => "@Ignore" ,
@@ -262,6 +285,7 @@ impl Parser {
262285
263286 let articles: Vec < Article > = vec ! [ ] ;
264287 let current_article = Article {
288+ order : 0 ,
265289 topic : String :: from ( "" ) ,
266290 content : String :: from ( "" ) ,
267291 path : String :: from ( "" ) ,
@@ -336,6 +360,7 @@ impl Parser {
336360
337361 fn new_article ( & self ) -> Article {
338362 Article {
363+ order : 0 ,
339364 topic : String :: from ( "" ) ,
340365 content : String :: from ( "" ) ,
341366 path : String :: from ( "" ) ,
@@ -350,6 +375,7 @@ impl Parser {
350375 let topic = name_chunks[ 2 ..] . join ( "." ) ;
351376
352377 vec ! [ Article {
378+ order: 0 ,
353379 topic,
354380 content: String :: from( file_content) ,
355381 path: String :: from( file_path) ,
@@ -429,6 +455,17 @@ impl Parser {
429455 self . current_article . topic = self . trim_article_line ( topic) ;
430456 self . current_article . start_line = line_number;
431457 self . state_machine . to_article_mut ( ) ;
458+ } else if trimmed_line. starts_with ( Keyword :: Order . as_str ( ) )
459+ && self . state_machine . is_in ( ParserState :: ArticleParsing )
460+ {
461+ let parsed_order = trimmed_line
462+ . replace ( Keyword :: Order . as_str ( ) , "" )
463+ . trim ( )
464+ . parse ( )
465+ . unwrap_or ( 0 ) ;
466+
467+ self . current_article . order = parsed_order;
468+ self . current_article . start_line = line_number;
432469 } else if trimmed_line. starts_with ( Keyword :: Ignore . as_str ( ) ) {
433470 self . state_machine . to_skippintg_mut ( ) ;
434471 self . current_article = self . new_article ( ) ;
@@ -484,6 +521,7 @@ impl Parser {
484521 line_number += 1 ;
485522 }
486523
524+ self . articles . sort_by_key ( |a| a. order ) ;
487525 self . articles . clone ( )
488526 }
489527
@@ -559,6 +597,7 @@ pub fn test () {}
559597
560598 let articles = parser. parse_file ( file_content, "" ) ;
561599 let expected_result = vec ! [ Article {
600+ order: 0 ,
562601 topic: String :: from( "Test article" ) ,
563602 content: String :: from( "some text" ) ,
564603 path: "" . to_string( ) ,
@@ -569,6 +608,48 @@ pub fn test () {}
569608 assert_eq ! ( articles, expected_result) ;
570609}
571610
611+ #[ test]
612+ fn parse_articles_with_custom_order ( ) {
613+ let mut parser = Parser :: new ( get_test_config ( ) ) ;
614+ let file_content = "
615+ /**
616+ * @Article Test article3
617+ * @Order 3
618+ * some text3
619+ */
620+ pub fn test () {}
621+
622+ /**
623+ * @Article Test article1
624+ * @Order 1
625+ * some text
626+ */
627+ pub fn test2 () {}
628+ " ;
629+
630+ let articles = parser. parse_file ( file_content, "" ) ;
631+ let expected_result = vec ! [
632+ Article {
633+ order: 1 ,
634+ topic: String :: from( "Test article1" ) ,
635+ content: String :: from( "some text" ) ,
636+ path: "" . to_string( ) ,
637+ start_line: 11 ,
638+ end_line: 12 ,
639+ } ,
640+ Article {
641+ order: 3 ,
642+ topic: String :: from( "Test article3" ) ,
643+ content: String :: from( "some text3" ) ,
644+ path: "" . to_string( ) ,
645+ start_line: 4 ,
646+ end_line: 5 ,
647+ } ,
648+ ] ;
649+
650+ assert_eq ! ( articles, expected_result) ;
651+ }
652+
572653#[ test]
573654fn ignore_comments_with_ignore_mark ( ) {
574655 let mut parser = Parser :: new ( get_test_config ( ) ) ;
@@ -604,6 +685,7 @@ pub fn test () {}
604685
605686 let articles = parser. parse_file ( file_content, "" ) ;
606687 let expected_result = vec ! [ Article {
688+ order: 0 ,
607689 topic: String :: from( "Test article" ) ,
608690 content: String :: from( "some multiline\n awesome text" ) ,
609691 path: "" . to_string( ) ,
@@ -650,6 +732,7 @@ pub fn test () {}
650732
651733 let articles = parser. parse_file ( file_content, "" ) ;
652734 let expected_result = vec ! [ Article {
735+ order: 0 ,
653736 topic: String :: from( "Test article" ) ,
654737 content: String :: from( "```rust\n fn main() {\n println!(\" Hello world!\" );\n }\n ```\n \n ```rust\n fn test() {\n println!(\" Hello world!\" );\n }\n ```" ) ,
655738 path: "" . to_string( ) ,
@@ -684,6 +767,7 @@ fn parse_documentation_with_indentation_before_comments() {
684767
685768 let articles = parser. parse_file ( file_content, "" ) ;
686769 let expected_result = vec ! [ Article {
770+ order: 0 ,
687771 topic: String :: from( "Test article" ) ,
688772 content: String :: from( "#### [no-implicit-coercion](https://eslint.org/docs/rules/no-implicit-coercion)\n All implicit coercions except `!!` are disallowed:\n ```js\n // Fail\n +foo\n 1 * foo\n \' \' + foo\n `${foo}`\n ~foo.indexOf(bar)\n \n // Pass\n !!foo\n ```" ) ,
689773 path: "" . to_string( ) ,
@@ -714,6 +798,7 @@ pub fn test () {}
714798
715799 let articles = parser. parse_file ( file_content, "" ) ;
716800 let expected_result = vec ! [ Article {
801+ order: 0 ,
717802 topic: String :: from( "Test article" ) ,
718803 content: String :: from( "List:\n * Item 1\n * Item 2\n \n Item 2 subtext\n * Item 3" ) ,
719804 path: "" . to_string( ) ,
@@ -738,6 +823,7 @@ use std::io::prelude::*;
738823
739824 let articles = parser. parse_file ( file_content, "" ) ;
740825 let expected_result = vec ! [ Article {
826+ order: 0 ,
741827 topic: String :: from( "Test article" ) ,
742828 content: String :: from( "" ) ,
743829 path: "" . to_string( ) ,
760846
761847 let articles = parser. parse_file ( file_content, "" ) ;
762848 let expected_result = vec ! [ Article {
849+ order: 0 ,
763850 topic: String :: from( "Test article" ) ,
764851 content: String :: from( "test" ) ,
765852 path: "" . to_string( ) ,
@@ -784,6 +871,7 @@ const b = 2
784871
785872 let articles = parser. parse_file ( file_content, "" ) ;
786873 let expected_result = vec ! [ Article {
874+ order: 0 ,
787875 topic: String :: from( "Test article" ) ,
788876 content: String :: from( "test" ) ,
789877 path: "" . to_string( ) ,
@@ -816,13 +904,15 @@ fn use_global_article_attribute() {
816904 let articles = parser. parse_file ( file_content, "" ) ;
817905 let expected_result = vec ! [
818906 Article {
907+ order: 0 ,
819908 topic: String :: from( "Test article" ) ,
820909 content: String :: from( "test" ) ,
821910 path: "" . to_string( ) ,
822911 start_line: 6 ,
823912 end_line: 7 ,
824913 } ,
825914 Article {
915+ order: 0 ,
826916 topic: String :: from( "Test article" ) ,
827917 content: String :: from( "test" ) ,
828918 path: "" . to_string( ) ,
@@ -856,6 +946,7 @@ fn ignore_sections_in_case_of_global_article() {
856946
857947 let articles = parser. parse_file ( file_content, "" ) ;
858948 let expected_result = vec ! [ Article {
949+ order: 0 ,
859950 topic: String :: from( "Test article" ) ,
860951 content: String :: from( "test" ) ,
861952 path: "" . to_string( ) ,
@@ -881,6 +972,7 @@ const TIMEOUT = 3000
881972
882973 let articles = parser. parse_file ( file_content, "" ) ;
883974 let expected_result = vec ! [ Article {
975+ order: 0 ,
884976 topic: String :: from( "Test article" ) ,
885977 content: String :: from( "Request timeout:\n ```js/\n const TIMEOUT = 3000\n ```" ) ,
886978 path: "" . to_string( ) ,
@@ -908,6 +1000,7 @@ const TIMEOUT = 3000
9081000
9091001 let articles = parser. parse_file ( file_content, "" ) ;
9101002 let expected_result = vec ! [ Article {
1003+ order: 0 ,
9111004 topic: String :: from( "Test article" ) ,
9121005 content: String :: from( "Request timeout:\n ```js/\n const TIMEOUT = 3000\n ```" ) ,
9131006 path: "" . to_string( ) ,
@@ -933,6 +1026,7 @@ fn parse_code_block_attribute_from_ending_comment_only() {
9331026
9341027 let articles = parser. parse_file ( file_content, "" ) ;
9351028 let expected_result = vec ! [ Article {
1029+ order: 0 ,
9361030 topic: String :: from( "Test article" ) ,
9371031 content: String :: from( "Should ignore @CodeBlockEnd in a text block\n ```rust/\n ...\n ```" ) ,
9381032 path: "" . to_string( ) ,
@@ -960,6 +1054,7 @@ fn parse_nested_commends() {
9601054
9611055 let articles = parser. parse_file ( file_content, "" ) ;
9621056 let expected_result = vec ! [ Article {
1057+ order: 0 ,
9631058 topic: String :: from( "Test article" ) ,
9641059 content: String :: from( "Example:\n /**\n * @Article Example article\n * Example\n */\n test" ) ,
9651060 path: "" . to_string( ) ,
@@ -998,6 +1093,7 @@ fn parse_fdoc_file_check() {
9981093 let parser = Parser :: new ( get_test_config ( ) ) ;
9991094 let result = parser. parse_fdoc_file ( "test" , "/some/long/path/to/file.fdoc.md" ) ;
10001095 let expected_result = vec ! [ Article {
1096+ order: 0 ,
10011097 topic: String :: from( "file" ) ,
10021098 content: String :: from( "test" ) ,
10031099 path: "/some/long/path/to/file.fdoc.md" . to_string( ) ,
0 commit comments