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
use id_arena::Id;
use std::{
    collections::HashMap,
    fs::File,
    io::Write,
    path::{Path, PathBuf},
};
use wit_bindgen_core::{
    wit_parser::{
        Enum, Field, Function, FunctionKind, Interface, Package, Record, Resolve, Results, Type, TypeDef, TypeDefKind,
        TypeOwner, UnresolvedPackage, World, WorldItem, WorldKey,
    },
    Files,
};
use wit_bindgen_rust::ExportKey;

pub struct ForeignGenerator {
    package_name: String,
    pid: Id<Package>,
    wid: Id<World>,
    map_module: HashMap<String, Id<Interface>>,
    resolve: Resolve,
}

const LANGUAGE_ID: &'static str = "v";

impl ForeignGenerator {
    pub fn new(package: &str) -> anyhow::Result<Self> {
        let mut resolve = Resolve::default();
        let root = UnresolvedPackage::parse(
            &Path::new("<anonymous>"),
            &format!("package {LANGUAGE_ID}:{package};world {package} {{}}"),
        )?;
        let pid = resolve.push(root)?;
        for (wid, w) in &resolve.worlds {
            if w.name.eq(package) {
                return Ok(Self { package_name: package.to_string(), pid, wid, map_module: Default::default(), resolve });
            }
        }
        unreachable!()
    }
    /// Get the language name and package infos
    pub fn get_language(&self) -> &Package {
        self.resolve.packages.get(self.pid).expect("")
    }
    /// Get the package name and world infos
    pub fn get_package(&self) -> &World {
        self.resolve.worlds.get(self.wid).expect("")
    }
    pub fn make_module(&mut self, target: &str) -> Id<Interface> {
        match self.map_module.get(target) {
            None => {}
            Some(s) => {
                return *s;
            }
        }
        let mid = self.resolve.interfaces.alloc(Interface {
            name: Some(target.to_string()),
            types: Default::default(),
            functions: Default::default(),
            docs: Default::default(),
            package: Some(self.pid),
        });
        self.map_module.insert(target.to_string(), mid);
        let world = self.resolve.worlds.get_mut(self.wid).unwrap();
        world.exports.insert(WorldKey::Interface(mid), WorldItem::Interface(mid));
        mid
    }
    pub fn make_function(&mut self, mid: Id<Interface>, f: Function) {
        let module = self.mut_module(mid);
        module.functions.insert(f.name.clone(), f);
    }
    pub fn get_module(&self, id: Id<Interface>) -> &Interface {
        self.resolve.interfaces.get(id).expect("")
    }
    pub fn mut_module(&mut self, id: Id<Interface>) -> &mut Interface {
        self.resolve.interfaces.get_mut(id).expect("")
    }
    pub fn make_type(&mut self, mid: Id<Interface>, name: &str, kind: TypeDefKind) -> Id<TypeDef> {
        let tid = self.resolve.types.alloc(TypeDef {
            name: Some(name.to_string()),
            kind,
            owner: TypeOwner::Interface(mid),
            docs: Default::default(),
        });
        let module = self.mut_module(mid);
        module.types.insert(name.to_string(), tid);
        tid
    }
}

impl ForeignGenerator {
    pub fn build_rust<P: AsRef<Path>>(&self, dir: P) -> anyhow::Result<()> {
        let path = ensure_dir(dir)?;
        let mut builder =
            wit_bindgen_rust::Opts { rustfmt: false, exports: self.ensure_export(), ..Default::default() }.build();
        let mut files = Files::default();
        builder.generate(&self.resolve, self.wid, &mut files)?;
        for (name, content) in files.iter() {
            let mut file = File::create(path.join(name))?;
            file.write_all(content)?;
        }
        Ok(())
    }
    pub fn build_markdown<P: AsRef<Path>>(&self, dir: P) -> anyhow::Result<()> {
        let path = ensure_dir(dir)?;
        let mut builder = wit_bindgen_markdown::Opts::default().build();
        let mut files = Files::default();
        builder.generate(&self.resolve, self.wid, &mut files)?;
        for (name, content) in files.iter() {
            let mut file = File::create(path.join(name))?;
            file.write_all(content)?;
        }
        Ok(())
    }
    fn ensure_export(&self) -> HashMap<ExportKey, String> {
        let mut exports = HashMap::default();
        for (_, i) in &self.get_package().exports {
            match i {
                WorldItem::Interface(i) => match &self.get_module(*i).name {
                    None => {}
                    Some(s) => {
                        exports.insert(ExportKey::Name(format!("{}:{}/{}", LANGUAGE_ID, self.package_name, s)), s.to_string());
                    }
                },
                WorldItem::Function(_) => {}
                WorldItem::Type(_) => {}
            }
        }
        exports
    }
}

fn ensure_dir<'i, P>(path: P) -> anyhow::Result<PathBuf>
where
    P: AsRef<Path> + 'i,
{
    let path = path.as_ref();
    if !path.exists() {
        std::fs::create_dir_all(path)?;
    }
    if !path.is_dir() {
        anyhow::bail!("{} is not a directory", path.display());
    }
    Ok(path.canonicalize()?)
}