oxygengine_build_tools/
lib.rs

1use serde::Deserialize;
2use std::{collections::HashMap, fs::read_to_string, io::Write, path::PathBuf};
3
4pub trait ParamsFromArgs: Sized {
5    fn params_from_args(_args: impl Iterator<Item = String>) -> Option<Self> {
6        None
7    }
8}
9
10#[derive(Debug, Default, Clone)]
11pub struct StructuredArguments(HashMap<String, Vec<String>>);
12
13impl StructuredArguments {
14    pub fn read(&self, name: &'static str) -> Option<impl Iterator<Item = &str>> {
15        Some(self.0.get(name)?.iter().map(|item| item.as_str()))
16    }
17
18    pub fn read_many(&self, name: &'static [&'static str]) -> impl Iterator<Item = &str> {
19        name.iter().filter_map(|name| self.read(name)).flatten()
20    }
21
22    pub fn read_default(&self) -> impl Iterator<Item = &str> {
23        self.read("").unwrap()
24    }
25
26    pub fn consume(&mut self, name: &'static str) -> Option<impl Iterator<Item = String>> {
27        Some(self.0.remove(name)?.into_iter())
28    }
29
30    pub fn consume_many<'a>(
31        &'a mut self,
32        name: &'static [&'static str],
33    ) -> impl Iterator<Item = String> + 'a {
34        name.iter().filter_map(|name| self.consume(name)).flatten()
35    }
36
37    pub fn consume_default(&mut self) -> impl Iterator<Item = String> {
38        self.consume("").unwrap()
39    }
40
41    pub fn new(args: impl Iterator<Item = String>) -> StructuredArguments {
42        let mut result = HashMap::<_, Vec<_>>::default();
43        result.insert(Default::default(), Default::default());
44        let mut name = String::new();
45        for arg in args {
46            if let Some(arg) = arg.strip_prefix("--") {
47                name = arg.to_owned();
48            } else if arg.starts_with('-') && arg.len() == 2 {
49                name = arg[1..].to_owned()
50            } else {
51                result.entry(name.to_owned()).or_default().push(arg);
52            }
53        }
54        StructuredArguments(result)
55    }
56}
57
58pub struct AssetPipelinePlugin;
59
60impl AssetPipelinePlugin {
61    pub fn run<T, E>(
62        f: impl FnOnce(AssetPipelineInput<T>) -> Result<Vec<String>, E>,
63    ) -> Result<(), E>
64    where
65        T: for<'de> Deserialize<'de> + ParamsFromArgs,
66    {
67        let output = f(AssetPipelineInput::<T>::consume())?;
68        serde_json::to_writer(std::io::stdout(), &output)
69            .expect("Could not serialize output content");
70        let _ = std::io::stdout().flush();
71        Ok(())
72    }
73}
74
75pub struct AssetPipelineInput<T> {
76    pub source: Vec<PathBuf>,
77    pub target: PathBuf,
78    pub assets: String,
79    pub params: T,
80}
81
82impl<T> AssetPipelineInput<T> {
83    fn consume() -> Self
84    where
85        T: for<'de> Deserialize<'de> + ParamsFromArgs,
86    {
87        let mut args = std::env::args();
88        args.next();
89        let mut source = vec![];
90        let mut target = Default::default();
91        let mut assets = Default::default();
92        for arg in args.by_ref() {
93            if arg == "--" {
94                break;
95            } else {
96                source.push(arg.into());
97            }
98        }
99        for arg in args.by_ref() {
100            if arg == "--" {
101                break;
102            } else {
103                target = arg.into();
104            }
105        }
106        for arg in args.by_ref() {
107            if arg == "--" {
108                break;
109            } else {
110                assets = arg;
111            }
112        }
113        let params = if let Some(arg) = args.next() {
114            if arg == "--" {
115                T::params_from_args(args).expect("Could not read args input content")
116            } else if let Some((t, i)) = arg.find('=').map(|i| (&arg[0..i], i + 1)) {
117                let content = match t {
118                    "file" => {
119                        let path = &arg[i..];
120                        read_to_string(path)
121                            .unwrap_or_else(|_| panic!("Could not read file: {}", path))
122                    }
123                    "data" => arg[i..].to_owned(),
124                    name => panic!("Unexpected type: {}", name),
125                };
126                serde_json::from_str::<T>(&content).expect("Could not deserialize input content")
127            } else {
128                panic!("Wrong input: {:?}", arg)
129            }
130        } else {
131            serde_json::from_reader::<_, T>(std::io::stdin())
132                .expect("Could not deserialize input stream")
133        };
134        Self {
135            source,
136            target,
137            assets,
138            params,
139        }
140    }
141}