1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
extern crate proc_macro;
use std::collections::HashSet;

#[proc_macro_derive(RuntimeDef, attributes(cli, conf))]
pub fn derive_runtime_def(stream: proc_macro::TokenStream) -> proc_macro::TokenStream {
    let mut parsed = syn::parse_macro_input!(stream as syn::Item);
    match &mut parsed {
        syn::Item::Struct(item) => impl_mod_struct(item),
        _ => {
            let error = syn::Error::new(proc_macro2::Span::call_site(), "not a struct type");
            error.to_compile_error()
        }
    }
    .into()
}

fn impl_mod_struct(item: &syn::ItemStruct) -> proc_macro2::TokenStream {
    let attrs = parse_attributes(&item.attrs);
    let runtime = item.ident.clone();
    let generics = item.generics.clone();
    impl_mod(attrs, runtime, generics)
}

fn impl_mod(
    attrs: HashSet<DefParam>,
    name: syn::Ident,
    generics: syn::Generics,
) -> proc_macro2::TokenStream {
    let mut impl_cli = quote::quote!();
    let mut impl_conf = quote::quote!(
        #[derive(Default, ::serde::Serialize, ::serde::Deserialize)]
        pub struct Conf {}
    );

    for attr in attrs {
        match attr {
            DefParam::Cli(ident) => {
                impl_cli = quote::quote!(
                    #[structopt(flatten)]
                    pub runtime: super::#ident,
                );
            }
            DefParam::Conf(ident) => {
                impl_conf = quote::quote!(
                    pub type Conf = super::#ident;
                )
            }
        }
    }

    let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();

    quote::quote!(

        #[doc(hidden)]
        pub mod ya_runtime_sdk_impl {
            #[derive(structopt::StructOpt)]
            #[structopt(setting = structopt::clap::AppSettings::ColoredHelp)]
            #[structopt(setting = structopt::clap::AppSettings::DeriveDisplayOrder)]
            #[structopt(setting = structopt::clap::AppSettings::VersionlessSubcommands)]
            pub struct Cli {
                /// Working directory
                #[structopt(short, long)]
                #[structopt(required_ifs(&[
                    ("command", "deploy"),
                    ("command", "start"),
                    ("command", "run"),
                ]))]
                pub workdir: Option<std::path::PathBuf>,

                #impl_cli

                /// Command to execute
                #[structopt(subcommand)]
                pub command: ::ya_runtime_sdk::cli::Command,
            }

            impl ::ya_runtime_sdk::cli::CommandCli for Cli {
                fn workdir(&self) -> Option<std::path::PathBuf> {
                    self.workdir.clone()
                }

                fn command(&self) -> &::ya_runtime_sdk::cli::Command {
                    &self.command
                }
            }

            #impl_conf
        }

        impl #impl_generics ::ya_runtime_sdk::RuntimeDef for #name #ty_generics #where_clause {
            const NAME: &'static str = env!("CARGO_PKG_NAME");
            const VERSION: &'static str = env!("CARGO_PKG_VERSION");

            type Cli = ya_runtime_sdk_impl::Cli;
            type Conf = ya_runtime_sdk_impl::Conf;
        }
    )
}

fn parse_attributes(attrs: &Vec<syn::Attribute>) -> HashSet<DefParam> {
    attrs
        .iter()
        .map(|attr| (attr, attr.path.segments[0].ident.to_string()))
        .filter(|(_, variant)| {
            DefParam::VARIANTS
                .iter()
                .position(|v| *v == variant.as_str())
                .is_some()
        })
        .map(|(attr, variant)| {
            let ident = syn::parse2::<DefIdent>(attr.tokens.clone())
                .expect(&format!("invalid value {}", variant));
            DefParam::new(&variant, ident.0)
        })
        .collect()
}

#[derive(Clone, Debug, Eq, Hash, PartialEq)]
enum DefParam {
    Cli(syn::Ident),
    Conf(syn::Ident),
}

impl DefParam {
    const VARIANTS: [&'static str; 2] = ["cli", "conf"];

    fn new(variant: &String, ident: syn::Ident) -> Self {
        match variant.as_str() {
            "cli" => DefParam::Cli(ident),
            "conf" => DefParam::Conf(ident),
            _ => panic!("invalid attribute: {}", variant),
        }
    }
}

struct DefIdent(syn::Ident);

impl syn::parse::Parse for DefIdent {
    fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
        let content;
        syn::parenthesized!(content in input);
        let item = content.parse()?;
        Ok(DefIdent(item))
    }
}