From 8e74abfc55f86dbd9142a9f38e070d2583454ddd Mon Sep 17 00:00:00 2001 From: Nadrieril Date: Mon, 23 Mar 2020 22:30:48 +0000 Subject: Go mad with doc macros --- serde_dhall/Cargo.toml | 2 +- serde_dhall/src/lib.rs | 5 +- serde_dhall/src/shortcuts.rs | 366 +++++++++++++++++++++++++++++++------------ 3 files changed, 267 insertions(+), 106 deletions(-) (limited to 'serde_dhall') diff --git a/serde_dhall/Cargo.toml b/serde_dhall/Cargo.toml index 7fd7b3e..6af883a 100644 --- a/serde_dhall/Cargo.toml +++ b/serde_dhall/Cargo.toml @@ -13,9 +13,9 @@ edition = "2018" serde = { version = "1.0", features = ["derive"] } dhall = { version = "=0.4.0", path = "../dhall" } dhall_proc_macros = { version = "=0.4.0", path = "../dhall_proc_macros" } +doc-comment = "0.3" reqwest = { version = "0.10", features = ["blocking"] } url = "2.1" [dev-dependencies] -doc-comment = "0.3" version-sync = "0.8" diff --git a/serde_dhall/src/lib.rs b/serde_dhall/src/lib.rs index 02b34a7..4cb25e5 100644 --- a/serde_dhall/src/lib.rs +++ b/serde_dhall/src/lib.rs @@ -189,6 +189,9 @@ pub use dhall_proc_macros::StaticType; pub use deserialize::Deserialize; pub(crate) use deserialize::Sealed; pub use error::{Error, Result}; -pub use shortcuts::{from_str, from_str_manual_type, from_str_static_type}; +pub use shortcuts::{ + from_file, from_file_manual_type, from_file_static_type, from_str, + from_str_manual_type, from_str_static_type, +}; pub use static_type::StaticType; pub use value::{SimpleType, Value}; diff --git a/serde_dhall/src/shortcuts.rs b/serde_dhall/src/shortcuts.rs index 54888c5..4aba9d1 100644 --- a/serde_dhall/src/shortcuts.rs +++ b/serde_dhall/src/shortcuts.rs @@ -1,111 +1,269 @@ +use doc_comment::doc_comment; +use std::path::Path; + use crate::{options, Deserialize, Result, SimpleType, StaticType}; -/// Deserialize an instance of type `T` from a string of Dhall text. -/// -/// This will recursively resolve all imports in the expression, and typecheck it before -/// deserialization. Relative imports will be resolved relative to the current directory. -/// See [`options`] for more control over this process. -/// -/// For additional type safety, prefer [`from_str_static_type`] or [`from_str_manual_type`]. -/// -/// [`options`]: options/index.html -/// [`from_str_manual_type`]: fn.from_str_manual_type.html -/// [`from_str_static_type`]: fn.from_str_static_type.html -/// -/// # Example -/// -/// ```rust -/// # fn main() -> serde_dhall::Result<()> { -/// use serde::Deserialize; -/// -/// // We use serde's derive feature -/// #[derive(Debug, Deserialize)] -/// struct Point { -/// x: u64, -/// y: u64, -/// } -/// -/// // Some Dhall data -/// let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }"; -/// -/// // Parse the Dhall string as a Point. -/// let point: Point = serde_dhall::from_str(data)?; -/// -/// assert_eq!(point.x, 1); -/// assert_eq!(point.y, 2); -/// -/// # Ok(()) -/// # } -/// ``` -pub fn from_str(s: &str) -> Result -where - T: Deserialize, -{ - options::from_str(s).parse() +// Avoid copy-pasting documentation + +#[rustfmt::skip] +macro_rules! gen_doc { + (@source_desc, str) => {"a string of Dhall text"}; + (@source_desc, file) => {"a Dhall file"}; + (@source_desc, url) => {"a remote url"}; + + (@tck_info1, none) => {""}; + (@tck_info1, manual) => {", additionally checking that it matches the supplied type"}; + (@tck_info1, static) => {", additionally checking that it matches the type of `T`"}; + + (@tck_info2, none) => {""}; + (@tck_info2, manual) => {" against the supplied type"}; + (@tck_info2, static) => {" against the type of `T`"}; + + (@tck_req, none) => {""}; + (@tck_req, manual) => {""}; + (@tck_req, static) => {"`T` must implement the [`StaticType`] trait.\n"}; + + (@tck_comment, $src:tt, none) => { + concat!("For additional type safety, prefer [`from_", stringify!($src), "_static_type`] + or [`from_", stringify!($src), "_manual_type`].\n") + }; + (@tck_comment, $src:tt, manual) => {concat!("See also [`from_", stringify!($src), "_static_type`].\n")}; + (@tck_comment, $src:tt, static) => {""}; + + (@run_example, str) => {""}; + (@run_example, file) => {"no_run"}; + (@run_example, url) => {"no_run"}; + + ($src:tt, $ty:tt) => {concat!(" +Deserialize an instance of type `T` from ", gen_doc!(@source_desc, $src), gen_doc!(@tck_info1, $ty),". + +", gen_doc!(@tck_req, $ty), " +This will recursively resolve all imports in the expression, and typecheck it", gen_doc!(@tck_info2, $ty)," +before deserialization. Relative imports will be resolved relative to the current directory. +See [`options`] for more control over this process. + +", gen_doc!(@tck_comment, $src, $ty), " + +# Example + +```", gen_doc!(@run_example, $src), " +# fn main() -> serde_dhall::Result<()> {", +gen_example!($src, $ty), " +# Ok(()) +# } +``` + +[`options`]: options/index.html +[`from_", stringify!($src), "_manual_type`]: fn.from_", stringify!($src), "_manual_type.html +[`from_", stringify!($src), "_static_type`]: fn.from_", stringify!($src), "_static_type.html +[`StaticType`]: trait.StaticType.html +")}; +} + +#[rustfmt::skip] +macro_rules! gen_example { + (str, none) => {concat!(r#" +use serde::Deserialize; + +// We use serde's derive feature +#[derive(Debug, Deserialize)] +struct Point { + x: u64, + y: u64, +} + +// Some Dhall data +let data = "{ x = 1, y = 1 + 1 } : { x: Natural, y: Natural }"; + +// Parse the Dhall string as a Point. +let point: Point = serde_dhall::from_str(data)?; + +assert_eq!(point.x, 1); +assert_eq!(point.y, 2); +"#)}; + + (str, manual) => {concat!(r#" +use std::collections::HashMap; +use serde_dhall::SimpleType; + +// Parse a Dhall type +let point_type_str = "{ x: Natural, y: Natural }"; +let point_type: SimpleType = serde_dhall::from_str(point_type_str)?; + +// Some Dhall data +let point_data = "{ x = 1, y = 1 + 1 }"; + +// Deserialize the data to a Rust type. This checks that +// the data matches the provided type. +let deserialized_map: HashMap = + serde_dhall::from_str_manual_type(point_data, &point_type)?; + +let mut expected_map = HashMap::new(); +expected_map.insert("x".to_string(), 1); +expected_map.insert("y".to_string(), 2); + +assert_eq!(deserialized_map, expected_map); +"#)}; + + (str, static) => {concat!(r#" +use serde::Deserialize; +use serde_dhall::StaticType; + +#[derive(Debug, Deserialize, StaticType)] +struct Point { + x: u64, + y: u64, } -/// Deserialize an instance of type `T` from a string of Dhall text, -/// additionally checking that it matches the supplied type. -/// -/// This will recursively resolve all imports in the expression, and typecheck it against the -/// supplied type before deserialization. Relative imports will be resolved relative to the current -/// directory. See [`options`] for more control over this process. -/// -/// See also [`from_str_static_type`]. -/// -/// [`options`]: options/index.html -/// [`from_str_static_type`]: fn.from_str_static_type.html -/// -/// # Example -/// -/// ```rust -/// # fn main() -> serde_dhall::Result<()> { -/// use serde_dhall::SimpleType; -/// use std::collections::HashMap; -/// -/// // Parse a Dhall type -/// let point_type_str = "{ x: Natural, y: Natural }"; -/// let point_type: SimpleType = serde_dhall::from_str(point_type_str)?; -/// -/// // Some Dhall data -/// let point_data = "{ x = 1, y = 1 + 1 }"; -/// -/// // Deserialize the data to a Rust type. This checks that -/// // the data matches the provided type. -/// let deserialized_map: HashMap = -/// serde_dhall::from_str_manual_type(point_data, &point_type)?; -/// -/// let mut expected_map = HashMap::new(); -/// expected_map.insert("x".to_string(), 1); -/// expected_map.insert("y".to_string(), 2); -/// -/// assert_eq!(deserialized_map, expected_map); -/// # Ok(()) -/// # } -/// ``` -pub fn from_str_manual_type(s: &str, ty: &SimpleType) -> Result -where - T: Deserialize, -{ - options::from_str(s).type_annotation(ty).parse() +// Some Dhall data +let data = "{ x = 1, y = 1 + 1 }"; + +// Convert the Dhall string to a Point. +let point: Point = serde_dhall::from_str_static_type(data)?; +assert_eq!(point.x, 1); +assert_eq!(point.y, 2); + +// Invalid data fails the type validation +let invalid_data = "{ x = 1, z = 0.3 }"; +assert!(serde_dhall::from_str_static_type::(invalid_data).is_err()); +"#)}; + + (file, none) => {concat!(r#" +use serde::Deserialize; + +// We use serde's derive feature +#[derive(Debug, Deserialize)] +struct Point { + x: u64, + y: u64, } -/// Deserialize an instance of type `T` from a string of Dhall text, -/// additionally checking that it matches the type of `T`. -/// -/// `T` must implement the [`StaticType`] trait. -/// -/// This will recursively resolve all imports in the expression, and typecheck it against the -/// type of `T`. Relative imports will be resolved relative to the current -/// directory. See [`options`] for more control over this process. -/// -/// [`options`]: options/index.html -/// [`StaticType`]: trait.StaticType.html -/// -/// TODO -pub fn from_str_static_type(s: &str) -> Result -where - T: Deserialize + StaticType, -{ - options::from_str(s).static_type_annotation().parse() +// Parse the Dhall file as a Point. +let point: Point = serde_dhall::from_file("foo.dhall")?; +"#)}; + + (file, manual) => {concat!(r#" +use std::collections::HashMap; +use serde_dhall::SimpleType; + +// Parse a Dhall type +let point_type_str = "{ x: Natural, y: Natural }"; +let point_type: SimpleType = serde_dhall::from_str(point_type_str)?; + +// Deserialize the data to a Rust type. This checks that +// the data matches the provided type. +let deserialized_map: HashMap = + serde_dhall::from_file_manual_type("foo.dhall", &point_type)?; +"#)}; + + (file, static) => {concat!(r#" +use serde::Deserialize; +use serde_dhall::StaticType; + +#[derive(Debug, Deserialize, StaticType)] +struct Point { + x: u64, + y: u64, +} + +// Convert the Dhall string to a Point. +let point: Point = serde_dhall::from_file_static_type("foo.dhall")?; +"#)}; + + ($src:tt, $ty:tt) => {""}; } + +macro_rules! generate_fn { + (@generate_src, + str, $ty:tt, $name:ident, + ) => { + generate_fn!(@generate_ty, + str, $ty, $name, + (), + (s: &str), + (options::from_str(s)), + ); + }; + (@generate_src, + file, $ty:tt, $name:ident, + ) => { + generate_fn!(@generate_ty, + file, $ty, $name, + (P: AsRef), + (path: P), + (options::from_file(path)), + ); + }; + + (@generate_ty, + $src:tt, none, $name:ident, + ($($ty_params:tt)*), + ($($input_args:tt)*), + ($($create_options:tt)*), + ) => { + generate_fn!(@generate, + $src, none, $name, + ($($ty_params)*), + ($($input_args)*), + (), + ($($create_options)*), + ); + }; + (@generate_ty, + $src:tt, manual, $name:ident, + ($($ty_params:tt)*), + ($($input_args:tt)*), + ($($create_options:tt)*), + ) => { + generate_fn!(@generate, + $src, manual, $name, + ($($ty_params)*), + ($($input_args)*, ty: &SimpleType), + (), + ($($create_options)* .type_annotation(ty)), + ); + }; + (@generate_ty, + $src:tt, static, $name:ident, + ($($ty_params:tt)*), + ($($input_args:tt)*), + ($($create_options:tt)*), + ) => { + generate_fn!(@generate, + $src, static, $name, + ($($ty_params)*), + ($($input_args)*), + (+ StaticType), + ($($create_options)* .static_type_annotation()), + ); + }; + + (@generate, + $src:tt, $ty:tt, $name:ident, + ($($ty_params:tt)*), + ($($input_args:tt)*), + ($($extra_bounds:tt)*), + ($($create_options:tt)*), + ) => { + doc_comment! { + gen_doc!($src, $ty), + pub fn $name ($($input_args)*) -> Result + where + T: Deserialize $($extra_bounds)*, + { + $($create_options)* .parse() + } + } + }; + + ($src:tt, $ty:tt, $name:ident) => { + generate_fn!(@generate_src, $src, $ty, $name,); + }; +} + +generate_fn!(str, none, from_str); +generate_fn!(str, manual, from_str_manual_type); +generate_fn!(str, static, from_str_static_type); +generate_fn!(file, none, from_file); +generate_fn!(file, manual, from_file_manual_type); +generate_fn!(file, static, from_file_static_type); -- cgit v1.2.3