1use crate::Options;
2use serde_json::Value;
3use std::{collections::HashMap, fs::read_to_string};
4use structopt::StructOpt;
5
6pub const CODEGENR_CONFIG_FILE: &str = "codegenr.toml";
7
8#[derive(StructOpt, Debug)]
10#[structopt(name = "codegenr")]
11pub struct Opt {
12 #[structopt(subcommand)]
21 pub cmd: Option<Command>,
22}
23
24#[derive(StructOpt, Debug)]
25#[structopt(about = "Commands")]
26pub enum Command {
27 #[structopt(name = "file", help = "Executes all generations from a config file.")]
28 FromFile {
29 #[structopt(
30 long,
31 help = "Path to a full codegenr configuration file. If existing, all other command line parameters are ignored",
32 default_value = CODEGENR_CONFIG_FILE
33 )]
34 file: String,
35 },
36 #[structopt(name = "gen", help = "Executes one generation from command line parameters.")]
37 FromLine {
38 #[structopt(long, short, help = "Source json/yaml/openapi file.")]
39 source: String,
40 #[structopt(long, short, help = "Output folder.")]
41 output: String,
42 #[structopt(
43 long,
44 short,
45 help = "Templates folder(s), in which only one .hbs file should have no `_` as prefix (Underscored templates are partial templates)."
46 )]
47 templates: Vec<String>,
48 #[structopt(
49 long,
50 short,
51 help = "Optional path to a file where the intermediate json representation of resolved source(s) will be output.
52 The resolved json will be output as <file>.resolved.json, the full text rendered result will be output as <file>.rendered.txt."
53 )]
54 intermediate: Option<String>,
55 #[structopt(long, short, help = "Path to custom helper files.")]
56 custom_helpers: Vec<String>,
57 #[structopt(
58 long,
59 short,
60 help = "Global parameters values formatted `key=value`. Values will be parsed as json or strings if the json parsing fails.",
61 parse(try_from_str = parse_key_val)
62 )]
63 global_parameters: Vec<(String, serde_json::Value)>,
64 },
65}
66
67impl Default for Command {
68 fn default() -> Self {
69 Self::FromFile {
70 file: CODEGENR_CONFIG_FILE.into(),
71 }
72 }
73}
74
75fn parse_key_val(s: &str) -> Result<(String, Value), anyhow::Error> {
78 let pos = s
79 .find('=')
80 .ok_or_else(|| anyhow::anyhow!("Invalid key=value: no `=` found in `{}`.", s))?;
81 let value = &s[pos + 1..];
82 Ok((
83 s[..pos].parse()?,
84 value.parse().unwrap_or_else(|_| Value::String(value.to_string())),
85 ))
86}
87
88impl TryFrom<Command> for HashMap<String, Options> {
89 type Error = anyhow::Error;
90
91 fn try_from(cmd: Command) -> Result<Self, Self::Error> {
92 match cmd {
93 Command::FromFile { file } => {
94 let config = read_to_string(&file).map_err(|e| {
95 anyhow::anyhow!(
96 "Unable to read `{}` file: `{}`. Did you run codegenr in the right directory ?",
97 file,
98 e
99 )
100 })?;
101 let opts: HashMap<String, Options> =
102 toml::from_str(&config).map_err(|e| anyhow::anyhow!("Unable to deserialize `{}` config file: `{}`.", file, e))?;
103 Ok(opts)
104 }
105 Command::FromLine {
106 source,
107 output,
108 templates,
109 intermediate,
110 custom_helpers,
111 global_parameters,
112 } => {
113 let options = Options {
114 source,
115 output,
116 templates,
117 intermediate,
118 custom_helpers,
119 global_parameters: global_parameters.into_iter().collect(),
120 };
121 let map = HashMap::<String, Options>::from_iter(std::iter::once(("command_line".into(), options)));
122 Ok(map)
123 }
124 }
125 }
126}