Documentation of "how it works"; rustfmt
This commit is contained in:
+5
-2
@@ -14,7 +14,10 @@ pub enum UnevalError {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl ser::Error for UnevalError {
|
impl ser::Error for UnevalError {
|
||||||
fn custom<T>(msg:T)->Self where T: std::fmt::Display {
|
fn custom<T>(msg: T) -> Self
|
||||||
|
where
|
||||||
|
T: std::fmt::Display,
|
||||||
|
{
|
||||||
Self::Custom(msg.to_string())
|
Self::Custom(msg.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+9
-6
@@ -1,7 +1,7 @@
|
|||||||
//! Convenience functions to be used with Uneval.
|
//! Convenience functions to be used with Uneval.
|
||||||
|
|
||||||
use crate::ser::{SerResult, Uneval};
|
|
||||||
use crate::error::UnevalError;
|
use crate::error::UnevalError;
|
||||||
|
use crate::ser::{SerResult, Uneval};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
|
|
||||||
/// Write generated Rust code to the provided [`Write`][std::io::Write] implementation.
|
/// Write generated Rust code to the provided [`Write`][std::io::Write] implementation.
|
||||||
@@ -35,14 +35,17 @@ pub fn to_file(value: impl Serialize, target: impl AsRef<std::path::Path>) -> Se
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Convenience wrapper around [`to_file`].
|
/// Convenience wrapper around [`to_file`].
|
||||||
///
|
///
|
||||||
/// This function finds out where the output directory is by looking at `OUT_DIR` environment variable
|
/// This function finds out where the output directory is by looking at `OUT_DIR` environment variable
|
||||||
/// and creates the file with the provided name there.
|
/// and creates the file with the provided name there.
|
||||||
pub fn to_out_dir(value: impl Serialize, file_name: impl AsRef<str>) -> SerResult {
|
pub fn to_out_dir(value: impl Serialize, file_name: impl AsRef<str>) -> SerResult {
|
||||||
let path: std::path::PathBuf = [
|
let path: std::path::PathBuf = [
|
||||||
std::env::var("OUT_DIR").expect("OUT_DIR not set, check if you're running this from the build script"),
|
std::env::var("OUT_DIR")
|
||||||
file_name.as_ref().into()
|
.expect("OUT_DIR not set, check if you're running this from the build script"),
|
||||||
].iter().collect();
|
file_name.as_ref().into(),
|
||||||
|
]
|
||||||
|
.iter()
|
||||||
|
.collect();
|
||||||
value.serialize(&mut Uneval::new(std::fs::File::create(path)?))
|
value.serialize(&mut Uneval::new(std::fs::File::create(path)?))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,4 +54,4 @@ pub fn to_string(value: impl Serialize) -> Result<String, UnevalError> {
|
|||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
value.serialize(&mut Uneval::new(&mut out))?;
|
value.serialize(&mut Uneval::new(&mut out))?;
|
||||||
Ok(String::from_utf8(out)?)
|
Ok(String::from_utf8(out)?)
|
||||||
}
|
}
|
||||||
|
|||||||
+5
-2
@@ -2,11 +2,14 @@ use crate::ser::SerResult;
|
|||||||
use std::io::Write;
|
use std::io::Write;
|
||||||
|
|
||||||
pub(crate) fn tuple_converter(mut output: impl Write, len: usize) -> SerResult {
|
pub(crate) fn tuple_converter(mut output: impl Write, len: usize) -> SerResult {
|
||||||
write!(output, "
|
write!(
|
||||||
|
output,
|
||||||
|
"
|
||||||
trait FromTuple<T>: Sized {{
|
trait FromTuple<T>: Sized {{
|
||||||
fn from_tuple(tuple: T) -> Self;
|
fn from_tuple(tuple: T) -> Self;
|
||||||
}}
|
}}
|
||||||
")?;
|
"
|
||||||
|
)?;
|
||||||
let array = format!("[T; {}]", len);
|
let array = format!("[T; {}]", len);
|
||||||
let tuple = format!("({})", (0..len).map(|_| "T,").collect::<String>());
|
let tuple = format!("({})", (0..len).map(|_| "T,").collect::<String>());
|
||||||
let mapping = format!(
|
let mapping = format!(
|
||||||
|
|||||||
+199
-6
@@ -4,7 +4,8 @@
|
|||||||
//! data, like JSON or YAML. This way, you'll mostly escape runtime cost of deserialization,
|
//! data, like JSON or YAML. This way, you'll mostly escape runtime cost of deserialization,
|
||||||
//! nearly as if you've written the same data directly in code by hand.
|
//! nearly as if you've written the same data directly in code by hand.
|
||||||
//! Of course, in most cases this cost is already negligible, but for crates which use
|
//! Of course, in most cases this cost is already negligible, but for crates which use
|
||||||
//! large blobs of data this crate can come in handy, improving startup times.
|
//! large blobs of data this crate can come in handy, improving startup times, and can
|
||||||
|
//! eliminate the need for `serde` as runtime dependency.
|
||||||
//!
|
//!
|
||||||
//! ## Usage
|
//! ## Usage
|
||||||
//! In general, to embed some code into crate, you have to use the build script
|
//! In general, to embed some code into crate, you have to use the build script
|
||||||
@@ -15,6 +16,197 @@
|
|||||||
//! let value = include!(concat!(env!(OUT_DIR), "/file_name.rs"));
|
//! let value = include!(concat!(env!(OUT_DIR), "/file_name.rs"));
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
|
//! ## How does it work?
|
||||||
|
//!
|
||||||
|
//! Of course, we can't always directly construct the code for the desired value (more on this
|
||||||
|
//! in the [Limitations](#limitations) section below).
|
||||||
|
//! However, in many cases the information provided by Serde is enough.
|
||||||
|
//!
|
||||||
|
//! For every case, we'll provide an example of how the generated code can look like, as a sequence of
|
||||||
|
//! `let` statements, where the left part is written by hand and the right one is assumed to be generated.
|
||||||
|
//!
|
||||||
|
//! ### Primitives
|
||||||
|
//!
|
||||||
|
//! Number literals, such as `i8` or `f32`, are directly written into the output. The only tricky part
|
||||||
|
//! is that we have to use suffixed literals, e.g. `1u8` or `1.1f64` - otherwise we'd run into the problem
|
||||||
|
//! with the float values which are in fact integers, since they would be output as integer literals,
|
||||||
|
//! not as float ones (i.e. `1` and not `1.0`) and so wouldn't typecheck.
|
||||||
|
//!
|
||||||
|
//! Boolean and character literals are also simply written directly - no surprises here.
|
||||||
|
//!
|
||||||
|
//! Example:
|
||||||
|
//! ```
|
||||||
|
//! let _: i8 = 12i8;
|
||||||
|
//! let _: u128 = 12345u128;
|
||||||
|
//! let _: f32 = -1f32;
|
||||||
|
//! let _: f64 = 12345.6789f64;
|
||||||
|
//! let _: char = 'c';
|
||||||
|
//! let _: bool = true;
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Strings
|
||||||
|
//! When Serde gives us something string-like, we have to make some kind of conversion, since
|
||||||
|
//! string literals are of type `&'static str`, and string-like fields in serializable structs are
|
||||||
|
//! usually of some owned type, like `String`. We assume that every such type would be convertible to
|
||||||
|
//! `String` using [`Into`][std::convert::Into], so we simply emit a string literal with call to `into`.
|
||||||
|
//!
|
||||||
|
//! Example:
|
||||||
|
//! ```
|
||||||
|
//! let _: String = "string value".into();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Byte strings are handled as byte sequences, [as recommended by Serde itself][::serde::Serializer::serialize_bytes],
|
||||||
|
//! and so we'll discuss them [below](#vec-like-types-sequences).
|
||||||
|
//!
|
||||||
|
//! ### Tuple structs and unit values
|
||||||
|
//!
|
||||||
|
//! Unit type (`()`), unit structs and unit variants (including `None`) are emitted simply by using
|
||||||
|
//! the type name. Tuple structs and variants (and newtype-flavored ones, including `Some`)
|
||||||
|
//! are emitted by writing their name (with the enum name, if necessary), parenthesis,
|
||||||
|
//! and serializing the inner values.
|
||||||
|
//!
|
||||||
|
//! Example:
|
||||||
|
//! ```
|
||||||
|
//! struct TupleStruct((), Option<u8>, Option<u8>);
|
||||||
|
//! let _: TupleStruct = TupleStruct((), None, Some(1u8));
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Vec-like types (sequences)
|
||||||
|
//!
|
||||||
|
//! `Vec`-like structures are constructed using the temporary `Vec`. We assume that every such type will
|
||||||
|
//! implement [`FromIterator`][std::iter::FromIterator], so we emit the call to `vec!` macro,
|
||||||
|
//! serialize the data and finalize the emit with call to `into_iter().collect()`.
|
||||||
|
//! This is not exactly zero-cost, but it seems that this is the minimal.
|
||||||
|
//!
|
||||||
|
//! Example:
|
||||||
|
//! ```
|
||||||
|
//! let _: Vec<u32> = vec![1u32, 2u32, 3u32].into_iter().collect();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Tuples and arrays
|
||||||
|
//!
|
||||||
|
//! That's where it becomes tricky.
|
||||||
|
//!
|
||||||
|
//! The problem is that Serde doesn't distinguish between this two kinds of values: they both are treated
|
||||||
|
//! as sequences with known length, called "tuples" internally; as a consequence, we don't know at the emit time,
|
||||||
|
//! which of them we'll be generating. But in the Rust code, they are created with entirely different syntax,
|
||||||
|
//! and there's no easy way to convert one into another. So, we decided to emit a little "runtime"
|
||||||
|
//! (consisting of small `#[inline]` functions, so it should in fact be zero-cost), which will
|
||||||
|
//! correctly handle the data according to the type being requested.
|
||||||
|
//!
|
||||||
|
//! The idea is, in fact, directly borrowed from the [`collect`]/[`FromIterator`] pair: we can call `collect`
|
||||||
|
//! on every iterator value, and, as long as the target type implements `FromIterator` with the necessary
|
||||||
|
//! parameters, `collect` will do its job. We're using not the trait method, but the free function (the reason is
|
||||||
|
//! that with the trait we would sometimes have a chain of type inferences, which Rust is unable to solve);
|
||||||
|
//! however, this doesn't change the overall picture.
|
||||||
|
//!
|
||||||
|
//! [`collect`]: std::iter::Iterator::collect
|
||||||
|
//! [`FromIterator`]: std::iter::FromIterator
|
||||||
|
//!
|
||||||
|
//! In general, here's what being generated:
|
||||||
|
//! - A `FromTuple<T>` trait with `from_tuple(input: T) -> Self` associated function.
|
||||||
|
//! - Two implementations: `impl<T> FromTuple<(T,...,T,)> for [T; N]` and
|
||||||
|
//! `impl<T1, ... TN> FromTuple<(T1,...TN,)> for (T1,...TN,)`.
|
||||||
|
//! - Function `convert<T1, ... TN, Out: FromTuple<(T1,...TN,)>>(tuple: (T1,...TN,)) -> Out`,
|
||||||
|
//! which simply calls `Out::from_tuple(tuple)`.
|
||||||
|
//!
|
||||||
|
//! Then, the value itself is created by the call to `convert`, with tuple of serialized values as argument.
|
||||||
|
//! Depending on whether the target expects the array or tuple, `convert` will select one particular implementation.
|
||||||
|
//!
|
||||||
|
//! Example:
|
||||||
|
//! ```
|
||||||
|
//! let tuple: (i32, f32, String) = {
|
||||||
|
//! trait FromTuple<T>: Sized {
|
||||||
|
//! fn from_tuple(tuple: T) -> Self;
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl<T> FromTuple<(T,T,T,)> for [T; 3] {
|
||||||
|
//! #[inline]
|
||||||
|
//! fn from_tuple(tuple: (T,T,T,)) -> Self {
|
||||||
|
//! [tuple.0,tuple.1,tuple.2]
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl<T0,T1,T2> FromTuple<(T0,T1,T2,)> for (T0,T1,T2,) {
|
||||||
|
//! #[inline]
|
||||||
|
//! fn from_tuple(tuple: (T0,T1,T2,)) -> Self {
|
||||||
|
//! tuple
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[inline]
|
||||||
|
//! fn convert<T0,T1,T2, Out: FromTuple<(T0,T1,T2,)>>(tuple: (T0,T1,T2,)) -> Out {
|
||||||
|
//! Out::from_tuple(tuple)
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! convert((1i32,1f32,"tuple entry".into()))
|
||||||
|
//! };
|
||||||
|
//! // Check that the tuple is indeed created as desired.
|
||||||
|
//! assert_eq!(tuple, (1i32,1f32,"tuple entry".to_string()));
|
||||||
|
//!
|
||||||
|
//! let arr: [i32; 4] = {
|
||||||
|
//! trait FromTuple<T>: Sized {
|
||||||
|
//! fn from_tuple(tuple: T) -> Self;
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl<T> FromTuple<(T,T,T,T,)> for [T; 4] {
|
||||||
|
//! #[inline]
|
||||||
|
//! fn from_tuple(tuple: (T,T,T,T,)) -> Self {
|
||||||
|
//! [tuple.0,tuple.1,tuple.2,tuple.3]
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! impl<T0,T1,T2,T3> FromTuple<(T0,T1,T2,T3,)> for (T0,T1,T2,T3,) {
|
||||||
|
//! #[inline]
|
||||||
|
//! fn from_tuple(tuple: (T0,T1,T2,T3,)) -> Self {
|
||||||
|
//! tuple
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! #[inline]
|
||||||
|
//! fn convert<T0,T1,T2,T3, Out: FromTuple<(T0,T1,T2,T3,)>>(tuple: (T0,T1,T2,T3,)) -> Out {
|
||||||
|
//! Out::from_tuple(tuple)
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! convert((1,2,3,4))
|
||||||
|
//! };
|
||||||
|
//! // Check that the array is indeed created as desired.
|
||||||
|
//! assert_eq!(arr, [1, 2, 3, 4]);
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! ### Maps
|
||||||
|
//!
|
||||||
|
//! Since Rust doesn't have the notion of map literals, we can't construct one directly. However, standard map-like
|
||||||
|
//! types ([`HashMap`], [`BTreeMap`]) implement `FromIterator<(K, V)>`, i.e. they can be built from the iterator of
|
||||||
|
//! key-value pairs. `uneval` generates code according to this convention: we create a `Vec` of pairs, which is then
|
||||||
|
//! converted into map with `into_iter().collect()`.
|
||||||
|
//!
|
||||||
|
//! Example:
|
||||||
|
//! ```
|
||||||
|
//! let _: std::collections::HashMap<i32, String> = vec![
|
||||||
|
//! (1, "first".into()),
|
||||||
|
//! (100, "one hundredth".into()),
|
||||||
|
//! ].into_iter().collect();
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! [`HashMap`]: std::collections::HashMap
|
||||||
|
//! [`BTreeMap`]: std::collections::BTreeMap
|
||||||
|
//!
|
||||||
|
//! ### Structs
|
||||||
|
//!
|
||||||
|
//! Last but not the least, this case is relatively simple. Emitted code is simply the struct construction -
|
||||||
|
//! i.e. the struct name, the curly braces and a list of pairs of the form `{field name}: {serialized value}`.
|
||||||
|
//!
|
||||||
|
//! Example:
|
||||||
|
//! ```
|
||||||
|
//! struct Struct { boolean: bool, number: i32, string: String }
|
||||||
|
//! let _: Struct = Struct {
|
||||||
|
//! boolean: true,
|
||||||
|
//! number: 1i32,
|
||||||
|
//! string: "string".into()
|
||||||
|
//! };
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
//! ## Limitations
|
//! ## Limitations
|
||||||
//! There are some cases when `uneval` will be unable to generate valid code. Namely:
|
//! There are some cases when `uneval` will be unable to generate valid code. Namely:
|
||||||
//! 1. Since Serde doesn't provide us the full path to the type in question (and in most cases it's simply unable to),
|
//! 1. Since Serde doesn't provide us the full path to the type in question (and in most cases it's simply unable to),
|
||||||
@@ -33,9 +225,10 @@
|
|||||||
//!
|
//!
|
||||||
//! [include]: https://doc.rust-lang.org/stable/std/macro.include.html
|
//! [include]: https://doc.rust-lang.org/stable/std/macro.include.html
|
||||||
|
|
||||||
pub mod error;
|
mod helpers;
|
||||||
pub mod ser;
|
|
||||||
pub mod funcs;
|
|
||||||
pub mod helpers;
|
|
||||||
|
|
||||||
pub use funcs::{to_file, to_out_dir, to_string, write};
|
pub mod error;
|
||||||
|
pub mod funcs;
|
||||||
|
pub mod ser;
|
||||||
|
|
||||||
|
pub use funcs::{to_file, to_out_dir, to_string, write};
|
||||||
|
|||||||
@@ -49,6 +49,7 @@ use std::collections::HashMap;
|
|||||||
pub struct Complex {
|
pub struct Complex {
|
||||||
pub tuple: (i32, f32, String),
|
pub tuple: (i32, f32, String),
|
||||||
pub vector: Vec<i32>,
|
pub vector: Vec<i32>,
|
||||||
|
pub byte_arr: Vec<u8>,
|
||||||
pub map: HashMap<i128, String>,
|
pub map: HashMap<i128, String>,
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
@@ -60,6 +61,7 @@ value = """
|
|||||||
definition::Complex {
|
definition::Complex {
|
||||||
tuple: (1, 1.0, "tuple entry".into()),
|
tuple: (1, 1.0, "tuple entry".into()),
|
||||||
vector: vec![1, 2, 3, 4, 5, -1, -2, -3, -4, -5],
|
vector: vec![1, 2, 3, 4, 5, -1, -2, -3, -4, -5],
|
||||||
|
byte_arr: b"asdfghj".to_vec(),
|
||||||
map
|
map
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
use batch_run::Batch;
|
use batch_run::Batch;
|
||||||
|
use serde::Deserialize;
|
||||||
use std::{
|
use std::{
|
||||||
|
collections::HashMap,
|
||||||
fs::{create_dir, read_to_string, File},
|
fs::{create_dir, read_to_string, File},
|
||||||
io::Write,
|
io::Write,
|
||||||
path::{Path, PathBuf}, collections::HashMap,
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
use serde::Deserialize;
|
|
||||||
use toml::from_str;
|
use toml::from_str;
|
||||||
|
|
||||||
#[derive(Deserialize, Default)]
|
#[derive(Deserialize, Default)]
|
||||||
@@ -39,19 +40,28 @@ impl Data {
|
|||||||
write!(
|
write!(
|
||||||
File::create(&path.with_file_name(format!("{}-user.rs", name))).unwrap(),
|
File::create(&path.with_file_name(format!("{}-user.rs", name))).unwrap(),
|
||||||
include_str!("user.tpl"),
|
include_str!("user.tpl"),
|
||||||
types = self.support_types.as_ref().map_or(self.main_type.clone(), |types| format!("{},{}", self.main_type, types)),
|
types = self
|
||||||
|
.support_types
|
||||||
|
.as_ref()
|
||||||
|
.map_or(self.main_type.clone(), |types| format!(
|
||||||
|
"{},{}",
|
||||||
|
self.main_type, types
|
||||||
|
)),
|
||||||
ser_type = self.main_type,
|
ser_type = self.main_type,
|
||||||
value = self.value
|
value = self.value
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
write!(
|
write!(
|
||||||
File::create(&path.with_file_name(format!("{}-main.snapshot", name))).unwrap(),
|
File::create(&path.with_file_name(format!("{}-main.snapshot", name))).unwrap(),
|
||||||
include_str!("main.snapshot.tpl"),
|
include_str!("main.snapshot.tpl"),
|
||||||
name = name
|
name = name
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
write!(
|
write!(
|
||||||
File::create(&path.with_file_name(format!("{}-user.snapshot", name))).unwrap(),
|
File::create(&path.with_file_name(format!("{}-user.snapshot", name))).unwrap(),
|
||||||
include_str!("user.snapshot.tpl"),
|
include_str!("user.snapshot.tpl"),
|
||||||
).unwrap();
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +70,8 @@ fn main() {
|
|||||||
let toml = read_to_string("test_fixtures/data.toml").unwrap();
|
let toml = read_to_string("test_fixtures/data.toml").unwrap();
|
||||||
let data: HashMap<String, Data> = from_str(&toml).unwrap();
|
let data: HashMap<String, Data> = from_str(&toml).unwrap();
|
||||||
let path: PathBuf = "test_fixtures".into();
|
let path: PathBuf = "test_fixtures".into();
|
||||||
data.into_iter().for_each(|(key, value)| value.write(&key, &path));
|
data.into_iter()
|
||||||
|
.for_each(|(key, value)| value.write(&key, &path));
|
||||||
|
|
||||||
let b = Batch::new();
|
let b = Batch::new();
|
||||||
b.run_match("test_fixtures/**/*-main.rs");
|
b.run_match("test_fixtures/**/*-main.rs");
|
||||||
|
|||||||
Reference in New Issue
Block a user