123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619 |
- use microrm::{
- prelude::*,
- schema::{
- datum::{ConcreteDatumList, Datum, DatumDiscriminator, DatumDiscriminatorRef},
- entity::{Entity, EntityID, EntityPartList, EntityPartVisitor, EntityVisitor},
- },
- };
- use super::{CLIError, CLIObject};
- 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: microrm::schema::entity::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: microrm::schema::entity::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
- 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: microrm::schema::entity::EntityPart>(&mut self) {
- if EP::part_name() != self.subcommand {
- return;
- }
- println!("EP: {}", std::any::type_name::<EP>());
- // println!("EP: {}", std::any::
- EP::Datum::accept_entity_visitor(self);
- }
- }
- impl<'l, IC: InterfaceCustomization, OE: Entity> EntityVisitor for RelationFinder<'l, IC, OE> {
- fn visit<E: Entity>(&mut self) {
- println!("\trelationfinder visiting entity {}", E::entity_name());
- *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 AssocInterface<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: microrm::schema::entity::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_assoc_map<E: Entity>(&mut self, map: &AssocMap<E>) {
- self.do_operation(map);
- }
- fn visit_assoc_range<R: microrm::schema::Relation>(
- &mut self,
- map: µrm::schema::AssocRange<R>,
- ) {
- self.do_operation(map);
- }
- fn visit_assoc_domain<R: microrm::schema::Relation>(
- &mut self,
- map: µrm::schema::AssocDomain<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)]
- /// Type implementing `Subcommand` for a given CLIObject and specialization.
- ///
- ///
- pub struct ClapInterface<O: CLIObject, IC: InterfaceCustomization> {
- verb: InterfaceVerb<O>,
- _ghost: std::marker::PhantomData<IC>,
- }
- impl<O: CLIObject, IC: InterfaceCustomization> ClapInterface<O, IC> {
- /// Execute the action that this ClapInterface instance describes. Note that if `Spec` is
- /// chosen as [`EmptyList`](../schema/entity/struct.EmptyList.html), the value passed for the
- /// `spec` parameter should be the unit type, `()`.
- pub fn perform(
- &self,
- data: &O::CommandData,
- ic: &IC,
- query_ctx: impl microrm::prelude::Queryable<EntityOutput = O>,
- insert_ctx: &impl microrm::prelude::Insertable<O>,
- ) -> Result<(), O::Error> {
- match &self.verb {
- InterfaceVerb::Attach {
- local_keys,
- relation,
- remote_keys,
- } => {
- let local_keys = EntityKey::to_string_vec(local_keys, ic);
- let remote_keys = EntityKey::to_string_vec(remote_keys, ic);
- let outer_obj = query_ctx
- .keyed(
- UniqueList::<O>::build_equivalent(local_keys.iter().map(String::as_str))
- .unwrap(),
- )
- .get()?
- .ok_or(<O::Error>::no_such_entity(
- O::entity_name(),
- local_keys
- .iter()
- .cloned()
- .reduce(|a, b| format!("{},{}", a, b))
- .unwrap()
- .to_string(),
- ))?;
- let mut attacher = Attacher {
- do_attach: true,
- relation,
- remote_keys,
- err: None,
- _ghost: Default::default(),
- };
- outer_obj.accept_part_visitor_ref(&mut attacher);
- if let Some(err) = attacher.err {
- return Err(err);
- }
- }
- InterfaceVerb::Create(params) => {
- insert_ctx.insert(O::create_from_params(data, params)?)?;
- }
- InterfaceVerb::Delete(keys) => {
- let keys = EntityKey::to_string_vec(keys, ic);
- query_ctx
- .keyed(
- UniqueList::<O>::build_equivalent(keys.iter().map(String::as_str)).unwrap(),
- )
- .delete()?;
- }
- InterfaceVerb::Detach {
- local_keys,
- relation,
- remote_keys,
- } => {
- let local_keys = EntityKey::to_string_vec(local_keys, ic);
- let remote_keys = EntityKey::to_string_vec(remote_keys, ic);
- let outer_obj = query_ctx
- .keyed(
- UniqueList::<O>::build_equivalent(local_keys.iter().map(String::as_str))
- .unwrap(),
- )
- .get()?
- .ok_or(<O::Error>::no_such_entity(
- O::entity_name(),
- local_keys
- .iter()
- .cloned()
- .reduce(|a, b| format!("{},{}", a, b))
- .unwrap()
- .to_string(),
- ))?;
- let mut attacher = Attacher {
- do_attach: false,
- relation,
- remote_keys,
- err: None,
- _ghost: Default::default(),
- };
- outer_obj.accept_part_visitor_ref(&mut attacher);
- if let Some(err) = attacher.err {
- return Err(err);
- }
- }
- InterfaceVerb::ListAll => {
- println!(
- "Listing all {}(s): ({})",
- O::entity_name(),
- query_ctx.clone().count()?
- );
- for obj in query_ctx.get()?.into_iter() {
- println!(" - {}", obj.shortname());
- }
- }
- InterfaceVerb::Inspect(keys) => {
- let keys = EntityKey::to_string_vec(keys, ic);
- let obj = query_ctx
- .keyed(
- UniqueList::<O>::build_equivalent(keys.iter().map(String::as_str)).unwrap(),
- )
- .get()?
- .ok_or(<O::Error>::no_such_entity(
- O::entity_name(),
- keys.iter()
- .cloned()
- .reduce(|a, b| format!("{},{}", a, b))
- .unwrap()
- .to_string(),
- ))?;
- println!("{:#?}", obj.as_ref());
- fn inspect_ai<AI: AssocInterface>(name: &'static str, ai: &AI) {
- println!("{}: ({})", name, ai.count().unwrap());
- for a in ai.get().expect("couldn't get object associations") {
- println!("[#{:3}]: {:?}", a.id().into_raw(), a.wrapped());
- }
- }
- struct AssocFieldWalker<E: Entity>(std::marker::PhantomData<E>);
- impl<E: Entity> EntityPartVisitor for AssocFieldWalker<E> {
- type Entity = E;
- fn visit_datum<EP: microrm::schema::entity::EntityPart>(
- &mut self,
- datum: &EP::Datum,
- ) {
- struct Discriminator(&'static str);
- impl DatumDiscriminatorRef for Discriminator {
- fn visit_serialized<
- T: serde::Serialize + serde::de::DeserializeOwned,
- >(
- &mut self,
- _: &T,
- ) {
- }
- fn visit_bare_field<T: Datum>(&mut self, _: &T) {}
- fn visit_entity_id<E: Entity>(&mut self, _: &E::ID) {}
- fn visit_assoc_map<E: Entity>(&mut self, amap: &AssocMap<E>) {
- inspect_ai(self.0, amap);
- }
- fn visit_assoc_domain<R: microrm::schema::Relation>(
- &mut self,
- adomain: µrm::schema::AssocDomain<R>,
- ) {
- inspect_ai(self.0, adomain);
- }
- fn visit_assoc_range<R: microrm::schema::Relation>(
- &mut self,
- arange: µrm::schema::AssocRange<R>,
- ) {
- inspect_ai(self.0, arange);
- }
- }
- datum.accept_discriminator_ref(&mut Discriminator(EP::part_name()));
- }
- }
- obj.accept_part_visitor_ref(&mut AssocFieldWalker(Default::default()));
- }
- InterfaceVerb::Extra(extra) => {
- O::run_extra_command(data, extra, query_ctx, insert_ctx)?;
- }
- }
- 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: microrm::schema::entity::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_assoc_map<E: Entity>(&mut self) {
- self.0.push(add_keys::<E, IC>(
- clap::Command::new(self.1),
- ValueRole::AttachmentTarget,
- ));
- }
- fn visit_assoc_domain<R: microrm::schema::Relation>(&mut self) {
- self.0.push(add_keys::<R::Range, IC>(
- clap::Command::new(self.1),
- ValueRole::AttachmentTarget,
- ));
- }
- fn visit_assoc_range<R: microrm::schema::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!()
- }
- }
|