1use std::collections::BTreeMap;
4use std::str::FromStr;
5
6use super::Emitted;
7
8#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ValueType {
12 Int,
14 Int64,
16 Uint64,
18 Float64,
20 Bool,
22 Duration,
24}
25
26impl FromStr for ValueType {
27 type Err = String;
28
29 fn from_str(value: &str) -> Result<Self, Self::Err> {
31 match value {
32 "int" => Ok(Self::Int),
33 "int64" => Ok(Self::Int64),
34 "uint64" => Ok(Self::Uint64),
35 "float64" => Ok(Self::Float64),
36 "bool" => Ok(Self::Bool),
37 "duration" => Ok(Self::Duration),
38 _ => Err(format!("unknown Go field type {value:?}; expected int, int64, uint64, float64, bool, or duration")),
39 }
40 }
41}
42
43impl ValueType {
44 pub(super) fn field_type(self, many: bool) -> String {
46 let scalar = match self {
47 Self::Int => "int",
48 Self::Int64 => "int64",
49 Self::Uint64 => "uint64",
50 Self::Float64 => "float64",
51 Self::Bool => "bool",
52 Self::Duration => "time.Duration",
53 };
54 if many {
55 format!("[]{scalar}")
56 } else {
57 scalar.to_string()
58 }
59 }
60
61 pub(super) fn converter(self) -> &'static str {
63 match self {
64 Self::Int => "NativeInt",
65 Self::Int64 => "Int",
66 Self::Uint64 => "Uint",
67 Self::Float64 => "Float",
68 Self::Bool => "Bool",
69 Self::Duration => "Duration",
70 }
71 }
72}
73
74pub(super) fn validate(
76 commands: &[Emitted],
77 types: &BTreeMap<String, ValueType>,
78) -> Result<(), String> {
79 for key in types.keys() {
80 let flag = commands
81 .iter()
82 .flat_map(|e| &e.flags)
83 .find(|(_, named)| &named.key == key);
84 if let Some((flag, _)) = flag {
85 if flag.count || flag.arg.is_none() {
86 return Err(format!("{key} is not a value-taking flag"));
87 }
88 continue;
89 }
90 if commands
91 .iter()
92 .flat_map(|e| &e.args)
93 .any(|(_, named)| &named.key == key)
94 {
95 continue;
96 }
97 return Err(format!("{key} is not a generated command-level flag or argument key; clause fields are not supported yet"));
98 }
99 Ok(())
100}
101
102#[cfg(test)]
103mod tests {
104 use super::*;
105 use crate::go::{generate, generate_with_types, GoOptions};
106 use crate::Spec;
107
108 #[test]
110 fn explicit_types_are_checked() {
111 let spec: Spec = "name \"ex\"\nflag \"--timeout <value>\"\nflag \"-v\" count=#true\nflag \"--force\"\narg \"[file]\"".parse().unwrap();
112 let opts = GoOptions::default();
113 assert_eq!(
114 generate(&spec, &opts),
115 generate_with_types(&spec, &opts, &BTreeMap::new()).unwrap()
116 );
117 for key in ["FlagMissing", "FlagV", "FlagForce", "CmdRoot"] {
118 assert!(
119 generate_with_types(
120 &spec,
121 &opts,
122 &BTreeMap::from([(key.to_string(), ValueType::Duration)])
123 )
124 .is_err(),
125 "{key}"
126 );
127 }
128 let out = generate_with_types(
129 &spec,
130 &opts,
131 &BTreeMap::from([("FlagTimeout".to_string(), ValueType::Duration)]),
132 )
133 .unwrap();
134 assert!(out.contains("time.Duration"));
135 assert!(out.contains("argv.Duration(\"--timeout\", values[len(values)-1])"));
136 assert!(!out.contains("out.Timeout = ev.Value"));
137 assert!("typo".parse::<ValueType>().is_err());
138 }
139}