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
//! # New Home Application (Macro)
//!
//! This crate provides a macro for deriving the [Method](new_home_application::method::Method) trait.
//!
//! ## Attributes
//!
//! There are a couple of attributes that can be used on the struct and/or fields in the struct
//!
//! | Name          | Scope  | Description                                                            |
//! |---------------|--------|------------------------------------------------------------------------|
//! | `description` | Struct | Provides a description for the method                                  |
//! | `help`        | Struct | Provides a help text for an argument/struct field or the method itself |
//!
//! ## Arguments
//!
//! The word "Arguments" describes fields on the Method struct, that are set through the [Method::set_arguments](new_home_application::method::Method::set_arguments)
//! method. While you can implement the Method trait by yourself and just handle it on your own, this
//! macro allows you to have the `argument` attribute on a field which causes the field to be automatically
//! set by the set_arguments method. This however requires the field to have the type [serde_json::Value].
//!
//! The argument attribute also has to have a content, which will become the description for the field.
//!
//! To set a default value for the argument, there is the `default` attribute. The content of this
//! attribute will be literally passed to the [serde_json::Value]::from method. So you will have to
//! wrap strings in quotes. On the other side however, you can easily put a plain number or boolean
//! in there and it will be filled in to the argument.
//!
//! ## Example
//!
//! You can see a full example in the `examples/macro-example.rs` file, but here you also have a small
//! example for applying the macro onto a struct:
//!
//! ```rust
//! use new_home_application::method::{Method, MethodCallable};
//! use new_home_application::communication::{MethodCall, MethodResult};
//! use new_home_application_macro::Method;
//!
//! use serde::Deserialize;
//!
//! /// The arguments used in the callable later
//! #[derive(Deserialize)]
//! struct Arguments {
//!     /// The word that should be converted
//!     pub word: String,
//!
//!     /// Determines if the word will be converted to uppercase
//!     pub uppercase: bool,
//! }
//!
//! #[derive(Method)]
//! #[description("Can convert a word into uppercase")]
//! #[help("Will convert the word to uppercase if uppercase flag is set")]
//! struct ExampleMethod;
//!
//! impl MethodCallable for ExampleMethod {
//!     type ArgumentsType = Arguments;
//!
//!     fn secure_call(&mut self, name: String, arguments: Self::ArgumentsType) -> MethodResult {
//!         unimplemented!()
//!     }
//! }
//! ```
//!
//! The logic for executing whatever the method should do is not implemented here as the Method trait
//! only provides basic information for the method. The logic will be added through the [MethodCallable](new_home_application::method::MethodCallable)
//! trait which cannot be derived and has to be implemented by yourself.
//!

extern crate inflector;
extern crate new_home_application;
extern crate quote;
extern crate serde;
extern crate syn;
extern crate toml;

use proc_macro::TokenStream;


use syn::export::ToTokens;
use syn::{DeriveInput};

fn build_impl(struct_name: String, description: String, help: String) -> String {
    format!(
        r##"
        impl new_home_application::method::Method for {struct_name} {{
            fn name(&self) -> String {{
                String::from(r#"{name}"#)
            }}

            fn description(&self) -> String {{
                String::from(r#"{description}"#)
            }}

            fn help(&self) -> String {{
                String::from(r#"{help}"#)
            }}
        }}
        "##,
        struct_name = struct_name.clone(),
        name = inflector::cases::snakecase::to_snake_case(struct_name.as_str()),
        description = description,
        help = help,
    )
}

#[proc_macro_derive(Method, attributes(default, description, argument, help))]
pub fn derive_method(input: TokenStream) -> TokenStream {
    let ast = syn::parse::<DeriveInput>(input.clone()).unwrap();
    let mut description = String::new();
    let mut help = String::new();

    for attr in ast.attrs {
        let name = attr.path.to_token_stream().to_string();
        let value = String::from(
            attr.tokens
                .to_string()
                .trim_start()
                .trim_end()
                .trim_start_matches("(")
                .trim_end_matches(")")
                .trim_start_matches("\"")
                .trim_end_matches("\""),
        );

        if name.eq("description") {
            description = value;

            continue;
        }

        if name.eq("help") {
            help = value;

            continue;
        }
    }

    build_impl(ast.ident.to_string(), description, help)
        .parse()
        .unwrap()
}