From 3559ac6c27abc3765a4a0a1df2fa70577e2b0664 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 09:22:22 +0000 Subject: [PATCH 01/29] Make md_parser its own file --- qbittorrent-web-api-gen/src/{md_parser/mod.rs => md_parser.rs} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename qbittorrent-web-api-gen/src/{md_parser/mod.rs => md_parser.rs} (100%) diff --git a/qbittorrent-web-api-gen/src/md_parser/mod.rs b/qbittorrent-web-api-gen/src/md_parser.rs similarity index 100% rename from qbittorrent-web-api-gen/src/md_parser/mod.rs rename to qbittorrent-web-api-gen/src/md_parser.rs From f0124d762033c5dd49a5f06fa519f8cc653c45ad Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 10:28:03 +0000 Subject: [PATCH 02/29] Refactor --- .../src/{group.rs => generate/mod.rs} | 34 ++++++++++++++----- .../src/{ => generate}/skeleton.rs | 0 qbittorrent-web-api-gen/src/lib.rs | 25 ++------------ .../src/parser/group_parser/parameters.rs | 13 +++---- .../src/parser/group_parser/return_type.rs | 4 +-- qbittorrent-web-api-gen/src/parser/mod.rs | 11 +++--- .../src/parser/object_types.rs | 4 +-- .../src/{parser => }/types.rs | 0 8 files changed, 44 insertions(+), 47 deletions(-) rename qbittorrent-web-api-gen/src/{group.rs => generate/mod.rs} (95%) rename qbittorrent-web-api-gen/src/{ => generate}/skeleton.rs (100%) rename qbittorrent-web-api-gen/src/{parser => }/types.rs (100%) diff --git a/qbittorrent-web-api-gen/src/group.rs b/qbittorrent-web-api-gen/src/generate/mod.rs similarity index 95% rename from qbittorrent-web-api-gen/src/group.rs rename to qbittorrent-web-api-gen/src/generate/mod.rs index b7809eb..6ef8cad 100644 --- a/qbittorrent-web-api-gen/src/group.rs +++ b/qbittorrent-web-api-gen/src/generate/mod.rs @@ -1,14 +1,32 @@ +mod skeleton; + use std::{collections::HashMap, vec::Vec}; use case::CaseExt; +use proc_macro2::TokenStream; use quote::{format_ident, quote}; use regex::Regex; -use crate::{ - parser::{self, types::TypeInfo}, - skeleton::auth_ident, - util, -}; +use crate::{parser, types, util}; + +use self::skeleton::{auth_ident, generate_skeleton}; + +pub fn generate(ast: &syn::DeriveInput, api_content: &str) -> TokenStream { + let ident = &ast.ident; + + let api_groups = parser::parse_api_groups(api_content); + + let skeleton = generate_skeleton(ident); + let groups = generate_groups(api_groups); + let impl_ident = syn::Ident::new(&format!("{}_impl", ident).to_snake(), ident.span()); + + quote! { + pub mod #impl_ident { + #skeleton + #groups + } + } +} pub fn generate_groups(groups: Vec) -> proc_macro2::TokenStream { let gr = groups @@ -138,7 +156,7 @@ fn create_method_without_params( fn create_method_with_params( group: &parser::ApiGroup, method: &parser::ApiMethod, - params: &[parser::types::Type], + params: &[types::Type], method_name: &proc_macro2::Ident, url: &str, ) -> (proc_macro2::TokenStream, Option) { @@ -314,7 +332,7 @@ fn create_return_type( .parameters .iter() .flat_map(|parameter| match ¶meter.return_type { - parser::types::Type::Number(TypeInfo { + types::Type::Number(types::TypeInfo { ref name, type_description: Some(type_description), .. @@ -352,7 +370,7 @@ fn create_return_type( }, )) } - parser::types::Type::String(TypeInfo { + types::Type::String(types::TypeInfo { ref name, type_description: Some(type_description), .. diff --git a/qbittorrent-web-api-gen/src/skeleton.rs b/qbittorrent-web-api-gen/src/generate/skeleton.rs similarity index 100% rename from qbittorrent-web-api-gen/src/skeleton.rs rename to qbittorrent-web-api-gen/src/generate/skeleton.rs diff --git a/qbittorrent-web-api-gen/src/lib.rs b/qbittorrent-web-api-gen/src/lib.rs index ae63a37..276da66 100644 --- a/qbittorrent-web-api-gen/src/lib.rs +++ b/qbittorrent-web-api-gen/src/lib.rs @@ -1,35 +1,16 @@ -mod group; +mod generate; mod md_parser; mod parser; -mod skeleton; mod util; +mod types; -use case::CaseExt; use proc_macro::TokenStream; -use quote::quote; -use skeleton::generate_skeleton; use syn::parse_macro_input; -use crate::group::generate_groups; - const API_CONTENT: &str = include_str!("../api-4_1.md"); #[proc_macro_derive(QBittorrentApiGen, attributes(api_gen))] pub fn derive(input: TokenStream) -> TokenStream { let ast = parse_macro_input!(input as syn::DeriveInput); - let ident = &ast.ident; - - let api_groups = parser::parse_api_groups(API_CONTENT); - - let skeleton = generate_skeleton(ident); - let groups = generate_groups(api_groups); - let impl_ident = syn::Ident::new(&format!("{}_impl", ident).to_snake(), ident.span()); - - quote! { - pub mod #impl_ident { - #skeleton - #groups - } - } - .into() + generate::generate(&ast, API_CONTENT).into() } diff --git a/qbittorrent-web-api-gen/src/parser/group_parser/parameters.rs b/qbittorrent-web-api-gen/src/parser/group_parser/parameters.rs index 2b6bc12..4b65682 100644 --- a/qbittorrent-web-api-gen/src/parser/group_parser/parameters.rs +++ b/qbittorrent-web-api-gen/src/parser/group_parser/parameters.rs @@ -1,11 +1,8 @@ use std::collections::HashMap; -use crate::{ - md_parser::MdContent, - parser::types::{Type, OPTIONAL}, -}; +use crate::{md_parser::MdContent, parser::types}; -pub fn get_parameters(content: &[MdContent]) -> Option> { +pub fn get_parameters(content: &[MdContent]) -> Option> { let mut it = content .iter() .skip_while(|row| match row { @@ -37,10 +34,10 @@ pub fn get_parameters(content: &[MdContent]) -> Option> { // If the description contains a default value it means that the parameter is optional. Some(desc) if desc.contains("default: ") => { // type defines a variable as default if it contains: _optional_ - let name_with_optional = format!("{} {}", row.columns[0], OPTIONAL); - Type::from(&row.columns[1], &name_with_optional, description, &type_map) + let name_with_optional = format!("{} {}", row.columns[0], types::OPTIONAL); + types::Type::from(&row.columns[1], &name_with_optional, description, &type_map) } - _ => Type::from(&row.columns[1], &row.columns[0], description, &type_map), + _ => types::Type::from(&row.columns[1], &row.columns[0], description, &type_map), } }) .collect(); diff --git a/qbittorrent-web-api-gen/src/parser/group_parser/return_type.rs b/qbittorrent-web-api-gen/src/parser/group_parser/return_type.rs index c79c929..940bd99 100644 --- a/qbittorrent-web-api-gen/src/parser/group_parser/return_type.rs +++ b/qbittorrent-web-api-gen/src/parser/group_parser/return_type.rs @@ -1,6 +1,6 @@ use crate::{ md_parser::MdContent, - parser::{object_types::get_object_types, types::Type, ReturnType, ReturnTypeParameter}, + parser::{object_types::get_object_types, types, ReturnType, ReturnTypeParameter}, }; pub fn get_return_type(content: &[MdContent]) -> Option { @@ -26,7 +26,7 @@ pub fn get_return_type(content: &[MdContent]) -> Option { .map(|parameter| ReturnTypeParameter { name: parameter.columns[0].clone(), description: parameter.columns[2].clone(), - return_type: Type::from( + return_type: types::Type::from( ¶meter.columns[1], ¶meter.columns[0], Some(parameter.columns[2].clone()), diff --git a/qbittorrent-web-api-gen/src/parser/mod.rs b/qbittorrent-web-api-gen/src/parser/mod.rs index 9c4c0ae..93104a5 100644 --- a/qbittorrent-web-api-gen/src/parser/mod.rs +++ b/qbittorrent-web-api-gen/src/parser/mod.rs @@ -1,12 +1,13 @@ mod group_parser; mod object_types; -pub mod types; mod util; use group_parser::parse_groups; -use types::Type; -use crate::md_parser::{self, TokenTree}; +use crate::{ + md_parser::{self, TokenTree}, + types, +}; #[derive(Debug)] pub struct ApiGroup { @@ -20,7 +21,7 @@ pub struct ApiGroup { pub struct ApiMethod { pub name: String, pub description: Option, - pub parameters: Option>, + pub parameters: Option>, pub return_type: Option, pub url: String, } @@ -35,7 +36,7 @@ pub struct ReturnType { pub struct ReturnTypeParameter { pub name: String, pub description: String, - pub return_type: Type, + pub return_type: types::Type, } fn extract_relevant_parts(tree: TokenTree) -> Vec { diff --git a/qbittorrent-web-api-gen/src/parser/object_types.rs b/qbittorrent-web-api-gen/src/parser/object_types.rs index a935c53..1c013ad 100644 --- a/qbittorrent-web-api-gen/src/parser/object_types.rs +++ b/qbittorrent-web-api-gen/src/parser/object_types.rs @@ -1,6 +1,6 @@ use std::collections::HashMap; -use crate::{md_parser::MdContent, parser::types::TypeDescriptions}; +use crate::{md_parser::MdContent, parser::types}; use super::types::TypeDescription; @@ -17,7 +17,7 @@ pub fn get_object_types(content: &[MdContent]) -> HashMap Date: Tue, 12 Jul 2022 10:45:19 +0000 Subject: [PATCH 03/29] Add access test --- .../tests/access_impl_types.rs | 16 ++++++++++++++++ qbittorrent-web-api-gen/tests/tests.rs | 1 + 2 files changed, 17 insertions(+) create mode 100644 qbittorrent-web-api-gen/tests/access_impl_types.rs diff --git a/qbittorrent-web-api-gen/tests/access_impl_types.rs b/qbittorrent-web-api-gen/tests/access_impl_types.rs new file mode 100644 index 0000000..cc59017 --- /dev/null +++ b/qbittorrent-web-api-gen/tests/access_impl_types.rs @@ -0,0 +1,16 @@ +use anyhow::Result; + +mod foo { + use qbittorrent_web_api_gen::QBittorrentApiGen; + + #[allow(dead_code)] + #[derive(QBittorrentApiGen)] + struct Api {} +} + +#[tokio::main] +async fn main() -> Result<()> { + let _ = foo::api_impl::ApplicationPreferencesBittorrentProtocol::TCP; + + Ok(()) +} diff --git a/qbittorrent-web-api-gen/tests/tests.rs b/qbittorrent-web-api-gen/tests/tests.rs index b2bbc2b..61f2cd2 100644 --- a/qbittorrent-web-api-gen/tests/tests.rs +++ b/qbittorrent-web-api-gen/tests/tests.rs @@ -18,4 +18,5 @@ fn tests() { // --- Misc --- t.pass("tests/add_torrent.rs"); t.pass("tests/another_struct_name.rs"); + t.pass("tests/access_impl_types.rs"); } From 15ea70c72c99f976ee5601d77ebf010d13c65732 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 10:45:35 +0000 Subject: [PATCH 04/29] Fmt fixes --- qbittorrent-web-api-gen/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbittorrent-web-api-gen/src/lib.rs b/qbittorrent-web-api-gen/src/lib.rs index 276da66..0d0814c 100644 --- a/qbittorrent-web-api-gen/src/lib.rs +++ b/qbittorrent-web-api-gen/src/lib.rs @@ -1,8 +1,8 @@ mod generate; mod md_parser; mod parser; -mod util; mod types; +mod util; use proc_macro::TokenStream; use syn::parse_macro_input; From bc5487757809a028f3a48418708c0469f4c195bf Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 10:57:14 +0000 Subject: [PATCH 05/29] Refactor --- .../parser/{group_parser => }/description.rs | 0 .../src/parser/group_parser/mod.rs | 65 ---------------- qbittorrent-web-api-gen/src/parser/mod.rs | 77 ++++++++++++++++--- .../parser/{group_parser => }/parameters.rs | 0 .../parser/{group_parser => }/return_type.rs | 0 .../parser/{group_parser => }/url_parser.rs | 0 6 files changed, 66 insertions(+), 76 deletions(-) rename qbittorrent-web-api-gen/src/parser/{group_parser => }/description.rs (100%) delete mode 100644 qbittorrent-web-api-gen/src/parser/group_parser/mod.rs rename qbittorrent-web-api-gen/src/parser/{group_parser => }/parameters.rs (100%) rename qbittorrent-web-api-gen/src/parser/{group_parser => }/return_type.rs (100%) rename qbittorrent-web-api-gen/src/parser/{group_parser => }/url_parser.rs (100%) diff --git a/qbittorrent-web-api-gen/src/parser/group_parser/description.rs b/qbittorrent-web-api-gen/src/parser/description.rs similarity index 100% rename from qbittorrent-web-api-gen/src/parser/group_parser/description.rs rename to qbittorrent-web-api-gen/src/parser/description.rs diff --git a/qbittorrent-web-api-gen/src/parser/group_parser/mod.rs b/qbittorrent-web-api-gen/src/parser/group_parser/mod.rs deleted file mode 100644 index 7b8d799..0000000 --- a/qbittorrent-web-api-gen/src/parser/group_parser/mod.rs +++ /dev/null @@ -1,65 +0,0 @@ -use crate::md_parser::TokenTree; - -use self::{parameters::get_parameters, return_type::get_return_type}; - -use super::{util, ApiGroup, ApiMethod}; - -mod description; -mod parameters; -mod return_type; -mod url_parser; - -pub fn parse_groups(trees: Vec) -> Vec { - trees.into_iter().map(parse_api_group).collect() -} - -fn parse_api_group(tree: TokenTree) -> ApiGroup { - let methods = tree - .children - .into_iter() - .flat_map(parse_api_method) - .collect(); - - let group_description = description::get_group_description(&tree.content); - let group_url = url_parser::get_group_url(&tree.content); - - let name = tree - .title - .unwrap() - .to_lowercase() - .trim_end_matches("(experimental)") - .trim() - .replace(' ', "_"); - - ApiGroup { - name, - methods, - description: group_description, - url: group_url, - } -} - -fn parse_api_method(child: TokenTree) -> Option { - util::find_content_starts_with(&child.content, "Name: ") - .map(|name| { - name.trim_start_matches("Name: ") - .trim_matches('`') - .to_string() - }) - .map(|name| to_api_method(&child, &name)) -} - -fn to_api_method(child: &TokenTree, name: &str) -> ApiMethod { - let method_description = description::get_method_description(&child.content); - let return_type = get_return_type(&child.content); - let parameters = get_parameters(&child.content); - let method_url = url_parser::get_method_url(&child.content); - - ApiMethod { - name: name.to_string(), - description: method_description, - parameters, - return_type, - url: method_url, - } -} diff --git a/qbittorrent-web-api-gen/src/parser/mod.rs b/qbittorrent-web-api-gen/src/parser/mod.rs index 93104a5..ebca428 100644 --- a/qbittorrent-web-api-gen/src/parser/mod.rs +++ b/qbittorrent-web-api-gen/src/parser/mod.rs @@ -1,14 +1,14 @@ -mod group_parser; +use crate::{md_parser, types}; + +use self::{parameters::get_parameters, return_type::get_return_type, url_parser::get_method_url}; + +mod description; mod object_types; +mod parameters; +mod return_type; +mod url_parser; mod util; -use group_parser::parse_groups; - -use crate::{ - md_parser::{self, TokenTree}, - types, -}; - #[derive(Debug)] pub struct ApiGroup { pub name: String, @@ -39,8 +39,8 @@ pub struct ReturnTypeParameter { pub return_type: types::Type, } -fn extract_relevant_parts(tree: TokenTree) -> Vec { - let relevant: Vec = tree +fn extract_relevant_parts(tree: md_parser::TokenTree) -> Vec { + let relevant: Vec = tree .children .into_iter() .skip_while(|row| match &row.title { @@ -62,12 +62,67 @@ pub fn parse_api_groups(content: &str) -> Vec { ))) } +pub fn parse_groups(trees: Vec) -> Vec { + trees.into_iter().map(parse_api_group).collect() +} + +fn parse_api_group(tree: md_parser::TokenTree) -> ApiGroup { + let methods = tree + .children + .into_iter() + .flat_map(parse_api_method) + .collect(); + + let group_description = description::get_group_description(&tree.content); + let group_url = url_parser::get_group_url(&tree.content); + + let name = tree + .title + .unwrap() + .to_lowercase() + .trim_end_matches("(experimental)") + .trim() + .replace(' ', "_"); + + ApiGroup { + name, + methods, + description: group_description, + url: group_url, + } +} + +fn parse_api_method(child: md_parser::TokenTree) -> Option { + util::find_content_starts_with(&child.content, "Name: ") + .map(|name| { + name.trim_start_matches("Name: ") + .trim_matches('`') + .to_string() + }) + .map(|name| to_api_method(&child, &name)) +} + +fn to_api_method(child: &md_parser::TokenTree, name: &str) -> ApiMethod { + let method_description = description::get_method_description(&child.content); + let return_type = get_return_type(&child.content); + let parameters = get_parameters(&child.content); + let method_url = get_method_url(&child.content); + + ApiMethod { + name: name.to_string(), + description: method_description, + parameters, + return_type, + url: method_url, + } +} + #[cfg(test)] mod tests { use super::*; use std::fs; - fn parse() -> TokenTree { + fn parse() -> md_parser::TokenTree { let content = include_str!("../../api-4_1.md"); let md_tree = md_parser::TokenTreeFactory::create(content); diff --git a/qbittorrent-web-api-gen/src/parser/group_parser/parameters.rs b/qbittorrent-web-api-gen/src/parser/parameters.rs similarity index 100% rename from qbittorrent-web-api-gen/src/parser/group_parser/parameters.rs rename to qbittorrent-web-api-gen/src/parser/parameters.rs diff --git a/qbittorrent-web-api-gen/src/parser/group_parser/return_type.rs b/qbittorrent-web-api-gen/src/parser/return_type.rs similarity index 100% rename from qbittorrent-web-api-gen/src/parser/group_parser/return_type.rs rename to qbittorrent-web-api-gen/src/parser/return_type.rs diff --git a/qbittorrent-web-api-gen/src/parser/group_parser/url_parser.rs b/qbittorrent-web-api-gen/src/parser/url_parser.rs similarity index 100% rename from qbittorrent-web-api-gen/src/parser/group_parser/url_parser.rs rename to qbittorrent-web-api-gen/src/parser/url_parser.rs From 3360b2f3bd854ea03c48355e6a52beb015543fe9 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 11:07:28 +0000 Subject: [PATCH 06/29] Refactor api parsing --- qbittorrent-web-api-gen/src/parser/mod.rs | 16 ++++---- .../src/parser/object_types.rs | 35 ++++++++++------- .../src/parser/parameters.rs | 39 +++++++++++-------- .../src/parser/return_type.rs | 4 +- 4 files changed, 52 insertions(+), 42 deletions(-) diff --git a/qbittorrent-web-api-gen/src/parser/mod.rs b/qbittorrent-web-api-gen/src/parser/mod.rs index ebca428..d1f9b2d 100644 --- a/qbittorrent-web-api-gen/src/parser/mod.rs +++ b/qbittorrent-web-api-gen/src/parser/mod.rs @@ -1,6 +1,6 @@ use crate::{md_parser, types}; -use self::{parameters::get_parameters, return_type::get_return_type, url_parser::get_method_url}; +use self::{parameters::parse_parameters, return_type::get_return_type, url_parser::get_method_url}; mod description; mod object_types; @@ -39,6 +39,12 @@ pub struct ReturnTypeParameter { pub return_type: types::Type, } +pub fn parse_api_groups(content: &str) -> Vec { + parse_groups(extract_relevant_parts(md_parser::TokenTreeFactory::create( + content, + ))) +} + fn extract_relevant_parts(tree: md_parser::TokenTree) -> Vec { let relevant: Vec = tree .children @@ -56,12 +62,6 @@ fn extract_relevant_parts(tree: md_parser::TokenTree) -> Vec Vec { - parse_groups(extract_relevant_parts(md_parser::TokenTreeFactory::create( - content, - ))) -} - pub fn parse_groups(trees: Vec) -> Vec { trees.into_iter().map(parse_api_group).collect() } @@ -105,7 +105,7 @@ fn parse_api_method(child: md_parser::TokenTree) -> Option { fn to_api_method(child: &md_parser::TokenTree, name: &str) -> ApiMethod { let method_description = description::get_method_description(&child.content); let return_type = get_return_type(&child.content); - let parameters = get_parameters(&child.content); + let parameters = parse_parameters(&child.content); let method_url = get_method_url(&child.content); ApiMethod { diff --git a/qbittorrent-web-api-gen/src/parser/object_types.rs b/qbittorrent-web-api-gen/src/parser/object_types.rs index 1c013ad..1dd6d72 100644 --- a/qbittorrent-web-api-gen/src/parser/object_types.rs +++ b/qbittorrent-web-api-gen/src/parser/object_types.rs @@ -1,34 +1,28 @@ use std::collections::HashMap; -use crate::{md_parser::MdContent, parser::types}; +use crate::{md_parser, parser::types}; -use super::types::TypeDescription; - -pub fn get_object_types(content: &[MdContent]) -> HashMap { +pub fn parse_object_types( + content: &[md_parser::MdContent], +) -> HashMap { let mut output = HashMap::new(); let mut content_it = content.iter(); + while let Some(entry) = content_it.next() { - if let MdContent::Text(content) = entry { + if let md_parser::MdContent::Text(content) = entry { const POSSIBLE_VALUES_OF: &str = "Possible values of "; if content.contains(POSSIBLE_VALUES_OF) { // is empty content_it.next(); - if let Some(MdContent::Table(table)) = content_it.next() { - let enum_types = table - .rows - .iter() - .map(|row| types::TypeDescriptions { - value: row.columns[0].to_string(), - description: row.columns[1].to_string(), - }) - .collect(); + if let Some(md_parser::MdContent::Table(table)) = content_it.next() { + let enum_types = to_type_descriptions(table); let name = content .trim_start_matches(POSSIBLE_VALUES_OF) .replace('`', "") .replace(':', ""); - output.insert(name, TypeDescription { values: enum_types }); + output.insert(name, types::TypeDescription { values: enum_types }); } } } @@ -36,3 +30,14 @@ pub fn get_object_types(content: &[MdContent]) -> HashMap Vec { + table + .rows + .iter() + .map(|row| types::TypeDescriptions { + value: row.columns[0].to_string(), + description: row.columns[1].to_string(), + }) + .collect() +} diff --git a/qbittorrent-web-api-gen/src/parser/parameters.rs b/qbittorrent-web-api-gen/src/parser/parameters.rs index 4b65682..ebbfb07 100644 --- a/qbittorrent-web-api-gen/src/parser/parameters.rs +++ b/qbittorrent-web-api-gen/src/parser/parameters.rs @@ -1,12 +1,12 @@ use std::collections::HashMap; -use crate::{md_parser::MdContent, parser::types}; +use crate::{md_parser, parser::types}; -pub fn get_parameters(content: &[MdContent]) -> Option> { +pub fn parse_parameters(content: &[md_parser::MdContent]) -> Option> { let mut it = content .iter() .skip_while(|row| match row { - MdContent::Asterix(content) | MdContent::Text(content) => { + md_parser::MdContent::Asterix(content) | md_parser::MdContent::Text(content) => { !content.starts_with("Parameters:") } _ => true, @@ -17,7 +17,7 @@ pub fn get_parameters(content: &[MdContent]) -> Option> { .skip(2); let parameter_table = match it.next() { - Some(MdContent::Table(table)) => table, + Some(md_parser::MdContent::Table(table)) => table, _ => return None, }; @@ -27,20 +27,25 @@ pub fn get_parameters(content: &[MdContent]) -> Option> { let table = parameter_table .rows .iter() - .flat_map(|row| { - let description = row.columns.get(2).cloned(); - - match &row.columns.get(2) { - // If the description contains a default value it means that the parameter is optional. - Some(desc) if desc.contains("default: ") => { - // type defines a variable as default if it contains: _optional_ - let name_with_optional = format!("{} {}", row.columns[0], types::OPTIONAL); - types::Type::from(&row.columns[1], &name_with_optional, description, &type_map) - } - _ => types::Type::from(&row.columns[1], &row.columns[0], description, &type_map), - } - }) + .flat_map(|row| parse_parameter(row, &type_map)) .collect(); Some(table) } + +fn parse_parameter( + row: &md_parser::TableRow, + type_map: &HashMap, +) -> Option { + let description = row.columns.get(2).cloned(); + + match &row.columns.get(2) { + // If the description contains a default value it means that the parameter is optional. + Some(desc) if desc.contains("default: ") => { + // type defines a variable as default if it contains: _optional_ + let name_with_optional = format!("{} {}", row.columns[0], types::OPTIONAL); + types::Type::from(&row.columns[1], &name_with_optional, description, type_map) + } + _ => types::Type::from(&row.columns[1], &row.columns[0], description, type_map), + } +} diff --git a/qbittorrent-web-api-gen/src/parser/return_type.rs b/qbittorrent-web-api-gen/src/parser/return_type.rs index 940bd99..8c3fda3 100644 --- a/qbittorrent-web-api-gen/src/parser/return_type.rs +++ b/qbittorrent-web-api-gen/src/parser/return_type.rs @@ -1,6 +1,6 @@ use crate::{ md_parser::MdContent, - parser::{object_types::get_object_types, types, ReturnType, ReturnTypeParameter}, + parser::{object_types::parse_object_types, types, ReturnType, ReturnTypeParameter}, }; pub fn get_return_type(content: &[MdContent]) -> Option { @@ -18,7 +18,7 @@ pub fn get_return_type(content: &[MdContent]) -> Option { _ => None, })?; - let types = get_object_types(content); + let types = parse_object_types(content); let parameters = table .rows From 201ba025f3a7535993630957cf6314ad3308bc49 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 11:13:54 +0000 Subject: [PATCH 07/29] Extract api group and method --- .../src/parser/api_group.rs | 32 +++++++++++ .../src/parser/api_method.rs | 31 +++++++++++ .../src/parser/description.rs | 4 +- qbittorrent-web-api-gen/src/parser/mod.rs | 55 +------------------ .../src/parser/return_type.rs | 2 +- 5 files changed, 69 insertions(+), 55 deletions(-) create mode 100644 qbittorrent-web-api-gen/src/parser/api_group.rs create mode 100644 qbittorrent-web-api-gen/src/parser/api_method.rs diff --git a/qbittorrent-web-api-gen/src/parser/api_group.rs b/qbittorrent-web-api-gen/src/parser/api_group.rs new file mode 100644 index 0000000..cdb605f --- /dev/null +++ b/qbittorrent-web-api-gen/src/parser/api_group.rs @@ -0,0 +1,32 @@ +use crate::md_parser; + +use super::{ + api_method::parse_api_method, description::parse_group_description, url_parser::get_group_url, + ApiGroup, +}; + +pub fn parse_api_group(tree: md_parser::TokenTree) -> ApiGroup { + let methods = tree + .children + .into_iter() + .flat_map(parse_api_method) + .collect(); + + let group_description = parse_group_description(&tree.content); + let group_url = get_group_url(&tree.content); + + let name = tree + .title + .unwrap() + .to_lowercase() + .trim_end_matches("(experimental)") + .trim() + .replace(' ', "_"); + + ApiGroup { + name, + methods, + description: group_description, + url: group_url, + } +} diff --git a/qbittorrent-web-api-gen/src/parser/api_method.rs b/qbittorrent-web-api-gen/src/parser/api_method.rs new file mode 100644 index 0000000..e58dd9c --- /dev/null +++ b/qbittorrent-web-api-gen/src/parser/api_method.rs @@ -0,0 +1,31 @@ +use crate::md_parser; + +use super::{ + description::parse_method_description, parameters::parse_parameters, + return_type::parse_return_type, url_parser::get_method_url, util, ApiMethod, +}; + +pub fn parse_api_method(child: md_parser::TokenTree) -> Option { + util::find_content_starts_with(&child.content, "Name: ") + .map(|name| { + name.trim_start_matches("Name: ") + .trim_matches('`') + .to_string() + }) + .map(|name| to_api_method(&child, &name)) +} + +fn to_api_method(child: &md_parser::TokenTree, name: &str) -> ApiMethod { + let method_description = parse_method_description(&child.content); + let return_type = parse_return_type(&child.content); + let parameters = parse_parameters(&child.content); + let method_url = get_method_url(&child.content); + + ApiMethod { + name: name.to_string(), + description: method_description, + parameters, + return_type, + url: method_url, + } +} diff --git a/qbittorrent-web-api-gen/src/parser/description.rs b/qbittorrent-web-api-gen/src/parser/description.rs index 0819ec2..52577ed 100644 --- a/qbittorrent-web-api-gen/src/parser/description.rs +++ b/qbittorrent-web-api-gen/src/parser/description.rs @@ -1,6 +1,6 @@ use crate::md_parser::MdContent; -pub fn get_group_description(content: &[MdContent]) -> Option { +pub fn parse_group_description(content: &[MdContent]) -> Option { let return_desc = content .iter() .map(|row| row.inner_value_as_string()) @@ -16,7 +16,7 @@ pub fn get_group_description(content: &[MdContent]) -> Option { } } -pub fn get_method_description(content: &[MdContent]) -> Option { +pub fn parse_method_description(content: &[MdContent]) -> Option { let return_desc = content .iter() // skip until we get to the "Returns:" text diff --git a/qbittorrent-web-api-gen/src/parser/mod.rs b/qbittorrent-web-api-gen/src/parser/mod.rs index d1f9b2d..c198ff6 100644 --- a/qbittorrent-web-api-gen/src/parser/mod.rs +++ b/qbittorrent-web-api-gen/src/parser/mod.rs @@ -1,7 +1,9 @@ use crate::{md_parser, types}; -use self::{parameters::parse_parameters, return_type::get_return_type, url_parser::get_method_url}; +use self::api_group::parse_api_group; +mod api_group; +mod api_method; mod description; mod object_types; mod parameters; @@ -66,57 +68,6 @@ pub fn parse_groups(trees: Vec) -> Vec { trees.into_iter().map(parse_api_group).collect() } -fn parse_api_group(tree: md_parser::TokenTree) -> ApiGroup { - let methods = tree - .children - .into_iter() - .flat_map(parse_api_method) - .collect(); - - let group_description = description::get_group_description(&tree.content); - let group_url = url_parser::get_group_url(&tree.content); - - let name = tree - .title - .unwrap() - .to_lowercase() - .trim_end_matches("(experimental)") - .trim() - .replace(' ', "_"); - - ApiGroup { - name, - methods, - description: group_description, - url: group_url, - } -} - -fn parse_api_method(child: md_parser::TokenTree) -> Option { - util::find_content_starts_with(&child.content, "Name: ") - .map(|name| { - name.trim_start_matches("Name: ") - .trim_matches('`') - .to_string() - }) - .map(|name| to_api_method(&child, &name)) -} - -fn to_api_method(child: &md_parser::TokenTree, name: &str) -> ApiMethod { - let method_description = description::get_method_description(&child.content); - let return_type = get_return_type(&child.content); - let parameters = parse_parameters(&child.content); - let method_url = get_method_url(&child.content); - - ApiMethod { - name: name.to_string(), - description: method_description, - parameters, - return_type, - url: method_url, - } -} - #[cfg(test)] mod tests { use super::*; diff --git a/qbittorrent-web-api-gen/src/parser/return_type.rs b/qbittorrent-web-api-gen/src/parser/return_type.rs index 8c3fda3..ab7d491 100644 --- a/qbittorrent-web-api-gen/src/parser/return_type.rs +++ b/qbittorrent-web-api-gen/src/parser/return_type.rs @@ -3,7 +3,7 @@ use crate::{ parser::{object_types::parse_object_types, types, ReturnType, ReturnTypeParameter}, }; -pub fn get_return_type(content: &[MdContent]) -> Option { +pub fn parse_return_type(content: &[MdContent]) -> Option { let table = content .iter() // The response is a ... <-- Trying to find this line From aaf315320526578a79ed2946e05bc8c929c151f1 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 11:35:11 +0000 Subject: [PATCH 08/29] Group by function --- qbittorrent-web-api-gen/src/generate/mod.rs | 5 +- .../src/parser/api_group.rs | 32 ------------ .../src/parser/group/description.rs | 17 +++++++ .../parser/{ => group/method}/description.rs | 16 ------ .../{api_method.rs => group/method/mod.rs} | 30 ++++++++--- .../parser/{ => group/method}/return_type.rs | 8 ++- .../src/parser/group/method/url.rs | 9 ++++ .../src/parser/group/mod.rs | 40 +++++++++++++++ .../src/parser/group/url.rs | 14 +++++ qbittorrent-web-api-gen/src/parser/mod.rs | 51 +++++-------------- .../src/parser/url_parser.rs | 22 -------- qbittorrent-web-api-gen/src/parser/util.rs | 16 +----- 12 files changed, 130 insertions(+), 130 deletions(-) delete mode 100644 qbittorrent-web-api-gen/src/parser/api_group.rs create mode 100644 qbittorrent-web-api-gen/src/parser/group/description.rs rename qbittorrent-web-api-gen/src/parser/{ => group/method}/description.rs (73%) rename qbittorrent-web-api-gen/src/parser/{api_method.rs => group/method/mod.rs} (53%) rename qbittorrent-web-api-gen/src/parser/{ => group/method}/return_type.rs (92%) create mode 100644 qbittorrent-web-api-gen/src/parser/group/method/url.rs create mode 100644 qbittorrent-web-api-gen/src/parser/group/mod.rs create mode 100644 qbittorrent-web-api-gen/src/parser/group/url.rs delete mode 100644 qbittorrent-web-api-gen/src/parser/url_parser.rs diff --git a/qbittorrent-web-api-gen/src/generate/mod.rs b/qbittorrent-web-api-gen/src/generate/mod.rs index 6ef8cad..2a29a99 100644 --- a/qbittorrent-web-api-gen/src/generate/mod.rs +++ b/qbittorrent-web-api-gen/src/generate/mod.rs @@ -7,14 +7,15 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; use regex::Regex; -use crate::{parser, types, util}; +use crate::{md_parser, parser, types, util}; use self::skeleton::{auth_ident, generate_skeleton}; pub fn generate(ast: &syn::DeriveInput, api_content: &str) -> TokenStream { let ident = &ast.ident; - let api_groups = parser::parse_api_groups(api_content); + let token_tree = md_parser::TokenTreeFactory::create(api_content); + let api_groups = parser::parse_api_groups(token_tree); let skeleton = generate_skeleton(ident); let groups = generate_groups(api_groups); diff --git a/qbittorrent-web-api-gen/src/parser/api_group.rs b/qbittorrent-web-api-gen/src/parser/api_group.rs deleted file mode 100644 index cdb605f..0000000 --- a/qbittorrent-web-api-gen/src/parser/api_group.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::md_parser; - -use super::{ - api_method::parse_api_method, description::parse_group_description, url_parser::get_group_url, - ApiGroup, -}; - -pub fn parse_api_group(tree: md_parser::TokenTree) -> ApiGroup { - let methods = tree - .children - .into_iter() - .flat_map(parse_api_method) - .collect(); - - let group_description = parse_group_description(&tree.content); - let group_url = get_group_url(&tree.content); - - let name = tree - .title - .unwrap() - .to_lowercase() - .trim_end_matches("(experimental)") - .trim() - .replace(' ', "_"); - - ApiGroup { - name, - methods, - description: group_description, - url: group_url, - } -} diff --git a/qbittorrent-web-api-gen/src/parser/group/description.rs b/qbittorrent-web-api-gen/src/parser/group/description.rs new file mode 100644 index 0000000..c47d531 --- /dev/null +++ b/qbittorrent-web-api-gen/src/parser/group/description.rs @@ -0,0 +1,17 @@ +use crate::md_parser; + +pub fn parse_group_description(content: &[md_parser::MdContent]) -> Option { + let return_desc = content + .iter() + .map(|row| row.inner_value_as_string()) + .collect::>() + .join("\n") + .trim() + .to_string(); + + if return_desc.is_empty() { + None + } else { + Some(return_desc) + } +} diff --git a/qbittorrent-web-api-gen/src/parser/description.rs b/qbittorrent-web-api-gen/src/parser/group/method/description.rs similarity index 73% rename from qbittorrent-web-api-gen/src/parser/description.rs rename to qbittorrent-web-api-gen/src/parser/group/method/description.rs index 52577ed..73f3fce 100644 --- a/qbittorrent-web-api-gen/src/parser/description.rs +++ b/qbittorrent-web-api-gen/src/parser/group/method/description.rs @@ -1,21 +1,5 @@ use crate::md_parser::MdContent; -pub fn parse_group_description(content: &[MdContent]) -> Option { - let return_desc = content - .iter() - .map(|row| row.inner_value_as_string()) - .collect::>() - .join("\n") - .trim() - .to_string(); - - if return_desc.is_empty() { - None - } else { - Some(return_desc) - } -} - pub fn parse_method_description(content: &[MdContent]) -> Option { let return_desc = content .iter() diff --git a/qbittorrent-web-api-gen/src/parser/api_method.rs b/qbittorrent-web-api-gen/src/parser/group/method/mod.rs similarity index 53% rename from qbittorrent-web-api-gen/src/parser/api_method.rs rename to qbittorrent-web-api-gen/src/parser/group/method/mod.rs index e58dd9c..22e3584 100644 --- a/qbittorrent-web-api-gen/src/parser/api_method.rs +++ b/qbittorrent-web-api-gen/src/parser/group/method/mod.rs @@ -1,18 +1,36 @@ -use crate::md_parser; +mod description; +mod return_type; +mod url; -use super::{ - description::parse_method_description, parameters::parse_parameters, - return_type::parse_return_type, url_parser::get_method_url, util, ApiMethod, +use crate::{ + md_parser, + parser::{parameters::parse_parameters, util}, + types, }; -pub fn parse_api_method(child: md_parser::TokenTree) -> Option { +use self::{ + description::parse_method_description, + return_type::{parse_return_type, ReturnType}, + url::get_method_url, +}; + +#[derive(Debug)] +pub struct ApiMethod { + pub name: String, + pub description: Option, + pub parameters: Option>, + pub return_type: Option, + pub url: String, +} + +pub fn parse_api_method(child: &md_parser::TokenTree) -> Option { util::find_content_starts_with(&child.content, "Name: ") .map(|name| { name.trim_start_matches("Name: ") .trim_matches('`') .to_string() }) - .map(|name| to_api_method(&child, &name)) + .map(|name| to_api_method(child, &name)) } fn to_api_method(child: &md_parser::TokenTree, name: &str) -> ApiMethod { diff --git a/qbittorrent-web-api-gen/src/parser/return_type.rs b/qbittorrent-web-api-gen/src/parser/group/method/return_type.rs similarity index 92% rename from qbittorrent-web-api-gen/src/parser/return_type.rs rename to qbittorrent-web-api-gen/src/parser/group/method/return_type.rs index ab7d491..293f1fa 100644 --- a/qbittorrent-web-api-gen/src/parser/return_type.rs +++ b/qbittorrent-web-api-gen/src/parser/group/method/return_type.rs @@ -1,8 +1,14 @@ use crate::{ md_parser::MdContent, - parser::{object_types::parse_object_types, types, ReturnType, ReturnTypeParameter}, + parser::{object_types::parse_object_types, types, ReturnTypeParameter}, }; +#[derive(Debug)] +pub struct ReturnType { + pub is_list: bool, + pub parameters: Vec, +} + pub fn parse_return_type(content: &[MdContent]) -> Option { let table = content .iter() diff --git a/qbittorrent-web-api-gen/src/parser/group/method/url.rs b/qbittorrent-web-api-gen/src/parser/group/method/url.rs new file mode 100644 index 0000000..5358362 --- /dev/null +++ b/qbittorrent-web-api-gen/src/parser/group/method/url.rs @@ -0,0 +1,9 @@ +use crate::{md_parser, parser::util}; + +pub fn get_method_url(content: &[md_parser::MdContent]) -> String { + const START: &str = "Name: "; + + util::find_content_starts_with(content, START) + .map(|text| text.trim_start_matches(START).trim_matches('`').to_string()) + .expect("Could find method url") +} diff --git a/qbittorrent-web-api-gen/src/parser/group/mod.rs b/qbittorrent-web-api-gen/src/parser/group/mod.rs new file mode 100644 index 0000000..9debf71 --- /dev/null +++ b/qbittorrent-web-api-gen/src/parser/group/mod.rs @@ -0,0 +1,40 @@ +mod description; +mod method; +mod url; + +use crate::md_parser; + +use self::{description::parse_group_description, method::parse_api_method, url::get_group_url}; + +#[derive(Debug)] +pub struct ApiGroup { + pub name: String, + pub methods: Vec, + pub description: Option, + pub url: String, +} + +pub use method::ApiMethod; + +pub fn parse_api_group(tree: &md_parser::TokenTree) -> ApiGroup { + let methods = tree.children.iter().flat_map(parse_api_method).collect(); + + let group_description = parse_group_description(&tree.content); + let group_url = get_group_url(&tree.content); + + let name = tree + .title + .clone() + .unwrap() + .to_lowercase() + .trim_end_matches("(experimental)") + .trim() + .replace(' ', "_"); + + ApiGroup { + name, + methods, + description: group_description, + url: group_url, + } +} diff --git a/qbittorrent-web-api-gen/src/parser/group/url.rs b/qbittorrent-web-api-gen/src/parser/group/url.rs new file mode 100644 index 0000000..993c9b8 --- /dev/null +++ b/qbittorrent-web-api-gen/src/parser/group/url.rs @@ -0,0 +1,14 @@ +use regex::Regex; + +use crate::{md_parser, parser::util}; + +pub fn get_group_url(content: &[md_parser::MdContent]) -> String { + let row = util::find_content_contains(content, "API methods are under") + .expect("Could not find api method"); + + let re = Regex::new(r#"All (?:\w+\s?)+ API methods are under "(\w+)", e.g."#) + .expect("Failed to create regex"); + + let res = re.captures(&row).expect("Failed find capture"); + res[1].to_string() +} diff --git a/qbittorrent-web-api-gen/src/parser/mod.rs b/qbittorrent-web-api-gen/src/parser/mod.rs index c198ff6..a386430 100644 --- a/qbittorrent-web-api-gen/src/parser/mod.rs +++ b/qbittorrent-web-api-gen/src/parser/mod.rs @@ -1,39 +1,12 @@ use crate::{md_parser, types}; -use self::api_group::parse_api_group; +use self::group::parse_api_group; -mod api_group; -mod api_method; -mod description; +mod group; mod object_types; mod parameters; -mod return_type; -mod url_parser; mod util; -#[derive(Debug)] -pub struct ApiGroup { - pub name: String, - pub methods: Vec, - pub description: Option, - pub url: String, -} - -#[derive(Debug)] -pub struct ApiMethod { - pub name: String, - pub description: Option, - pub parameters: Option>, - pub return_type: Option, - pub url: String, -} - -#[derive(Debug)] -pub struct ReturnType { - pub is_list: bool, - pub parameters: Vec, -} - #[derive(Debug)] pub struct ReturnTypeParameter { pub name: String, @@ -41,10 +14,18 @@ pub struct ReturnTypeParameter { pub return_type: types::Type, } -pub fn parse_api_groups(content: &str) -> Vec { - parse_groups(extract_relevant_parts(md_parser::TokenTreeFactory::create( - content, - ))) +pub use group::ApiGroup; +pub use group::ApiMethod; + +pub fn parse_api_groups(token_tree: md_parser::TokenTree) -> Vec { + parse_groups(extract_relevant_parts(token_tree)) +} + +pub fn parse_groups(trees: Vec) -> Vec { + trees + .into_iter() + .map(|tree| parse_api_group(&tree)) + .collect() } fn extract_relevant_parts(tree: md_parser::TokenTree) -> Vec { @@ -64,10 +45,6 @@ fn extract_relevant_parts(tree: md_parser::TokenTree) -> Vec) -> Vec { - trees.into_iter().map(parse_api_group).collect() -} - #[cfg(test)] mod tests { use super::*; diff --git a/qbittorrent-web-api-gen/src/parser/url_parser.rs b/qbittorrent-web-api-gen/src/parser/url_parser.rs deleted file mode 100644 index 43a5ac7..0000000 --- a/qbittorrent-web-api-gen/src/parser/url_parser.rs +++ /dev/null @@ -1,22 +0,0 @@ -use regex::Regex; - -use crate::{md_parser::MdContent, parser::util}; - -pub fn get_group_url(content: &[MdContent]) -> String { - let row = util::find_content_contains(content, "API methods are under") - .expect("Could not find api method"); - - let re = Regex::new(r#"All (?:\w+\s?)+ API methods are under "(\w+)", e.g."#) - .expect("Failed to create regex"); - - let res = re.captures(&row).expect("Failed find capture"); - res[1].to_string() -} - -pub fn get_method_url(content: &[MdContent]) -> String { - const START: &str = "Name: "; - - util::find_content_starts_with(content, START) - .map(|text| text.trim_start_matches(START).trim_matches('`').to_string()) - .expect("Could find method url") -} diff --git a/qbittorrent-web-api-gen/src/parser/util.rs b/qbittorrent-web-api-gen/src/parser/util.rs index 8cd9b9d..71e9160 100644 --- a/qbittorrent-web-api-gen/src/parser/util.rs +++ b/qbittorrent-web-api-gen/src/parser/util.rs @@ -2,26 +2,14 @@ use crate::md_parser::MdContent; pub fn find_content_starts_with(content: &[MdContent], starts_with: &str) -> Option { content.iter().find_map(|row| match row { - MdContent::Text(content) => { - if content.starts_with(starts_with) { - Some(content.into()) - } else { - None - } - } + MdContent::Text(content) if content.starts_with(starts_with) => Some(content.into()), _ => None, }) } pub fn find_content_contains(content: &[MdContent], contains: &str) -> Option { content.iter().find_map(|row| match row { - MdContent::Text(content) => { - if content.contains(contains) { - Some(content.into()) - } else { - None - } - } + MdContent::Text(content) if content.contains(contains) => Some(content.into()), _ => None, }) } From 11e40156e8aef743e8e2aae31d7eca72b6b64a8e Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 11:41:03 +0000 Subject: [PATCH 09/29] Move files to correct dirs --- qbittorrent-web-api-gen/src/generate/mod.rs | 3 +- .../src/generate/skeleton.rs | 2 +- .../src/{ => generate}/util.rs | 0 qbittorrent-web-api-gen/src/lib.rs | 1 - .../src/parser/group/method/mod.rs | 8 ++-- .../parser/{ => group/method}/parameters.rs | 0 .../src/parser/group/method/return_type.rs | 46 ++++++++++++++++++- qbittorrent-web-api-gen/src/parser/mod.rs | 2 - .../src/parser/object_types.rs | 43 ----------------- 9 files changed, 50 insertions(+), 55 deletions(-) rename qbittorrent-web-api-gen/src/{ => generate}/util.rs (100%) rename qbittorrent-web-api-gen/src/parser/{ => group/method}/parameters.rs (100%) delete mode 100644 qbittorrent-web-api-gen/src/parser/object_types.rs diff --git a/qbittorrent-web-api-gen/src/generate/mod.rs b/qbittorrent-web-api-gen/src/generate/mod.rs index 2a29a99..5f123a3 100644 --- a/qbittorrent-web-api-gen/src/generate/mod.rs +++ b/qbittorrent-web-api-gen/src/generate/mod.rs @@ -1,4 +1,5 @@ mod skeleton; +mod util; use std::{collections::HashMap, vec::Vec}; @@ -7,7 +8,7 @@ use proc_macro2::TokenStream; use quote::{format_ident, quote}; use regex::Regex; -use crate::{md_parser, parser, types, util}; +use crate::{md_parser, parser, types}; use self::skeleton::{auth_ident, generate_skeleton}; diff --git a/qbittorrent-web-api-gen/src/generate/skeleton.rs b/qbittorrent-web-api-gen/src/generate/skeleton.rs index e08050e..e2118a7 100644 --- a/qbittorrent-web-api-gen/src/generate/skeleton.rs +++ b/qbittorrent-web-api-gen/src/generate/skeleton.rs @@ -1,6 +1,6 @@ use quote::quote; -use crate::util; +use super::util; pub const AUTH_IDENT: &str = "Authenticated"; diff --git a/qbittorrent-web-api-gen/src/util.rs b/qbittorrent-web-api-gen/src/generate/util.rs similarity index 100% rename from qbittorrent-web-api-gen/src/util.rs rename to qbittorrent-web-api-gen/src/generate/util.rs diff --git a/qbittorrent-web-api-gen/src/lib.rs b/qbittorrent-web-api-gen/src/lib.rs index 0d0814c..5953ad2 100644 --- a/qbittorrent-web-api-gen/src/lib.rs +++ b/qbittorrent-web-api-gen/src/lib.rs @@ -2,7 +2,6 @@ mod generate; mod md_parser; mod parser; mod types; -mod util; use proc_macro::TokenStream; use syn::parse_macro_input; diff --git a/qbittorrent-web-api-gen/src/parser/group/method/mod.rs b/qbittorrent-web-api-gen/src/parser/group/method/mod.rs index 22e3584..83aaf67 100644 --- a/qbittorrent-web-api-gen/src/parser/group/method/mod.rs +++ b/qbittorrent-web-api-gen/src/parser/group/method/mod.rs @@ -1,15 +1,13 @@ mod description; +mod parameters; mod return_type; mod url; -use crate::{ - md_parser, - parser::{parameters::parse_parameters, util}, - types, -}; +use crate::{md_parser, parser::util, types}; use self::{ description::parse_method_description, + parameters::parse_parameters, return_type::{parse_return_type, ReturnType}, url::get_method_url, }; diff --git a/qbittorrent-web-api-gen/src/parser/parameters.rs b/qbittorrent-web-api-gen/src/parser/group/method/parameters.rs similarity index 100% rename from qbittorrent-web-api-gen/src/parser/parameters.rs rename to qbittorrent-web-api-gen/src/parser/group/method/parameters.rs diff --git a/qbittorrent-web-api-gen/src/parser/group/method/return_type.rs b/qbittorrent-web-api-gen/src/parser/group/method/return_type.rs index 293f1fa..6ff8342 100644 --- a/qbittorrent-web-api-gen/src/parser/group/method/return_type.rs +++ b/qbittorrent-web-api-gen/src/parser/group/method/return_type.rs @@ -1,6 +1,8 @@ +use std::collections::HashMap; + use crate::{ - md_parser::MdContent, - parser::{object_types::parse_object_types, types, ReturnTypeParameter}, + md_parser::{self, MdContent}, + parser::{types, ReturnTypeParameter}, }; #[derive(Debug)] @@ -56,3 +58,43 @@ pub fn parse_return_type(content: &[MdContent]) -> Option { is_list, }) } + +pub fn parse_object_types( + content: &[md_parser::MdContent], +) -> HashMap { + let mut output = HashMap::new(); + let mut content_it = content.iter(); + + while let Some(entry) = content_it.next() { + if let md_parser::MdContent::Text(content) = entry { + const POSSIBLE_VALUES_OF: &str = "Possible values of "; + if content.contains(POSSIBLE_VALUES_OF) { + // is empty + content_it.next(); + if let Some(md_parser::MdContent::Table(table)) = content_it.next() { + let enum_types = to_type_descriptions(table); + + let name = content + .trim_start_matches(POSSIBLE_VALUES_OF) + .replace('`', "") + .replace(':', ""); + + output.insert(name, types::TypeDescription { values: enum_types }); + } + } + } + } + + output +} + +fn to_type_descriptions(table: &md_parser::Table) -> Vec { + table + .rows + .iter() + .map(|row| types::TypeDescriptions { + value: row.columns[0].to_string(), + description: row.columns[1].to_string(), + }) + .collect() +} diff --git a/qbittorrent-web-api-gen/src/parser/mod.rs b/qbittorrent-web-api-gen/src/parser/mod.rs index a386430..35f7a2e 100644 --- a/qbittorrent-web-api-gen/src/parser/mod.rs +++ b/qbittorrent-web-api-gen/src/parser/mod.rs @@ -3,8 +3,6 @@ use crate::{md_parser, types}; use self::group::parse_api_group; mod group; -mod object_types; -mod parameters; mod util; #[derive(Debug)] diff --git a/qbittorrent-web-api-gen/src/parser/object_types.rs b/qbittorrent-web-api-gen/src/parser/object_types.rs deleted file mode 100644 index 1dd6d72..0000000 --- a/qbittorrent-web-api-gen/src/parser/object_types.rs +++ /dev/null @@ -1,43 +0,0 @@ -use std::collections::HashMap; - -use crate::{md_parser, parser::types}; - -pub fn parse_object_types( - content: &[md_parser::MdContent], -) -> HashMap { - let mut output = HashMap::new(); - let mut content_it = content.iter(); - - while let Some(entry) = content_it.next() { - if let md_parser::MdContent::Text(content) = entry { - const POSSIBLE_VALUES_OF: &str = "Possible values of "; - if content.contains(POSSIBLE_VALUES_OF) { - // is empty - content_it.next(); - if let Some(md_parser::MdContent::Table(table)) = content_it.next() { - let enum_types = to_type_descriptions(table); - - let name = content - .trim_start_matches(POSSIBLE_VALUES_OF) - .replace('`', "") - .replace(':', ""); - - output.insert(name, types::TypeDescription { values: enum_types }); - } - } - } - } - - output -} - -fn to_type_descriptions(table: &md_parser::Table) -> Vec { - table - .rows - .iter() - .map(|row| types::TypeDescriptions { - value: row.columns[0].to_string(), - description: row.columns[1].to_string(), - }) - .collect() -} From a383e60294a37d5d934d4c3b972cbca5f06ae608 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 11:51:56 +0000 Subject: [PATCH 10/29] Split generation --- .../src/generate/group/method/mod.rs | 413 ++++++++++++++++ .../src/generate/group/mod.rs | 49 ++ qbittorrent-web-api-gen/src/generate/mod.rs | 457 +----------------- 3 files changed, 466 insertions(+), 453 deletions(-) create mode 100644 qbittorrent-web-api-gen/src/generate/group/method/mod.rs create mode 100644 qbittorrent-web-api-gen/src/generate/group/mod.rs diff --git a/qbittorrent-web-api-gen/src/generate/group/method/mod.rs b/qbittorrent-web-api-gen/src/generate/group/method/mod.rs new file mode 100644 index 0000000..449695f --- /dev/null +++ b/qbittorrent-web-api-gen/src/generate/group/method/mod.rs @@ -0,0 +1,413 @@ +use std::collections::HashMap; + +use crate::{generate::util, parser, types}; +use case::CaseExt; +use quote::{format_ident, quote}; +use regex::Regex; + +pub fn generate_methods( + group: &parser::ApiGroup, + auth: &syn::Ident, + group_name_camel: &syn::Ident, +) -> proc_macro2::TokenStream { + let methods_and_param_structs = group + .methods + .iter() + .map(|method| generate_method(group, method)); + + let methods = methods_and_param_structs.clone().map(|(method, ..)| method); + let structs = methods_and_param_structs.flat_map(|(_, s)| s); + + quote! { + impl <'a> #group_name_camel<'a> { + pub fn new(auth: &'a #auth) -> Self { + Self { auth } + } + + #(#methods)* + } + + #(#structs)* + } +} + +fn generate_method( + group: &parser::ApiGroup, + method: &parser::ApiMethod, +) -> (proc_macro2::TokenStream, Option) { + let method_name = util::to_ident(&method.name.to_snake()); + let url = format!("/api/v2/{}/{}", group.url, method.url); + + match &method.parameters { + Some(params) => create_method_with_params(group, method, params, &method_name, &url), + None => create_method_without_params(group, method, method_name, &url), + } +} + +fn create_method_without_params( + group: &parser::ApiGroup, + method: &parser::ApiMethod, + method_name: proc_macro2::Ident, + url: &str, +) -> (proc_macro2::TokenStream, Option) { + match create_return_type(group, method) { + Some((return_type_name, return_type)) => ( + util::add_docs( + &method.description, + quote! { + pub async fn #method_name(&self) -> Result<#return_type_name> { + let res = self.auth + .authenticated_client(#url) + .send() + .await? + .json::<#return_type_name>() + .await?; + + Ok(res) + } + }, + ), + Some(return_type), + ), + None => ( + util::add_docs( + &method.description, + quote! { + pub async fn #method_name(&self) -> Result { + let res = self.auth + .authenticated_client(#url) + .send() + .await? + .text() + .await?; + + Ok(res) + } + }, + ), // assume that all methods without a return type returns a string + None, + ), + } +} + +fn create_method_with_params( + group: &parser::ApiGroup, + method: &parser::ApiMethod, + params: &[types::Type], + method_name: &proc_macro2::Ident, + url: &str, +) -> (proc_macro2::TokenStream, Option) { + let parameter_type = util::to_ident(&format!( + "{}{}Parameters", + group.name.to_camel(), + method.name.to_camel() + )); + + let mandatory_params = params + .iter() + .filter(|param| !param.get_type_info().is_optional); + + let mandatory_param_args = mandatory_params.clone().map(|param| { + let name = util::to_ident(¶m.get_type_info().name.to_snake()); + let t = util::to_ident(¶m.to_borrowed_type()); + + if param.should_borrow() { + quote! { + #name: &#t + } + } else { + quote! { + #name: #t + } + } + }); + + let mandatory_param_names = mandatory_params.clone().map(|param| { + let name = util::to_ident(¶m.get_type_info().name.to_snake()); + + quote! { + #name + } + }); + + let mandatory_param_args_clone = mandatory_param_args.clone(); + let mandatory_param_form_build = mandatory_params.map(|param| { + let n = ¶m.get_type_info().name; + let name = util::to_ident(&n.to_snake()); + + quote! { + let form = form.text(#n, #name.to_string()); + } + }); + + let optional_params = params + .iter() + .filter(|param| param.get_type_info().is_optional) + .map(|param| { + let n = ¶m.get_type_info().name; + let name = util::to_ident(&n.to_snake()); + let t = util::to_ident(¶m.to_borrowed_type()); + + let method = if param.should_borrow() { + quote! { + pub fn #name(mut self, value: &#t) -> Self { + self.form = self.form.text(#n, value.to_string()); + self + } + } + } else { + quote! { + pub fn #name(mut self, value: #t) -> Self { + self.form = self.form.text(#n, value.to_string()); + self + } + } + }; + + util::add_docs(¶m.get_type_info().description, method) + }); + + let group_name = util::to_ident(&group.name.to_camel()); + + let send = match create_return_type(group, method) { + Some((return_type_name, return_type)) => { + quote! { + impl<'a> #parameter_type<'a> { + fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { + let form = reqwest::multipart::Form::new(); + #(#mandatory_param_form_build)* + Self { group, form } + } + + #(#optional_params)* + + pub async fn send(self) -> Result<#return_type_name> { + let res = self.group + .auth + .authenticated_client(#url) + .multipart(self.form) + .send() + .await? + .json::<#return_type_name>() + .await?; + + Ok(res) + } + } + + #return_type + } + } + None => { + quote! { + impl<'a> #parameter_type<'a> { + fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { + let form = reqwest::multipart::Form::new(); + #(#mandatory_param_form_build)* + Self { group, form } + } + + #(#optional_params)* + + pub async fn send(self) -> Result { + let res = self.group + .auth + .authenticated_client(#url) + .multipart(self.form) + .send() + .await? + .text() + .await?; + + Ok(res) + } + } + } + } + }; + + ( + util::add_docs( + &method.description, + quote! { + pub fn #method_name(&self, #(#mandatory_param_args_clone),*) -> #parameter_type { + #parameter_type::new(self, #(#mandatory_param_names),*) + } + }, + ), + Some(quote! { + pub struct #parameter_type<'a> { + group: &'a #group_name<'a>, + form: reqwest::multipart::Form, + } + + #send + }), + ) +} + +fn create_return_type( + group: &parser::ApiGroup, + method: &parser::ApiMethod, +) -> Option<(proc_macro2::TokenStream, proc_macro2::TokenStream)> { + let return_type = match &method.return_type { + Some(t) => t, + None => return None, + }; + + let to_enum_name = |name: &str| { + format!( + "{}{}{}", + group.name.to_camel(), + method.name.to_camel(), + name.to_camel() + ) + }; + + let enum_types_with_names = + return_type + .parameters + .iter() + .flat_map(|parameter| match ¶meter.return_type { + types::Type::Number(types::TypeInfo { + ref name, + type_description: Some(type_description), + .. + }) => { + let enum_fields = type_description.values.iter().map(|value| { + let v = &value.value; + let re = Regex::new(r#"\(.*\)"#).unwrap(); + let desc = &value + .description + .replace(' ', "_") + .replace('-', "_") + .replace(',', "_"); + let desc_without_parentheses = re.replace_all(desc, ""); + let ident = util::to_ident(&desc_without_parentheses.to_camel()); + + util::add_docs( + &Some(value.description.clone()), + quote! { + #[serde(rename = #v)] + #ident + }, + ) + }); + + let enum_name = util::to_ident(&to_enum_name(name)); + + Some(( + name, + quote! { + #[allow(clippy::enum_variant_names)] + #[derive(Debug, Deserialize, PartialEq, Eq)] + pub enum #enum_name { + #(#enum_fields,)* + } + }, + )) + } + types::Type::String(types::TypeInfo { + ref name, + type_description: Some(type_description), + .. + }) => { + let enum_fields = type_description.values.iter().map(|type_description| { + let value = &type_description.value; + let value_as_ident = util::to_ident(&value.to_camel()); + + util::add_docs( + &Some(type_description.description.clone()), + quote! { + #[serde(rename = #value)] + #value_as_ident + }, + ) + }); + + let enum_name = util::to_ident(&to_enum_name(name)); + + Some(( + name, + quote! { + #[allow(clippy::enum_variant_names)] + #[derive(Debug, Deserialize, PartialEq, Eq)] + pub enum #enum_name { + #(#enum_fields,)* + } + }, + )) + } + _ => None, + }); + + let enum_names: HashMap<&String, String> = enum_types_with_names + .clone() + .map(|(enum_name, _)| (enum_name, to_enum_name(enum_name))) + .collect(); + + let enum_types = enum_types_with_names.map(|(_, enum_type)| enum_type); + + let parameters = return_type.parameters.iter().map(|parameter| { + let namestr = ¶meter.name; + let name = util::to_ident(&namestr.to_snake().replace("__", "_")); + let rtype = if let Some(enum_type) = enum_names.get(namestr) { + util::to_ident(enum_type) + } else { + util::to_ident(¶meter.return_type.to_owned_type()) + }; + let type_info = parameter.return_type.get_type_info(); + + let rtype_as_quote = if type_info.is_list { + quote! { + std::vec::Vec<#rtype> + } + } else { + quote! { + #rtype + } + }; + + // "type" is a reserved keyword in Rust, so we use a different name. + if namestr == "type" { + let non_reserved_name = format_ident!("t_{}", name); + quote! { + #[serde(rename = #namestr)] + pub #non_reserved_name: #rtype_as_quote + } + } else { + quote! { + #[serde(rename = #namestr)] + pub #name: #rtype_as_quote + } + } + }); + + let return_type_name = util::to_ident(&format!( + "{}{}Result", + &group.name.to_camel(), + &method.name.to_camel() + )); + + let result_type = if return_type.is_list { + quote! { + std::vec::Vec<#return_type_name> + } + } else { + quote! { + #return_type_name + } + }; + + Some(( + result_type, + quote! { + #[derive(Debug, Deserialize)] + pub struct #return_type_name { + #(#parameters,)* + } + + #(#enum_types)* + }, + )) +} diff --git a/qbittorrent-web-api-gen/src/generate/group/mod.rs b/qbittorrent-web-api-gen/src/generate/group/mod.rs new file mode 100644 index 0000000..bf4c911 --- /dev/null +++ b/qbittorrent-web-api-gen/src/generate/group/mod.rs @@ -0,0 +1,49 @@ +mod method; + +use crate::parser; +use case::CaseExt; +use quote::quote; + +use self::method::generate_methods; + +use super::{skeleton::auth_ident, util}; + +pub fn generate_groups(groups: Vec) -> proc_macro2::TokenStream { + let gr = groups + .iter() + // implemented manually + .filter(|group| group.name != "authentication") + .map(generate_group); + + quote! { + #(#gr)* + } +} + +fn generate_group(group: &parser::ApiGroup) -> proc_macro2::TokenStream { + let group_name_camel = util::to_ident(&group.name.to_camel()); + let group_name_snake = util::to_ident(&group.name.to_snake()); + let auth = auth_ident(); + let methods = generate_methods(group, &auth, &group_name_camel); + + let group_method = util::add_docs( + &group.description, + quote! { + pub fn #group_name_snake(&self) -> #group_name_camel { + #group_name_camel::new(self) + } + }, + ); + + quote! { + pub struct #group_name_camel<'a> { + auth: &'a #auth, + } + + #methods + + impl #auth { + #group_method + } + } +} diff --git a/qbittorrent-web-api-gen/src/generate/mod.rs b/qbittorrent-web-api-gen/src/generate/mod.rs index 5f123a3..9e68fa9 100644 --- a/qbittorrent-web-api-gen/src/generate/mod.rs +++ b/qbittorrent-web-api-gen/src/generate/mod.rs @@ -1,16 +1,14 @@ +mod group; mod skeleton; mod util; -use std::{collections::HashMap, vec::Vec}; - use case::CaseExt; use proc_macro2::TokenStream; -use quote::{format_ident, quote}; -use regex::Regex; +use quote::quote; -use crate::{md_parser, parser, types}; +use crate::{md_parser, parser}; -use self::skeleton::{auth_ident, generate_skeleton}; +use self::{group::generate_groups, skeleton::generate_skeleton}; pub fn generate(ast: &syn::DeriveInput, api_content: &str) -> TokenStream { let ident = &ast.ident; @@ -29,450 +27,3 @@ pub fn generate(ast: &syn::DeriveInput, api_content: &str) -> TokenStream { } } } - -pub fn generate_groups(groups: Vec) -> proc_macro2::TokenStream { - let gr = groups - .iter() - // implemented manually - .filter(|group| group.name != "authentication") - .map(generate_group); - - quote! { - #(#gr)* - } -} - -fn generate_group(group: &parser::ApiGroup) -> proc_macro2::TokenStream { - let group_name_camel = util::to_ident(&group.name.to_camel()); - let group_name_snake = util::to_ident(&group.name.to_snake()); - let auth = auth_ident(); - let methods = generate_methods(group, &auth, &group_name_camel); - - let group_method = util::add_docs( - &group.description, - quote! { - pub fn #group_name_snake(&self) -> #group_name_camel { - #group_name_camel::new(self) - } - }, - ); - - quote! { - pub struct #group_name_camel<'a> { - auth: &'a #auth, - } - - #methods - - impl #auth { - #group_method - } - } -} - -fn generate_methods( - group: &parser::ApiGroup, - auth: &syn::Ident, - group_name_camel: &syn::Ident, -) -> proc_macro2::TokenStream { - let methods_and_param_structs = group - .methods - .iter() - .map(|method| generate_method(group, method)); - - let methods = methods_and_param_structs.clone().map(|(method, ..)| method); - let structs = methods_and_param_structs.flat_map(|(_, s)| s); - - quote! { - impl <'a> #group_name_camel<'a> { - pub fn new(auth: &'a #auth) -> Self { - Self { auth } - } - - #(#methods)* - } - - #(#structs)* - } -} - -fn generate_method( - group: &parser::ApiGroup, - method: &parser::ApiMethod, -) -> (proc_macro2::TokenStream, Option) { - let method_name = util::to_ident(&method.name.to_snake()); - let url = format!("/api/v2/{}/{}", group.url, method.url); - - match &method.parameters { - Some(params) => create_method_with_params(group, method, params, &method_name, &url), - None => create_method_without_params(group, method, method_name, &url), - } -} - -fn create_method_without_params( - group: &parser::ApiGroup, - method: &parser::ApiMethod, - method_name: proc_macro2::Ident, - url: &str, -) -> (proc_macro2::TokenStream, Option) { - match create_return_type(group, method) { - Some((return_type_name, return_type)) => ( - util::add_docs( - &method.description, - quote! { - pub async fn #method_name(&self) -> Result<#return_type_name> { - let res = self.auth - .authenticated_client(#url) - .send() - .await? - .json::<#return_type_name>() - .await?; - - Ok(res) - } - }, - ), - Some(return_type), - ), - None => ( - util::add_docs( - &method.description, - quote! { - pub async fn #method_name(&self) -> Result { - let res = self.auth - .authenticated_client(#url) - .send() - .await? - .text() - .await?; - - Ok(res) - } - }, - ), // assume that all methods without a return type returns a string - None, - ), - } -} - -fn create_method_with_params( - group: &parser::ApiGroup, - method: &parser::ApiMethod, - params: &[types::Type], - method_name: &proc_macro2::Ident, - url: &str, -) -> (proc_macro2::TokenStream, Option) { - let parameter_type = util::to_ident(&format!( - "{}{}Parameters", - group.name.to_camel(), - method.name.to_camel() - )); - - let mandatory_params = params - .iter() - .filter(|param| !param.get_type_info().is_optional); - - let mandatory_param_args = mandatory_params.clone().map(|param| { - let name = util::to_ident(¶m.get_type_info().name.to_snake()); - let t = util::to_ident(¶m.to_borrowed_type()); - - if param.should_borrow() { - quote! { - #name: &#t - } - } else { - quote! { - #name: #t - } - } - }); - - let mandatory_param_names = mandatory_params.clone().map(|param| { - let name = util::to_ident(¶m.get_type_info().name.to_snake()); - - quote! { - #name - } - }); - - let mandatory_param_args_clone = mandatory_param_args.clone(); - let mandatory_param_form_build = mandatory_params.map(|param| { - let n = ¶m.get_type_info().name; - let name = util::to_ident(&n.to_snake()); - - quote! { - let form = form.text(#n, #name.to_string()); - } - }); - - let optional_params = params - .iter() - .filter(|param| param.get_type_info().is_optional) - .map(|param| { - let n = ¶m.get_type_info().name; - let name = util::to_ident(&n.to_snake()); - let t = util::to_ident(¶m.to_borrowed_type()); - - let method = if param.should_borrow() { - quote! { - pub fn #name(mut self, value: &#t) -> Self { - self.form = self.form.text(#n, value.to_string()); - self - } - } - } else { - quote! { - pub fn #name(mut self, value: #t) -> Self { - self.form = self.form.text(#n, value.to_string()); - self - } - } - }; - - util::add_docs(¶m.get_type_info().description, method) - }); - - let group_name = util::to_ident(&group.name.to_camel()); - - let send = match create_return_type(group, method) { - Some((return_type_name, return_type)) => { - quote! { - impl<'a> #parameter_type<'a> { - fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { - let form = reqwest::multipart::Form::new(); - #(#mandatory_param_form_build)* - Self { group, form } - } - - #(#optional_params)* - - pub async fn send(self) -> Result<#return_type_name> { - let res = self.group - .auth - .authenticated_client(#url) - .multipart(self.form) - .send() - .await? - .json::<#return_type_name>() - .await?; - - Ok(res) - } - } - - #return_type - } - } - None => { - quote! { - impl<'a> #parameter_type<'a> { - fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { - let form = reqwest::multipart::Form::new(); - #(#mandatory_param_form_build)* - Self { group, form } - } - - #(#optional_params)* - - pub async fn send(self) -> Result { - let res = self.group - .auth - .authenticated_client(#url) - .multipart(self.form) - .send() - .await? - .text() - .await?; - - Ok(res) - } - } - } - } - }; - - ( - util::add_docs( - &method.description, - quote! { - pub fn #method_name(&self, #(#mandatory_param_args_clone),*) -> #parameter_type { - #parameter_type::new(self, #(#mandatory_param_names),*) - } - }, - ), - Some(quote! { - pub struct #parameter_type<'a> { - group: &'a #group_name<'a>, - form: reqwest::multipart::Form, - } - - #send - }), - ) -} - -fn create_return_type( - group: &parser::ApiGroup, - method: &parser::ApiMethod, -) -> Option<(proc_macro2::TokenStream, proc_macro2::TokenStream)> { - let return_type = match &method.return_type { - Some(t) => t, - None => return None, - }; - - let to_enum_name = |name: &str| { - format!( - "{}{}{}", - group.name.to_camel(), - method.name.to_camel(), - name.to_camel() - ) - }; - - let enum_types_with_names = - return_type - .parameters - .iter() - .flat_map(|parameter| match ¶meter.return_type { - types::Type::Number(types::TypeInfo { - ref name, - type_description: Some(type_description), - .. - }) => { - let enum_fields = type_description.values.iter().map(|value| { - let v = &value.value; - let re = Regex::new(r#"\(.*\)"#).unwrap(); - let desc = &value - .description - .replace(' ', "_") - .replace('-', "_") - .replace(',', "_"); - let desc_without_parentheses = re.replace_all(desc, ""); - let ident = util::to_ident(&desc_without_parentheses.to_camel()); - - util::add_docs( - &Some(value.description.clone()), - quote! { - #[serde(rename = #v)] - #ident - }, - ) - }); - - let enum_name = util::to_ident(&to_enum_name(name)); - - Some(( - name, - quote! { - #[allow(clippy::enum_variant_names)] - #[derive(Debug, Deserialize, PartialEq, Eq)] - pub enum #enum_name { - #(#enum_fields,)* - } - }, - )) - } - types::Type::String(types::TypeInfo { - ref name, - type_description: Some(type_description), - .. - }) => { - let enum_fields = type_description.values.iter().map(|type_description| { - let value = &type_description.value; - let value_as_ident = util::to_ident(&value.to_camel()); - - util::add_docs( - &Some(type_description.description.clone()), - quote! { - #[serde(rename = #value)] - #value_as_ident - }, - ) - }); - - let enum_name = util::to_ident(&to_enum_name(name)); - - Some(( - name, - quote! { - #[allow(clippy::enum_variant_names)] - #[derive(Debug, Deserialize, PartialEq, Eq)] - pub enum #enum_name { - #(#enum_fields,)* - } - }, - )) - } - _ => None, - }); - - let enum_names: HashMap<&String, String> = enum_types_with_names - .clone() - .map(|(enum_name, _)| (enum_name, to_enum_name(enum_name))) - .collect(); - - let enum_types = enum_types_with_names.map(|(_, enum_type)| enum_type); - - let parameters = return_type.parameters.iter().map(|parameter| { - let namestr = ¶meter.name; - let name = util::to_ident(&namestr.to_snake().replace("__", "_")); - let rtype = if let Some(enum_type) = enum_names.get(namestr) { - util::to_ident(enum_type) - } else { - util::to_ident(¶meter.return_type.to_owned_type()) - }; - let type_info = parameter.return_type.get_type_info(); - - let rtype_as_quote = if type_info.is_list { - quote! { - std::vec::Vec<#rtype> - } - } else { - quote! { - #rtype - } - }; - - // "type" is a reserved keyword in Rust, so we use a different name. - if namestr == "type" { - let non_reserved_name = format_ident!("t_{}", name); - quote! { - #[serde(rename = #namestr)] - pub #non_reserved_name: #rtype_as_quote - } - } else { - quote! { - #[serde(rename = #namestr)] - pub #name: #rtype_as_quote - } - } - }); - - let return_type_name = util::to_ident(&format!( - "{}{}Result", - &group.name.to_camel(), - &method.name.to_camel() - )); - - let result_type = if return_type.is_list { - quote! { - std::vec::Vec<#return_type_name> - } - } else { - quote! { - #return_type_name - } - }; - - Some(( - result_type, - quote! { - #[derive(Debug, Deserialize)] - pub struct #return_type_name { - #(#parameters,)* - } - - #(#enum_types)* - }, - )) -} From c633a6c33fc6d188aec17d3446d24ce995fb3709 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 11:59:13 +0000 Subject: [PATCH 11/29] Split group method generation --- .../group/method/method_with_params.rs | 162 ++++++++ .../group/method/method_without_params.rs | 50 +++ .../src/generate/group/method/mod.rs | 382 +----------------- .../src/generate/group/method/return_type.rs | 173 ++++++++ 4 files changed, 395 insertions(+), 372 deletions(-) create mode 100644 qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs create mode 100644 qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs create mode 100644 qbittorrent-web-api-gen/src/generate/group/method/return_type.rs diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs new file mode 100644 index 0000000..4e3f80c --- /dev/null +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs @@ -0,0 +1,162 @@ +use case::CaseExt; +use quote::quote; + +use crate::{generate::util, parser, types}; + +use super::return_type::create_return_type; + +pub fn create_method_with_params( + group: &parser::ApiGroup, + method: &parser::ApiMethod, + params: &[types::Type], + method_name: &proc_macro2::Ident, + url: &str, +) -> (proc_macro2::TokenStream, Option) { + let parameter_type = util::to_ident(&format!( + "{}{}Parameters", + group.name.to_camel(), + method.name.to_camel() + )); + + let mandatory_params = params + .iter() + .filter(|param| !param.get_type_info().is_optional); + + let mandatory_param_args = mandatory_params.clone().map(|param| { + let name = util::to_ident(¶m.get_type_info().name.to_snake()); + let t = util::to_ident(¶m.to_borrowed_type()); + + if param.should_borrow() { + quote! { + #name: &#t + } + } else { + quote! { + #name: #t + } + } + }); + + let mandatory_param_names = mandatory_params.clone().map(|param| { + let name = util::to_ident(¶m.get_type_info().name.to_snake()); + + quote! { + #name + } + }); + + let mandatory_param_args_clone = mandatory_param_args.clone(); + let mandatory_param_form_build = mandatory_params.map(|param| { + let n = ¶m.get_type_info().name; + let name = util::to_ident(&n.to_snake()); + + quote! { + let form = form.text(#n, #name.to_string()); + } + }); + + let optional_params = params + .iter() + .filter(|param| param.get_type_info().is_optional) + .map(|param| { + let n = ¶m.get_type_info().name; + let name = util::to_ident(&n.to_snake()); + let t = util::to_ident(¶m.to_borrowed_type()); + + let method = if param.should_borrow() { + quote! { + pub fn #name(mut self, value: &#t) -> Self { + self.form = self.form.text(#n, value.to_string()); + self + } + } + } else { + quote! { + pub fn #name(mut self, value: #t) -> Self { + self.form = self.form.text(#n, value.to_string()); + self + } + } + }; + + util::add_docs(¶m.get_type_info().description, method) + }); + + let group_name = util::to_ident(&group.name.to_camel()); + + let send = match create_return_type(group, method) { + Some((return_type_name, return_type)) => { + quote! { + impl<'a> #parameter_type<'a> { + fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { + let form = reqwest::multipart::Form::new(); + #(#mandatory_param_form_build)* + Self { group, form } + } + + #(#optional_params)* + + pub async fn send(self) -> Result<#return_type_name> { + let res = self.group + .auth + .authenticated_client(#url) + .multipart(self.form) + .send() + .await? + .json::<#return_type_name>() + .await?; + + Ok(res) + } + } + + #return_type + } + } + None => { + quote! { + impl<'a> #parameter_type<'a> { + fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { + let form = reqwest::multipart::Form::new(); + #(#mandatory_param_form_build)* + Self { group, form } + } + + #(#optional_params)* + + pub async fn send(self) -> Result { + let res = self.group + .auth + .authenticated_client(#url) + .multipart(self.form) + .send() + .await? + .text() + .await?; + + Ok(res) + } + } + } + } + }; + + ( + util::add_docs( + &method.description, + quote! { + pub fn #method_name(&self, #(#mandatory_param_args_clone),*) -> #parameter_type { + #parameter_type::new(self, #(#mandatory_param_names),*) + } + }, + ), + Some(quote! { + pub struct #parameter_type<'a> { + group: &'a #group_name<'a>, + form: reqwest::multipart::Form, + } + + #send + }), + ) +} diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs new file mode 100644 index 0000000..3f91f7f --- /dev/null +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs @@ -0,0 +1,50 @@ +use crate::{generate::util, parser}; +use quote::quote; + +use super::return_type::create_return_type; + +pub fn create_method_without_params( + group: &parser::ApiGroup, + method: &parser::ApiMethod, + method_name: proc_macro2::Ident, + url: &str, +) -> (proc_macro2::TokenStream, Option) { + match create_return_type(group, method) { + Some((return_type_name, return_type)) => ( + util::add_docs( + &method.description, + quote! { + pub async fn #method_name(&self) -> Result<#return_type_name> { + let res = self.auth + .authenticated_client(#url) + .send() + .await? + .json::<#return_type_name>() + .await?; + + Ok(res) + } + }, + ), + Some(return_type), + ), + None => ( + util::add_docs( + &method.description, + quote! { + pub async fn #method_name(&self) -> Result { + let res = self.auth + .authenticated_client(#url) + .send() + .await? + .text() + .await?; + + Ok(res) + } + }, + ), // assume that all methods without a return type returns a string + None, + ), + } +} diff --git a/qbittorrent-web-api-gen/src/generate/group/method/mod.rs b/qbittorrent-web-api-gen/src/generate/group/method/mod.rs index 449695f..764f8e5 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/mod.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/mod.rs @@ -1,9 +1,15 @@ -use std::collections::HashMap; +mod method_with_params; +mod method_without_params; +mod return_type; -use crate::{generate::util, parser, types}; +use crate::{generate::util, parser}; use case::CaseExt; -use quote::{format_ident, quote}; -use regex::Regex; +use quote::quote; + +use self::{ + method_with_params::create_method_with_params, + method_without_params::create_method_without_params, +}; pub fn generate_methods( group: &parser::ApiGroup, @@ -43,371 +49,3 @@ fn generate_method( None => create_method_without_params(group, method, method_name, &url), } } - -fn create_method_without_params( - group: &parser::ApiGroup, - method: &parser::ApiMethod, - method_name: proc_macro2::Ident, - url: &str, -) -> (proc_macro2::TokenStream, Option) { - match create_return_type(group, method) { - Some((return_type_name, return_type)) => ( - util::add_docs( - &method.description, - quote! { - pub async fn #method_name(&self) -> Result<#return_type_name> { - let res = self.auth - .authenticated_client(#url) - .send() - .await? - .json::<#return_type_name>() - .await?; - - Ok(res) - } - }, - ), - Some(return_type), - ), - None => ( - util::add_docs( - &method.description, - quote! { - pub async fn #method_name(&self) -> Result { - let res = self.auth - .authenticated_client(#url) - .send() - .await? - .text() - .await?; - - Ok(res) - } - }, - ), // assume that all methods without a return type returns a string - None, - ), - } -} - -fn create_method_with_params( - group: &parser::ApiGroup, - method: &parser::ApiMethod, - params: &[types::Type], - method_name: &proc_macro2::Ident, - url: &str, -) -> (proc_macro2::TokenStream, Option) { - let parameter_type = util::to_ident(&format!( - "{}{}Parameters", - group.name.to_camel(), - method.name.to_camel() - )); - - let mandatory_params = params - .iter() - .filter(|param| !param.get_type_info().is_optional); - - let mandatory_param_args = mandatory_params.clone().map(|param| { - let name = util::to_ident(¶m.get_type_info().name.to_snake()); - let t = util::to_ident(¶m.to_borrowed_type()); - - if param.should_borrow() { - quote! { - #name: &#t - } - } else { - quote! { - #name: #t - } - } - }); - - let mandatory_param_names = mandatory_params.clone().map(|param| { - let name = util::to_ident(¶m.get_type_info().name.to_snake()); - - quote! { - #name - } - }); - - let mandatory_param_args_clone = mandatory_param_args.clone(); - let mandatory_param_form_build = mandatory_params.map(|param| { - let n = ¶m.get_type_info().name; - let name = util::to_ident(&n.to_snake()); - - quote! { - let form = form.text(#n, #name.to_string()); - } - }); - - let optional_params = params - .iter() - .filter(|param| param.get_type_info().is_optional) - .map(|param| { - let n = ¶m.get_type_info().name; - let name = util::to_ident(&n.to_snake()); - let t = util::to_ident(¶m.to_borrowed_type()); - - let method = if param.should_borrow() { - quote! { - pub fn #name(mut self, value: &#t) -> Self { - self.form = self.form.text(#n, value.to_string()); - self - } - } - } else { - quote! { - pub fn #name(mut self, value: #t) -> Self { - self.form = self.form.text(#n, value.to_string()); - self - } - } - }; - - util::add_docs(¶m.get_type_info().description, method) - }); - - let group_name = util::to_ident(&group.name.to_camel()); - - let send = match create_return_type(group, method) { - Some((return_type_name, return_type)) => { - quote! { - impl<'a> #parameter_type<'a> { - fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { - let form = reqwest::multipart::Form::new(); - #(#mandatory_param_form_build)* - Self { group, form } - } - - #(#optional_params)* - - pub async fn send(self) -> Result<#return_type_name> { - let res = self.group - .auth - .authenticated_client(#url) - .multipart(self.form) - .send() - .await? - .json::<#return_type_name>() - .await?; - - Ok(res) - } - } - - #return_type - } - } - None => { - quote! { - impl<'a> #parameter_type<'a> { - fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { - let form = reqwest::multipart::Form::new(); - #(#mandatory_param_form_build)* - Self { group, form } - } - - #(#optional_params)* - - pub async fn send(self) -> Result { - let res = self.group - .auth - .authenticated_client(#url) - .multipart(self.form) - .send() - .await? - .text() - .await?; - - Ok(res) - } - } - } - } - }; - - ( - util::add_docs( - &method.description, - quote! { - pub fn #method_name(&self, #(#mandatory_param_args_clone),*) -> #parameter_type { - #parameter_type::new(self, #(#mandatory_param_names),*) - } - }, - ), - Some(quote! { - pub struct #parameter_type<'a> { - group: &'a #group_name<'a>, - form: reqwest::multipart::Form, - } - - #send - }), - ) -} - -fn create_return_type( - group: &parser::ApiGroup, - method: &parser::ApiMethod, -) -> Option<(proc_macro2::TokenStream, proc_macro2::TokenStream)> { - let return_type = match &method.return_type { - Some(t) => t, - None => return None, - }; - - let to_enum_name = |name: &str| { - format!( - "{}{}{}", - group.name.to_camel(), - method.name.to_camel(), - name.to_camel() - ) - }; - - let enum_types_with_names = - return_type - .parameters - .iter() - .flat_map(|parameter| match ¶meter.return_type { - types::Type::Number(types::TypeInfo { - ref name, - type_description: Some(type_description), - .. - }) => { - let enum_fields = type_description.values.iter().map(|value| { - let v = &value.value; - let re = Regex::new(r#"\(.*\)"#).unwrap(); - let desc = &value - .description - .replace(' ', "_") - .replace('-', "_") - .replace(',', "_"); - let desc_without_parentheses = re.replace_all(desc, ""); - let ident = util::to_ident(&desc_without_parentheses.to_camel()); - - util::add_docs( - &Some(value.description.clone()), - quote! { - #[serde(rename = #v)] - #ident - }, - ) - }); - - let enum_name = util::to_ident(&to_enum_name(name)); - - Some(( - name, - quote! { - #[allow(clippy::enum_variant_names)] - #[derive(Debug, Deserialize, PartialEq, Eq)] - pub enum #enum_name { - #(#enum_fields,)* - } - }, - )) - } - types::Type::String(types::TypeInfo { - ref name, - type_description: Some(type_description), - .. - }) => { - let enum_fields = type_description.values.iter().map(|type_description| { - let value = &type_description.value; - let value_as_ident = util::to_ident(&value.to_camel()); - - util::add_docs( - &Some(type_description.description.clone()), - quote! { - #[serde(rename = #value)] - #value_as_ident - }, - ) - }); - - let enum_name = util::to_ident(&to_enum_name(name)); - - Some(( - name, - quote! { - #[allow(clippy::enum_variant_names)] - #[derive(Debug, Deserialize, PartialEq, Eq)] - pub enum #enum_name { - #(#enum_fields,)* - } - }, - )) - } - _ => None, - }); - - let enum_names: HashMap<&String, String> = enum_types_with_names - .clone() - .map(|(enum_name, _)| (enum_name, to_enum_name(enum_name))) - .collect(); - - let enum_types = enum_types_with_names.map(|(_, enum_type)| enum_type); - - let parameters = return_type.parameters.iter().map(|parameter| { - let namestr = ¶meter.name; - let name = util::to_ident(&namestr.to_snake().replace("__", "_")); - let rtype = if let Some(enum_type) = enum_names.get(namestr) { - util::to_ident(enum_type) - } else { - util::to_ident(¶meter.return_type.to_owned_type()) - }; - let type_info = parameter.return_type.get_type_info(); - - let rtype_as_quote = if type_info.is_list { - quote! { - std::vec::Vec<#rtype> - } - } else { - quote! { - #rtype - } - }; - - // "type" is a reserved keyword in Rust, so we use a different name. - if namestr == "type" { - let non_reserved_name = format_ident!("t_{}", name); - quote! { - #[serde(rename = #namestr)] - pub #non_reserved_name: #rtype_as_quote - } - } else { - quote! { - #[serde(rename = #namestr)] - pub #name: #rtype_as_quote - } - } - }); - - let return_type_name = util::to_ident(&format!( - "{}{}Result", - &group.name.to_camel(), - &method.name.to_camel() - )); - - let result_type = if return_type.is_list { - quote! { - std::vec::Vec<#return_type_name> - } - } else { - quote! { - #return_type_name - } - }; - - Some(( - result_type, - quote! { - #[derive(Debug, Deserialize)] - pub struct #return_type_name { - #(#parameters,)* - } - - #(#enum_types)* - }, - )) -} diff --git a/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs b/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs new file mode 100644 index 0000000..55ea88c --- /dev/null +++ b/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs @@ -0,0 +1,173 @@ +use std::collections::HashMap; + +use case::CaseExt; +use quote::{quote, format_ident}; +use regex::Regex; + +use crate::{generate::util, parser, types}; + +pub fn create_return_type( + group: &parser::ApiGroup, + method: &parser::ApiMethod, +) -> Option<(proc_macro2::TokenStream, proc_macro2::TokenStream)> { + let return_type = match &method.return_type { + Some(t) => t, + None => return None, + }; + + let to_enum_name = |name: &str| { + format!( + "{}{}{}", + group.name.to_camel(), + method.name.to_camel(), + name.to_camel() + ) + }; + + let enum_types_with_names = + return_type + .parameters + .iter() + .flat_map(|parameter| match ¶meter.return_type { + types::Type::Number(types::TypeInfo { + ref name, + type_description: Some(type_description), + .. + }) => { + let enum_fields = type_description.values.iter().map(|value| { + let v = &value.value; + let re = Regex::new(r#"\(.*\)"#).unwrap(); + let desc = &value + .description + .replace(' ', "_") + .replace('-', "_") + .replace(',', "_"); + let desc_without_parentheses = re.replace_all(desc, ""); + let ident = util::to_ident(&desc_without_parentheses.to_camel()); + + util::add_docs( + &Some(value.description.clone()), + quote! { + #[serde(rename = #v)] + #ident + }, + ) + }); + + let enum_name = util::to_ident(&to_enum_name(name)); + + Some(( + name, + quote! { + #[allow(clippy::enum_variant_names)] + #[derive(Debug, Deserialize, PartialEq, Eq)] + pub enum #enum_name { + #(#enum_fields,)* + } + }, + )) + } + types::Type::String(types::TypeInfo { + ref name, + type_description: Some(type_description), + .. + }) => { + let enum_fields = type_description.values.iter().map(|type_description| { + let value = &type_description.value; + let value_as_ident = util::to_ident(&value.to_camel()); + + util::add_docs( + &Some(type_description.description.clone()), + quote! { + #[serde(rename = #value)] + #value_as_ident + }, + ) + }); + + let enum_name = util::to_ident(&to_enum_name(name)); + + Some(( + name, + quote! { + #[allow(clippy::enum_variant_names)] + #[derive(Debug, Deserialize, PartialEq, Eq)] + pub enum #enum_name { + #(#enum_fields,)* + } + }, + )) + } + _ => None, + }); + + let enum_names: HashMap<&String, String> = enum_types_with_names + .clone() + .map(|(enum_name, _)| (enum_name, to_enum_name(enum_name))) + .collect(); + + let enum_types = enum_types_with_names.map(|(_, enum_type)| enum_type); + + let parameters = return_type.parameters.iter().map(|parameter| { + let namestr = ¶meter.name; + let name = util::to_ident(&namestr.to_snake().replace("__", "_")); + let rtype = if let Some(enum_type) = enum_names.get(namestr) { + util::to_ident(enum_type) + } else { + util::to_ident(¶meter.return_type.to_owned_type()) + }; + let type_info = parameter.return_type.get_type_info(); + + let rtype_as_quote = if type_info.is_list { + quote! { + std::vec::Vec<#rtype> + } + } else { + quote! { + #rtype + } + }; + + // "type" is a reserved keyword in Rust, so we use a different name. + if namestr == "type" { + let non_reserved_name = format_ident!("t_{}", name); + quote! { + #[serde(rename = #namestr)] + pub #non_reserved_name: #rtype_as_quote + } + } else { + quote! { + #[serde(rename = #namestr)] + pub #name: #rtype_as_quote + } + } + }); + + let return_type_name = util::to_ident(&format!( + "{}{}Result", + &group.name.to_camel(), + &method.name.to_camel() + )); + + let result_type = if return_type.is_list { + quote! { + std::vec::Vec<#return_type_name> + } + } else { + quote! { + #return_type_name + } + }; + + Some(( + result_type, + quote! { + #[derive(Debug, Deserialize)] + pub struct #return_type_name { + #(#parameters,)* + } + + #(#enum_types)* + }, + )) +} From 64803a2d16bc95dbd74fc1133e08f641f31d7418 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 12:15:33 +0000 Subject: [PATCH 12/29] Fmt fixes --- .../src/generate/group/method/return_type.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs b/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs index 55ea88c..da8c2c7 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs @@ -1,7 +1,7 @@ use std::collections::HashMap; use case::CaseExt; -use quote::{quote, format_ident}; +use quote::{format_ident, quote}; use regex::Regex; use crate::{generate::util, parser, types}; From 95104b653b2f69e05108ffe140e731722fe04265 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 12:20:05 +0000 Subject: [PATCH 13/29] Add comon docs --- .../group/method/method_without_params.rs | 57 +++++++++---------- 1 file changed, 27 insertions(+), 30 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs index 3f91f7f..185b023 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs @@ -9,42 +9,39 @@ pub fn create_method_without_params( method_name: proc_macro2::Ident, url: &str, ) -> (proc_macro2::TokenStream, Option) { - match create_return_type(group, method) { + let res = match create_return_type(group, method) { Some((return_type_name, return_type)) => ( - util::add_docs( - &method.description, - quote! { - pub async fn #method_name(&self) -> Result<#return_type_name> { - let res = self.auth - .authenticated_client(#url) - .send() - .await? - .json::<#return_type_name>() - .await?; + quote! { + pub async fn #method_name(&self) -> Result<#return_type_name> { + let res = self.auth + .authenticated_client(#url) + .send() + .await? + .json::<#return_type_name>() + .await?; - Ok(res) - } - }, - ), + Ok(res) + } + }, Some(return_type), ), None => ( - util::add_docs( - &method.description, - quote! { - pub async fn #method_name(&self) -> Result { - let res = self.auth - .authenticated_client(#url) - .send() - .await? - .text() - .await?; + quote! { + pub async fn #method_name(&self) -> Result { + let res = self.auth + .authenticated_client(#url) + .send() + .await? + .text() + .await?; - Ok(res) - } - }, - ), // assume that all methods without a return type returns a string + Ok(res) + } + }, + // assume that all methods without a return type returns a string None, ), - } + }; + + (util::add_docs(&method.description, res.0), res.1) } From 8ed9a98843335711004499677c1a0b2634197312 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 13:06:07 +0000 Subject: [PATCH 14/29] Add method builder --- .../group/method/method_without_params.rs | 71 ++++++++++++------- 1 file changed, 47 insertions(+), 24 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs index 185b023..91c3a51 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs @@ -3,6 +3,49 @@ use quote::quote; use super::return_type::create_return_type; +struct MethodBuilder { + method_name: syn::Ident, + url: String, + return_type: Option, +} + +impl MethodBuilder { + fn new(method_name: &syn::Ident, url: &str) -> Self { + Self { + method_name: method_name.clone(), + url: url.to_string(), + return_type: None, + } + } + + fn return_type(mut self, value: &proc_macro2::TokenStream) -> Self { + self.return_type = Some(value.clone()); + self + } + + fn build(&self) -> proc_macro2::TokenStream { + let method_name = &self.method_name; + let (return_type, parse_type) = match &self.return_type { + Some(t) => (t.clone(), quote! { .json::<#t>() }), + None => (quote! { String }, quote! { .text() }), + }; + let url = &self.url; + + quote! { + pub async fn #method_name(&self) -> Result<#return_type> { + let res = self.auth + .authenticated_client(#url) + .send() + .await? + #parse_type + .await?; + + Ok(res) + } + } + } +} + pub fn create_method_without_params( group: &parser::ApiGroup, method: &parser::ApiMethod, @@ -11,33 +54,13 @@ pub fn create_method_without_params( ) -> (proc_macro2::TokenStream, Option) { let res = match create_return_type(group, method) { Some((return_type_name, return_type)) => ( - quote! { - pub async fn #method_name(&self) -> Result<#return_type_name> { - let res = self.auth - .authenticated_client(#url) - .send() - .await? - .json::<#return_type_name>() - .await?; - - Ok(res) - } - }, + MethodBuilder::new(&method_name, url) + .return_type(&return_type_name) + .build(), Some(return_type), ), None => ( - quote! { - pub async fn #method_name(&self) -> Result { - let res = self.auth - .authenticated_client(#url) - .send() - .await? - .text() - .await?; - - Ok(res) - } - }, + MethodBuilder::new(&method_name, url).build(), // assume that all methods without a return type returns a string None, ), From dfa61d203a06799ddd2c8bc155363621cc33530b Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 13:08:41 +0000 Subject: [PATCH 15/29] Extract method_builder to own file --- .../generate/group/method/method_builder.rs | 44 ++++++++++++++++++ .../group/method/method_without_params.rs | 46 +------------------ .../src/generate/group/method/mod.rs | 1 + .../src/generate/skeleton.rs | 4 +- 4 files changed, 47 insertions(+), 48 deletions(-) create mode 100644 qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs new file mode 100644 index 0000000..92bd4c3 --- /dev/null +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs @@ -0,0 +1,44 @@ +use quote::quote; + +pub struct MethodBuilder { + method_name: syn::Ident, + url: String, + return_type: Option, +} + +impl MethodBuilder { + pub fn new(method_name: &syn::Ident, url: &str) -> Self { + Self { + method_name: method_name.clone(), + url: url.to_string(), + return_type: None, + } + } + + pub fn return_type(mut self, value: &proc_macro2::TokenStream) -> Self { + self.return_type = Some(value.clone()); + self + } + + pub fn build(&self) -> proc_macro2::TokenStream { + let method_name = &self.method_name; + let (return_type, parse_type) = match &self.return_type { + Some(t) => (t.clone(), quote! { .json::<#t>() }), + None => (quote! { String }, quote! { .text() }), + }; + let url = &self.url; + + quote! { + pub async fn #method_name(&self) -> Result<#return_type> { + let res = self.auth + .authenticated_client(#url) + .send() + .await? + #parse_type + .await?; + + Ok(res) + } + } + } +} diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs index 91c3a51..e3d2bc3 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs @@ -1,50 +1,6 @@ use crate::{generate::util, parser}; -use quote::quote; -use super::return_type::create_return_type; - -struct MethodBuilder { - method_name: syn::Ident, - url: String, - return_type: Option, -} - -impl MethodBuilder { - fn new(method_name: &syn::Ident, url: &str) -> Self { - Self { - method_name: method_name.clone(), - url: url.to_string(), - return_type: None, - } - } - - fn return_type(mut self, value: &proc_macro2::TokenStream) -> Self { - self.return_type = Some(value.clone()); - self - } - - fn build(&self) -> proc_macro2::TokenStream { - let method_name = &self.method_name; - let (return_type, parse_type) = match &self.return_type { - Some(t) => (t.clone(), quote! { .json::<#t>() }), - None => (quote! { String }, quote! { .text() }), - }; - let url = &self.url; - - quote! { - pub async fn #method_name(&self) -> Result<#return_type> { - let res = self.auth - .authenticated_client(#url) - .send() - .await? - #parse_type - .await?; - - Ok(res) - } - } - } -} +use super::{method_builder::MethodBuilder, return_type::create_return_type}; pub fn create_method_without_params( group: &parser::ApiGroup, diff --git a/qbittorrent-web-api-gen/src/generate/group/method/mod.rs b/qbittorrent-web-api-gen/src/generate/group/method/mod.rs index 764f8e5..5237f52 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/mod.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/mod.rs @@ -1,6 +1,7 @@ mod method_with_params; mod method_without_params; mod return_type; +mod method_builder; use crate::{generate::util, parser}; use case::CaseExt; diff --git a/qbittorrent-web-api-gen/src/generate/skeleton.rs b/qbittorrent-web-api-gen/src/generate/skeleton.rs index e2118a7..fb74c23 100644 --- a/qbittorrent-web-api-gen/src/generate/skeleton.rs +++ b/qbittorrent-web-api-gen/src/generate/skeleton.rs @@ -2,10 +2,8 @@ use quote::quote; use super::util; -pub const AUTH_IDENT: &str = "Authenticated"; - pub fn auth_ident() -> proc_macro2::Ident { - util::to_ident(AUTH_IDENT) + util::to_ident("Authenticated") } pub fn generate_skeleton(ident: &syn::Ident) -> proc_macro2::TokenStream { From 0684b4681a5ebecf435fa6d7b2d3326aabeb8e99 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 13:17:50 +0000 Subject: [PATCH 16/29] Add docs to method_builder --- .../generate/group/method/method_builder.rs | 43 +++++++++++++------ .../group/method/method_without_params.rs | 18 ++++---- 2 files changed, 40 insertions(+), 21 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs index 92bd4c3..57f7ba9 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs @@ -1,17 +1,27 @@ use quote::quote; +use crate::generate::util; + pub struct MethodBuilder { method_name: syn::Ident, url: String, + auth_module_path: proc_macro2::TokenStream, return_type: Option, + description: Option, } impl MethodBuilder { - pub fn new(method_name: &syn::Ident, url: &str) -> Self { + pub fn new( + method_name: &syn::Ident, + url: &str, + auth_module_path: proc_macro2::TokenStream, + ) -> Self { Self { method_name: method_name.clone(), url: url.to_string(), + auth_module_path, return_type: None, + description: None, } } @@ -20,6 +30,11 @@ impl MethodBuilder { self } + pub fn description(mut self, value: &Option) -> Self { + self.description = value.clone(); + self + } + pub fn build(&self) -> proc_macro2::TokenStream { let method_name = &self.method_name; let (return_type, parse_type) = match &self.return_type { @@ -27,18 +42,22 @@ impl MethodBuilder { None => (quote! { String }, quote! { .text() }), }; let url = &self.url; + let auth_module_path = &self.auth_module_path; - quote! { - pub async fn #method_name(&self) -> Result<#return_type> { - let res = self.auth - .authenticated_client(#url) - .send() - .await? - #parse_type - .await?; + util::add_docs( + &self.description, + quote! { + pub async fn #method_name(&self) -> Result<#return_type> { + let res = #auth_module_path + .authenticated_client(#url) + .send() + .await? + #parse_type + .await?; - Ok(res) - } - } + Ok(res) + } + }, + ) } } diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs index e3d2bc3..0b74ac1 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs @@ -1,6 +1,7 @@ -use crate::{generate::util, parser}; +use quote::quote; use super::{method_builder::MethodBuilder, return_type::create_return_type}; +use crate::parser; pub fn create_method_without_params( group: &parser::ApiGroup, @@ -8,19 +9,18 @@ pub fn create_method_without_params( method_name: proc_macro2::Ident, url: &str, ) -> (proc_macro2::TokenStream, Option) { - let res = match create_return_type(group, method) { + let builder = MethodBuilder::new(&method_name, url, quote! { self.auth }) + .description(&method.description); + + match create_return_type(group, method) { Some((return_type_name, return_type)) => ( - MethodBuilder::new(&method_name, url) - .return_type(&return_type_name) - .build(), + builder.return_type(&return_type_name).build(), Some(return_type), ), None => ( - MethodBuilder::new(&method_name, url).build(), + builder.build(), // assume that all methods without a return type returns a string None, ), - }; - - (util::add_docs(&method.description, res.0), res.1) + } } From 6d996f42538f33310164cd0e79efe7bfec196832 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 13:31:53 +0000 Subject: [PATCH 17/29] Use method builder --- .../generate/group/method/method_builder.rs | 15 +++++++- .../group/method/method_with_params.rs | 34 +++++-------------- 2 files changed, 23 insertions(+), 26 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs index 57f7ba9..be895f1 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs @@ -8,6 +8,7 @@ pub struct MethodBuilder { auth_module_path: proc_macro2::TokenStream, return_type: Option, description: Option, + form: bool, } impl MethodBuilder { @@ -22,6 +23,7 @@ impl MethodBuilder { auth_module_path, return_type: None, description: None, + form: false, } } @@ -35,6 +37,11 @@ impl MethodBuilder { self } + pub fn with_form(mut self) -> Self { + self.form = true; + self + } + pub fn build(&self) -> proc_macro2::TokenStream { let method_name = &self.method_name; let (return_type, parse_type) = match &self.return_type { @@ -43,13 +50,19 @@ impl MethodBuilder { }; let url = &self.url; let auth_module_path = &self.auth_module_path; + let form = if self.form { + quote! { .multipart(self.form) } + } else { + quote! {} + }; util::add_docs( &self.description, quote! { - pub async fn #method_name(&self) -> Result<#return_type> { + pub async fn #method_name(self) -> Result<#return_type> { let res = #auth_module_path .authenticated_client(#url) + #form .send() .await? #parse_type diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs index 4e3f80c..6618ce5 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs @@ -3,7 +3,7 @@ use quote::quote; use crate::{generate::util, parser, types}; -use super::return_type::create_return_type; +use super::{method_builder::MethodBuilder, return_type::create_return_type}; pub fn create_method_with_params( group: &parser::ApiGroup, @@ -83,9 +83,13 @@ pub fn create_method_with_params( }); let group_name = util::to_ident(&group.name.to_camel()); + let send_builder = + MethodBuilder::new(&util::to_ident("send"), url, quote! { self.group.auth }).with_form(); let send = match create_return_type(group, method) { Some((return_type_name, return_type)) => { + let send_method = send_builder.return_type(&return_type_name).build(); + quote! { impl<'a> #parameter_type<'a> { fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { @@ -96,24 +100,15 @@ pub fn create_method_with_params( #(#optional_params)* - pub async fn send(self) -> Result<#return_type_name> { - let res = self.group - .auth - .authenticated_client(#url) - .multipart(self.form) - .send() - .await? - .json::<#return_type_name>() - .await?; - - Ok(res) - } + #send_method } #return_type } } None => { + let send_method = send_builder.build(); + quote! { impl<'a> #parameter_type<'a> { fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { @@ -124,18 +119,7 @@ pub fn create_method_with_params( #(#optional_params)* - pub async fn send(self) -> Result { - let res = self.group - .auth - .authenticated_client(#url) - .multipart(self.form) - .send() - .await? - .text() - .await?; - - Ok(res) - } + #send_method } } } From f8046e4cd256d8f8428889346c00c4486a1f95b3 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 13:32:17 +0000 Subject: [PATCH 18/29] Format --- qbittorrent-web-api-gen/src/generate/group/method/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/mod.rs b/qbittorrent-web-api-gen/src/generate/group/method/mod.rs index 5237f52..051e5e5 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/mod.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/mod.rs @@ -1,7 +1,7 @@ +mod method_builder; mod method_with_params; mod method_without_params; mod return_type; -mod method_builder; use crate::{generate::util, parser}; use case::CaseExt; From 499f60266a94b4e5fa9d746dae217368d980332b Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 13:35:01 +0000 Subject: [PATCH 19/29] Extract constructor --- .../group/method/method_with_params.rs | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs index 6618ce5..26372ce 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs @@ -86,20 +86,22 @@ pub fn create_method_with_params( let send_builder = MethodBuilder::new(&util::to_ident("send"), url, quote! { self.group.auth }).with_form(); + let send_new_method = quote! { + fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { + let form = reqwest::multipart::Form::new(); + #(#mandatory_param_form_build)* + Self { group, form } + } + }; + let send = match create_return_type(group, method) { Some((return_type_name, return_type)) => { let send_method = send_builder.return_type(&return_type_name).build(); quote! { impl<'a> #parameter_type<'a> { - fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { - let form = reqwest::multipart::Form::new(); - #(#mandatory_param_form_build)* - Self { group, form } - } - + #send_new_method #(#optional_params)* - #send_method } @@ -111,14 +113,8 @@ pub fn create_method_with_params( quote! { impl<'a> #parameter_type<'a> { - fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { - let form = reqwest::multipart::Form::new(); - #(#mandatory_param_form_build)* - Self { group, form } - } - + #send_new_method #(#optional_params)* - #send_method } } From 934868df42ba5ebfc91fa3b1b1ec45f47a27e34c Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 13:38:10 +0000 Subject: [PATCH 20/29] Simplify borrow building --- .../group/method/method_with_params.rs | 26 +++++++++---------- 1 file changed, 12 insertions(+), 14 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs index 26372ce..591b164 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs @@ -63,23 +63,21 @@ pub fn create_method_with_params( let name = util::to_ident(&n.to_snake()); let t = util::to_ident(¶m.to_borrowed_type()); - let method = if param.should_borrow() { - quote! { - pub fn #name(mut self, value: &#t) -> Self { - self.form = self.form.text(#n, value.to_string()); - self - } - } + let builder_param = if param.should_borrow() { + quote! { &#t } } else { - quote! { - pub fn #name(mut self, value: #t) -> Self { - self.form = self.form.text(#n, value.to_string()); - self - } - } + quote! { #t } }; - util::add_docs(¶m.get_type_info().description, method) + util::add_docs( + ¶m.get_type_info().description, + quote! { + pub fn #name(mut self, value: #builder_param) -> Self { + self.form = self.form.text(#n, value.to_string()); + self + } + }, + ) }); let group_name = util::to_ident(&group.name.to_camel()); From b8918942c0ce592334edf4f9db573a8d04dcadb1 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 13:49:45 +0000 Subject: [PATCH 21/29] Combine logic --- .../group/method/method_with_params.rs | 42 ++++++++++--------- 1 file changed, 22 insertions(+), 20 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs index 591b164..08944d2 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs @@ -22,37 +22,39 @@ pub fn create_method_with_params( .iter() .filter(|param| !param.get_type_info().is_optional); - let mandatory_param_args = mandatory_params.clone().map(|param| { - let name = util::to_ident(¶m.get_type_info().name.to_snake()); + let param_to_ident = |param: &types::Type| { let t = util::to_ident(¶m.to_borrowed_type()); if param.should_borrow() { - quote! { - #name: &#t - } + quote! { &#t } } else { - quote! { - #name: #t - } + quote! { #t } } - }); + }; + + let param_name = |param: &types::Type| { + let name_as_str = param.get_type_info().name.to_snake(); + (util::to_ident(&name_as_str), name_as_str) + }; + + let param_with_name = |param: &types::Type| { + let (name, ..) = param_name(param); + let t = param_to_ident(param); + + quote! { #name: #t } + }; + + let mandatory_param_args = mandatory_params.clone().map(|param| param_with_name(param)); let mandatory_param_names = mandatory_params.clone().map(|param| { - let name = util::to_ident(¶m.get_type_info().name.to_snake()); - - quote! { - #name - } + let (name, ..) = param_name(param); + quote! { #name } }); let mandatory_param_args_clone = mandatory_param_args.clone(); let mandatory_param_form_build = mandatory_params.map(|param| { - let n = ¶m.get_type_info().name; - let name = util::to_ident(&n.to_snake()); - - quote! { - let form = form.text(#n, #name.to_string()); - } + let (name, name_as_str) = param_name(param); + quote! { let form = form.text(#name_as_str, #name.to_string()); } }); let optional_params = params From 4d7400c3bfa6445163b08f4ef78b633f222c2f1d Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 13:58:18 +0000 Subject: [PATCH 22/29] Generify logic --- .../group/method/method_with_params.rs | 49 ++++++++----------- 1 file changed, 20 insertions(+), 29 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs index 08944d2..dcf0061 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs @@ -22,24 +22,20 @@ pub fn create_method_with_params( .iter() .filter(|param| !param.get_type_info().is_optional); - let param_to_ident = |param: &types::Type| { - let t = util::to_ident(¶m.to_borrowed_type()); - - if param.should_borrow() { - quote! { &#t } - } else { - quote! { #t } - } - }; - let param_name = |param: &types::Type| { let name_as_str = param.get_type_info().name.to_snake(); (util::to_ident(&name_as_str), name_as_str) }; let param_with_name = |param: &types::Type| { + let t = util::to_ident(¶m.to_borrowed_type()); + let (name, ..) = param_name(param); - let t = param_to_ident(param); + let t = if param.should_borrow() { + quote! { &#t } + } else { + quote! { #t } + }; quote! { #name: #t } }; @@ -94,31 +90,26 @@ pub fn create_method_with_params( } }; + let generate_send_impl = |send_method: proc_macro2::TokenStream| { + quote! { + impl<'a> #parameter_type<'a> { + #send_new_method + #(#optional_params)* + #send_method + } + } + }; + let send = match create_return_type(group, method) { Some((return_type_name, return_type)) => { - let send_method = send_builder.return_type(&return_type_name).build(); + let send_impl = generate_send_impl(send_builder.return_type(&return_type_name).build()); quote! { - impl<'a> #parameter_type<'a> { - #send_new_method - #(#optional_params)* - #send_method - } - + #send_impl #return_type } } - None => { - let send_method = send_builder.build(); - - quote! { - impl<'a> #parameter_type<'a> { - #send_new_method - #(#optional_params)* - #send_method - } - } - } + None => generate_send_impl(send_builder.build()), }; ( From 2776870e0d40896bbea42b14c6a541a2e13f3c13 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 14:14:25 +0000 Subject: [PATCH 23/29] Extract methods --- .../group/method/method_with_params.rs | 115 +++++++++--------- 1 file changed, 59 insertions(+), 56 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs index dcf0061..1bb2aad 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs @@ -18,37 +18,18 @@ pub fn create_method_with_params( method.name.to_camel() )); - let mandatory_params = params + let mandatory_params = mandatory_params(params); + let mandatory_param_args = mandatory_params .iter() - .filter(|param| !param.get_type_info().is_optional); + .map(|param| param_with_name(param)) + .collect::>(); - let param_name = |param: &types::Type| { - let name_as_str = param.get_type_info().name.to_snake(); - (util::to_ident(&name_as_str), name_as_str) - }; - - let param_with_name = |param: &types::Type| { - let t = util::to_ident(¶m.to_borrowed_type()); - - let (name, ..) = param_name(param); - let t = if param.should_borrow() { - quote! { &#t } - } else { - quote! { #t } - }; - - quote! { #name: #t } - }; - - let mandatory_param_args = mandatory_params.clone().map(|param| param_with_name(param)); - - let mandatory_param_names = mandatory_params.clone().map(|param| { + let mandatory_param_names = mandatory_params.iter().map(|param| { let (name, ..) = param_name(param); quote! { #name } }); - let mandatory_param_args_clone = mandatory_param_args.clone(); - let mandatory_param_form_build = mandatory_params.map(|param| { + let mandatory_param_form_build = mandatory_params.iter().map(|param| { let (name, name_as_str) = param_name(param); quote! { let form = form.text(#name_as_str, #name.to_string()); } }); @@ -56,44 +37,21 @@ pub fn create_method_with_params( let optional_params = params .iter() .filter(|param| param.get_type_info().is_optional) - .map(|param| { - let n = ¶m.get_type_info().name; - let name = util::to_ident(&n.to_snake()); - let t = util::to_ident(¶m.to_borrowed_type()); - - let builder_param = if param.should_borrow() { - quote! { &#t } - } else { - quote! { #t } - }; - - util::add_docs( - ¶m.get_type_info().description, - quote! { - pub fn #name(mut self, value: #builder_param) -> Self { - self.form = self.form.text(#n, value.to_string()); - self - } - }, - ) - }); + .map(generate_optional_parameter); let group_name = util::to_ident(&group.name.to_camel()); let send_builder = MethodBuilder::new(&util::to_ident("send"), url, quote! { self.group.auth }).with_form(); - let send_new_method = quote! { - fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { - let form = reqwest::multipart::Form::new(); - #(#mandatory_param_form_build)* - Self { group, form } - } - }; - let generate_send_impl = |send_method: proc_macro2::TokenStream| { quote! { impl<'a> #parameter_type<'a> { - #send_new_method + fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { + let form = reqwest::multipart::Form::new(); + #(#mandatory_param_form_build)* + Self { group, form } + } + #(#optional_params)* #send_method } @@ -116,7 +74,7 @@ pub fn create_method_with_params( util::add_docs( &method.description, quote! { - pub fn #method_name(&self, #(#mandatory_param_args_clone),*) -> #parameter_type { + pub fn #method_name(&self, #(#mandatory_param_args),*) -> #parameter_type { #parameter_type::new(self, #(#mandatory_param_names),*) } }, @@ -131,3 +89,48 @@ pub fn create_method_with_params( }), ) } + +fn mandatory_params(params: &[types::Type]) -> Vec<&types::Type> { + params + .iter() + .filter(|param| !param.get_type_info().is_optional) + .collect() +} + +fn generate_optional_parameter(param: &types::Type) -> proc_macro2::TokenStream { + let n = ¶m.get_type_info().name; + let name = util::to_ident(&n.to_snake()); + let t = util::to_ident(¶m.to_borrowed_type()); + let builder_param = if param.should_borrow() { + quote! { &#t } + } else { + quote! { #t } + }; + util::add_docs( + ¶m.get_type_info().description, + quote! { + pub fn #name(mut self, value: #builder_param) -> Self { + self.form = self.form.text(#n, value.to_string()); + self + } + }, + ) +} + +fn param_name(param: &types::Type) -> (proc_macro2::Ident, String) { + let name_as_str = param.get_type_info().name.to_snake(); + (util::to_ident(&name_as_str), name_as_str) +} + +fn param_with_name(param: &types::Type) -> proc_macro2::TokenStream { + let t = util::to_ident(¶m.to_borrowed_type()); + + let (name, ..) = param_name(param); + let t = if param.should_borrow() { + quote! { &#t } + } else { + quote! { #t } + }; + + quote! { #name: #t } +} From 9cafd434b2442c4e4cb1422ad5b08cd0ec9539a3 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 14:32:49 +0000 Subject: [PATCH 24/29] Extract more methods --- .../group/method/method_with_params.rs | 89 +++++++++++-------- .../group/method/method_without_params.rs | 4 +- .../src/generate/group/method/mod.rs | 2 +- ...thod_builder.rs => send_method_builder.rs} | 4 +- 4 files changed, 59 insertions(+), 40 deletions(-) rename qbittorrent-web-api-gen/src/generate/group/method/{method_builder.rs => send_method_builder.rs} (97%) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs index 1bb2aad..0700ba4 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs @@ -3,7 +3,7 @@ use quote::quote; use crate::{generate::util, parser, types}; -use super::{method_builder::MethodBuilder, return_type::create_return_type}; +use super::{send_method_builder::SendMethodBuilder, return_type::create_return_type}; pub fn create_method_with_params( group: &parser::ApiGroup, @@ -12,40 +12,30 @@ pub fn create_method_with_params( method_name: &proc_macro2::Ident, url: &str, ) -> (proc_macro2::TokenStream, Option) { - let parameter_type = util::to_ident(&format!( + let param_type = util::to_ident(&format!( "{}{}Parameters", group.name.to_camel(), method.name.to_camel() )); let mandatory_params = mandatory_params(params); - let mandatory_param_args = mandatory_params - .iter() - .map(|param| param_with_name(param)) - .collect::>(); + let mandatory_param_args = generate_mandatory_params(&mandatory_params); let mandatory_param_names = mandatory_params.iter().map(|param| { let (name, ..) = param_name(param); quote! { #name } }); - let mandatory_param_form_build = mandatory_params.iter().map(|param| { - let (name, name_as_str) = param_name(param); - quote! { let form = form.text(#name_as_str, #name.to_string()); } - }); - - let optional_params = params - .iter() - .filter(|param| param.get_type_info().is_optional) - .map(generate_optional_parameter); - let group_name = util::to_ident(&group.name.to_camel()); let send_builder = - MethodBuilder::new(&util::to_ident("send"), url, quote! { self.group.auth }).with_form(); + SendMethodBuilder::new(&util::to_ident("send"), url, quote! { self.group.auth }).with_form(); let generate_send_impl = |send_method: proc_macro2::TokenStream| { + let optional_params = generate_optional_params(params); + let mandatory_param_form_build = generate_mandatory_param_builder(&mandatory_params); + quote! { - impl<'a> #parameter_type<'a> { + impl<'a> #param_type<'a> { fn new(group: &'a #group_name, #(#mandatory_param_args),*) -> Self { let form = reqwest::multipart::Form::new(); #(#mandatory_param_form_build)* @@ -70,24 +60,52 @@ pub fn create_method_with_params( None => generate_send_impl(send_builder.build()), }; - ( - util::add_docs( - &method.description, - quote! { - pub fn #method_name(&self, #(#mandatory_param_args),*) -> #parameter_type { - #parameter_type::new(self, #(#mandatory_param_names),*) - } - }, - ), - Some(quote! { - pub struct #parameter_type<'a> { - group: &'a #group_name<'a>, - form: reqwest::multipart::Form, + let builder = util::add_docs( + &method.description, + quote! { + pub fn #method_name(&self, #(#mandatory_param_args),*) -> #param_type { + #param_type::new(self, #(#mandatory_param_names),*) } + }, + ); - #send - }), - ) + let group_impl = quote! { + pub struct #param_type<'a> { + group: &'a #group_name<'a>, + form: reqwest::multipart::Form, + } + + #send + }; + + (builder, Some(group_impl)) +} + +fn generate_mandatory_params(mandatory_params: &[&types::Type]) -> Vec { + mandatory_params + .iter() + .map(|param| param_with_name(param)) + .collect() +} + +fn generate_mandatory_param_builder( + mandatory_params: &[&types::Type], +) -> Vec { + mandatory_params + .iter() + .map(|param| { + let (name, name_as_str) = param_name(param); + quote! { let form = form.text(#name_as_str, #name.to_string()); } + }) + .collect() +} + +fn generate_optional_params(params: &[types::Type]) -> Vec { + params + .iter() + .filter(|param| param.get_type_info().is_optional) + .map(generate_optional_param) + .collect() } fn mandatory_params(params: &[types::Type]) -> Vec<&types::Type> { @@ -97,7 +115,7 @@ fn mandatory_params(params: &[types::Type]) -> Vec<&types::Type> { .collect() } -fn generate_optional_parameter(param: &types::Type) -> proc_macro2::TokenStream { +fn generate_optional_param(param: &types::Type) -> proc_macro2::TokenStream { let n = ¶m.get_type_info().name; let name = util::to_ident(&n.to_snake()); let t = util::to_ident(¶m.to_borrowed_type()); @@ -106,6 +124,7 @@ fn generate_optional_parameter(param: &types::Type) -> proc_macro2::TokenStream } else { quote! { #t } }; + util::add_docs( ¶m.get_type_info().description, quote! { diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs index 0b74ac1..0a53c41 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs @@ -1,6 +1,6 @@ use quote::quote; -use super::{method_builder::MethodBuilder, return_type::create_return_type}; +use super::{send_method_builder::SendMethodBuilder, return_type::create_return_type}; use crate::parser; pub fn create_method_without_params( @@ -9,7 +9,7 @@ pub fn create_method_without_params( method_name: proc_macro2::Ident, url: &str, ) -> (proc_macro2::TokenStream, Option) { - let builder = MethodBuilder::new(&method_name, url, quote! { self.auth }) + let builder = SendMethodBuilder::new(&method_name, url, quote! { self.auth }) .description(&method.description); match create_return_type(group, method) { diff --git a/qbittorrent-web-api-gen/src/generate/group/method/mod.rs b/qbittorrent-web-api-gen/src/generate/group/method/mod.rs index 051e5e5..f1948b9 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/mod.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/mod.rs @@ -1,4 +1,4 @@ -mod method_builder; +mod send_method_builder; mod method_with_params; mod method_without_params; mod return_type; diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs b/qbittorrent-web-api-gen/src/generate/group/method/send_method_builder.rs similarity index 97% rename from qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs rename to qbittorrent-web-api-gen/src/generate/group/method/send_method_builder.rs index be895f1..da0c44a 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_builder.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/send_method_builder.rs @@ -2,7 +2,7 @@ use quote::quote; use crate::generate::util; -pub struct MethodBuilder { +pub struct SendMethodBuilder { method_name: syn::Ident, url: String, auth_module_path: proc_macro2::TokenStream, @@ -11,7 +11,7 @@ pub struct MethodBuilder { form: bool, } -impl MethodBuilder { +impl SendMethodBuilder { pub fn new( method_name: &syn::Ident, url: &str, From f5a06eb0039c9b9c218341f5d1395a068b076865 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 14:48:41 +0000 Subject: [PATCH 25/29] Extract common parts --- .../src/generate/group/method/return_type.rs | 91 +++++++++---------- 1 file changed, 45 insertions(+), 46 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs b/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs index da8c2c7..3c7b469 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs @@ -24,17 +24,19 @@ pub fn create_return_type( ) }; - let enum_types_with_names = - return_type - .parameters - .iter() - .flat_map(|parameter| match ¶meter.return_type { - types::Type::Number(types::TypeInfo { - ref name, - type_description: Some(type_description), - .. - }) => { - let enum_fields = type_description.values.iter().map(|value| { + let enum_types_with_names = return_type + .parameters + .iter() + .flat_map(|parameter| match ¶meter.return_type { + types::Type::Number(types::TypeInfo { + ref name, + type_description: Some(type_description), + .. + }) => { + let enum_fields: Vec = type_description + .values + .iter() + .map(|value| { let v = &value.value; let re = Regex::new(r#"\(.*\)"#).unwrap(); let desc = &value @@ -52,27 +54,20 @@ pub fn create_return_type( #ident }, ) - }); + }) + .collect(); - let enum_name = util::to_ident(&to_enum_name(name)); - - Some(( - name, - quote! { - #[allow(clippy::enum_variant_names)] - #[derive(Debug, Deserialize, PartialEq, Eq)] - pub enum #enum_name { - #(#enum_fields,)* - } - }, - )) - } - types::Type::String(types::TypeInfo { - ref name, - type_description: Some(type_description), - .. - }) => { - let enum_fields = type_description.values.iter().map(|type_description| { + Some((name, enum_fields)) + } + types::Type::String(types::TypeInfo { + ref name, + type_description: Some(type_description), + .. + }) => { + let enum_fields: Vec = type_description + .values + .iter() + .map(|type_description| { let value = &type_description.value; let value_as_ident = util::to_ident(&value.to_camel()); @@ -83,23 +78,27 @@ pub fn create_return_type( #value_as_ident }, ) - }); + }) + .collect(); - let enum_name = util::to_ident(&to_enum_name(name)); + Some((name, enum_fields)) + } + _ => None, + }) + .flat_map(|(name, enum_fields)| { + let enum_name = util::to_ident(&to_enum_name(name)); - Some(( - name, - quote! { - #[allow(clippy::enum_variant_names)] - #[derive(Debug, Deserialize, PartialEq, Eq)] - pub enum #enum_name { - #(#enum_fields,)* - } - }, - )) - } - _ => None, - }); + Some(( + name, + quote! { + #[allow(clippy::enum_variant_names)] + #[derive(Debug, Deserialize, PartialEq, Eq)] + pub enum #enum_name { + #(#enum_fields,)* + } + }, + )) + }); let enum_names: HashMap<&String, String> = enum_types_with_names .clone() From 44eb0bcc578fe6a94d2019320605820239251fcd Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 15:01:00 +0000 Subject: [PATCH 26/29] Extract --- .../src/generate/group/method/return_type.rs | 69 ++++++++++--------- 1 file changed, 38 insertions(+), 31 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs b/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs index 3c7b469..3a4ef53 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs @@ -36,25 +36,7 @@ pub fn create_return_type( let enum_fields: Vec = type_description .values .iter() - .map(|value| { - let v = &value.value; - let re = Regex::new(r#"\(.*\)"#).unwrap(); - let desc = &value - .description - .replace(' ', "_") - .replace('-', "_") - .replace(',', "_"); - let desc_without_parentheses = re.replace_all(desc, ""); - let ident = util::to_ident(&desc_without_parentheses.to_camel()); - - util::add_docs( - &Some(value.description.clone()), - quote! { - #[serde(rename = #v)] - #ident - }, - ) - }) + .map(create_number_enum_value) .collect(); Some((name, enum_fields)) @@ -67,18 +49,7 @@ pub fn create_return_type( let enum_fields: Vec = type_description .values .iter() - .map(|type_description| { - let value = &type_description.value; - let value_as_ident = util::to_ident(&value.to_camel()); - - util::add_docs( - &Some(type_description.description.clone()), - quote! { - #[serde(rename = #value)] - #value_as_ident - }, - ) - }) + .map(create_string_enum_value) .collect(); Some((name, enum_fields)) @@ -170,3 +141,39 @@ pub fn create_return_type( }, )) } + +fn create_string_enum_value( + type_description: &types::TypeDescriptions, +) -> proc_macro2::TokenStream { + let value = &type_description.value; + let value_as_ident = util::to_ident(&value.to_camel()); + create_enum_field(&value_as_ident, value, &type_description.description) +} + +fn create_number_enum_value(value: &types::TypeDescriptions) -> proc_macro2::TokenStream { + let v = &value.value; + let re = Regex::new(r#"\(.*\)"#).unwrap(); + let desc = &value + .description + .replace(' ', "_") + .replace('-', "_") + .replace(',', "_"); + let desc_without_parentheses = re.replace_all(desc, ""); + let ident = util::to_ident(&desc_without_parentheses.to_camel()); + + create_enum_field(&ident, v, &value.description) +} + +fn create_enum_field( + ident: &syn::Ident, + rename: &str, + description: &str, +) -> proc_macro2::TokenStream { + util::add_docs( + &Some(description.to_string()), + quote! { + #[serde(rename = #rename)] + #ident + }, + ) +} From 7349f305fbb88c036c11cbdf1442a1ead178e43a Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 15:01:40 +0000 Subject: [PATCH 27/29] Format --- .../src/generate/group/method/method_with_params.rs | 5 +++-- .../src/generate/group/method/method_without_params.rs | 2 +- qbittorrent-web-api-gen/src/generate/group/method/mod.rs | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs index 0700ba4..5af6ef0 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_with_params.rs @@ -3,7 +3,7 @@ use quote::quote; use crate::{generate::util, parser, types}; -use super::{send_method_builder::SendMethodBuilder, return_type::create_return_type}; +use super::{return_type::create_return_type, send_method_builder::SendMethodBuilder}; pub fn create_method_with_params( group: &parser::ApiGroup, @@ -28,7 +28,8 @@ pub fn create_method_with_params( let group_name = util::to_ident(&group.name.to_camel()); let send_builder = - SendMethodBuilder::new(&util::to_ident("send"), url, quote! { self.group.auth }).with_form(); + SendMethodBuilder::new(&util::to_ident("send"), url, quote! { self.group.auth }) + .with_form(); let generate_send_impl = |send_method: proc_macro2::TokenStream| { let optional_params = generate_optional_params(params); diff --git a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs index 0a53c41..b705257 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/method_without_params.rs @@ -1,6 +1,6 @@ use quote::quote; -use super::{send_method_builder::SendMethodBuilder, return_type::create_return_type}; +use super::{return_type::create_return_type, send_method_builder::SendMethodBuilder}; use crate::parser; pub fn create_method_without_params( diff --git a/qbittorrent-web-api-gen/src/generate/group/method/mod.rs b/qbittorrent-web-api-gen/src/generate/group/method/mod.rs index f1948b9..3c2ea73 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/mod.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/mod.rs @@ -1,7 +1,7 @@ -mod send_method_builder; mod method_with_params; mod method_without_params; mod return_type; +mod send_method_builder; use crate::{generate::util, parser}; use case::CaseExt; From 5f120f39fc1b8d597fef5c04860f8f261cadbb77 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 15:34:27 +0000 Subject: [PATCH 28/29] Extract common parts --- .../src/generate/group/method/return_type.rs | 85 +++++++++---------- 1 file changed, 41 insertions(+), 44 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs b/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs index 3a4ef53..c6264f6 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs @@ -32,32 +32,16 @@ pub fn create_return_type( ref name, type_description: Some(type_description), .. - }) => { - let enum_fields: Vec = type_description - .values - .iter() - .map(create_number_enum_value) - .collect(); - - Some((name, enum_fields)) - } + }) => create_enum_field_value(type_description, name, create_number_enum_value), types::Type::String(types::TypeInfo { ref name, type_description: Some(type_description), .. - }) => { - let enum_fields: Vec = type_description - .values - .iter() - .map(create_string_enum_value) - .collect(); - - Some((name, enum_fields)) - } + }) => create_enum_field_value(type_description, name, create_string_enum_value), _ => None, }) .flat_map(|(name, enum_fields)| { - let enum_name = util::to_ident(&to_enum_name(name)); + let enum_name = util::to_ident(&to_enum_name(&name)); Some(( name, @@ -71,45 +55,39 @@ pub fn create_return_type( )) }); - let enum_names: HashMap<&String, String> = enum_types_with_names + let enum_names: HashMap = enum_types_with_names .clone() - .map(|(enum_name, _)| (enum_name, to_enum_name(enum_name))) + .map(|(enum_name, _)| (enum_name.clone(), to_enum_name(&enum_name))) .collect(); let enum_types = enum_types_with_names.map(|(_, enum_type)| enum_type); - let parameters = return_type.parameters.iter().map(|parameter| { + let builder_fields = return_type.parameters.iter().map(|parameter| { let namestr = ¶meter.name; let name = util::to_ident(&namestr.to_snake().replace("__", "_")); - let rtype = if let Some(enum_type) = enum_names.get(namestr) { - util::to_ident(enum_type) - } else { - util::to_ident(¶meter.return_type.to_owned_type()) + let enum_name = match enum_names.get(namestr) { + Some(enum_type) => enum_type.to_owned(), + None => parameter.return_type.to_owned_type(), }; - let type_info = parameter.return_type.get_type_info(); - - let rtype_as_quote = if type_info.is_list { - quote! { - std::vec::Vec<#rtype> - } + let rtype = util::to_ident(&enum_name); + let rtype_as_quote = if parameter.return_type.get_type_info().is_list { + quote! { std::vec::Vec<#rtype> } } else { + quote! { #rtype } + }; + + let generate_field = |field_name| { quote! { - #rtype + #[serde(rename = #namestr)] + pub #field_name: #rtype_as_quote } }; - // "type" is a reserved keyword in Rust, so we use a different name. + // "type" is a reserved keyword in Rust, so we just add "t_" to it. if namestr == "type" { - let non_reserved_name = format_ident!("t_{}", name); - quote! { - #[serde(rename = #namestr)] - pub #non_reserved_name: #rtype_as_quote - } + generate_field(format_ident!("t_{}", name)) } else { - quote! { - #[serde(rename = #namestr)] - pub #name: #rtype_as_quote - } + generate_field(name) } }); @@ -134,7 +112,7 @@ pub fn create_return_type( quote! { #[derive(Debug, Deserialize)] pub struct #return_type_name { - #(#parameters,)* + #(#builder_fields,)* } #(#enum_types)* @@ -142,6 +120,25 @@ pub fn create_return_type( )) } +fn create_enum_field_value( + type_description: &types::TypeDescription, + name: &str, + f: F, +) -> Option<(String, Vec)> +where + F: Fn(&types::TypeDescriptions) -> proc_macro2::TokenStream, +{ + let enum_fields: Vec = type_description + .values + .iter() + .map(f) + .collect::>(); + + let nn = name.to_string(); + + Some((nn, enum_fields)) +} + fn create_string_enum_value( type_description: &types::TypeDescriptions, ) -> proc_macro2::TokenStream { From 5b70b293b9d01cea6d6a2b553fe4ef94234dac83 Mon Sep 17 00:00:00 2001 From: Joel Wachsler Date: Tue, 12 Jul 2022 16:01:47 +0000 Subject: [PATCH 29/29] Extract more fields --- .../src/generate/group/method/return_type.rs | 177 ++++++++++-------- .../src/parser/group/method/mod.rs | 8 +- .../src/parser/group/mod.rs | 3 +- qbittorrent-web-api-gen/src/parser/mod.rs | 3 +- 4 files changed, 108 insertions(+), 83 deletions(-) diff --git a/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs b/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs index c6264f6..4fc68f5 100644 --- a/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs +++ b/qbittorrent-web-api-gen/src/generate/group/method/return_type.rs @@ -15,81 +15,22 @@ pub fn create_return_type( None => return None, }; - let to_enum_name = |name: &str| { - format!( - "{}{}{}", - group.name.to_camel(), - method.name.to_camel(), - name.to_camel() - ) - }; + let to_enum_name = |name: &str| to_enum_name(&group.name, &method.name, name); - let enum_types_with_names = return_type - .parameters - .iter() - .flat_map(|parameter| match ¶meter.return_type { - types::Type::Number(types::TypeInfo { - ref name, - type_description: Some(type_description), - .. - }) => create_enum_field_value(type_description, name, create_number_enum_value), - types::Type::String(types::TypeInfo { - ref name, - type_description: Some(type_description), - .. - }) => create_enum_field_value(type_description, name, create_string_enum_value), - _ => None, - }) - .flat_map(|(name, enum_fields)| { - let enum_name = util::to_ident(&to_enum_name(&name)); - - Some(( - name, - quote! { - #[allow(clippy::enum_variant_names)] - #[derive(Debug, Deserialize, PartialEq, Eq)] - pub enum #enum_name { - #(#enum_fields,)* - } - }, - )) - }); + let enum_types_with_names: Vec<(String, proc_macro2::TokenStream)> = + create_enum_with_names(return_type, &group.name, &method.name); let enum_names: HashMap = enum_types_with_names - .clone() - .map(|(enum_name, _)| (enum_name.clone(), to_enum_name(&enum_name))) + .iter() + .map(|(enum_name, _)| (enum_name.clone(), to_enum_name(enum_name))) .collect(); - let enum_types = enum_types_with_names.map(|(_, enum_type)| enum_type); + let enum_types = enum_types_with_names.iter().map(|(_, enum_type)| enum_type); - let builder_fields = return_type.parameters.iter().map(|parameter| { - let namestr = ¶meter.name; - let name = util::to_ident(&namestr.to_snake().replace("__", "_")); - let enum_name = match enum_names.get(namestr) { - Some(enum_type) => enum_type.to_owned(), - None => parameter.return_type.to_owned_type(), - }; - let rtype = util::to_ident(&enum_name); - let rtype_as_quote = if parameter.return_type.get_type_info().is_list { - quote! { std::vec::Vec<#rtype> } - } else { - quote! { #rtype } - }; - - let generate_field = |field_name| { - quote! { - #[serde(rename = #namestr)] - pub #field_name: #rtype_as_quote - } - }; - - // "type" is a reserved keyword in Rust, so we just add "t_" to it. - if namestr == "type" { - generate_field(format_ident!("t_{}", name)) - } else { - generate_field(name) - } - }); + let builder_fields = return_type + .parameters + .iter() + .map(|parameter| generate_builder_field(parameter, &enum_names)); let return_type_name = util::to_ident(&format!( "{}{}Result", @@ -98,13 +39,9 @@ pub fn create_return_type( )); let result_type = if return_type.is_list { - quote! { - std::vec::Vec<#return_type_name> - } + quote! { std::vec::Vec<#return_type_name> } } else { - quote! { - #return_type_name - } + quote! { #return_type_name } }; Some(( @@ -120,6 +57,87 @@ pub fn create_return_type( )) } +fn create_enum_with_names( + return_type: &parser::ReturnType, + group_name: &str, + method_name: &str, +) -> Vec<(String, proc_macro2::TokenStream)> { + return_type + .parameters + .iter() + .flat_map(create_enum_fields) + .map(|(name, enum_fields)| create_enum(enum_fields, group_name, method_name, name)) + .collect() +} + +fn create_enum( + enum_fields: Vec, + group_name: &str, + method_name: &str, + name: String, +) -> (String, proc_macro2::TokenStream) { + let enum_name = util::to_ident(&to_enum_name(group_name, method_name, &name)); + ( + name, + quote! { + #[allow(clippy::enum_variant_names)] + #[derive(Debug, Deserialize, PartialEq, Eq)] + pub enum #enum_name { + #(#enum_fields,)* + } + }, + ) +} + +fn create_enum_fields( + parameter: &parser::ReturnTypeParameter, +) -> Option<(String, Vec)> { + match ¶meter.return_type { + types::Type::Number(types::TypeInfo { + ref name, + type_description: Some(type_description), + .. + }) => create_enum_field_value(type_description, name, create_number_enum_value), + types::Type::String(types::TypeInfo { + ref name, + type_description: Some(type_description), + .. + }) => create_enum_field_value(type_description, name, create_string_enum_value), + _ => None, + } +} + +fn generate_builder_field( + parameter: &parser::ReturnTypeParameter, + enum_names: &HashMap, +) -> proc_macro2::TokenStream { + let namestr = ¶meter.name; + let name = util::to_ident(&namestr.to_snake().replace("__", "_")); + let enum_name = match enum_names.get(namestr) { + Some(enum_type) => enum_type.to_owned(), + None => parameter.return_type.to_owned_type(), + }; + let rtype = util::to_ident(&enum_name); + let rtype_as_quote = if parameter.return_type.get_type_info().is_list { + quote! { std::vec::Vec<#rtype> } + } else { + quote! { #rtype } + }; + let generate_field = |field_name| { + quote! { + #[serde(rename = #namestr)] + pub #field_name: #rtype_as_quote + } + }; + + // "type" is a reserved keyword in Rust, so we just add "t_" to it. + if namestr == "type" { + generate_field(format_ident!("t_{}", name)) + } else { + generate_field(name) + } +} + fn create_enum_field_value( type_description: &types::TypeDescription, name: &str, @@ -174,3 +192,12 @@ fn create_enum_field( }, ) } + +fn to_enum_name(group_name: &str, method_name: &str, name: &str) -> String { + format!( + "{}{}{}", + group_name.to_camel(), + method_name.to_camel(), + name.to_camel() + ) +} diff --git a/qbittorrent-web-api-gen/src/parser/group/method/mod.rs b/qbittorrent-web-api-gen/src/parser/group/method/mod.rs index 83aaf67..3d374f8 100644 --- a/qbittorrent-web-api-gen/src/parser/group/method/mod.rs +++ b/qbittorrent-web-api-gen/src/parser/group/method/mod.rs @@ -5,11 +5,11 @@ mod url; use crate::{md_parser, parser::util, types}; +pub use return_type::ReturnType; + use self::{ - description::parse_method_description, - parameters::parse_parameters, - return_type::{parse_return_type, ReturnType}, - url::get_method_url, + description::parse_method_description, parameters::parse_parameters, + return_type::parse_return_type, url::get_method_url, }; #[derive(Debug)] diff --git a/qbittorrent-web-api-gen/src/parser/group/mod.rs b/qbittorrent-web-api-gen/src/parser/group/mod.rs index 9debf71..b7b4036 100644 --- a/qbittorrent-web-api-gen/src/parser/group/mod.rs +++ b/qbittorrent-web-api-gen/src/parser/group/mod.rs @@ -5,6 +5,7 @@ mod url; use crate::md_parser; use self::{description::parse_group_description, method::parse_api_method, url::get_group_url}; +pub use method::{ApiMethod, ReturnType}; #[derive(Debug)] pub struct ApiGroup { @@ -14,8 +15,6 @@ pub struct ApiGroup { pub url: String, } -pub use method::ApiMethod; - pub fn parse_api_group(tree: &md_parser::TokenTree) -> ApiGroup { let methods = tree.children.iter().flat_map(parse_api_method).collect(); diff --git a/qbittorrent-web-api-gen/src/parser/mod.rs b/qbittorrent-web-api-gen/src/parser/mod.rs index 35f7a2e..8974168 100644 --- a/qbittorrent-web-api-gen/src/parser/mod.rs +++ b/qbittorrent-web-api-gen/src/parser/mod.rs @@ -12,8 +12,7 @@ pub struct ReturnTypeParameter { pub return_type: types::Type, } -pub use group::ApiGroup; -pub use group::ApiMethod; +pub use group::{ApiGroup, ApiMethod, ReturnType}; pub fn parse_api_groups(token_tree: md_parser::TokenTree) -> Vec { parse_groups(extract_relevant_parts(token_tree))