1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
use indexmap::IndexSet;
use std::io::{BufReader, BufWriter, Read, Write};

mod infer;
mod util;
pub use util::Wrapper;

mod generate;
use generate::{Print, Program};

pub fn generate<R, W>(opts: Options, read: &mut R, write: &mut W) -> anyhow::Result<()>
where
    R: Read + ?Sized,
    W: Write + ?Sized,
{
    let mut reader = BufReader::new(read);
    let mut buf = String::new();
    reader.read_to_string(&mut buf)?;

    let program = Program::generate(json::parse(&buf)?, &buf, &opts);

    let mut writer = BufWriter::new(write);
    program.print(&mut writer, &opts)?;

    Ok(())
}

#[derive(Debug)]
pub struct Options {
    pub json_name: Option<String>,
    pub root_name: String,

    pub make_unit_test: bool,
    pub make_main: bool,

    pub collapse_option_vec: bool,

    pub tuple_max: Option<usize>,

    pub default_derives: String,
    pub field_naming: CasingScheme,
    pub struct_naming: CasingScheme,

    pub vec_wrapper: Wrapper,
    pub map_wrapper: Wrapper,
}

#[derive(Copy, Clone, Debug, PartialEq, PartialOrd, Eq, Ord, Hash)]
pub enum CasingScheme {
    Snake,
    Pascal,
    Constant,
    Camel,
    Identity,
}

impl CasingScheme {
    fn convert(self, input: &str) -> String {
        use inflections::Inflect as _;
        match self {
            Self::Snake => input.to_snake_case(),
            Self::Pascal => input.to_pascal_case(),
            Self::Constant => input.to_constant_case(),
            Self::Camel => input.to_camel_case(),
            Self::Identity => input.to_string(),
        }
    }
}

pub fn no_derives() -> String {
    custom::<&str, _>(std::iter::empty())
}

pub fn all_std_derives() -> String {
    custom(&[
        "Clone",
        "Debug",
        "PartialEq",
        "PartialOrd",
        "Eq",
        "Ord",
        "Hash",
    ])
}

pub fn custom<S, L>(list: L) -> String
where
    S: ToString,
    L: IntoIterator<Item = S>,
{
    const DEFAULT: [&str; 2] = ["Serialize", "Deserialize"];

    list.into_iter()
        .flat_map(|s| {
            s.to_string()
                .split(',') // split if the user provided a comma-separated list

                .filter(|c| !c.starts_with(char::is_numeric)) // invalid trait name (TODO: make this more rigid)

                .map(ToString::to_string)
                .collect::<IndexSet<_>>() // de-dupe

        })
        .filter(|s| !DEFAULT.contains(&&**s)) // remove defaults if they are in the middle

        .chain(DEFAULT.iter().map(ToString::to_string)) // append defaults to the end

        .collect::<IndexSet<_>>() // de-dupe

        .into_iter()
        .fold(String::new(), |mut a, c| {
            if !a.is_empty() {
                a.push_str(", ");
            }
            a.push_str(&c);
            a
        })
}