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
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
use std::fs;
use std::ops::Add;
use std::path::Path;

use anyhow::{anyhow, Result};
use lazy_static::lazy_static;
use log::{info, warn};
use pathdiff::diff_paths;

// NOTE [DartPostCObjectFnType] was originally [*mut DartCObject] but I changed it to [*mut c_void]
// because cannot automatically generate things related to [DartCObject]. Anyway this works fine.
// NOTE please sync [DUMMY_WIRE_CODE_FOR_BINDGEN] and [EXTRA_EXTERN_FUNC_NAMES]
pub const DUMMY_WIRE_CODE_FOR_BINDGEN: &str = r#"
    // ----------- DUMMY CODE FOR BINDGEN ----------

    // copied from: allo-isolate
    pub type DartPort = i64;
    pub type DartPostCObjectFnType = unsafe extern "C" fn(port_id: DartPort, message: *mut std::ffi::c_void) -> bool;
    #[no_mangle] pub unsafe extern "C" fn store_dart_post_cobject(ptr: DartPostCObjectFnType) { panic!("dummy code") }
    #[no_mangle] pub unsafe extern "C" fn get_dart_object(ptr: usize) -> Dart_Handle { panic!("dummy code") }
    #[no_mangle] pub unsafe extern "C" fn drop_dart_object(ptr: usize) { panic!("dummy code") }
    #[no_mangle] pub unsafe extern "C" fn new_dart_opaque(handle: Dart_Handle) -> usize { panic!("dummy code") }
    #[no_mangle] pub unsafe extern "C" fn init_frb_dart_api_dl(obj: *mut c_void) -> isize { panic!("dummy code") }

    pub struct DartCObject;
    pub type WireSyncReturn = *mut DartCObject;

    // ---------------------------------------------
    "#;

lazy_static! {
    pub static ref EXTRA_EXTERN_FUNC_NAMES: Vec<String> = vec![
        "store_dart_post_cobject".to_owned(),
        "get_dart_object".to_owned(),
        "drop_dart_object".to_owned(),
        "new_dart_opaque".to_owned()
    ];
}

pub fn code_header() -> String {
    format!(
        "// AUTO GENERATED FILE, DO NOT EDIT.
// Generated by `flutter_rust_bridge`@ {}.",
        env!("CARGO_PKG_VERSION")
    )
}

pub fn modify_dart_wire_content(content_raw: &str, dart_wire_class_name: &str) -> String {
    let content = content_raw.replace(
        &format!("class {dart_wire_class_name} {{",),
        &format!(
            "class {dart_wire_class_name} implements FlutterRustBridgeWireBase {{
            @internal
            late final dartApi = DartApiDl(init_frb_dart_api_dl);",
        ),
    );

    content
        .replace("class DartCObject extends ffi.Opaque {}", "")
        .replace("typedef WireSyncReturn = ffi.Pointer<DartCObject>;", "")
}

#[derive(Default)]
pub struct DartBasicCode {
    pub import: String,
    pub part: String,
    pub body: String,
}

impl Add for &DartBasicCode {
    type Output = DartBasicCode;

    fn add(self, rhs: Self) -> Self::Output {
        DartBasicCode {
            import: format!("{}\n{}", self.import, rhs.import),
            part: format!("{}\n{}", self.part, rhs.part),
            body: format!("{}\n{}", self.body, rhs.body),
        }
    }
}

impl Add<&DartBasicCode> for DartBasicCode {
    type Output = DartBasicCode;

    fn add(self, rhs: &DartBasicCode) -> Self::Output {
        (&self).add(rhs)
    }
}

impl DartBasicCode {
    pub fn to_text(&self) -> String {
        format!("{}\n{}\n{}", self.import, self.part, self.body)
    }
}

pub fn extract_dart_wire_content(content: &str) -> DartBasicCode {
    let (mut imports, mut body) = (Vec::new(), Vec::new());
    for line in content.split('\n') {
        (if line.starts_with("import ") {
            &mut imports
        } else {
            &mut body
        })
        .push(line);
    }
    DartBasicCode {
        import: imports.join("\n"),
        part: "".to_string(),
        body: body.join("\n"),
    }
}

pub fn sanity_check(
    generated_dart_wire_code: &str,
    dart_wire_class_name: &str,
) -> anyhow::Result<()> {
    if !generated_dart_wire_code.contains(dart_wire_class_name) {
        return Err(crate::error::Error::string(
            "Nothing is generated for dart wire class. \
            Maybe you forget to put code like `mod the_generated_bridge_code;` to your `lib.rs`?",
        )
        .into());
    }
    Ok(())
}

pub fn try_add_mod_to_lib(rust_crate_dir: &str, rust_output_path: &str) {
    if let Err(e) = auto_add_mod_to_lib_core(rust_crate_dir, rust_output_path) {
        warn!(
            "auto_add_mod_to_lib fail, the generated code may or may not have problems. \
            Please ensure you have add code like `mod the_generated_bridge_code;` to your `lib.rs`. \
            Details: {}",
            e
        );
    }
}

pub fn auto_add_mod_to_lib_core(rust_crate_dir: &str, rust_output_path: &str) -> Result<()> {
    let path_src_folder = Path::new(rust_crate_dir).join("src");
    let rust_output_path_relative_to_src_folder =
        diff_paths(rust_output_path, path_src_folder.clone()).ok_or_else(|| {
            anyhow!(
                "rust_output_path={} is unrelated to path_src_folder={:?}",
                rust_output_path,
                &path_src_folder,
            )
        })?;

    let mod_name = rust_output_path_relative_to_src_folder
        .file_stem()
        .ok_or_else(|| anyhow!(""))?
        .to_str()
        .ok_or_else(|| anyhow!(""))?
        .to_string()
        .replace('/', "::");
    let expect_code = format!("mod {mod_name};");

    let path_lib_rs = path_src_folder.join("lib.rs");

    let raw_content_lib_rs = fs::read_to_string(path_lib_rs.clone())?;
    if !raw_content_lib_rs.contains(&expect_code) {
        info!("Inject `{}` into {:?}", &expect_code, &path_lib_rs);

        let comments = " /* AUTO INJECTED BY flutter_rust_bridge. This line may not be accurate, and you can change it according to your needs. */";
        let modified_content_lib_rs = format!("{expect_code}{comments}\n{raw_content_lib_rs}");

        fs::write(&path_lib_rs, modified_content_lib_rs).unwrap();
    }

    Ok(())
}