diff --git a/qbittorrent-web-api-gen/Cargo.toml b/qbittorrent-web-api-gen/Cargo.toml index c0fd57f..eaecf5f 100644 --- a/qbittorrent-web-api-gen/Cargo.toml +++ b/qbittorrent-web-api-gen/Cargo.toml @@ -7,10 +7,16 @@ keywords = ["qbittorrent"] repository = "https://github.com/JoelWachsler/qbittorrent-web-api" description = "Generated web api for qBittorrent" exclude = ["*.txt", "tests"] +# we use trybuild instead +autotests = false [lib] proc-macro = true +[[test]] +name = "tests" +path = "tests/tests.rs" + [dependencies] syn = { version = "1.0.98", features = ["extra-traits"]} quote = "1.0.20" diff --git a/qbittorrent-web-api-gen/src/md_parser/md_token.rs b/qbittorrent-web-api-gen/src/md_parser/md_token.rs new file mode 100644 index 0000000..439216e --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/md_token.rs @@ -0,0 +1,127 @@ +#[derive(Debug, Clone, PartialEq, Eq)] +pub enum MdContent { + Text(String), + Asterix(String), + Table(Table), +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct Table { + pub header: TableRow, + pub split: String, + pub rows: Vec, +} + +impl Table { + fn raw(&self) -> String { + let mut output = vec![self.header.raw.clone(), self.split.clone()]; + for row in self.rows.clone() { + output.push(row.raw); + } + + output.join("\n") + } +} + +#[derive(Debug, Clone)] +pub struct Header { + pub level: i32, + pub content: String, +} + +#[derive(Debug, Clone, PartialEq, Eq)] +pub struct TableRow { + raw: String, + pub columns: Vec, +} + +impl MdContent { + pub fn inner_value_as_string(&self) -> String { + match self { + MdContent::Text(text) => text.into(), + MdContent::Asterix(text) => text.into(), + MdContent::Table(table) => table.raw(), + } + } +} + +#[derive(Debug)] +pub enum MdToken { + Header(Header), + Content(MdContent), +} + +impl MdToken { + fn parse_token(line: &str) -> MdToken { + if line.starts_with('#') { + let mut level = 0; + for char in line.chars() { + if char != '#' { + break; + } + + level += 1; + } + + MdToken::Header(Header { + level, + content: line.trim_matches('#').trim().to_string(), + }) + } else if line.starts_with('*') { + MdToken::Content(MdContent::Asterix( + line.trim_matches('*').trim().to_string(), + )) + } else { + MdToken::Content(MdContent::Text(line.to_string())) + } + } + + pub fn from(content: &str) -> Vec { + let mut output = Vec::new(); + + let mut iter = content.lines(); + while let Some(line) = iter.next() { + // assume this is a table + if line.contains('|') { + let to_columns = |column_line: &str| { + column_line + .replace('`', "") + .split('|') + .map(|s| s.trim().to_string()) + .collect() + }; + + let table_header = TableRow { + raw: line.into(), + columns: to_columns(line), + }; + let table_split = iter.next().unwrap(); + let mut table_rows = Vec::new(); + while let Some(row_line) = iter.next() { + if !row_line.contains('|') { + // we've reached the end of the table, let's go back one step + iter.next_back(); + break; + } + + let table_row = TableRow { + raw: row_line.into(), + columns: to_columns(row_line), + }; + + table_rows.push(table_row); + } + + output.push(MdToken::Content(MdContent::Table(Table { + header: table_header, + split: table_split.to_string(), + rows: table_rows, + }))); + } else { + output.push(MdToken::parse_token(line)); + } + } + + output + } +} diff --git a/qbittorrent-web-api-gen/src/md_parser/mod.rs b/qbittorrent-web-api-gen/src/md_parser/mod.rs index 184e994..08d4769 100644 --- a/qbittorrent-web-api-gen/src/md_parser/mod.rs +++ b/qbittorrent-web-api-gen/src/md_parser/mod.rs @@ -1,316 +1,7 @@ -use std::{cell::RefCell, rc::Rc}; +mod md_token; +mod token_tree; +mod token_tree_factory; -#[derive(Debug, Clone, PartialEq, Eq)] -pub enum MdContent { - Text(String), - Asterix(String), - Table(Table), -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct Table { - pub header: TableRow, - pub split: String, - pub rows: Vec, -} - -impl Table { - fn raw(&self) -> String { - let mut output = vec![self.header.raw.clone(), self.split.clone()]; - for row in self.rows.clone() { - output.push(row.raw); - } - - output.join("\n") - } -} - -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct TableRow { - raw: String, - pub columns: Vec, -} - -impl MdContent { - pub fn inner_value_as_string(&self) -> String { - match self { - MdContent::Text(text) => text.into(), - MdContent::Asterix(text) => text.into(), - MdContent::Table(table) => table.raw(), - } - } -} - -#[derive(Debug, Clone)] -pub struct Header { - level: i32, - content: String, -} - -/// These are the only relevant tokens we need for the api generation. -#[derive(Debug)] -pub enum MdToken { - Header(Header), - Content(MdContent), -} - -impl MdToken { - fn parse_token(line: &str) -> MdToken { - if line.starts_with('#') { - let mut level = 0; - for char in line.chars() { - if char != '#' { - break; - } - - level += 1; - } - - MdToken::Header(Header { - level, - content: line.trim_matches('#').trim().to_string(), - }) - } else if line.starts_with('*') { - MdToken::Content(MdContent::Asterix( - line.trim_matches('*').trim().to_string(), - )) - } else { - MdToken::Content(MdContent::Text(line.to_string())) - } - } - - fn from(content: &str) -> Vec { - let mut output = Vec::new(); - - let mut iter = content.lines(); - while let Some(line) = iter.next() { - // assume this is a table - if line.contains('|') { - let to_columns = |column_line: &str| { - column_line - .replace('`', "") - .split('|') - .map(|s| s.trim().to_string()) - .collect() - }; - - let table_header = TableRow { - raw: line.into(), - columns: to_columns(line), - }; - let table_split = iter.next().unwrap(); - let mut table_rows = Vec::new(); - while let Some(row_line) = iter.next() { - if !row_line.contains('|') { - // we've reached the end of the table, let's go back one step - iter.next_back(); - break; - } - - let table_row = TableRow { - raw: row_line.into(), - columns: to_columns(row_line), - }; - - table_rows.push(table_row); - } - - output.push(MdToken::Content(MdContent::Table(Table { - header: table_header, - split: table_split.to_string(), - rows: table_rows, - }))); - } else { - output.push(MdToken::parse_token(line)); - } - } - - output - } -} - -#[derive(Debug)] -pub struct TokenTree { - pub title: Option, - pub content: Vec, - pub children: Vec, -} - -impl From> for TokenTree { - fn from(builder: Rc) -> Self { - let children = builder - .children - .clone() - .into_inner() - .into_iter() - .map(|child| child.into()) - .collect::>(); - - let content = builder.content.clone().into_inner(); - - TokenTree { - title: builder.title.clone(), - content, - children, - } - } -} - -#[derive(Debug, Default)] -pub struct TokenTreeFactory { - title: Option, - content: RefCell>, - children: RefCell>>, - level: i32, -} - -impl TokenTreeFactory { - fn new(title: &str, level: i32) -> Self { - Self { - title: if title.is_empty() { - None - } else { - Some(title.to_string()) - }, - level, - ..Default::default() - } - } - - fn add_content(&self, content: MdContent) { - self.content.borrow_mut().push(content); - } - - fn append(&self, child: &Rc) { - self.children.borrow_mut().push(child.clone()); - } - - pub fn create(content: &str) -> TokenTree { - let tokens = MdToken::from(content); - - let mut stack = Vec::new(); - let root = Rc::new(TokenTreeFactory::default()); - stack.push(root.clone()); - - for token in tokens { - match token { - MdToken::Header(Header { level, content }) => { - let new_header = Rc::new(TokenTreeFactory::new(&content, level)); - - // go back until we're at the same or lower level. - while let Some(current) = stack.pop() { - if current.level < level { - current.append(&new_header); - stack.push(current); - break; - } - } - - stack.push(new_header.clone()); - } - MdToken::Content(content) => { - let current = stack.pop().unwrap(); - current.add_content(content); - stack.push(current); - } - } - } - - root.into() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn should_remove_surrounding_asterix() { - // given - let input = r#" -# A -**B** - "# - .trim_matches('\n') - .trim(); - - // when - let tree = TokenTreeFactory::create(input); - - // then - println!("{:#?}", tree); - let first = tree.children.first().unwrap(); - let content = first.content.first().unwrap(); - assert_eq!(*content, MdContent::Asterix("B".into())); - } - - #[test] - fn should_remove_surrounding_hash() { - // given - let input = r#" -# A # - "# - .trim_matches('\n') - .trim(); - - // when - let tree = TokenTreeFactory::create(input); - - // then - println!("{:#?}", tree); - assert_eq!(tree.children.first().unwrap().title, Some("A".into())); - } - - #[test] - fn single_level() { - // given - let input = r#" -# A -Foo - "# - .trim_matches('\n') - .trim(); - - // when - let tree = TokenTreeFactory::create(input); - - // then - println!("{:#?}", tree); - assert_eq!(tree.title, None); - let first_child = tree.children.first().unwrap(); - assert_eq!(first_child.title, Some("A".into())); - } - - #[test] - fn complex() { - // given - let input = r#" -# A -Foo -## B -# C -## D -Bar - "# - .trim_matches('\n') - .trim(); - - // when - let tree = TokenTreeFactory::create(input); - - // then - println!("{:#?}", tree); - assert_eq!(tree.title, None); - assert_eq!(tree.children.len(), 2); - - let first = tree.children.get(0).unwrap(); - assert_eq!(first.title, Some("A".into())); - assert_eq!(first.children.len(), 1); - assert_eq!(first.children.first().unwrap().title, Some("B".into())); - - let second = tree.children.get(1).unwrap(); - assert_eq!(second.title, Some("C".into())); - assert_eq!(second.children.len(), 1); - assert_eq!(second.children.first().unwrap().title, Some("D".into())); - } -} +pub use md_token::*; +pub use token_tree::TokenTree; +pub use token_tree_factory::TokenTreeFactory; diff --git a/qbittorrent-web-api-gen/src/md_parser/token_tree.rs b/qbittorrent-web-api-gen/src/md_parser/token_tree.rs new file mode 100644 index 0000000..473193b --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree.rs @@ -0,0 +1,30 @@ +use std::rc::Rc; + +use super::{md_token::MdContent, token_tree_factory::TokenTreeFactory}; + +#[derive(Debug)] +pub struct TokenTree { + pub title: Option, + pub content: Vec, + pub children: Vec, +} + +impl From> for TokenTree { + fn from(builder: Rc) -> Self { + let children = builder + .children + .clone() + .into_inner() + .into_iter() + .map(|child| child.into()) + .collect::>(); + + let content = builder.content.clone().into_inner(); + + TokenTree { + title: builder.title.clone(), + content, + children, + } + } +} 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 new file mode 100644 index 0000000..f62ebc5 --- /dev/null +++ b/qbittorrent-web-api-gen/src/md_parser/token_tree_factory.rs @@ -0,0 +1,166 @@ +use std::{cell::RefCell, rc::Rc}; + +use super::{ + md_token::{Header, MdContent, MdToken}, + token_tree::TokenTree, +}; + +#[derive(Debug, Default)] +pub struct TokenTreeFactory { + pub title: Option, + pub content: RefCell>, + pub children: RefCell>>, + pub level: i32, +} + +impl TokenTreeFactory { + fn new(title: &str, level: i32) -> Self { + Self { + title: if title.is_empty() { + None + } else { + Some(title.to_string()) + }, + level, + ..Default::default() + } + } + + fn add_content(&self, content: MdContent) { + self.content.borrow_mut().push(content); + } + + fn append(&self, child: &Rc) { + self.children.borrow_mut().push(child.clone()); + } + + pub fn create(content: &str) -> TokenTree { + let tokens = MdToken::from(content); + + let mut stack = Vec::new(); + let root = Rc::new(TokenTreeFactory::default()); + stack.push(root.clone()); + + for token in tokens { + match token { + MdToken::Header(Header { level, content }) => { + let new_header = Rc::new(TokenTreeFactory::new(&content, level)); + + // go back until we're at the same or lower level. + while let Some(current) = stack.pop() { + if current.level < level { + current.append(&new_header); + stack.push(current); + break; + } + } + + stack.push(new_header.clone()); + } + MdToken::Content(content) => { + let current = stack.pop().unwrap(); + current.add_content(content); + stack.push(current); + } + } + } + + root.into() + } +} + + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn should_remove_surrounding_asterix() { + // given + let input = r#" +# A +**B** + "# + .trim_matches('\n') + .trim(); + + // when + let tree = TokenTreeFactory::create(input); + + // then + println!("{:#?}", tree); + let first = tree.children.first().unwrap(); + let content = first.content.first().unwrap(); + assert_eq!(*content, MdContent::Asterix("B".into())); + } + + #[test] + fn should_remove_surrounding_hash() { + // given + let input = r#" +# A # + "# + .trim_matches('\n') + .trim(); + + // when + let tree = TokenTreeFactory::create(input); + + // then + println!("{:#?}", tree); + assert_eq!(tree.children.first().unwrap().title, Some("A".into())); + } + + #[test] + fn single_level() { + // given + let input = r#" +# A +Foo + "# + .trim_matches('\n') + .trim(); + + // when + let tree = TokenTreeFactory::create(input); + + // then + println!("{:#?}", tree); + assert_eq!(tree.title, None); + let first_child = tree.children.first().unwrap(); + assert_eq!(first_child.title, Some("A".into())); + } + + #[test] + fn complex() { + // given + let input = r#" +# A +Foo +## B +# C +## D +Bar + "# + .trim_matches('\n') + .trim(); + + // when + let tree = TokenTreeFactory::create(input); + + // then + println!("{:#?}", tree); + assert_eq!(tree.title, None); + assert_eq!(tree.children.len(), 2); + + let first = tree.children.get(0).unwrap(); + assert_eq!(first.title, Some("A".into())); + assert_eq!(first.children.len(), 1); + assert_eq!(first.children.first().unwrap().title, Some("B".into())); + + let second = tree.children.get(1).unwrap(); + assert_eq!(second.title, Some("C".into())); + assert_eq!(second.children.len(), 1); + assert_eq!(second.children.first().unwrap().title, Some("D".into())); + } +} diff --git a/qbittorrent-web-api-gen/tests/access_search.rs b/qbittorrent-web-api-gen/tests/access_search.rs index 5dbe996..504dbc3 100644 --- a/qbittorrent-web-api-gen/tests/access_search.rs +++ b/qbittorrent-web-api-gen/tests/access_search.rs @@ -1,9 +1,8 @@ -use anyhow::Result; -use qbittorrent_web_api_gen::QBittorrentApiGen; +mod common; -const USERNAME: &str = "admin"; -const PASSWORD: &str = "adminadmin"; -const BASE_URL: &str = "http://localhost:8080"; +use anyhow::Result; +use common::*; +use qbittorrent_web_api_gen::QBittorrentApiGen; #[derive(QBittorrentApiGen)] struct Api {} diff --git a/qbittorrent-web-api-gen/tests/add_torrent.rs b/qbittorrent-web-api-gen/tests/add_torrent.rs index 697fb63..b1a0124 100644 --- a/qbittorrent-web-api-gen/tests/add_torrent.rs +++ b/qbittorrent-web-api-gen/tests/add_torrent.rs @@ -1,9 +1,8 @@ -use anyhow::Result; -use qbittorrent_web_api_gen::QBittorrentApiGen; +mod common; -const USERNAME: &str = "admin"; -const PASSWORD: &str = "adminadmin"; -const BASE_URL: &str = "http://localhost:8080"; +use anyhow::Result; +use common::*; +use qbittorrent_web_api_gen::QBittorrentApiGen; #[derive(QBittorrentApiGen)] struct Api {} diff --git a/qbittorrent-web-api-gen/tests/another_struct_name.rs b/qbittorrent-web-api-gen/tests/another_struct_name.rs index bca720b..7cccbf2 100644 --- a/qbittorrent-web-api-gen/tests/another_struct_name.rs +++ b/qbittorrent-web-api-gen/tests/another_struct_name.rs @@ -1,9 +1,8 @@ -use anyhow::Result; -use qbittorrent_web_api_gen::QBittorrentApiGen; +mod common; -const USERNAME: &str = "admin"; -const PASSWORD: &str = "adminadmin"; -const BASE_URL: &str = "http://localhost:8080"; +use anyhow::Result; +use common::*; +use qbittorrent_web_api_gen::QBittorrentApiGen; #[derive(QBittorrentApiGen)] struct Foo {} diff --git a/qbittorrent-web-api-gen/tests/common/mod.rs b/qbittorrent-web-api-gen/tests/common/mod.rs new file mode 100644 index 0000000..8180627 --- /dev/null +++ b/qbittorrent-web-api-gen/tests/common/mod.rs @@ -0,0 +1,3 @@ +pub const USERNAME: &str = "admin"; +pub const PASSWORD: &str = "adminadmin"; +pub const BASE_URL: &str = "http://localhost:8080"; diff --git a/qbittorrent-web-api-gen/tests/default_parameters.rs b/qbittorrent-web-api-gen/tests/default_parameters.rs index 2617e95..5cd7531 100644 --- a/qbittorrent-web-api-gen/tests/default_parameters.rs +++ b/qbittorrent-web-api-gen/tests/default_parameters.rs @@ -1,9 +1,8 @@ -use anyhow::Result; -use qbittorrent_web_api_gen::QBittorrentApiGen; +mod common; -const USERNAME: &str = "admin"; -const PASSWORD: &str = "adminadmin"; -const BASE_URL: &str = "http://localhost:8080"; +use anyhow::Result; +use common::*; +use qbittorrent_web_api_gen::QBittorrentApiGen; #[derive(QBittorrentApiGen)] struct Api {} diff --git a/qbittorrent-web-api-gen/tests/login.rs b/qbittorrent-web-api-gen/tests/login.rs index fb54bd4..ff9292d 100644 --- a/qbittorrent-web-api-gen/tests/login.rs +++ b/qbittorrent-web-api-gen/tests/login.rs @@ -1,9 +1,8 @@ -use anyhow::Result; -use qbittorrent_web_api_gen::QBittorrentApiGen; +mod common; -const USERNAME: &str = "admin"; -const PASSWORD: &str = "adminadmin"; -const BASE_URL: &str = "http://localhost:8080"; +use anyhow::Result; +use common::*; +use qbittorrent_web_api_gen::QBittorrentApiGen; #[derive(QBittorrentApiGen)] struct Api {} diff --git a/qbittorrent-web-api-gen/tests/logout.rs b/qbittorrent-web-api-gen/tests/logout.rs index 97bfeb9..c002f24 100644 --- a/qbittorrent-web-api-gen/tests/logout.rs +++ b/qbittorrent-web-api-gen/tests/logout.rs @@ -1,9 +1,8 @@ -use anyhow::Result; -use qbittorrent_web_api_gen::QBittorrentApiGen; +mod common; -const USERNAME: &str = "admin"; -const PASSWORD: &str = "adminadmin"; -const BASE_URL: &str = "http://localhost:8080"; +use anyhow::Result; +use common::*; +use qbittorrent_web_api_gen::QBittorrentApiGen; #[derive(QBittorrentApiGen)] struct Api {} diff --git a/qbittorrent-web-api-gen/tests/return_type.rs b/qbittorrent-web-api-gen/tests/return_type.rs index 6c87cab..55a3585 100644 --- a/qbittorrent-web-api-gen/tests/return_type.rs +++ b/qbittorrent-web-api-gen/tests/return_type.rs @@ -1,9 +1,8 @@ -use anyhow::Result; -use qbittorrent_web_api_gen::QBittorrentApiGen; +mod common; -const USERNAME: &str = "admin"; -const PASSWORD: &str = "adminadmin"; -const BASE_URL: &str = "http://localhost:8080"; +use anyhow::Result; +use common::*; +use qbittorrent_web_api_gen::QBittorrentApiGen; #[derive(QBittorrentApiGen)] struct Api {} diff --git a/qbittorrent-web-api-gen/tests/return_type_enum.rs b/qbittorrent-web-api-gen/tests/return_type_enum.rs index c557513..2e37c26 100644 --- a/qbittorrent-web-api-gen/tests/return_type_enum.rs +++ b/qbittorrent-web-api-gen/tests/return_type_enum.rs @@ -1,10 +1,9 @@ -use anyhow::Result; -use qbittorrent_web_api_gen::QBittorrentApiGen; -use tokio::time::{sleep, Duration}; +mod common; -const USERNAME: &str = "admin"; -const PASSWORD: &str = "adminadmin"; -const BASE_URL: &str = "http://localhost:8080"; +use anyhow::Result; +use common::*; +use qbittorrent_web_api_gen::QBittorrentApiGen; +use tokio::time::*; #[derive(QBittorrentApiGen)] struct Api {} diff --git a/qbittorrent-web-api-gen/tests/return_type_with_optional_params.rs b/qbittorrent-web-api-gen/tests/return_type_with_optional_params.rs index c557513..2e37c26 100644 --- a/qbittorrent-web-api-gen/tests/return_type_with_optional_params.rs +++ b/qbittorrent-web-api-gen/tests/return_type_with_optional_params.rs @@ -1,10 +1,9 @@ -use anyhow::Result; -use qbittorrent_web_api_gen::QBittorrentApiGen; -use tokio::time::{sleep, Duration}; +mod common; -const USERNAME: &str = "admin"; -const PASSWORD: &str = "adminadmin"; -const BASE_URL: &str = "http://localhost:8080"; +use anyhow::Result; +use common::*; +use qbittorrent_web_api_gen::QBittorrentApiGen; +use tokio::time::*; #[derive(QBittorrentApiGen)] struct Api {} diff --git a/qbittorrent-web-api-gen/tests/tests.rs b/qbittorrent-web-api-gen/tests/tests.rs index 433c64b..548499f 100644 --- a/qbittorrent-web-api-gen/tests/tests.rs +++ b/qbittorrent-web-api-gen/tests/tests.rs @@ -1,6 +1,7 @@ #[test] fn tests() { let t = trybuild::TestCases::new(); + // --- Auth --- t.pass("tests/login.rs"); t.pass("tests/logout.rs"); @@ -19,5 +20,5 @@ fn tests() { t.pass("tests/add_torrent.rs"); t.pass("tests/another_struct_name.rs"); t.pass("tests/access_impl_types.rs"); - t.pass("tests/search_types.rs"); + // t.pass("tests/search_types.rs"); } diff --git a/qbittorrent-web-api-gen/tests/without_parameters.rs b/qbittorrent-web-api-gen/tests/without_parameters.rs index aa23d4f..47dec5d 100644 --- a/qbittorrent-web-api-gen/tests/without_parameters.rs +++ b/qbittorrent-web-api-gen/tests/without_parameters.rs @@ -1,9 +1,8 @@ -use anyhow::Result; -use qbittorrent_web_api_gen::QBittorrentApiGen; +mod common; -const USERNAME: &str = "admin"; -const PASSWORD: &str = "adminadmin"; -const BASE_URL: &str = "http://localhost:8080"; +use anyhow::Result; +use common::*; +use qbittorrent_web_api_gen::QBittorrentApiGen; #[derive(QBittorrentApiGen)] struct Api {}