Skip to content

Commit

Permalink
Merge branch 'auto-option'
Browse files Browse the repository at this point in the history
  • Loading branch information
BrianLondon committed Sep 7, 2024
2 parents a84db4a + 8f55999 commit d8739dd
Show file tree
Hide file tree
Showing 9 changed files with 238 additions and 13 deletions.
1 change: 1 addition & 0 deletions build.nu
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ def "main docs" [] {
}

def "main test" [] {
cargo +nightly docs-rs
cargo test --workspace -- --include-ignored
cargo test --workspace --features experimental-write -- --include-ignored
}
Expand Down
4 changes: 2 additions & 2 deletions fixcol-derive/src/attrs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -711,15 +711,15 @@ mod tests {
fn parse_params_ident_only() {
let code: MetaList = syn::parse_str("fixcol(width)").unwrap();
let x: Vec<FieldParam> = get_config_params(code.tokens).unwrap();
println!("{:?}", x)
assert_eq!(x, x);
}

#[test]
#[should_panic(expected = "Expected value found end of input.")]
fn parse_params_ident_equal_only() {
let code: MetaList = syn::parse_str("fixcol(width=)").unwrap();
let x: Vec<FieldParam> = get_config_params(code.tokens).unwrap();
println!("{:?}", x)
assert_eq!(x, x);
}

#[test]
Expand Down
64 changes: 59 additions & 5 deletions fixcol-derive/src/fields.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,29 @@
use proc_macro2::{Ident, TokenStream};
use quote::{format_ident, quote};
use syn::spanned::Spanned;
use syn::{FieldsNamed, FieldsUnnamed, Index};
use syn::{FieldsNamed, FieldsUnnamed, Index, Token, Type};

use crate::attrs::{self, parse_field_attributes, FieldConfig, OuterConfig};
use crate::error::MacroError;

fn add_turbo_to_type(path: &syn::TypePath) -> syn::TypePath {
let mut new_path = path.clone();

for segment in new_path.path.segments.iter_mut() {
let span = segment.span();

match &mut segment.arguments {
syn::PathArguments::None => {},
syn::PathArguments::Parenthesized(_) => {},
syn::PathArguments::AngleBracketed(ref mut args) => {
args.colon2_token = Some(Token![::](span));
}
}
}

new_path
}

pub(crate) fn read_unnamed_fields(
fields: &FieldsUnnamed,
outer_config: &OuterConfig,
Expand All @@ -30,16 +48,13 @@ pub(crate) fn read_unnamed_fields(

let read_field = if field_num == last_field && !strict {
quote! {
println!("last/lax");
let n = buf.read(&mut s)
.map_err(|e| fixcol::error::Error::from(e))?;
println!("{}, [{}]", n, s);
let raw = String::from_utf8(s[..n].to_vec())
.map_err(|e| fixcol::error::Error::from(e))?;
}
} else {
quote! {
println!("last/strict");
buf.read_exact(&mut s)
.map_err(|e| fixcol::error::Error::from(e))?;
let raw = String::from_utf8(s.to_vec())
Expand Down Expand Up @@ -76,7 +91,10 @@ pub(crate) fn read_named_fields(
.map(|item| -> Result<(Ident, TokenStream), MacroError> {
let (field_num, field) = item;

let type_token = field.ty.clone();
let type_token = match &field.ty {
Type::Path(path) => Type::Path(add_turbo_to_type(&path)),
other => other.clone(),
};
let name = field.ident.as_ref().unwrap().clone();

let config = parse_field_attributes(&name.span(), &field.attrs, &outer_config)?;
Expand Down Expand Up @@ -152,3 +170,39 @@ pub(crate) fn write_unnamed_fields(

Ok(field_configs?.into_iter().unzip())
}

#[cfg(test)]
mod tests {
use syn::TypePath;

use super::*;

#[test]
fn add_turbo_where_needed() {
let orig: TypePath = syn::parse_str("Option<u64>").unwrap();
let actual = add_turbo_to_type(&orig);
let expected: TypePath = syn::parse_str("Option::<u64>").unwrap();
assert_eq!(actual, expected);
}

#[test]
fn dont_add_turbo_where_not_needed() {
let orig: TypePath = syn::parse_str("Option::<u64>").unwrap();
let actual = add_turbo_to_type(&orig);
let expected: TypePath = syn::parse_str("Option::<u64>").unwrap();
assert_eq!(actual, expected);
}

#[test]
fn qualified_turbo() {
let orig: TypePath = syn::parse_str("custom::Result<u64>").unwrap();
let actual = add_turbo_to_type(&orig);
let expected: TypePath = syn::parse_str("custom::Result::<u64>").unwrap();
assert_eq!(actual, expected);

let orig: TypePath = syn::parse_str("custom::Result::<u64>").unwrap();
let actual = add_turbo_to_type(&orig);
let expected: TypePath = syn::parse_str("custom::Result::<u64>").unwrap();
assert_eq!(actual, expected);
}
}
2 changes: 2 additions & 0 deletions src/fixcol.rs
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,8 @@ pub trait ReadFixed {
}
}



#[cfg(test)]
mod tests {
use fixcol_derive::ReadFixed;
Expand Down
42 changes: 38 additions & 4 deletions src/parse.rs
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,18 @@ impl<T: ReadFixed> FixedDeserializer for T {
}
}

impl<T: FixedDeserializer> FixedDeserializer for Option<T> {
fn parse_fixed(s: &str, desc: &FieldDescription) -> Result<Self, DataError> {
let slice = &s[desc.skip..desc.skip + desc.len];

if slice.trim_start().is_empty() {
Ok(None)
} else {
Ok(Some(T::parse_fixed(s, desc)?))
}
}
}

#[cfg(test)]
mod tests {
use std::str::from_utf8;
Expand Down Expand Up @@ -621,18 +633,14 @@ mod tests {

let mut tests_run = 0;
for desc in descs {
println!("a {:?}", desc);
let actual: f32 = f32::parse_fixed(" 3.14 ", &desc).unwrap();
assert_eq!(actual, expected);
println!("b {:?}", desc);

let actual: f32 = f32::parse_fixed("3.14 ", &desc).unwrap();
assert_eq!(actual, expected);
println!("c {:?}", desc);

let actual: f32 = f32::parse_fixed(" 3.14", &desc).unwrap();
assert_eq!(actual, expected);
println!("d {:?}", desc);

tests_run += 1;
}
Expand Down Expand Up @@ -901,4 +909,30 @@ mod tests {

assert_eq!(thing.unwrap(), Thing::Thing1);
}

#[test]
fn parse_option_some() {
let desc = FieldDescription {
skip: 0,
len: 5,
alignment: Alignment::Right,
strict: true,
};

let actual = Option::<u16>::parse_fixed(" 42", &desc).unwrap();
assert_eq!(actual, Some(42));
}

#[test]
fn parse_option_none() {
let desc = FieldDescription {
skip: 0,
len: 5,
alignment: Alignment::Right,
strict: true,
};

let actual = Option::<u16>::parse_fixed(" ", &desc).unwrap();
assert_eq!(actual, None);
}
}
13 changes: 13 additions & 0 deletions src/write.rs
Original file line number Diff line number Diff line change
Expand Up @@ -203,6 +203,19 @@ impl<T: WriteFixed> FixedSerializer for T {
}
}

impl<T: FixedSerializer> FixedSerializer for Option<T> {
fn write_fixed_field<W: Write>(
&self,
buf: &mut W,
desc: &FieldDescription,
) -> Result<(), Error> {
match self {
None => String::new().write_fixed_field(buf, desc),
Some(t) => t.write_fixed_field(buf, desc),
}
}
}

#[cfg(test)]
mod tests {
use core::str;
Expand Down
1 change: 0 additions & 1 deletion tests/test_derive_enum.rs
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,6 @@ fn edge(from: &str, to: &str, weight: u64) -> GraphObject {
fn read_enums() {
let mut buf = SAMPLE_DATA.as_bytes();
let data: Vec<_> = GraphObject::read_fixed_all(&mut buf).collect();
println!("{:?}", data);

let graph: Vec<GraphObject> = data.into_iter().map(|o| o.unwrap()).collect();

Expand Down
123 changes: 123 additions & 0 deletions tests/test_options.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
use fixcol::ReadFixed;
#[cfg(feature = "experimental-write")]
use fixcol::WriteFixed;

#[cfg_attr(feature = "experimental-write", derive(WriteFixed))]
#[derive(Debug, PartialEq, ReadFixed)]
struct Thing1 {
#[fixcol(width = 5)]
name: String,
#[fixcol(width = 5, align = "right")]
x: Option::<f32>,
#[fixcol(width = 5, align = "right")]
y: Option<f32>,
}

#[derive(Debug, PartialEq, ReadFixed)]
struct Thing2 {
#[fixcol(width = 5)]
name: String,
#[fixcol(width = 5, align = "right")]
x: Option<f32>,
#[fixcol(width = 5, align = "right")]
y: f32,
}

#[derive(Debug, PartialEq, ReadFixed)]
struct Thing3 {
#[fixcol(width = 5)]
name: Option<String>,
#[fixcol(width = 5, align = "right")]
x: f32,
#[fixcol(width = 5, align = "right")]
y: f32,
}

#[derive(Debug, PartialEq, ReadFixed)]
struct Thing4 {
#[fixcol(width = 5)]
name: String,
#[fixcol(width = 5, align = "right")]
x: f32,
#[fixcol(width = 5, align = "right")]
y: f32,
}

#[test]
fn parse_option() {
let actual = Thing1::read_fixed_str("foo 3.14 42").unwrap();
let expected = Thing1 { name: String::from("foo"), x: Some(3.14), y: Some(42.0)};
assert_eq!(actual, expected);
}

#[test]
fn parse_none() {
let actual = Thing1::read_fixed_str("foo 3.14 ").unwrap();
let expected = Thing1 { name: String::from("foo"), x: Some(3.14), y: None};
assert_eq!(actual, expected);

let actual = Thing1::read_fixed_str("foo 42").unwrap();
let expected = Thing1 { name: String::from("foo"), x: None, y: Some(42.0)};
assert_eq!(actual, expected);
}

#[test]
fn err_on_non_option_empty() {
let actual = Thing2::read_fixed_str("foo 42").unwrap();
let expected = Thing2 { name: String::from("foo"), x: None, y: 42.0};
assert_eq!(actual, expected);

let actual = Thing2::read_fixed_str("foo 3.14 ");
assert!(actual.is_err());
assert_eq!(
actual.unwrap_err().to_string(),
"Error handling data from \"\": cannot parse float from empty string\n"
);
}

#[test]
fn option_vs_empty_string() {
let actual = Thing3::read_fixed_str("foo 3.14 42").unwrap();
let expected = Thing3 { name: Some(String::from("foo")), x: 3.14, y: 42.0};
assert_eq!(actual, expected);

let actual = Thing3::read_fixed_str(" 3.14 42").unwrap();
let expected = Thing3 { name: None, x: 3.14, y: 42.0};
assert_eq!(actual, expected);

let actual = Thing4::read_fixed_str("foo 3.14 42").unwrap();
let expected = Thing4 { name: String::from("foo"), x: 3.14, y: 42.0};
assert_eq!(actual, expected);

let actual = Thing4::read_fixed_str(" 3.14 42").unwrap();
let expected = Thing4 { name: String::from(""), x: 3.14, y: 42.0};
assert_eq!(actual, expected);
}

#[test]
#[cfg(feature = "experimental-write")]
fn write_option_some() {
let thing = Thing1 { name: String::from("foo"), x: Some(3.14), y: Some(42.0)};

let mut v = Vec::new();
let res = thing.write_fixed(&mut v);

assert!(res.is_ok());

let text = std::str::from_utf8(v.as_slice()).unwrap();
assert_eq!(text, "foo 3.14 42");
}

#[test]
#[cfg(feature = "experimental-write")]
fn write_option_none() {
let thing = Thing1 { name: String::from("foo"), x: None, y: Some(42.0)};

let mut v = Vec::new();
let res = thing.write_fixed(&mut v);

assert!(res.is_ok());

let text = std::str::from_utf8(v.as_slice()).unwrap();
assert_eq!(text, "foo 42");
}
1 change: 0 additions & 1 deletion tests/test_strict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,6 @@ fn whitespace_malformed_strict() {

#[test]
fn short_line_lax() {
println!("foo");
let point = PointL::read_fixed_str("7 21").unwrap();
assert_eq!(point, PointL::new(7, 21));
}
Expand Down

0 comments on commit d8739dd

Please sign in to comment.