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
use serde::Deserialize;
use std::{collections::HashMap, fs::read_to_string, path::PathBuf};

pub struct AssetPipelineInput<T> {
    pub source: PathBuf,
    pub destination: PathBuf,
    pub params: T,
}

pub trait ParamsFromArgs: Sized {
    fn params_from_args(_args: impl Iterator<Item = String>) -> Option<Self> {
        None
    }
}

#[derive(Debug, Default, Clone)]
pub struct StructuredArguments(HashMap<String, Vec<String>>);

impl StructuredArguments {
    pub fn read(&self, name: &'static str) -> Option<impl Iterator<Item = &str>> {
        Some(self.0.get(name)?.iter().map(|item| item.as_str()))
    }

    pub fn read_many(&self, name: &'static [&'static str]) -> impl Iterator<Item = &str> {
        name.iter().filter_map(|name| self.read(name)).flatten()
    }

    pub fn read_default(&self) -> impl Iterator<Item = &str> {
        self.read("").unwrap()
    }

    pub fn consume(&mut self, name: &'static str) -> Option<impl Iterator<Item = String>> {
        Some(self.0.remove(name)?.into_iter())
    }

    pub fn consume_many<'a>(
        &'a mut self,
        name: &'static [&'static str],
    ) -> impl Iterator<Item = String> + 'a {
        name.iter().filter_map(|name| self.consume(name)).flatten()
    }

    pub fn consume_default(&mut self) -> impl Iterator<Item = String> {
        self.consume("").unwrap()
    }

    pub fn new(args: impl Iterator<Item = String>) -> StructuredArguments {
        let mut result = HashMap::<_, Vec<_>>::default();
        result.insert(Default::default(), Default::default());
        let mut name = String::new();
        for arg in args {
            if let Some(arg) = arg.strip_prefix("--") {
                name = arg.to_owned();
            } else if arg.starts_with('-') && arg.len() == 2 {
                name = arg[1..].to_owned()
            } else {
                result.entry(name.to_owned()).or_default().push(arg);
            }
        }
        StructuredArguments(result)
    }
}

impl<T> AssetPipelineInput<T> {
    pub fn consume() -> Self
    where
        T: for<'de> Deserialize<'de> + ParamsFromArgs,
    {
        let mut args = std::env::args();
        args.next();
        let source = PathBuf::from(args.next().expect("* Could not read source path"));
        let destination = PathBuf::from(args.next().expect("* Could not read destination path"));
        let params = if let Some(arg) = args.next() {
            if arg == "--" {
                T::params_from_args(args).expect("Could not read args input content")
            } else if let Some((t, i)) = arg.find('=').map(|i| (&arg[0..i], i + 1)) {
                let content = match t {
                    "file" => {
                        let path = &arg[i..];
                        read_to_string(path)
                            .unwrap_or_else(|_| panic!("Could not read file: {}", path))
                    }
                    "data" => arg[i..].to_owned(),
                    name => panic!("Unexpected type: {}", name),
                };
                serde_json::from_str::<T>(&content).expect("Could not deserialize input content")
            } else {
                panic!("Wrong input: {:?}", arg)
            }
        } else {
            serde_json::from_reader::<_, T>(std::io::stdin())
                .expect("Could not deserialize input stream")
        };
        Self {
            source,
            destination,
            params,
        }
    }

    pub fn unwrap(self) -> (PathBuf, PathBuf, T) {
        (self.source, self.destination, self.params)
    }
}