diff --git a/Cargo.lock b/Cargo.lock index 824c3d7..c5371e3 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1337,6 +1337,7 @@ name = "proc_macros" version = "0.0.1" dependencies = [ "convert_case 0.5.0", + "json", "parse_duration", "proc-macro2", "quote", diff --git a/proc_macros/Cargo.toml b/proc_macros/Cargo.toml index 75da460..5d2b6ec 100644 --- a/proc_macros/Cargo.toml +++ b/proc_macros/Cargo.toml @@ -12,6 +12,7 @@ proc-macro = true [dependencies] convert_case = "0.5" +json = "0" parse_duration = "2" proc-macro2 = "1" quote = "1" diff --git a/proc_macros/proc_macros.rs b/proc_macros/proc_macros.rs index edd69f1..d42007f 100644 --- a/proc_macros/proc_macros.rs +++ b/proc_macros/proc_macros.rs @@ -1,7 +1,10 @@ use convert_case::Case; use convert_case::Casing; use proc_macro::TokenStream; +use proc_macro2::Span; use quote::quote; +use std::io::Read; +use std::str::FromStr; use syn::bracketed; use syn::parse::Parse; use syn::parse_macro_input; @@ -735,3 +738,140 @@ pub fn create_fanbox_download_helper(item: TokenStream) -> TokenStream { ); stream.into() } + +fn read_json_file + ?Sized>(p: &P) -> json::JsonValue { + let mut f = std::fs::File::open(p).unwrap(); + let mut s = String::new(); + f.read_to_string(&mut s).unwrap(); + json::parse(s.as_str()).unwrap() +} + +#[proc_macro] +pub fn define_exif_data_source(item: TokenStream) -> TokenStream { + let file_path = parse_macro_input!(item as LitStr).value(); + let path = + std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join(file_path); + let data = read_json_file(&path); + let mut streams = Vec::new(); + for (key, data) in data.entries() { + let doc = match data["description"].as_str() { + Some(doc) => { + let doc = LitStr::new(doc, Span::call_site()); + quote!(#[doc = #doc]) + } + None => { + quote!() + } + }; + let fname = Ident::new(key, Span::call_site()); + let result = proc_macro2::TokenStream::from_str(match data["return"].as_str() { + Some(r) => r, + None => "String", + }) + .unwrap(); + streams.push(quote!( + #doc + #[inline] + fn #fname(&self) -> Option<#result> { + None + } + )); + } + let stream = quote!( + #(#streams)* + ); + stream.into() +} + +struct CallParentDataSourceFun { + pub file: LitStr, + pub expr: Expr, + pub keys: Vec, + pub is_include: bool, +} + +impl Parse for CallParentDataSourceFun { + fn parse(input: syn::parse::ParseStream) -> syn::Result { + let filel = Lit::parse(input)?; + let file = match filel { + Lit::Str(s) => s, + _ => { + return Err(syn::Error::new(filel.span(), "Unsupported literal.")); + } + }; + token::Comma::parse(input)?; + let expr = input.parse()?; + let mut keys = Vec::new(); + let mut is_include = false; + let mut is_first = true; + loop { + if input.cursor().eof() { + break; + } + token::Comma::parse(input)?; + if input.cursor().eof() { + break; + } + if is_first { + is_first = false; + match syn::Lit::parse(input) { + Ok(lit) => { + match lit { + syn::Lit::Bool(lit) => { + is_include = lit.value(); + } + _ => { + return Err(syn::Error::new(lit.span(), "Unsupported literal.")); + } + } + continue; + } + Err(_) => {} + } + } + keys.push(Ident::parse(input)?); + } + Ok(Self { + file, + expr, + keys, + is_include, + }) + } +} + +#[proc_macro] +pub fn call_parent_data_source_fun(item: TokenStream) -> TokenStream { + let CallParentDataSourceFun { + file, + expr, + keys, + is_include, + } = parse_macro_input!(item as CallParentDataSourceFun); + let path = + std::path::PathBuf::from(std::env::var("CARGO_MANIFEST_DIR").unwrap()).join(file.value()); + let data = read_json_file(&path); + let mut streams = Vec::new(); + for (key, data) in data.entries() { + let is_in = keys.iter().find(|&r| r.to_string() == key).is_some(); + if is_in != is_include { + continue; + } + let fname = Ident::new(key, Span::call_site()); + let result = proc_macro2::TokenStream::from_str(match data["return"].as_str() { + Some(r) => r, + None => "String", + }) + .unwrap(); + streams.push(quote!( + #[inline] + fn #fname(&self) -> Option<#result> { + (#expr).#fname() + } + )); + } + let stream = quote!( + #(#streams)* + ); + stream.into() +} diff --git a/src/data/exif.rs b/src/data/exif.rs index 24d643f..448f7a4 100644 --- a/src/data/exif.rs +++ b/src/data/exif.rs @@ -6,27 +6,31 @@ use crate::exif::ExifTypeID; use crate::exif::ExifValue; use crate::ext::try_err::TryErr; use crate::parser::description::parse_description; +use proc_macros::call_parent_data_source_fun; +use proc_macros::define_exif_data_source; use std::convert::TryFrom; use std::ffi::OsStr; use utf16string::LittleEndian; use utf16string::WString; pub trait ExifDataSource { - fn image_author(&self) -> Option { - None - } + define_exif_data_source!("src/data/exif_data_source.json"); +} - fn image_comment(&self) -> Option { - None - } +impl ExifDataSource for std::sync::Arc { + call_parent_data_source_fun!("src/data/exif_data_source.json", *self,); +} - fn image_id(&self) -> Option { - None - } - - fn image_title(&self) -> Option { - None - } +impl ExifDataSource for Option { + call_parent_data_source_fun!( + "src/data/exif_data_source.json", + match self { + Some(data) => data, + None => { + return None; + } + }, + ); } fn add_image_id(data: &mut ExifData, d: &D) -> Result<(), ()> { diff --git a/src/data/exif_data_source.json b/src/data/exif_data_source.json new file mode 100644 index 0000000..2171698 --- /dev/null +++ b/src/data/exif_data_source.json @@ -0,0 +1,6 @@ +{ + "image_author": { "return": "String", "description": "The author of the image." }, + "image_comment": { "return": "String", "description": "The comment of the image." }, + "image_id": { "return": "String", "description": "The image id." }, + "image_title": { "return": "String", "description": "The title of the image." } +} diff --git a/src/data/fanbox.rs b/src/data/fanbox.rs index 9107639..5240869 100644 --- a/src/data/fanbox.rs +++ b/src/data/fanbox.rs @@ -1,12 +1,18 @@ +#[cfg(feature = "exif")] +use super::exif::ExifDataSource; use crate::fanbox::post::FanboxPost; use crate::pixiv_link::PixivID; use crate::pixiv_link::ToPixivID; use json::JsonValue; +#[cfg(feature = "exif")] +use proc_macros::call_parent_data_source_fun; pub struct FanboxData { pub id: PixivID, /// Raw data pub raw: JsonValue, + #[cfg(feature = "exif")] + pub exif_data: Option>, } impl FanboxData { @@ -15,8 +21,28 @@ impl FanboxData { Some(id) => Some(Self { id, raw: post.get_json().clone(), + #[cfg(feature = "exif")] + exif_data: None, }), None => None, } } } + +#[cfg(feature = "exif")] +impl ExifDataSource for FanboxData { + call_parent_data_source_fun!( + "src/data/exif_data_source.json", + match &self.exif_data { + Some(d) => d, + None => { + return None; + } + }, + image_id, + ); + + fn image_id(&self) -> Option { + Some(self.id.to_link()) + } +} diff --git a/src/download.rs b/src/download.rs index 14780ab..e97c35f 100644 --- a/src/download.rs +++ b/src/download.rs @@ -131,7 +131,7 @@ impl Main { if d.is_downloaded() { #[cfg(feature = "exif")] { - if add_exifdata_to_image(&file_name, &*datas, np).is_err() { + if add_exifdata_to_image(&file_name, &datas, np).is_err() { println!( "{} {}", gettext("Failed to add exif data to image:"), @@ -150,7 +150,7 @@ impl Main { #[cfg(feature = "exif")] { if helper.update_exif() && file_name.exists() { - if add_exifdata_to_image(&file_name, &*datas, np).is_err() { + if add_exifdata_to_image(&file_name, &datas, np).is_err() { println!( "{} {}", gettext("Failed to add exif data to image:"),