cli

Command-line interface for Radroots
git clone https://radroots.dev/git/cli.git
Log | Files | Refs | README | LICENSE

commit 73a4cfc6dc523046d53d585fb188eb04c44ced8d
parent 50d64a827851c912d8e309170ceca3209ab971c9
Author: triesap <tyson@radroots.org>
Date:   Fri, 26 Jun 2026 23:31:39 +0000

cli: add canonical farm location modes

- replace the farm location lookup flag with coordinate, city, query, and GeoNames id modes
- add local-only location labels to target input translation
- cover parser rejection for mixed modes, incomplete coordinates, and removed lookup input

Diffstat:
Msrc/cli/farm.rs | 26+++++++++++++++++++++-----
Msrc/cli/input.rs | 12+++++++++++-
Msrc/cli/mod.rs | 122+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Msrc/registry/farm.rs | 4++--
4 files changed, 156 insertions(+), 8 deletions(-)

diff --git a/src/cli/farm.rs b/src/cli/farm.rs @@ -1,4 +1,4 @@ -use clap::{Args, Subcommand}; +use clap::{ArgGroup, Args, Subcommand}; #[derive(Debug, Clone, Args)] pub struct FarmArgs { @@ -86,15 +86,31 @@ pub enum FarmLocationCommand { } #[derive(Debug, Clone, Args)] +#[command(group( + ArgGroup::new("location_mode") + .args(["lat", "city", "query", "geonames_id"]) + .required(true) + .multiple(false) +))] pub struct FarmLocationSetArgs { - #[arg(long, allow_negative_numbers = true)] + #[arg(long, allow_negative_numbers = true, requires = "lng")] pub lat: Option<f64>, - #[arg(long, allow_negative_numbers = true)] + #[arg(long, allow_negative_numbers = true, requires = "lat")] pub lng: Option<f64>, #[arg(long = "farm-d-tag")] pub farm_d_tag: Option<String>, - #[arg(long = "lookup", default_value = "geonames")] - pub lookup: String, + #[arg(long)] + pub city: Option<String>, + #[arg(long, requires = "city", conflicts_with_all = ["lat", "query", "geonames_id"])] + pub region: Option<String>, + #[arg(long, requires = "city", conflicts_with_all = ["lat", "query", "geonames_id"])] + pub country: Option<String>, + #[arg(long)] + pub query: Option<String>, + #[arg(long = "geonames-id", value_parser = clap::value_parser!(i64).range(1..))] + pub geonames_id: Option<i64>, + #[arg(long)] + pub label: Option<String>, } #[derive(Debug, Clone, Args)] diff --git a/src/cli/input.rs b/src/cli/input.rs @@ -111,7 +111,17 @@ pub fn target_operation_input(command: &TargetCommand) -> OperationData { insert_number(&mut input, "longitude", longitude); } insert_string(&mut input, "farm_d_tag", &args.farm_d_tag); - input.insert("lookup".to_owned(), Value::String(args.lookup.clone())); + insert_string(&mut input, "city", &args.city); + insert_string(&mut input, "region", &args.region); + insert_string(&mut input, "country", &args.country); + insert_string(&mut input, "query", &args.query); + if let Some(geonames_id) = args.geonames_id { + input.insert( + "geonames_id".to_owned(), + Value::Number(serde_json::Number::from(geonames_id)), + ); + } + insert_string(&mut input, "label", &args.label); } FarmLocationCommand::Get(args) | FarmLocationCommand::Clear(args) => { insert_string(&mut input, "farm_d_tag", &args.farm_d_tag); diff --git a/src/cli/mod.rs b/src/cli/mod.rs @@ -461,6 +461,128 @@ mod tests { } #[test] + fn target_parser_accepts_structured_farm_location_city() { + let parsed = TargetCliArgs::try_parse_from([ + "radroots", + "farm", + "location", + "set", + "--city", + "Victoria", + "--region", + "BC", + "--country", + "CA", + "--label", + "main pickup point", + ]) + .expect("target args parse"); + + let crate::cli::TargetCommand::Farm(farm) = parsed.command else { + panic!("expected farm command") + }; + let FarmCommand::Location(location) = farm.command else { + panic!("expected farm location command") + }; + let FarmLocationCommand::Set(args) = location.command else { + panic!("expected farm location set command") + }; + assert_eq!(args.city.as_deref(), Some("Victoria")); + assert_eq!(args.region.as_deref(), Some("BC")); + assert_eq!(args.country.as_deref(), Some("CA")); + assert_eq!(args.label.as_deref(), Some("main pickup point")); + } + + #[test] + fn target_parser_accepts_query_and_geonames_id_farm_location_modes() { + let query = TargetCliArgs::try_parse_from([ + "radroots", + "farm", + "location", + "set", + "--query", + "Fixture Victoria, BC, CA", + ]) + .expect("query args parse"); + let crate::cli::TargetCommand::Farm(farm) = query.command else { + panic!("expected farm command") + }; + let FarmCommand::Location(location) = farm.command else { + panic!("expected farm location command") + }; + let FarmLocationCommand::Set(args) = location.command else { + panic!("expected farm location set command") + }; + assert_eq!(args.query.as_deref(), Some("Fixture Victoria, BC, CA")); + + let geonames_id = TargetCliArgs::try_parse_from([ + "radroots", + "farm", + "location", + "set", + "--geonames-id", + "3004", + ]) + .expect("geonames id args parse"); + let crate::cli::TargetCommand::Farm(farm) = geonames_id.command else { + panic!("expected farm command") + }; + let FarmCommand::Location(location) = farm.command else { + panic!("expected farm location command") + }; + let FarmLocationCommand::Set(args) = location.command else { + panic!("expected farm location set command") + }; + assert_eq!(args.geonames_id, Some(3004)); + } + + #[test] + fn target_parser_rejects_invalid_farm_location_modes() { + for argv in [ + vec!["radroots", "farm", "location", "set", "--lat", "48.429456"], + vec![ + "radroots", + "farm", + "location", + "set", + "--lat", + "48.429456", + "--lng", + "-123.349786", + "--city", + "Victoria", + ], + vec![ + "radroots", + "farm", + "location", + "set", + "--query", + "Victoria, BC", + "--country", + "CA", + ], + vec![ + "radroots", + "farm", + "location", + "set", + "--lookup", + "geonames", + "--lat", + "48.429456", + "--lng", + "-123.349786", + ], + ] { + assert!( + TargetCliArgs::try_parse_from(argv).is_err(), + "expected farm location args to fail" + ); + } + } + + #[test] fn target_parser_accepts_listing_rebind_inputs() { let parsed = TargetCliArgs::try_parse_from([ "radroots", diff --git a/src/registry/farm.rs b/src/registry/farm.rs @@ -66,12 +66,12 @@ pub const FARM_PROFILE_UPDATE: OperationSpec = operation!( pub const FARM_LOCATION_SET: OperationSpec = operation!( "farm.location.set", - "radroots farm location set", + "radroots farm location set --lat 48.429456 --lng -123.349786", "farm", "farm_location_set", "FarmLocationSetRequest", "FarmLocationSetResult", - "Set private exact farm location and derived public locality.", + "Set private farm location and derived public locality.", Seller, true, Conditional,