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
use argument::{DuchessDeclaration, MethodSelector};
use parse::Parser;
use proc_macro::TokenStream;
use rust_format::Formatter;
use std::path::PathBuf;

mod argument;
mod check;
mod class_info;
mod codegen;
mod derive;
mod java_function;
mod parse;
mod reflect;
mod signature;
mod substitution;
mod upcasts;

/// The main duchess macro, used like so
///
/// ```rust,ignore
/// duchess::java_package! {
///     package some.pkg.name;
///     class SomeDotId { * }
/// }
/// ```
///
/// see the tutorial in the [duchess book] for more info.
///
/// [duchess book]: https://nikomatsakis.github.io/duchess/
#[proc_macro]
pub fn java_package(input: TokenStream) -> TokenStream {
    let input: proc_macro2::TokenStream = input.into();
    let decl = match Parser::from(input).parse::<DuchessDeclaration>() {
        Ok(decl) => decl,
        Err(err) => return err.to_compile_error().into(),
    };

    match decl.to_tokens() {
        Ok(t) => return t.into(),
        Err(e) => return e.into_compile_error().into(),
    }
}

#[proc_macro_attribute]
pub fn java_function(args: TokenStream, input: TokenStream) -> TokenStream {
    let args: proc_macro2::TokenStream = args.into();
    let args = match Parser::from(args).parse::<MethodSelector>() {
        Ok(decl) => decl,
        Err(err) => return err.to_compile_error().into(),
    };

    let item_fn = match syn::parse::<syn::ItemFn>(input) {
        Ok(item_fn) => item_fn,
        Err(err) => return err.into_compile_error().into(),
    };

    match java_function::java_function(args, item_fn) {
        Ok(t) => t.into(),
        Err(err) => err.into_compile_error().into(),
    }
}

synstructure::decl_derive!([ToRust, attributes(java)] => derive::derive_to_rust);

synstructure::decl_derive!([ToJava, attributes(java)] => derive::derive_to_java);

lazy_static::lazy_static! {
    static ref DEBUG_DIR: PathBuf = {
        let tmp_dir = tempfile::TempDir::new().expect("failed to create temp directory");
        tmp_dir.into_path()
    };
}

fn debug_tokens(name: impl std::fmt::Display, token_stream: &proc_macro2::TokenStream) {
    let Ok(debug_filter) = std::env::var("DUCHESS_DEBUG") else {
        return;
    };
    let name = name.to_string();
    let debug_enabled = match debug_filter {
        f if f.eq_ignore_ascii_case("true") || f.eq_ignore_ascii_case("1") => true,
        filter => name.starts_with(&filter)
    };
    if debug_enabled {
        let path = DEBUG_DIR.join(name.replace('.', "_")).with_extension("rs");
        match rust_format::RustFmt::default().format_tokens(token_stream.clone()) {
            Ok(formatted_tokens) => {
                std::fs::write(&path, formatted_tokens).expect("failed to write to debug file");
            }
            Err(_) => {
                std::fs::write(&path, format!("{token_stream:?}")).expect("failed to write to debug file");
            }
        }
        // in JetBrains terminal, links are only clickable with a `file:///` prefix. But in VsCode
        // iTerm, and most other terminals, they are only clickable if they are absolute paths.
        if running_in_jetbrains() {
            eprintln!("file:///{}", path.display())
        } else {
            eprintln!("{}", path.display())
        }
    }
}

fn running_in_jetbrains() -> bool {
    std::env::var("TERMINAL_EMULATOR").map(|var|var.contains("JetBrains")).unwrap_or_default()
}