plox/
data_source_cli_builder.rs

1//! Common utils for building data source related args and parsing them.
2
3use std::{
4	num::{ParseFloatError, ParseIntError},
5	str::ParseBoolError,
6};
7
8use crate::graph_config::*;
9use clap::{Arg, ArgAction, Command, CommandFactory, Parser};
10
11#[derive(Debug, thiserror::Error)]
12pub enum Error {
13	#[error("CLI parsing error: {0}")]
14	GeneralCliParseError(String),
15	#[error("Parse int error: {0}")]
16	ParseIntError(#[from] ParseIntError),
17	#[error("Parse int error: {0}")]
18	ParseBoolError(#[from] ParseBoolError),
19	#[error("Parse float error: {0}")]
20	ParseFloatError(#[from] ParseFloatError),
21}
22
23impl DataSource {
24	//clenaup this mess
25	const CLI_NAME_PLOT_FIELD: &str = "plot";
26	const CLI_NAME_EVENT: &str = "event";
27	const CLI_NAME_EVENT_COUNT: &str = "event-count";
28	const CLI_NAME_EVENT_DELTA: &str = "event-delta";
29
30	pub fn get_cli_ids() -> Vec<String> {
31		DummyDataSourceSubcommand::command()
32			.get_subcommands()
33			.map(|sc| sc.get_name().to_string().clone())
34			.collect()
35	}
36}
37
38impl DataSource {
39	// note:
40	// we cannot use TypedValueParser (what would be cute) for DataSource because parse_ref is only
41	// receiving a single parameter from: '--plot x y' invocation (by desing in clap), so we cannot
42	// build a right instance.
43	//
44	// This could be worked around by specifying '--plot "x y"' but it is not convenient.
45	// So manual parsing is required.
46	pub fn try_from_flag(id: &str, val: &[&String]) -> Result<Self, Error> {
47		Ok(match id {
48			Self::CLI_NAME_EVENT => match val.len() {
49				2 => DataSource::EventValue {
50					guard: None,
51					pattern: val[0].to_string(),
52					yvalue: val[1].parse::<f64>()?,
53				},
54				3 => DataSource::EventValue {
55					guard: Some(val[0].to_string()),
56					pattern: val[1].to_string(),
57					yvalue: val[2].parse::<f64>()?,
58				},
59				_ => {
60					return Err(Error::GeneralCliParseError(format!(
61						"Bad parameter count ({}) for {}. This is bug.",
62						val.len(),
63						id
64					)));
65				},
66			},
67			// match val.len() {
68			// 	1 => { regex },
69			// 	2 => { guard + regex },
70			// 	3 => { regex + unit + convert-to },
71			// 	4 => { guard + regex + unit + convert-to },
72			// 	_ => error,
73			// }
74			//
75			//       regex
76			// guard regex
77			//       regex unit convert-to
78			// guard regex unit convert-to
79			Self::CLI_NAME_PLOT_FIELD => match val.len() {
80				1 => DataSource::FieldValue(FieldCaptureSpec {
81					guard: None,
82					field: val[0].to_string(),
83				}),
84				2 => DataSource::FieldValue(FieldCaptureSpec {
85					guard: Some(val[0].to_string()),
86					field: val[1].to_string(),
87				}),
88				_ => {
89					return Err(Error::GeneralCliParseError(format!(
90						"Bad parameter count ({}) for {}. This is bug.",
91						val.len(),
92						id
93					)));
94				},
95			},
96			Self::CLI_NAME_EVENT_COUNT => match val.len() {
97				1 => DataSource::EventCount { guard: None, pattern: val[0].to_string() },
98				2 => DataSource::EventCount {
99					guard: Some(val[0].to_string()),
100					pattern: val[1].to_string(),
101				},
102				_ => {
103					return Err(Error::GeneralCliParseError(format!(
104						"Bad parameter count ({}) for {}. This is bug.",
105						val.len(),
106						id
107					)));
108				},
109			},
110			Self::CLI_NAME_EVENT_DELTA => match val.len() {
111				1 => DataSource::EventDelta(EventDeltaSpec {
112					guard: None,
113					pattern: val[0].to_string(),
114				}),
115				2 => DataSource::EventDelta(EventDeltaSpec {
116					guard: Some(val[0].to_string()),
117					pattern: val[1].to_string(),
118				}),
119				_ => {
120					return Err(Error::GeneralCliParseError(format!(
121						"Bad parameter count ({}) for {}. This is bug.",
122						val.len(),
123						id
124					)));
125				},
126			},
127			_ => {
128				return Err(Error::GeneralCliParseError(format!(
129					"Unknown DataSource id:{}. This is bug",
130					id
131				)));
132			},
133		})
134	}
135}
136
137/// Dummy helper wrapper for `CommandFactory`
138///
139/// Used for injecting DataSource args and their parameters.
140#[derive(Parser, Debug)]
141#[command(name = "dummy")]
142pub struct DummyDataSourceSubcommand {
143	#[command(subcommand)]
144	line: DataSource,
145}
146
147fn extract_help_multiline(args: &[Arg]) -> String {
148	args.iter()
149		.filter_map(|arg| {
150			arg.get_long_help()
151				.or(arg.get_help())
152				.map(|h| format!("  <{}>: {}", arg.get_id(), h))
153		})
154		.collect::<Vec<_>>()
155		.join("\n")
156}
157
158fn extract_num_args_and_names(args: &[Arg]) -> (usize, usize, Vec<String>) {
159	let mut value_names = vec![];
160	let mut required_count = 0;
161
162	for a in args {
163		value_names.push(a.get_id().to_string());
164		if a.is_required_set() {
165			required_count += 1;
166		}
167	}
168
169	let total = args.len();
170	(required_count, total, value_names)
171}
172
173/// Build args from DataSource subcommands' parameters and append to given base command
174pub fn build_data_source_cli(mut base: Command) -> Command {
175	let dummy_data_source_subcommand = DummyDataSourceSubcommand::command();
176	for sub in dummy_data_source_subcommand.get_subcommands() {
177		let sub_name = sub.get_name().to_string();
178		let sub_args: Vec<Arg> = sub.get_arguments().cloned().collect();
179		let sub_help = sub.get_about().unwrap_or_default();
180		let field_help = extract_help_multiline(&sub_args);
181		let (min_args, max_args, value_names) = extract_num_args_and_names(&sub_args);
182
183		let full_help = if field_help.is_empty() {
184			sub_help.to_string()
185		} else {
186			format!("{sub_help}\n{field_help}\n")
187		};
188
189		let flag = Arg::new(sub_name.clone())
190			.long(&sub_name)
191			.num_args(min_args..=max_args)
192			.action(ArgAction::Append)
193			.value_names(&value_names)
194			.help(sub_help.to_string())
195			.long_help(full_help)
196			.next_line_help(true)
197			.help_heading("Data sources - plotted line types");
198
199		base = base.arg(flag);
200	}
201
202	base
203}