From f1c3656e6e079a1d4c268bfaa4e98fe8242bd7eb Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Thu, 14 Jul 2022 13:00:39 +0000 Subject: [PATCH] Fix table parsing --- .../src/md_parser/md_token.rs | 25 ++++-- .../src/md_parser/token_tree_factory.rs | 15 +++- .../token_tree_factory_tests/log.check | 18 ++++ .../multi_table.check | 86 +++++++++++++++++++ .../token_tree_factory_tests/multi_table.md | 12 +++ 5 files changed, 149 insertions(+), 7 deletions(-) create mode 100644 qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/multi_table.check create mode 100644 qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/multi_table.md diff --git a/qbittorrent-web-api-gen/src/md_parser/md_token.rs b/qbittorrent-web-api-gen/src/md_parser/md_token.rs index 439216e..39de178 100644 --- a/qbittorrent-web-api-gen/src/md_parser/md_token.rs +++ b/qbittorrent-web-api-gen/src/md_parser/md_token.rs @@ -77,10 +77,21 @@ impl MdToken { } pub fn from(content: &str) -> Vec { + // to prevent infinite loops + let mut max_iterations = 10000; + let mut decreate_max_iterations = || { + max_iterations -= 1; + if max_iterations <= 0 { + panic!("Max iterations reached, missing termination?"); + }; + }; + let mut output = Vec::new(); - let mut iter = content.lines(); + let mut iter = content.lines().peekable(); while let Some(line) = iter.next() { + decreate_max_iterations(); + // assume this is a table if line.contains('|') { let to_columns = |column_line: &str| { @@ -97,16 +108,18 @@ impl MdToken { }; let table_split = iter.next().unwrap(); let mut table_rows = Vec::new(); - while let Some(row_line) = iter.next() { - if !row_line.contains('|') { + while let Some(peeked_row_line) = iter.peek() { + decreate_max_iterations(); + + if !peeked_row_line.contains('|') { // we've reached the end of the table, let's go back one step - iter.next_back(); break; } + let next_row_line = iter.next().unwrap(); let table_row = TableRow { - raw: row_line.into(), - columns: to_columns(row_line), + raw: next_row_line.to_string(), + columns: to_columns(next_row_line), }; table_rows.push(table_row); diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory.rs b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory.rs index 9854f8c..d04ac8b 100644 --- a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory.rs +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory.rs @@ -96,11 +96,19 @@ mod tests { macro_rules! update_test { ($test_file:expr) => { use std::fs; + use std::path::Path; let input = include_str!(concat!("token_tree_factory_tests/", $test_file, ".md")); let tree = TokenTreeFactory::create(input); let tree_as_str = format!("{tree:#?}"); - fs::write(concat!($test_file, ".check"), tree_as_str).unwrap(); + let file = concat!("src/md_parser/token_tree_factory_tests/", $test_file, ".check"); + + // prevent user from accidentially leaving the current macro in a test + if Path::new(file).exists() { + panic!("Test case already exists: {file}"); + } + + fs::write(file, tree_as_str).unwrap(); }; } @@ -128,4 +136,9 @@ mod tests { fn log() { run_test!("log"); } + + #[test] + fn multi_table() { + run_test!("multi_table"); + } } diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/log.check b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/log.check index 0e7e5f1..e19fe27 100644 --- a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/log.check +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/log.check @@ -93,6 +93,9 @@ TokenTree { ], }, ), + Text( + "", + ), Text( "Example:", ), @@ -138,6 +141,9 @@ TokenTree { ], }, ), + Text( + "", + ), Text( "The response is a JSON array in which each element is an entry of the log.", ), @@ -197,6 +203,9 @@ TokenTree { ], }, ), + Text( + "", + ), Text( "Example:", ), @@ -494,6 +503,15 @@ TokenTree { Text( " \"type\":2", ), + Text( + " }", + ), + Text( + "]", + ), + Text( + "```", + ), ], children: [], }, diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/multi_table.check b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/multi_table.check new file mode 100644 index 0000000..7efc49d --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/multi_table.check @@ -0,0 +1,86 @@ +TokenTree { + title: None, + content: [], + children: [ + TokenTree { + title: Some( + "Foo", + ), + content: [ + Text( + "", + ), + ], + children: [ + TokenTree { + title: Some( + "Bar", + ), + content: [ + Table( + Table { + header: TableRow { + raw: "Parameter | Type | Description", + columns: [ + "Parameter", + "Type", + "Description", + ], + }, + split: "----------------|---------|------------", + rows: [ + TableRow { + raw: "`normal` | bool | Include normal messages (default: `true`)", + columns: [ + "normal", + "bool", + "Include normal messages (default: true)", + ], + }, + ], + }, + ), + Text( + "", + ), + ], + children: [], + }, + TokenTree { + title: Some( + "Baz", + ), + content: [ + Table( + Table { + header: TableRow { + raw: "Parameter | Type | Description", + columns: [ + "Parameter", + "Type", + "Description", + ], + }, + split: "----------------|---------|------------", + rows: [ + TableRow { + raw: "`last_known_id` | integer | Exclude messages with \"message id\" <= `last_known_id` (default: `-1`)", + columns: [ + "last_known_id", + "integer", + "Exclude messages with \"message id\" <= last_known_id (default: -1)", + ], + }, + ], + }, + ), + Text( + "", + ), + ], + children: [], + }, + ], + }, + ], +} \ No newline at end of file diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/multi_table.md b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/multi_table.md new file mode 100644 index 0000000..c5a3510 --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory_tests/multi_table.md @@ -0,0 +1,12 @@ +# Foo + +## Bar +Parameter | Type | Description +----------------|---------|------------ +`normal` | bool | Include normal messages (default: `true`) + +## Baz +Parameter | Type | Description +----------------|---------|------------ +`last_known_id` | integer | Exclude messages with "message id" <= `last_known_id` (default: `-1`) +