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
use paste::paste;
use proc_macro2::{Span, TokenStream};
use syn::parse_macro_input;
use vimwiki_core::*;

mod error;
use error::{Error, Result};

mod tokens;
use tokens::{Tokenize, TokenizeContext};

mod args;
use args::FormatArgs;

mod formatter;
use formatter::Formatter;

mod utils;

macro_rules! impl_macro {
    ($name:ident, $from_str:ident, $type:ty, $raw_mode:expr, $verbatim:expr) => {
        #[proc_macro]
        pub fn $name(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
            let input_2 = input.clone();
            let args = parse_macro_input!(input as FormatArgs);
            let input = TokenStream::from(input_2);

            fn try_expand(input: TokenStream, args: FormatArgs) -> Result<TokenStream> {
                let mut input = input.into_iter();

                let first = input.next().ok_or_else(|| {
                    Error::new(
                        Span::call_site(),
                        "unexpected end of macro invocation, expected format string",
                    )
                })?;

                // Validate we did indeed only get a single argument
                // utils::require_empty_or_trailing_comma(&mut input)?;

                // Load our input into a string
                let input = utils::input_to_string(first, $raw_mode)?;

                // Perform the action of parsing our language into a
                // structured format
                let element: $type = Language::$from_str(&input)
                    .parse()
                    .map_err(|x| Error::new(Span::call_site(), &format!("{}", x)))?;

                // Stuff our structure language into a proper token stream
                let ctx = TokenizeContext {
                    formatter: Formatter::new(args),
                    verbatim: $verbatim,
                };
                let mut stream = TokenStream::new();
                element.tokenize(&ctx, &mut stream);
                Ok(stream)
            }

            // Do the act of expanding our input language into Rust code
            // at compile-time, reporting an error if we fail
            let output = match try_expand(input, args) {
                Ok(tokens) => tokens,
                Err(err) => err.to_compile_error(),
            };

            proc_macro::TokenStream::from(output)
        }
    };
}

/// Macro that generates four macros in the form of
///
/// 1. vimwiki_${suffix}
/// 2. vimwiki_${suffix}_raw
/// 3. vimwiki_${suffix}_format
/// 4. vimwiki_${suffix}_raw_format
///
/// All convert the given text to the specified vimwiki type at compile time.
///
/// The raw versions use the string literal as-is while the non-raw
/// version removes all leading and trailing blank lines AND determines the
/// minimum indentation level (poor man's indoc) and removes that from the
/// beginning of each line.
///
/// The format versions perform variable substitution in the same way that
/// format!, println!, and write! can inject content. Note that formatting
/// with {} currently does not support lists and definition lists as the order
/// cannot be guaranteed. Instead, use named or numbered injection instead.
macro_rules! impl_macro_vimwiki {
    ($suffix:ident, $type:ty) => {
        paste! {
            impl_macro!([<vimwiki_ $suffix>], from_vimwiki_str, $type, false, true);
            impl_macro!([<vimwiki_ $suffix _raw>], from_vimwiki_str, $type, true, true);
            impl_macro!([<vimwiki_ $suffix _format>], from_vimwiki_str, $type, false, false);
            impl_macro!([<vimwiki_ $suffix _raw_format>], from_vimwiki_str, $type, true, false);
        }
    };
}

///////////////////////////////////////////////////////////////////////////////
// Implement macros for vimwiki
///////////////////////////////////////////////////////////////////////////////
impl_macro_vimwiki!(page, Page);
impl_macro_vimwiki!(block_element, Located<BlockElement>);
impl_macro_vimwiki!(inline_element_container, Located<InlineElementContainer>);
impl_macro_vimwiki!(inline_element, Located<InlineElement>);
impl_macro_vimwiki!(blockquote, Located<Blockquote>);
impl_macro_vimwiki!(comment, Located<Comment>);
impl_macro_vimwiki!(line_comment, Located<LineComment>);
impl_macro_vimwiki!(multi_line_comment, Located<MultiLineComment>);
impl_macro_vimwiki!(definition_list, Located<DefinitionList>);
impl_macro_vimwiki!(divider, Located<Divider>);
impl_macro_vimwiki!(header, Located<Header>);
impl_macro_vimwiki!(link, Located<Link>);
impl_macro_vimwiki!(list, Located<List>);
impl_macro_vimwiki!(list_item, Located<ListItem>);
impl_macro_vimwiki!(code_inline, Located<CodeInline>);
impl_macro_vimwiki!(math_inline, Located<MathInline>);
impl_macro_vimwiki!(math_block, Located<MathBlock>);
impl_macro_vimwiki!(paragraph, Located<Paragraph>);
impl_macro_vimwiki!(placeholder, Located<Placeholder>);
impl_macro_vimwiki!(code_block, Located<CodeBlock>);
impl_macro_vimwiki!(table, Located<Table>);
impl_macro_vimwiki!(tags, Located<Tags>);
impl_macro_vimwiki!(decorated_text, Located<DecoratedText>);
impl_macro_vimwiki!(keyword, Located<Keyword>);
impl_macro_vimwiki!(text, Located<Text>);