macro_rules! impl_from {
(
$FROM:path => $INTO:path {
move {
$( $MOVE:tt ),+
}
}
) => {
impl From<$FROM> for $INTO {
fn from(value: $FROM) -> $INTO {
let $FROM { $( $MOVE ),+ , .. } = value;
$INTO { $( $MOVE ),+ }
}
}
};
}
macro_rules! fn_default_matches_clap_args {
($TYPE:ty) => {
#[test]
fn default_matches_clap() {
let command = <$TYPE as clap::Args>::augment_args(clap::Command::new("testcase"));
let mut matches = command.get_matches_from([String::new(); 0]);
let clap = <$TYPE as clap::FromArgMatches>::from_arg_matches_mut(&mut matches).expect("error in test data");
let default = <$TYPE as Default>::default();
assert_eq!(default, clap);
}
};
}
macro_rules! fn_default_matches_clap_parser {
($TYPE:ty) => {
#[test]
fn default_matches_clap() {
let default = <$TYPE as Default>::default();
let clap = <$TYPE as clap::Parser>::parse_from([String::new(); 0]);
assert_eq!(default, clap);
}
};
}
macro_rules! fn_default_matches_serde {
($TYPE:ty) => {
#[test]
fn default_matches_serde() {
let default = <$TYPE as Default>::default();
let serde: $TYPE = serde_json::from_str("{}").expect("error in test data");
assert_eq!(default, serde)
}
};
}
macro_rules! defn_error {
(
$( #[$( $OUTER_ATTR:tt )+] )?
$OUTER:ident => {
$(
$( #[$( $VARIANT_ATTR:tt )+] )?
$VARIANT:ident
),+
}
) => {
paste::paste! {
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
#[error(transparent)]
#[diagnostic(transparent)]
#[remain::sorted]
$( #[$( $OUTER_ATTR )+] )?
pub(crate) enum $OUTER {
$(
$( #[$( $VARIANT_ATTR )+] )?
[< $VARIANT:camel >](#[from] [< $VARIANT:snake:lower >]::$OUTER)
),+
}
}
};
}
macro_rules! defn_subcommand {
(
$( #[$( $OUTER_ATTR:tt )+] )?
($OUTER:ident => $INNER:ident) => {
$(
$( #[$( $VARIANT_ATTR:tt )+] )?
$VARIANT:ident
),+
}
) => {
paste::paste! {
#[derive(Debug, Clone, clap::Subcommand)]
#[remain::sorted]
$( #[$( $OUTER_ATTR )+] )?
pub(crate) enum $OUTER {
$(
$( #[$( $VARIANT_ATTR )+] )?
[< $VARIANT:camel >]([< $VARIANT:snake:lower >]::$INNER)
),+
}
}
};
(
$( #[$( $OUTER_ATTR:tt )+] )?
($OUTER:ident) => {
$( $REST:tt )+
}
) => {
$crate::macros::defn_subcommand! {
$( #[$( $OUTER_ATTR )+] )?
($OUTER => $OUTER) => {
$( $REST )+
}
}
}
}
macro_rules! impl_subcommand {
(
($OUTER:ident => $INNER:ident) => ($FN:ident($ARG1:ident : &$ARG1_TYPE:path)) => {
$( $VARIANT:ident ),+
}
) => {
paste::paste! {
impl $OUTER {
pub(crate) async fn $FN(self, $ARG1: &$ARG1_TYPE) -> Result<Box<dyn $crate::Output>, Error> {
match self {
$(
Self::[< $VARIANT:camel >](inner) => inner.$FN(&$ARG1).await.map_err(Into::into)
),+
}
}
}
}
};
(
($OUTER:ident => $INNER:ident) => ($FN:ident()) => {
$( $VARIANT:ident ),+
}
) => {
paste::paste! {
impl $OUTER {
pub(crate) async fn $FN(self) -> Result<Box<dyn $crate::Output>, Error> {
match self {
$(
Self::[< $VARIANT:camel >](inner) => inner.$FN().await.map_err(Into::into)
),+
}
}
}
}
};
(
($OUTER:ident => $INNER:ident) => {
$( $REST:tt )+
}
) => {
$crate::macros::impl_subcommand! {
($OUTER => $INNER) => (with(agent: &bsky_sdk::BskyAgent)) => {
$( $REST )+
}
}
};
(
($OUTER:ident) => ($( $IMPL_DETAILS:tt )+) => {
$( $REST:tt )+
}
) => {
$crate::macros::impl_subcommand! {
($OUTER => $OUTER) => ($( $IMPL_DETAILS )+) => {
$( $REST )+
}
}
};
(
($OUTER:ident) => {
$( $REST:tt )+
}
) => {
crate::impl_subcommand! {
($OUTER => $OUTER) => {
$( $REST )+
}
}
};
}
macro_rules! auto_subcommand {
(
$( #[$( $OUTER_ATTR:tt )+] )?
($( $OUTER_DEFN:tt )+) => ($( $OUTER_IMPL:tt )*) => {
$(
$( #[$( $VARIANT_ATTR:tt )+] )?
$VARIANT:ident
),+
}
) => {
$crate::macros::defn_subcommand! {
$( #[$( $OUTER_ATTR )+] )?
($( $OUTER_DEFN )+) => {
$(
$( #[$( $VARIANT_ATTR )+] )?
$VARIANT
),+
}
}
$crate::macros::impl_subcommand! {
($( $OUTER_DEFN )+) => ($( $OUTER_IMPL )*) => {
$( $VARIANT ),+
}
}
};
}
macro_rules! impl_hierarchy {
(
$(
$IDENT:ident
),+
) => {
$crate::macros::defn_error! {
Error => {
$( $IDENT ),+
}
}
$crate::macros::auto_subcommand! {
(Command => Command) => (with(agent: &bsky_sdk::BskyAgent)) => {
$(
#[command(subcommand)]
$IDENT
),+
}
}
$crate::macros::auto_subcommand! {
(FlatCommand => FlatCommand) => (with(agent: &bsky_sdk::BskyAgent)) => {
$(
#[command(flatten)]
$IDENT
),+
}
}
};
}
macro_rules! spec {
(
$(
$TLD:ident => {
$(
$DOMAIN:ident => {
$(
$HOST:ident => {
$(
#[command(long_about = $COMMAND_LONG_ABOUT:literal)]
$API:ident => {
$(
input =>
$(
{
$(
$( #[$( $ARG_ATTR:tt )+] )*
$ARG_NAME:ident : $ARG_TYPE:path
),*
}
)?
$(
(
$( #[$( $PATH_ATTR:tt )+] )*
$PATH_TYPE:path
)
)?
=> $INPUT:ident
)?
$(
custom => {
$(
struct => {
$(
$( #[$( $CUSTOM_ATTR:tt )+] )*
$CUSTOM_NAME:ident : $CUSTOM_TYPE:path
),*
}
)?
$(
impl => {
$( $IMPL:tt )+
}
)?
}
)?
$(
output => { impl } => $OUTPUT:ident
)?
}
),+
}
),+
}
),+
}
),+
) => {
paste::paste! {
$crate::macros::impl_hierarchy! { $( $TLD ),+ }
$(
$(
impl From<[< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::Error> for Error {
fn from(error: [< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::Error) -> Self {
[< $TLD:snake:lower >]::Error::from(error).into()
}
}
$(
impl From<[< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::Error> for Error {
fn from(error: [< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::Error) -> Self {
[< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::Error::from(error).into()
}
}
$(
impl From<[< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error> for Error {
fn from(error: [< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error) -> Self {
[< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::Error::from(error).into()
}
}
impl From<bsky_sdk::api::xrpc::Error<bsky_sdk::api::[< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error>> for Error {
fn from(error: bsky_sdk::api::xrpc::Error<bsky_sdk::api::[< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error>) -> Self {
[< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error::from(error).into()
}
}
)+
)+
)+
)+
$(
pub(crate) mod [< $TLD:snake:lower >] {
$crate::macros::impl_hierarchy! { $( $DOMAIN ),+ }
$(
$(
impl From<[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::Error> for Error {
fn from(error: [< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::Error) -> Self {
[< $DOMAIN:snake:lower >]::Error::from(error).into()
}
}
$(
impl From<[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error> for Error {
fn from(error: [< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error) -> Self {
[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::Error::from(error).into()
}
}
impl From<bsky_sdk::api::xrpc::Error<bsky_sdk::api::[< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error>> for Error {
fn from(error: bsky_sdk::api::xrpc::Error<bsky_sdk::api::[< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error>) -> Self {
[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error::from(error).into()
}
}
)+
)+
)+
$(
pub(crate) mod [< $DOMAIN:snake:lower >] {
$crate::macros::impl_hierarchy! { $( $HOST ),+ }
$(
$(
impl From<[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error> for Error {
fn from(error: [< $HOST:snake:lower >]::[< $API:snake:lower >]::Error) -> Self {
[< $HOST:snake:lower >]::Error::from(error).into()
}
}
impl From<bsky_sdk::api::xrpc::Error<bsky_sdk::api::[< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error>> for Error {
fn from(error: bsky_sdk::api::xrpc::Error<bsky_sdk::api::[< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error>) -> Self {
[< $HOST:snake:lower >]::[< $API:snake:lower >]::Error::from(error).into()
}
}
)+
)+
$(
pub(crate) mod [< $HOST:snake:lower >] {
$crate::macros::defn_error! {
Error => { $( $API ),+ }
}
$crate::macros::auto_subcommand! {
#[clap(rename_all = "PascalCase")]
(Command => Command) => (with(agent: &bsky_sdk::BskyAgent)) => { $( $API ),+ }
}
$crate::macros::auto_subcommand! {
#[clap(rename_all = "PascalCase")]
(FlatCommand => Command) => (with(agent: &bsky_sdk::BskyAgent)) => {
$(
#[command(name = bsky_sdk::api::[< $TLD:snake:lower >]::[< $DOMAIN:snake:lower >]::[< $HOST:snake:lower >]::[< $API:snake:lower >]::NSID, long_about = $COMMAND_LONG_ABOUT)]
$API
),+
}
}
$(
pub(crate) mod [< $API:snake:lower >] {
#[derive(Debug, thiserror::Error, miette::Diagnostic)]
#[error(transparent)]
#[remain::sorted]
pub(crate) enum Error {
Api(#[from] bsky_sdk::api::xrpc::Error<bsky_sdk::api::[< $TLD:lower >]::[< $DOMAIN:lower >]::[< $HOST:lower >]::[< $API:snake:lower >]::Error>),
Io(#[from] std::io::Error),
Yaml(#[from] serde_yml::Error),
}
$(
impl $crate::Output for bsky_sdk::api::[< $TLD:lower >]::[< $DOMAIN:lower >]::[< $HOST:lower >]::[< $API:snake:lower >]::$OUTPUT {}
)?
$(
#[derive(Debug, Clone, clap::Parser)]
#[remain::sorted]
#[command(long_about = $COMMAND_LONG_ABOUT)]
pub(crate) struct Command
$(
{
$(
$( #[$( $ARG_ATTR )+] )*
pub(crate) $ARG_NAME : $ARG_TYPE
),*
}
)?
$(
{
$( #[$( $PATH_ATTR )+] )*
path: $PATH_TYPE
}
)?
)?
$(
$(
impl From<Command> for bsky_sdk::api::[< $TLD:lower >]::[< $DOMAIN:lower >]::[< $HOST:lower >]::[< $API:snake:lower >]::[< $INPUT Data >] {
fn from(value: Command) -> Self {
let Command { $( $ARG_NAME, )* } = value;
Self { $( $ARG_NAME, )* }
}
}
impl From<Command> for bsky_sdk::api::[< $TLD:lower >]::[< $DOMAIN:lower >]::[< $HOST:lower >]::[< $API:snake:lower >]::$INPUT {
fn from(value: Command) -> Self {
bsky_sdk::api::[< $TLD:lower >]::[< $DOMAIN:lower >]::[< $HOST:lower >]::[< $API:snake:lower >]::[< $INPUT Data >]::from(value).into()
}
}
impl Command {
pub(crate) async fn with(self, agent: &bsky_sdk::BskyAgent) -> Result<Box<dyn $crate::Output>, Error> {
Ok(
Box::new(
agent
.api
.[< $TLD:lower >]
.[< $DOMAIN:lower >]
.[< $HOST:lower >]
.[< $API:snake:lower >](self.into())
.await?
)
)
}
}
)?
)?
$(
$(
impl From<Command> for $PATH_TYPE {
fn from(value: Command) -> Self {
value.path
}
}
impl TryFrom<Command> for bsky_sdk::api::[< $TLD:lower >]::[< $DOMAIN:lower >]::[< $HOST:lower >]::[< $API:snake:lower >]::[< $INPUT Data >] {
type Error = Error;
fn try_from(value: Command) -> Result<Self, Self::Error> {
let path = $PATH_TYPE::from(value);
let yaml = fs_err::read_to_string(path)?;
let data = serde_yml::from_str(&yaml)?;
Ok(data)
}
}
impl TryFrom<Command> for bsky_sdk::api::[< $TLD:lower >]::[< $DOMAIN:lower >]::[< $HOST:lower >]::[< $API:snake:lower >]::$INPUT {
type Error = Error;
fn try_from(value: Command) -> Result<Self, Self::Error> {
bsky_sdk::api::[< $TLD:lower >]::[< $DOMAIN:lower >]::[< $HOST:lower >]::[< $API:snake:lower >]::[< $INPUT Data >]::try_from(value).map(Into::into)
}
}
impl Command {
pub(crate) async fn with(self, agent: &bsky_sdk::BskyAgent) -> Result<Box<dyn $crate::Output>, Error> {
Ok(
Box::new(
agent
.api
.[< $TLD:lower >]
.[< $DOMAIN:lower >]
.[< $HOST:lower >]
.[< $API:snake:lower >](self.try_into()?)
.await?
)
)
}
}
)?
)?
$(
$(
#[derive(Debug, Clone, clap::Parser)]
#[remain::sorted]
pub(crate) struct Command {
$(
$( #[$( $CUSTOM_ATTR )+] )*
pub(crate) $CUSTOM_NAME : $CUSTOM_TYPE
),*
}
)?
$(
impl Command {
$( $IMPL )+
}
)?
)?
}
)+
}
)+
}
)+
}
)+
}
};
}
pub(crate) use auto_subcommand;
pub(crate) use defn_error;
pub(crate) use defn_subcommand;
pub(crate) use fn_default_matches_clap_args;
pub(crate) use fn_default_matches_clap_parser;
pub(crate) use fn_default_matches_serde;
pub(crate) use impl_from;
pub(crate) use impl_hierarchy;
pub(crate) use impl_subcommand;
pub(crate) use spec;
macro_rules! flag {
(
$IDENT:ident => {
exclude => $EXCLUDE:literal,
include => $INCLUDE:literal
}
) => {
paste::paste! {
#[derive(Debug, Clone, Copy, clap::Args)]
#[group(required = true, multiple = false)]
struct [< $IDENT:camel >] {
#[arg(short = $EXCLUDE, long, env = "" [< PASSWORD_EXCLUDE_ $IDENT:snake:upper >])]
[< exclude_ $IDENT:snake:lower >]: bool,
#[arg(short = $INCLUDE, long, env = "" [< PASSWORD_INCLUDE_ $IDENT:snake:upper >])]
[< include_ $IDENT:snake:lower >]: bool,
}
impl From<[< $IDENT:camel >]> for bool {
fn from(value: [< $IDENT:camel >]) -> bool {
let [< $IDENT:camel >] {
[< exclude_ $IDENT:snake:lower >]: exclude,
[< include_ $IDENT:snake:lower >]: include,
..
} = value;
include && !exclude
}
}
}
};
}