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