|
@@ -7,318 +7,17 @@ use crate::{
|
|
},
|
|
},
|
|
};
|
|
};
|
|
|
|
|
|
-use super::{CLIError, CLIObject};
|
|
|
|
|
|
+use super::{CLIError, EntityInterface, ValueRole};
|
|
use clap::{FromArgMatches, Subcommand};
|
|
use clap::{FromArgMatches, Subcommand};
|
|
|
|
|
|
-/// iterate across the list of key parts (E::Keys) and add args for each
|
|
|
|
-fn add_keys<E: Entity, IC: InterfaceCustomization>(
|
|
|
|
- mut cmd: clap::Command,
|
|
|
|
- role: ValueRole,
|
|
|
|
-) -> clap::Command {
|
|
|
|
- struct UVisitor<'a, IC: InterfaceCustomization, E: Entity>(
|
|
|
|
- &'a mut clap::Command,
|
|
|
|
- ValueRole,
|
|
|
|
- std::marker::PhantomData<(IC, E)>,
|
|
|
|
- );
|
|
|
|
- impl<'a, IC: InterfaceCustomization, E: Entity> EntityPartVisitor for UVisitor<'a, IC, E> {
|
|
|
|
- type Entity = E;
|
|
|
|
- fn visit<EP: EntityPart>(&mut self) {
|
|
|
|
- if !IC::has_value_for(EP::Entity::entity_name(), EP::part_name(), self.1) {
|
|
|
|
- let arg = clap::Arg::new(EP::part_name())
|
|
|
|
- .required(true)
|
|
|
|
- .help(EP::desc());
|
|
|
|
- *self.0 = self.0.clone().arg(arg);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- <E::Keys as EntityPartList>::accept_part_visitor(&mut UVisitor::<IC, E>(
|
|
|
|
- &mut cmd,
|
|
|
|
- role,
|
|
|
|
- Default::default(),
|
|
|
|
- ));
|
|
|
|
-
|
|
|
|
- cmd
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-fn collect_keys<E: Entity, IC: InterfaceCustomization>(
|
|
|
|
- matches: &clap::ArgMatches,
|
|
|
|
- role: ValueRole,
|
|
|
|
-) -> Vec<EntityKey> {
|
|
|
|
- struct UVisitor<'a, IC: InterfaceCustomization, E: Entity>(
|
|
|
|
- &'a clap::ArgMatches,
|
|
|
|
- &'a mut Vec<EntityKey>,
|
|
|
|
- ValueRole,
|
|
|
|
- std::marker::PhantomData<(IC, E)>,
|
|
|
|
- );
|
|
|
|
- impl<'a, IC: InterfaceCustomization, E: Entity> EntityPartVisitor for UVisitor<'a, IC, E> {
|
|
|
|
- type Entity = E;
|
|
|
|
- fn visit<EP: EntityPart>(&mut self) {
|
|
|
|
- if !IC::has_value_for(EP::Entity::entity_name(), EP::part_name(), self.2) {
|
|
|
|
- self.1.push(EntityKey::UserInput(
|
|
|
|
- self.0
|
|
|
|
- .get_one::<std::string::String>(EP::part_name())
|
|
|
|
- .unwrap()
|
|
|
|
- .clone(),
|
|
|
|
- ));
|
|
|
|
- } else {
|
|
|
|
- self.1.push(EntityKey::Placeholder(
|
|
|
|
- EP::Entity::entity_name(),
|
|
|
|
- EP::part_name(),
|
|
|
|
- self.2,
|
|
|
|
- ));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- let mut key_values = vec![];
|
|
|
|
- <E::Keys as EntityPartList>::accept_part_visitor(&mut UVisitor::<IC, E>(
|
|
|
|
- matches,
|
|
|
|
- &mut key_values,
|
|
|
|
- role,
|
|
|
|
- Default::default(),
|
|
|
|
- ));
|
|
|
|
- key_values
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-#[derive(Debug, Clone)]
|
|
|
|
-pub enum EntityKey {
|
|
|
|
- Placeholder(&'static str, &'static str, ValueRole),
|
|
|
|
- UserInput(String),
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl EntityKey {
|
|
|
|
- fn to_string_vec(vec: &[Self], ic: &impl InterfaceCustomization) -> Vec<String> {
|
|
|
|
- vec.iter()
|
|
|
|
- .map(|v| match v {
|
|
|
|
- EntityKey::UserInput(s) => s.to_owned(),
|
|
|
|
- EntityKey::Placeholder(entity, field, role) => ic.value_for(entity, field, *role),
|
|
|
|
- })
|
|
|
|
- .collect()
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-#[derive(Clone, Debug)]
|
|
|
|
-pub enum InterfaceVerb<O: CLIObject> {
|
|
|
|
- Attach {
|
|
|
|
- local_keys: Vec<EntityKey>,
|
|
|
|
- relation: String,
|
|
|
|
- remote_keys: Vec<EntityKey>,
|
|
|
|
- },
|
|
|
|
- Create(O::CreateParameters),
|
|
|
|
- Delete(Vec<EntityKey>),
|
|
|
|
- Detach {
|
|
|
|
- local_keys: Vec<EntityKey>,
|
|
|
|
- relation: String,
|
|
|
|
- remote_keys: Vec<EntityKey>,
|
|
|
|
- },
|
|
|
|
- ListAll,
|
|
|
|
- Inspect(Vec<EntityKey>),
|
|
|
|
- Extra(O::ExtraCommands),
|
|
|
|
-}
|
|
|
|
|
|
|
|
// helper alias for later
|
|
// helper alias for later
|
|
type UniqueList<E> = <<E as Entity>::Keys as EntityPartList>::DatumList;
|
|
type UniqueList<E> = <<E as Entity>::Keys as EntityPartList>::DatumList;
|
|
|
|
|
|
-impl<O: CLIObject> InterfaceVerb<O> {
|
|
|
|
- fn parse_attachment<IC: InterfaceCustomization>(
|
|
|
|
- matches: &clap::ArgMatches,
|
|
|
|
- ) -> Result<(Vec<EntityKey>, String, Vec<EntityKey>), clap::Error> {
|
|
|
|
- let local_keys = collect_keys::<O, IC>(matches, ValueRole::BaseTarget);
|
|
|
|
-
|
|
|
|
- let (subcommand, submatches) = matches
|
|
|
|
- .subcommand()
|
|
|
|
- .ok_or(clap::Error::new(clap::error::ErrorKind::MissingSubcommand))?;
|
|
|
|
-
|
|
|
|
- // find the relevant relation
|
|
|
|
- struct RelationFinder<'l, IC: InterfaceCustomization, E: Entity> {
|
|
|
|
- subcommand: &'l str,
|
|
|
|
- submatches: &'l clap::ArgMatches,
|
|
|
|
- keys: &'l mut Vec<EntityKey>,
|
|
|
|
- _ghost: std::marker::PhantomData<(IC, E)>,
|
|
|
|
- }
|
|
|
|
- impl<'l, IC: InterfaceCustomization, E: Entity> EntityPartVisitor for RelationFinder<'l, IC, E> {
|
|
|
|
- type Entity = E;
|
|
|
|
- fn visit<EP: EntityPart>(&mut self) {
|
|
|
|
- if EP::part_name() != self.subcommand {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- EP::Datum::accept_entity_visitor(self);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- impl<'l, IC: InterfaceCustomization, OE: Entity> EntityVisitor for RelationFinder<'l, IC, OE> {
|
|
|
|
- fn visit<E: Entity>(&mut self) {
|
|
|
|
- *self.keys = collect_keys::<E, IC>(self.submatches, ValueRole::AttachmentTarget);
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- let mut remote_keys = vec![];
|
|
|
|
- O::accept_part_visitor(&mut RelationFinder::<IC, O> {
|
|
|
|
- subcommand,
|
|
|
|
- submatches,
|
|
|
|
- keys: &mut remote_keys,
|
|
|
|
- _ghost: Default::default(),
|
|
|
|
- });
|
|
|
|
-
|
|
|
|
- Ok((local_keys, subcommand.into(), remote_keys))
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- fn from_matches<IC: InterfaceCustomization>(
|
|
|
|
- parent_matches: &clap::ArgMatches,
|
|
|
|
- ) -> Result<Self, clap::Error> {
|
|
|
|
- let (subcommand, matches) = parent_matches
|
|
|
|
- .subcommand()
|
|
|
|
- .ok_or(clap::Error::new(clap::error::ErrorKind::MissingSubcommand))?;
|
|
|
|
-
|
|
|
|
- Ok(match subcommand {
|
|
|
|
- "attach" => {
|
|
|
|
- let (local_keys, relation, remote_keys) = Self::parse_attachment::<IC>(matches)?;
|
|
|
|
- InterfaceVerb::Attach {
|
|
|
|
- local_keys,
|
|
|
|
- relation,
|
|
|
|
- remote_keys,
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- "create" => InterfaceVerb::Create(
|
|
|
|
- <O::CreateParameters as clap::FromArgMatches>::from_arg_matches(matches)?,
|
|
|
|
- ),
|
|
|
|
- "delete" => {
|
|
|
|
- InterfaceVerb::Delete(collect_keys::<O, IC>(matches, ValueRole::BaseTarget))
|
|
|
|
- },
|
|
|
|
- "detach" => {
|
|
|
|
- let (local_keys, relation, remote_keys) = Self::parse_attachment::<IC>(matches)?;
|
|
|
|
- InterfaceVerb::Detach {
|
|
|
|
- local_keys,
|
|
|
|
- relation,
|
|
|
|
- remote_keys,
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- "list" => InterfaceVerb::ListAll,
|
|
|
|
- "inspect" => {
|
|
|
|
- InterfaceVerb::Inspect(collect_keys::<O, IC>(matches, ValueRole::BaseTarget))
|
|
|
|
- },
|
|
|
|
- cmd => {
|
|
|
|
- if <O::ExtraCommands>::has_subcommand(cmd) {
|
|
|
|
- InterfaceVerb::Extra(<O::ExtraCommands>::from_arg_matches(parent_matches)?)
|
|
|
|
- } else {
|
|
|
|
- unreachable!()
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
|
|
-/// helper type for attach and detach verbs
|
|
|
|
-struct Attacher<'l, Error: CLIError, E: Entity> {
|
|
|
|
- do_attach: bool,
|
|
|
|
- relation: &'l str,
|
|
|
|
- remote_keys: Vec<String>,
|
|
|
|
- err: Option<Error>,
|
|
|
|
- _ghost: std::marker::PhantomData<E>,
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl<'l, Error: CLIError, OE: Entity> Attacher<'l, Error, OE> {
|
|
|
|
- fn do_operation<E: Entity>(&mut self, map: &impl RelationInterface<RemoteEntity = E>) {
|
|
|
|
- match map
|
|
|
|
- .query_all()
|
|
|
|
- .keyed(
|
|
|
|
- UniqueList::<E>::build_equivalent(self.remote_keys.iter().map(String::as_str))
|
|
|
|
- .unwrap(),
|
|
|
|
- )
|
|
|
|
- .get()
|
|
|
|
- {
|
|
|
|
- Ok(Some(obj)) => {
|
|
|
|
- if self.do_attach {
|
|
|
|
- self.err = map.connect_to(obj.id()).err().map(Into::into);
|
|
|
|
- } else {
|
|
|
|
- self.err = map.disconnect_from(obj.id()).err().map(Into::into);
|
|
|
|
- }
|
|
|
|
- },
|
|
|
|
- Ok(None) => {
|
|
|
|
- self.err = Some(Error::no_such_entity(
|
|
|
|
- E::entity_name(),
|
|
|
|
- self.remote_keys
|
|
|
|
- .iter()
|
|
|
|
- .cloned()
|
|
|
|
- .reduce(|a, b| format!("{},{}", a, b))
|
|
|
|
- .unwrap()
|
|
|
|
- .to_string(),
|
|
|
|
- ));
|
|
|
|
- },
|
|
|
|
- Err(e) => {
|
|
|
|
- self.err = Some(e.into());
|
|
|
|
- },
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl<'l, Error: CLIError, E: Entity> EntityPartVisitor for Attacher<'l, Error, E> {
|
|
|
|
- type Entity = E;
|
|
|
|
- fn visit_datum<EP: EntityPart>(&mut self, datum: &EP::Datum) {
|
|
|
|
- if EP::part_name() != self.relation {
|
|
|
|
- return;
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- datum.accept_discriminator_ref(self);
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl<'l, Error: CLIError, OE: Entity> DatumDiscriminatorRef for Attacher<'l, Error, OE> {
|
|
|
|
- fn visit_entity_id<E: Entity>(&mut self, _: &E::ID) {
|
|
|
|
- unreachable!()
|
|
|
|
- }
|
|
|
|
- fn visit_serialized<T: serde::Serialize + serde::de::DeserializeOwned>(&mut self, _: &T) {
|
|
|
|
- unreachable!()
|
|
|
|
- }
|
|
|
|
- fn visit_bare_field<T: Datum>(&mut self, _: &T) {
|
|
|
|
- unreachable!()
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- fn visit_relation_map<E: Entity>(&mut self, map: &RelationMap<E>) {
|
|
|
|
- self.do_operation(map);
|
|
|
|
- }
|
|
|
|
- fn visit_relation_range<R: Relation>(&mut self, map: &RelationRange<R>) {
|
|
|
|
- self.do_operation(map);
|
|
|
|
- }
|
|
|
|
- fn visit_relation_domain<R: Relation>(&mut self, map: &RelationDomain<R>) {
|
|
|
|
- self.do_operation(map);
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/// Enumeration that describes the role of a value in an [`InterfaceCustomization`] instance.
|
|
|
|
-#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq)]
|
|
|
|
-pub enum ValueRole {
|
|
|
|
- /// This value is for the 'base' object, i.e. the first object type specified in a command.
|
|
|
|
- BaseTarget,
|
|
|
|
- /// This value is for the 'attachment' object, i.e. the second object type specified in a command.
|
|
|
|
- AttachmentTarget,
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-/// Allows customization of the autogenerated commands by programmatically providing values so they
|
|
|
|
-/// don't have to be specified on the command-line.
|
|
|
|
-pub trait InterfaceCustomization {
|
|
|
|
- /// Returns true iff this customization will provide a value for this field.
|
|
|
|
- fn has_value_for(entity: &'static str, field: &'static str, role: ValueRole) -> bool;
|
|
|
|
- /// Returns a string representation of the value for this field, equivalent to a value entered
|
|
|
|
- /// on the command-line.
|
|
|
|
- fn value_for(&self, entity: &'static str, field: &'static str, role: ValueRole) -> String;
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl InterfaceCustomization for () {
|
|
|
|
- fn has_value_for(_entity: &'static str, _field: &'static str, _role: ValueRole) -> bool {
|
|
|
|
- false
|
|
|
|
- }
|
|
|
|
- fn value_for(&self, _: &'static str, _: &'static str, _: ValueRole) -> String {
|
|
|
|
- unreachable!()
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
#[derive(Debug)]
|
|
-/// Type implementing `Subcommand` for a given CLIObject and specialization.
|
|
|
|
-///
|
|
|
|
-///
|
|
|
|
|
|
+/// Type implementing `Subcommand` for a given entity and interface.
|
|
pub struct ClapInterface<O: CLIObject, IC: InterfaceCustomization> {
|
|
pub struct ClapInterface<O: CLIObject, IC: InterfaceCustomization> {
|
|
verb: InterfaceVerb<O>,
|
|
verb: InterfaceVerb<O>,
|
|
_ghost: std::marker::PhantomData<IC>,
|
|
_ghost: std::marker::PhantomData<IC>,
|
|
@@ -499,110 +198,5 @@ impl<O: CLIObject, IC: InterfaceCustomization> ClapInterface<O, IC> {
|
|
Ok(())
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
|
|
- fn make_relation_subcommands() -> impl Iterator<Item = clap::Command> {
|
|
|
|
- let mut out = vec![];
|
|
|
|
-
|
|
|
|
- struct PartVisitor<'l, IC: InterfaceCustomization, E: Entity>(
|
|
|
|
- &'l mut Vec<clap::Command>,
|
|
|
|
- std::marker::PhantomData<(IC, E)>,
|
|
|
|
- );
|
|
|
|
- impl<'l, IC: InterfaceCustomization, E: Entity> EntityPartVisitor for PartVisitor<'l, IC, E> {
|
|
|
|
- type Entity = E;
|
|
|
|
- fn visit<EP: EntityPart>(&mut self) {
|
|
|
|
- struct Discriminator<'l, IC: InterfaceCustomization>(
|
|
|
|
- &'l mut Vec<clap::Command>,
|
|
|
|
- &'static str,
|
|
|
|
- std::marker::PhantomData<IC>,
|
|
|
|
- );
|
|
|
|
-
|
|
|
|
- impl<'l, IC: InterfaceCustomization> DatumDiscriminator for Discriminator<'l, IC> {
|
|
|
|
- fn visit_entity_id<E: Entity>(&mut self) {}
|
|
|
|
- fn visit_serialized<T: serde::Serialize + serde::de::DeserializeOwned>(
|
|
|
|
- &mut self,
|
|
|
|
- ) {
|
|
|
|
- }
|
|
|
|
- fn visit_bare_field<T: Datum>(&mut self) {}
|
|
|
|
- fn visit_relation_map<E: Entity>(&mut self) {
|
|
|
|
- self.0.push(add_keys::<E, IC>(
|
|
|
|
- clap::Command::new(self.1),
|
|
|
|
- ValueRole::AttachmentTarget,
|
|
|
|
- ));
|
|
|
|
- }
|
|
|
|
- fn visit_relation_domain<R: Relation>(&mut self) {
|
|
|
|
- self.0.push(add_keys::<R::Range, IC>(
|
|
|
|
- clap::Command::new(self.1),
|
|
|
|
- ValueRole::AttachmentTarget,
|
|
|
|
- ));
|
|
|
|
- }
|
|
|
|
- fn visit_relation_range<R: Relation>(&mut self) {
|
|
|
|
- self.0.push(add_keys::<R::Domain, IC>(
|
|
|
|
- clap::Command::new(self.1),
|
|
|
|
- ValueRole::AttachmentTarget,
|
|
|
|
- ));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- <EP::Datum as Datum>::accept_discriminator(&mut Discriminator::<IC>(
|
|
|
|
- self.0,
|
|
|
|
- EP::part_name(),
|
|
|
|
- Default::default(),
|
|
|
|
- ));
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- O::accept_part_visitor(&mut PartVisitor::<IC, O>(&mut out, Default::default()));
|
|
|
|
-
|
|
|
|
- out.into_iter()
|
|
|
|
- }
|
|
|
|
}
|
|
}
|
|
|
|
|
|
-impl<O: CLIObject, IC: InterfaceCustomization> FromArgMatches for ClapInterface<O, IC> {
|
|
|
|
- fn from_arg_matches(matches: &clap::ArgMatches) -> Result<Self, clap::Error> {
|
|
|
|
- let verb = InterfaceVerb::from_matches::<IC>(matches);
|
|
|
|
-
|
|
|
|
- Ok(Self {
|
|
|
|
- verb: verb?,
|
|
|
|
- _ghost: Default::default(),
|
|
|
|
- })
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- fn update_from_arg_matches(&mut self, _matches: &clap::ArgMatches) -> Result<(), clap::Error> {
|
|
|
|
- todo!()
|
|
|
|
- }
|
|
|
|
-}
|
|
|
|
-
|
|
|
|
-impl<O: CLIObject, IC: InterfaceCustomization> Subcommand for ClapInterface<O, IC> {
|
|
|
|
- fn has_subcommand(_name: &str) -> bool {
|
|
|
|
- todo!()
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- fn augment_subcommands(cmd: clap::Command) -> clap::Command {
|
|
|
|
- let cmd = cmd
|
|
|
|
- .subcommand(
|
|
|
|
- add_keys::<O, IC>(clap::Command::new("attach"), ValueRole::BaseTarget)
|
|
|
|
- .subcommands(Self::make_relation_subcommands())
|
|
|
|
- .subcommand_required(true),
|
|
|
|
- )
|
|
|
|
- .subcommand(
|
|
|
|
- add_keys::<O, IC>(clap::Command::new("detach"), ValueRole::BaseTarget)
|
|
|
|
- .subcommands(Self::make_relation_subcommands())
|
|
|
|
- .subcommand_required(true),
|
|
|
|
- )
|
|
|
|
- .subcommand(<O::CreateParameters as clap::CommandFactory>::command().name("create"))
|
|
|
|
- .subcommand(add_keys::<O, IC>(
|
|
|
|
- clap::Command::new("delete"),
|
|
|
|
- ValueRole::BaseTarget,
|
|
|
|
- ))
|
|
|
|
- .subcommand(add_keys::<O, IC>(
|
|
|
|
- clap::Command::new("inspect"),
|
|
|
|
- ValueRole::BaseTarget,
|
|
|
|
- ))
|
|
|
|
- .subcommand(clap::Command::new("list"));
|
|
|
|
-
|
|
|
|
- <O::ExtraCommands>::augment_subcommands(cmd)
|
|
|
|
- }
|
|
|
|
-
|
|
|
|
- fn augment_subcommands_for_update(_cmd: clap::Command) -> clap::Command {
|
|
|
|
- todo!()
|
|
|
|
- }
|
|
|
|
-}
|
|
|