@tishlang/tish-format 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/Cargo.toml +49 -0
- package/LICENSE +13 -0
- package/README.md +138 -0
- package/bin/tish-format +0 -0
- package/crates/js_to_tish/Cargo.toml +11 -0
- package/crates/js_to_tish/README.md +18 -0
- package/crates/js_to_tish/src/error.rs +55 -0
- package/crates/js_to_tish/src/lib.rs +11 -0
- package/crates/js_to_tish/src/span_util.rs +35 -0
- package/crates/js_to_tish/src/transform/expr.rs +610 -0
- package/crates/js_to_tish/src/transform/stmt.rs +503 -0
- package/crates/js_to_tish/src/transform.rs +60 -0
- package/crates/tish/Cargo.toml +54 -0
- package/crates/tish/src/cargo_native_registry.rs +32 -0
- package/crates/tish/src/cli_help.rs +565 -0
- package/crates/tish/src/main.rs +781 -0
- package/crates/tish/src/repl_completion.rs +200 -0
- package/crates/tish/tests/cargo_example_compile.rs +67 -0
- package/crates/tish/tests/fixtures/cargo_example_project/Cargo.toml +3 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/Cargo.toml +11 -0
- package/crates/tish/tests/fixtures/cargo_example_project/crates/demo-shim/src/lib.rs +12 -0
- package/crates/tish/tests/fixtures/cargo_example_project/package.json +10 -0
- package/crates/tish/tests/fixtures/cargo_example_project/src/main.tish +3 -0
- package/crates/tish/tests/integration_test.rs +1095 -0
- package/crates/tish/tests/run_optimize_stdout_parity.rs +50 -0
- package/crates/tish/tests/shortcircuit.rs +65 -0
- package/crates/tish_ast/Cargo.toml +9 -0
- package/crates/tish_ast/src/ast.rs +620 -0
- package/crates/tish_ast/src/lib.rs +5 -0
- package/crates/tish_build_utils/Cargo.toml +11 -0
- package/crates/tish_build_utils/src/lib.rs +577 -0
- package/crates/tish_builtins/Cargo.toml +20 -0
- package/crates/tish_builtins/src/array.rs +441 -0
- package/crates/tish_builtins/src/construct.rs +159 -0
- package/crates/tish_builtins/src/globals.rs +213 -0
- package/crates/tish_builtins/src/helpers.rs +35 -0
- package/crates/tish_builtins/src/lib.rs +16 -0
- package/crates/tish_builtins/src/math.rs +89 -0
- package/crates/tish_builtins/src/object.rs +36 -0
- package/crates/tish_builtins/src/string.rs +647 -0
- package/crates/tish_builtins/src/symbol.rs +83 -0
- package/crates/tish_bytecode/Cargo.toml +17 -0
- package/crates/tish_bytecode/src/chunk.rs +96 -0
- package/crates/tish_bytecode/src/compiler.rs +1760 -0
- package/crates/tish_bytecode/src/encoding.rs +100 -0
- package/crates/tish_bytecode/src/lib.rs +19 -0
- package/crates/tish_bytecode/src/opcode.rs +142 -0
- package/crates/tish_bytecode/src/peephole.rs +189 -0
- package/crates/tish_bytecode/src/serialize.rs +163 -0
- package/crates/tish_bytecode/tests/break_continue_bytecode.rs +44 -0
- package/crates/tish_bytecode/tests/constant_folding.rs +84 -0
- package/crates/tish_bytecode/tests/sort_optimization.rs +31 -0
- package/crates/tish_compile/Cargo.toml +26 -0
- package/crates/tish_compile/src/codegen.rs +5332 -0
- package/crates/tish_compile/src/infer.rs +292 -0
- package/crates/tish_compile/src/lib.rs +164 -0
- package/crates/tish_compile/src/resolve.rs +1388 -0
- package/crates/tish_compile/src/types.rs +501 -0
- package/crates/tish_compile_js/Cargo.toml +18 -0
- package/crates/tish_compile_js/examples/jsx_vdom_smoke.tish +8 -0
- package/crates/tish_compile_js/src/codegen.rs +871 -0
- package/crates/tish_compile_js/src/error.rs +20 -0
- package/crates/tish_compile_js/src/lib.rs +26 -0
- package/crates/tish_compile_js/src/tests_jsx.rs +350 -0
- package/crates/tish_compiler_wasm/Cargo.toml +21 -0
- package/crates/tish_compiler_wasm/src/lib.rs +57 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +473 -0
- package/crates/tish_core/Cargo.toml +26 -0
- package/crates/tish_core/src/console_style.rs +160 -0
- package/crates/tish_core/src/json.rs +387 -0
- package/crates/tish_core/src/lib.rs +17 -0
- package/crates/tish_core/src/macros.rs +36 -0
- package/crates/tish_core/src/uri.rs +118 -0
- package/crates/tish_core/src/value.rs +696 -0
- package/crates/tish_core/src/vmref.rs +178 -0
- package/crates/tish_cranelift/Cargo.toml +19 -0
- package/crates/tish_cranelift/src/lib.rs +43 -0
- package/crates/tish_cranelift/src/link.rs +117 -0
- package/crates/tish_cranelift/src/lower.rs +85 -0
- package/crates/tish_cranelift_runtime/Cargo.toml +25 -0
- package/crates/tish_cranelift_runtime/src/lib.rs +45 -0
- package/crates/tish_eval/Cargo.toml +45 -0
- package/crates/tish_eval/src/eval.rs +3717 -0
- package/crates/tish_eval/src/http.rs +188 -0
- package/crates/tish_eval/src/lib.rs +99 -0
- package/crates/tish_eval/src/natives.rs +399 -0
- package/crates/tish_eval/src/promise.rs +179 -0
- package/crates/tish_eval/src/regex.rs +299 -0
- package/crates/tish_eval/src/timers.rs +120 -0
- package/crates/tish_eval/src/value.rs +318 -0
- package/crates/tish_eval/src/value_convert.rs +111 -0
- package/crates/tish_fmt/Cargo.toml +16 -0
- package/crates/tish_fmt/src/bin/tish-fmt.rs +41 -0
- package/crates/tish_fmt/src/lib.rs +2101 -0
- package/crates/tish_jsx_web/Cargo.toml +9 -0
- package/crates/tish_jsx_web/README.md +5 -0
- package/crates/tish_jsx_web/src/lib.rs +2 -0
- package/crates/tish_lexer/Cargo.toml +9 -0
- package/crates/tish_lexer/src/lib.rs +716 -0
- package/crates/tish_lexer/src/token.rs +163 -0
- package/crates/tish_lint/Cargo.toml +18 -0
- package/crates/tish_lint/src/bin/tish-lint.rs +195 -0
- package/crates/tish_lint/src/lib.rs +289 -0
- package/crates/tish_llvm/Cargo.toml +13 -0
- package/crates/tish_llvm/src/lib.rs +115 -0
- package/crates/tish_lsp/Cargo.toml +25 -0
- package/crates/tish_lsp/README.md +26 -0
- package/crates/tish_lsp/src/builtin_goto.rs +362 -0
- package/crates/tish_lsp/src/import_goto.rs +562 -0
- package/crates/tish_lsp/src/main.rs +1046 -0
- package/crates/tish_native/Cargo.toml +16 -0
- package/crates/tish_native/src/build.rs +427 -0
- package/crates/tish_native/src/config.rs +48 -0
- package/crates/tish_native/src/lib.rs +416 -0
- package/crates/tish_opt/Cargo.toml +13 -0
- package/crates/tish_opt/src/lib.rs +943 -0
- package/crates/tish_parser/Cargo.toml +11 -0
- package/crates/tish_parser/src/lib.rs +332 -0
- package/crates/tish_parser/src/parser.rs +2304 -0
- package/crates/tish_pg/Cargo.toml +34 -0
- package/crates/tish_pg/README.md +38 -0
- package/crates/tish_pg/src/error.rs +52 -0
- package/crates/tish_pg/src/lib.rs +955 -0
- package/crates/tish_resolve/Cargo.toml +13 -0
- package/crates/tish_resolve/src/lib.rs +3561 -0
- package/crates/tish_resolve/src/pos.rs +141 -0
- package/crates/tish_runtime/Cargo.toml +96 -0
- package/crates/tish_runtime/src/http.rs +1298 -0
- package/crates/tish_runtime/src/http_fetch.rs +471 -0
- package/crates/tish_runtime/src/http_hyper.rs +418 -0
- package/crates/tish_runtime/src/http_prefork.rs +189 -0
- package/crates/tish_runtime/src/lib.rs +1192 -0
- package/crates/tish_runtime/src/native_promise.rs +15 -0
- package/crates/tish_runtime/src/promise.rs +248 -0
- package/crates/tish_runtime/src/promise_io.rs +38 -0
- package/crates/tish_runtime/src/timers.rs +166 -0
- package/crates/tish_runtime/src/ws.rs +761 -0
- package/crates/tish_runtime/tests/fetch_readable_stream.rs +102 -0
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +682 -0
- package/crates/tish_ui/src/lib.rs +20 -0
- package/crates/tish_ui/src/runtime/hooks.rs +569 -0
- package/crates/tish_ui/src/runtime/mod.rs +180 -0
- package/crates/tish_vm/Cargo.toml +47 -0
- package/crates/tish_vm/src/lib.rs +39 -0
- package/crates/tish_vm/src/vm.rs +2192 -0
- package/crates/tish_vm/tests/fixtures/or_string_cmd.tish +2 -0
- package/crates/tish_vm/tests/lexical_scope_declare.rs +34 -0
- package/crates/tish_vm/tests/peephole_jump_chain_logical_or.rs +150 -0
- package/crates/tish_wasm/Cargo.toml +15 -0
- package/crates/tish_wasm/src/lib.rs +424 -0
- package/crates/tish_wasm_runtime/Cargo.toml +37 -0
- package/crates/tish_wasm_runtime/src/gpu.rs +413 -0
- package/crates/tish_wasm_runtime/src/lib.rs +42 -0
- package/crates/tishlang_cargo_bindgen/Cargo.toml +26 -0
- package/crates/tishlang_cargo_bindgen/src/classify.rs +263 -0
- package/crates/tishlang_cargo_bindgen/src/discover.rs +125 -0
- package/crates/tishlang_cargo_bindgen/src/infer.rs +382 -0
- package/crates/tishlang_cargo_bindgen/src/lib.rs +349 -0
- package/crates/tishlang_cargo_bindgen/src/main.rs +167 -0
- package/crates/tishlang_cargo_bindgen/src/metadata.rs +117 -0
- package/justfile +268 -0
- package/package.json +1 -1
- package/platform/darwin-arm64/tish-fmt +0 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
//! Generate Rust glue for Tish `cargo:` imports by **reading the dependency crate’s source**
|
|
2
|
+
//! (via `cargo metadata` + `syn`), classifying each `pub fn` by **signature shape**, then emitting
|
|
3
|
+
//! `pub fn …(args: &[Value]) -> Value` shims.
|
|
4
|
+
//!
|
|
5
|
+
//! This avoids a fixed catalog of crate names: behavior follows **discovered** `pub fn` signatures
|
|
6
|
+
//! (e.g. `Serialize` + `Result<String, _>`, or `&str` + `Deserialize` + `Result`).
|
|
7
|
+
//!
|
|
8
|
+
//! **Standalone use:** the CLI does not link `tishlang_runtime` (it only emits source). For generated
|
|
9
|
+
//! crates, pass **`--tishlang-runtime-version`** so `Cargo.toml` uses a crates.io semver requirement
|
|
10
|
+
//! instead of a `path` into the Tish repo (no workspace checkout required to **build** the glue crate).
|
|
11
|
+
//!
|
|
12
|
+
//! **Project mode (default):** with **`--project-root`** and **no** `--dependency`, the tool reads
|
|
13
|
+
//! **`package.json` → `tish.rustDependencies`**, picks the path-based glue crate, and reads the
|
|
14
|
+
//! **upstream** crate + semver from that glue crate’s **`Cargo.toml`** `[dependencies]` if it
|
|
15
|
+
//! exists; otherwise from the **project root** **`Cargo.toml`** (`[dependencies]` and
|
|
16
|
+
//! **`[dev-dependencies]`**), skipping **`tishlang_core`** / **`tishlang_runtime`** and path-only entries.
|
|
17
|
+
|
|
18
|
+
mod classify;
|
|
19
|
+
mod discover;
|
|
20
|
+
pub mod infer;
|
|
21
|
+
mod metadata;
|
|
22
|
+
|
|
23
|
+
pub use classify::SignatureClass;
|
|
24
|
+
pub use discover::rust_public_fn_location;
|
|
25
|
+
pub use metadata::{
|
|
26
|
+
resolve_dependency_from_manifest, resolve_registry_dependency, ResolvedDependency,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
use std::fs;
|
|
30
|
+
use std::io;
|
|
31
|
+
use std::path::Path;
|
|
32
|
+
|
|
33
|
+
use classify::classify_public_fn;
|
|
34
|
+
|
|
35
|
+
/// How the generated crate depends on `tishlang_runtime`.
|
|
36
|
+
#[derive(Debug, Clone)]
|
|
37
|
+
pub enum TishlangRuntimeDep {
|
|
38
|
+
/// `tishlang_runtime = { path = "..." }` (relative to `--out-dir`).
|
|
39
|
+
Path(String),
|
|
40
|
+
/// `tishlang_runtime = "1.0"` style crates.io requirement (standalone builds; publish `tishlang_runtime` first).
|
|
41
|
+
Version(String),
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/// Configuration for a generated wrapper crate on disk.
|
|
45
|
+
#[derive(Debug, Clone)]
|
|
46
|
+
pub struct BindgenConfig {
|
|
47
|
+
/// Cargo `[package].name` of the **generated** crate (must match `cargo:` / `rustDependencies`).
|
|
48
|
+
pub output_crate_name: String,
|
|
49
|
+
pub tishlang_runtime: TishlangRuntimeDep,
|
|
50
|
+
pub out_dir: std::path::PathBuf,
|
|
51
|
+
/// Registry dependency name (e.g. `serde_json`) and semver req for the probe + generated dep.
|
|
52
|
+
pub dependency_name: String,
|
|
53
|
+
pub dependency_version_req: String,
|
|
54
|
+
/// Tish export names to wrap (must match a `pub fn` in the dependency).
|
|
55
|
+
pub exports: Vec<String>,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Full generation: resolve dependency, scan sources, classify, write crate.
|
|
59
|
+
pub fn generate_from_registry_dependency(cfg: &BindgenConfig) -> Result<(), String> {
|
|
60
|
+
let resolved = resolve_registry_dependency(&cfg.dependency_name, &cfg.dependency_version_req)?;
|
|
61
|
+
generate_from_resolved(cfg, &resolved)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/// Same as [`generate_from_registry_dependency`] but dependency is already in `manifest_path`’s workspace.
|
|
65
|
+
pub fn generate_from_manifest(
|
|
66
|
+
cfg: &BindgenConfig,
|
|
67
|
+
manifest_path: &Path,
|
|
68
|
+
dependency_package_name: &str,
|
|
69
|
+
) -> Result<(), String> {
|
|
70
|
+
let resolved = resolve_dependency_from_manifest(manifest_path, dependency_package_name)?;
|
|
71
|
+
generate_from_resolved(cfg, &resolved)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
fn generate_from_resolved(
|
|
75
|
+
cfg: &BindgenConfig,
|
|
76
|
+
resolved: &ResolvedDependency,
|
|
77
|
+
) -> Result<(), String> {
|
|
78
|
+
let root = resolved.source_root();
|
|
79
|
+
let fns = discover::discover_public_functions(&root)?;
|
|
80
|
+
|
|
81
|
+
let mut need_json_helpers = false;
|
|
82
|
+
let mut emitted = Vec::new();
|
|
83
|
+
|
|
84
|
+
for export in &cfg.exports {
|
|
85
|
+
let item = fns.get(export).ok_or_else(|| {
|
|
86
|
+
format!(
|
|
87
|
+
"no public fn `{}` found under {}/src (exports must match a `pub fn` in the dependency sources)",
|
|
88
|
+
export,
|
|
89
|
+
root.display()
|
|
90
|
+
)
|
|
91
|
+
})?;
|
|
92
|
+
|
|
93
|
+
let class = classify_public_fn(item).ok_or_else(|| {
|
|
94
|
+
format!(
|
|
95
|
+
"public fn `{}` has no supported signature for automatic binding (need `&[Value] -> Value`, or `&T where T: Serialize` with `Result<String, _>`, or `&str` with `Deserialize` and `Result`)",
|
|
96
|
+
export
|
|
97
|
+
)
|
|
98
|
+
})?;
|
|
99
|
+
|
|
100
|
+
match class {
|
|
101
|
+
SignatureClass::SerializeRefToResultString | SignatureClass::DeserializeStrToResult => {
|
|
102
|
+
need_json_helpers = true;
|
|
103
|
+
}
|
|
104
|
+
SignatureClass::TishValueAbi => {}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
emitted.push((export.clone(), class));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
let lib_rs = render_generated_lib(&cfg.dependency_name, &emitted, need_json_helpers)?;
|
|
111
|
+
|
|
112
|
+
let cargo_toml = render_output_cargo_toml(cfg, resolved.version(), need_json_helpers)?;
|
|
113
|
+
|
|
114
|
+
fs::create_dir_all(cfg.out_dir.join("src")).map_err(|e| e.to_string())?;
|
|
115
|
+
fs::write(cfg.out_dir.join("Cargo.toml"), cargo_toml).map_err(|e| e.to_string())?;
|
|
116
|
+
fs::write(cfg.out_dir.join("src").join("lib.rs"), lib_rs).map_err(|e| e.to_string())?;
|
|
117
|
+
|
|
118
|
+
Ok(())
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
impl BindgenConfig {
|
|
122
|
+
/// Write using [`generate_from_registry_dependency`].
|
|
123
|
+
pub fn write_files(&self) -> io::Result<()> {
|
|
124
|
+
generate_from_registry_dependency(self).map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
fn render_output_cargo_toml(
|
|
129
|
+
cfg: &BindgenConfig,
|
|
130
|
+
dep_exact_version: &str,
|
|
131
|
+
need_serde_json: bool,
|
|
132
|
+
) -> Result<String, String> {
|
|
133
|
+
let rt_line = match &cfg.tishlang_runtime {
|
|
134
|
+
TishlangRuntimeDep::Path(p) => {
|
|
135
|
+
format!("tishlang_runtime = {{ path = {} }}\n", toml_string_value(p))
|
|
136
|
+
}
|
|
137
|
+
TishlangRuntimeDep::Version(req) => {
|
|
138
|
+
format!("tishlang_runtime = {}\n", toml_string_value(req))
|
|
139
|
+
}
|
|
140
|
+
};
|
|
141
|
+
let dep_line = format!(
|
|
142
|
+
"{} = {}\n",
|
|
143
|
+
cfg.dependency_name,
|
|
144
|
+
toml_string_value(dep_exact_version)
|
|
145
|
+
);
|
|
146
|
+
// JSON bridge helpers use `serde_json::Value`; only add a second dep when the upstream crate is not serde_json.
|
|
147
|
+
let extra_serde = if need_serde_json && cfg.dependency_name != "serde_json" {
|
|
148
|
+
"serde_json = \"1.0\"\n"
|
|
149
|
+
} else {
|
|
150
|
+
""
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
Ok(format!(
|
|
154
|
+
r#"[package]
|
|
155
|
+
name = "{name}"
|
|
156
|
+
version = "0.1.0"
|
|
157
|
+
edition = "2021"
|
|
158
|
+
publish = false
|
|
159
|
+
|
|
160
|
+
[lib]
|
|
161
|
+
path = "src/lib.rs"
|
|
162
|
+
|
|
163
|
+
[dependencies]
|
|
164
|
+
{rt_line}{dep_line}{extra_serde}"#,
|
|
165
|
+
name = cfg.output_crate_name,
|
|
166
|
+
rt_line = rt_line,
|
|
167
|
+
dep_line = dep_line,
|
|
168
|
+
extra_serde = extra_serde
|
|
169
|
+
))
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
fn render_generated_lib(
|
|
173
|
+
dependency_cargo_name: &str,
|
|
174
|
+
exports: &[(String, SignatureClass)],
|
|
175
|
+
need_json_helpers: bool,
|
|
176
|
+
) -> Result<String, String> {
|
|
177
|
+
let crate_ident = dependency_cargo_name.replace('-', "_");
|
|
178
|
+
let mut out = String::from(
|
|
179
|
+
"//! Generated by `tishlang-cargo-bindgen` — do not edit.\n\
|
|
180
|
+
//! Bindings inferred from the dependency crate’s `pub fn` signatures (syn + cargo metadata).\n\n\
|
|
181
|
+
use std::cell::RefCell;\n\
|
|
182
|
+
use std::rc::Rc;\n\
|
|
183
|
+
use std::sync::Arc;\n\
|
|
184
|
+
use tishlang_runtime::{ObjectMap, Value, VmRef};\n\n",
|
|
185
|
+
);
|
|
186
|
+
|
|
187
|
+
out.push_str(&format!("use {} as _tish_upstream;\n\n", crate_ident));
|
|
188
|
+
|
|
189
|
+
if need_json_helpers {
|
|
190
|
+
out.push_str(JSON_HELPERS);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
for (name, class) in exports {
|
|
194
|
+
let rust_fn = syn::parse_str::<syn::Ident>(name)
|
|
195
|
+
.map_err(|e| format!("invalid export name {}: {}", name, e))?;
|
|
196
|
+
let block = match class {
|
|
197
|
+
SignatureClass::TishValueAbi => format!(
|
|
198
|
+
"pub fn {name}(args: &[Value]) -> Value {{\n _tish_upstream::{name}(args)\n}}\n\n",
|
|
199
|
+
name = rust_fn
|
|
200
|
+
),
|
|
201
|
+
SignatureClass::SerializeRefToResultString => format!(
|
|
202
|
+
"pub fn {name}(args: &[Value]) -> Value {{\n let Some(v) = args.first() else {{ return Value::Null }};\n match _tish_upstream::{name}(&tish_to_json(v)) {{\n Ok(s) => Value::String(Arc::from(s)),\n Err(_) => Value::Null,\n }}\n}}\n\n",
|
|
203
|
+
name = rust_fn
|
|
204
|
+
),
|
|
205
|
+
SignatureClass::DeserializeStrToResult => format!(
|
|
206
|
+
"pub fn {name}(args: &[Value]) -> Value {{\n let s = match args.first() {{\n Some(Value::String(x)) => x.as_ref(),\n _ => return Value::Null,\n }};\n match _tish_upstream::{name}::<serde_json::Value>(s) {{\n Ok(j) => json_to_tish(j),\n Err(_) => Value::Null,\n }}\n}}\n\n",
|
|
207
|
+
name = rust_fn
|
|
208
|
+
),
|
|
209
|
+
};
|
|
210
|
+
out.push_str(&block);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
Ok(out)
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
const JSON_HELPERS: &str = r#"fn tish_to_json(v: &Value) -> serde_json::Value {
|
|
217
|
+
match v {
|
|
218
|
+
Value::Null => serde_json::Value::Null,
|
|
219
|
+
Value::Bool(b) => serde_json::Value::Bool(*b),
|
|
220
|
+
Value::Number(n) => serde_json::Number::from_f64(*n)
|
|
221
|
+
.map(serde_json::Value::Number)
|
|
222
|
+
.unwrap_or(serde_json::Value::Null),
|
|
223
|
+
Value::String(s) => serde_json::Value::String(s.to_string()),
|
|
224
|
+
Value::Array(a) => {
|
|
225
|
+
serde_json::Value::Array(a.borrow().iter().map(|x| tish_to_json(x)).collect())
|
|
226
|
+
}
|
|
227
|
+
Value::Object(o) => {
|
|
228
|
+
let mut m = serde_json::Map::new();
|
|
229
|
+
for (k, v) in o.borrow().iter() {
|
|
230
|
+
m.insert(k.to_string(), tish_to_json(v));
|
|
231
|
+
}
|
|
232
|
+
serde_json::Value::Object(m)
|
|
233
|
+
}
|
|
234
|
+
_ => serde_json::Value::Null,
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
fn json_to_tish(v: serde_json::Value) -> Value {
|
|
239
|
+
match v {
|
|
240
|
+
serde_json::Value::Null => Value::Null,
|
|
241
|
+
serde_json::Value::Bool(b) => Value::Bool(b),
|
|
242
|
+
serde_json::Value::Number(n) => Value::Number(n.as_f64().unwrap_or(0.0)),
|
|
243
|
+
serde_json::Value::String(s) => Value::String(s.into()),
|
|
244
|
+
serde_json::Value::Array(a) => Value::Array(VmRef::new(
|
|
245
|
+
a.into_iter().map(json_to_tish).collect(),
|
|
246
|
+
)),
|
|
247
|
+
serde_json::Value::Object(m) => {
|
|
248
|
+
let mut om = ObjectMap::default();
|
|
249
|
+
for (k, v) in m {
|
|
250
|
+
om.insert(Arc::from(k), json_to_tish(v));
|
|
251
|
+
}
|
|
252
|
+
Value::Object(VmRef::new(om))
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
"#;
|
|
258
|
+
|
|
259
|
+
fn toml_string_value(s: &str) -> String {
|
|
260
|
+
let escaped = s.replace('\\', "\\\\").replace('"', "\\\"");
|
|
261
|
+
format!("\"{}\"", escaped)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/// Resolve a path to `tishlang_runtime` for writing into generated `Cargo.toml`.
|
|
265
|
+
pub fn resolve_runtime_path_for_output(start: &Path) -> Result<String, String> {
|
|
266
|
+
if let Ok(p) = std::env::var("TISHLANG_RUNTIME_PATH") {
|
|
267
|
+
let pb = Path::new(&p);
|
|
268
|
+
if pb.join("Cargo.toml").is_file() {
|
|
269
|
+
return canonical_path_string(pb);
|
|
270
|
+
}
|
|
271
|
+
return Err(format!(
|
|
272
|
+
"TISHLANG_RUNTIME_PATH does not point to tishlang_runtime: {}",
|
|
273
|
+
p
|
|
274
|
+
));
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
let mut dir = if start.is_file() {
|
|
278
|
+
start.parent().unwrap_or(start).to_path_buf()
|
|
279
|
+
} else {
|
|
280
|
+
start.to_path_buf()
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
for _ in 0..64 {
|
|
284
|
+
let npm_rt = dir
|
|
285
|
+
.join("node_modules")
|
|
286
|
+
.join("@tishlang")
|
|
287
|
+
.join("tish")
|
|
288
|
+
.join("crates")
|
|
289
|
+
.join("tish_runtime");
|
|
290
|
+
if npm_rt.join("Cargo.toml").is_file() {
|
|
291
|
+
return canonical_path_string(&npm_rt);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let ws_rt = dir.join("crates").join("tish_runtime");
|
|
295
|
+
if ws_rt.join("Cargo.toml").is_file() {
|
|
296
|
+
return canonical_path_string(&ws_rt);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
if !dir.pop() {
|
|
300
|
+
break;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
Err(
|
|
305
|
+
"Could not find tishlang_runtime (set TISHLANG_RUNTIME_PATH or run from a project with node_modules/@tishlang/tish or a Tish repo checkout)"
|
|
306
|
+
.into(),
|
|
307
|
+
)
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
fn canonical_path_string(p: &Path) -> Result<String, String> {
|
|
311
|
+
p.canonicalize()
|
|
312
|
+
.map_err(|e| format!("Cannot canonicalize {}: {}", p.display(), e))
|
|
313
|
+
.map(|p| p.display().to_string().replace('\\', "/"))
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/// Relative path from `out_dir` to `runtime` for Cargo.toml `path =`.
|
|
317
|
+
pub fn runtime_path_relative_to_out_dir(
|
|
318
|
+
out_dir: &Path,
|
|
319
|
+
runtime: impl AsRef<Path>,
|
|
320
|
+
) -> Result<String, String> {
|
|
321
|
+
let abs_rt = runtime
|
|
322
|
+
.as_ref()
|
|
323
|
+
.canonicalize()
|
|
324
|
+
.map_err(|e| format!("Cannot canonicalize runtime path: {}", e))?;
|
|
325
|
+
fs::create_dir_all(out_dir).map_err(|e| e.to_string())?;
|
|
326
|
+
let abs_out = out_dir
|
|
327
|
+
.canonicalize()
|
|
328
|
+
.map_err(|e| format!("Cannot canonicalize out dir: {}", e))?;
|
|
329
|
+
|
|
330
|
+
pathdiff::diff_paths(&abs_rt, &abs_out)
|
|
331
|
+
.ok_or_else(|| "Could not compute relative path from out_dir to runtime".to_string())
|
|
332
|
+
.map(|p| p.display().to_string().replace('\\', "/"))
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
#[cfg(test)]
|
|
336
|
+
mod tests {
|
|
337
|
+
use super::*;
|
|
338
|
+
|
|
339
|
+
#[test]
|
|
340
|
+
fn generated_lib_contains_tish_abi_forward() {
|
|
341
|
+
let s = render_generated_lib(
|
|
342
|
+
"demo",
|
|
343
|
+
&[("greet".into(), SignatureClass::TishValueAbi)],
|
|
344
|
+
false,
|
|
345
|
+
)
|
|
346
|
+
.unwrap();
|
|
347
|
+
assert!(s.contains("_tish_upstream::greet"));
|
|
348
|
+
}
|
|
349
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
//! CLI for pre-generating `cargo:` binding crates (bindgen-style).
|
|
2
|
+
//!
|
|
3
|
+
//! Resolves the dependency with `cargo metadata`, scans its `src/**/*.rs` with `syn`, classifies
|
|
4
|
+
//! each requested `pub fn` by signature, and emits a wrapper crate.
|
|
5
|
+
|
|
6
|
+
use std::path::PathBuf;
|
|
7
|
+
|
|
8
|
+
use clap::Parser;
|
|
9
|
+
use tishlang_cargo_bindgen::{
|
|
10
|
+
generate_from_manifest, generate_from_registry_dependency, infer,
|
|
11
|
+
resolve_runtime_path_for_output, runtime_path_relative_to_out_dir, BindgenConfig,
|
|
12
|
+
TishlangRuntimeDep,
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
#[derive(Parser, Debug)]
|
|
16
|
+
#[command(name = "tishlang-cargo-bindgen")]
|
|
17
|
+
#[command(about = "Generate Rust glue for Tish `cargo:` imports from dependency source + metadata")]
|
|
18
|
+
struct Args {
|
|
19
|
+
/// Upstream Cargo package to wrap (e.g. serde_json). Omit to read `tish.rustDependencies` + glue `Cargo.toml`.
|
|
20
|
+
#[arg(long)]
|
|
21
|
+
dependency: Option<String>,
|
|
22
|
+
|
|
23
|
+
/// Semver for the upstream crate when using `--dependency` (ignored in full project auto mode).
|
|
24
|
+
#[arg(long, default_value = "1.0")]
|
|
25
|
+
dependency_version: String,
|
|
26
|
+
|
|
27
|
+
/// Generated crate `[package].name` when using `--dependency` with `--out-dir` (default: tish_serde_json).
|
|
28
|
+
#[arg(long)]
|
|
29
|
+
crate_name: Option<String>,
|
|
30
|
+
|
|
31
|
+
/// Output directory. Required with `--dependency` unless `--project-root` selects a path from `package.json`.
|
|
32
|
+
#[arg(long)]
|
|
33
|
+
out_dir: Option<PathBuf>,
|
|
34
|
+
|
|
35
|
+
/// Comma-separated export names to bind (must match `pub fn` names in the dependency).
|
|
36
|
+
#[arg(long, default_value = "to_string,from_str")]
|
|
37
|
+
exports: String,
|
|
38
|
+
|
|
39
|
+
/// If set, resolve `dependency` from this workspace instead of a temporary probe crate.
|
|
40
|
+
#[arg(long)]
|
|
41
|
+
manifest_path: Option<PathBuf>,
|
|
42
|
+
|
|
43
|
+
/// Directory containing `package.json` (default: current directory). Drives automatic glue discovery.
|
|
44
|
+
#[arg(long)]
|
|
45
|
+
project_root: Option<PathBuf>,
|
|
46
|
+
|
|
47
|
+
/// Crates.io semver for `tishlang_runtime` in the generated `Cargo.toml` (no path into the Tish repo).
|
|
48
|
+
/// Mutually exclusive with `--tishlang-runtime-path`.
|
|
49
|
+
#[arg(long, conflicts_with = "tishlang_runtime_path")]
|
|
50
|
+
tishlang_runtime_version: Option<String>,
|
|
51
|
+
|
|
52
|
+
/// Absolute path to the `tishlang_runtime` crate root for generated `path = ...` (overrides search).
|
|
53
|
+
/// Mutually exclusive with `--tishlang-runtime-version`.
|
|
54
|
+
#[arg(long, conflicts_with = "tishlang_runtime_version")]
|
|
55
|
+
tishlang_runtime_path: Option<PathBuf>,
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
fn main() {
|
|
59
|
+
if let Err(e) = run() {
|
|
60
|
+
eprintln!("tishlang-cargo-bindgen: error: {}", e);
|
|
61
|
+
std::process::exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
fn run() -> Result<(), String> {
|
|
66
|
+
let args = Args::parse();
|
|
67
|
+
|
|
68
|
+
let exports: Vec<String> = args
|
|
69
|
+
.exports
|
|
70
|
+
.split(',')
|
|
71
|
+
.map(|s| s.trim().to_string())
|
|
72
|
+
.filter(|s| !s.is_empty())
|
|
73
|
+
.collect();
|
|
74
|
+
if exports.is_empty() {
|
|
75
|
+
return Err("no exports given".into());
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
if args.dependency.is_none() && args.out_dir.is_some() {
|
|
79
|
+
return Err(
|
|
80
|
+
"--out-dir is only used with --dependency (without --dependency, output path comes from package.json tish.rustDependencies)"
|
|
81
|
+
.into(),
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
let current_dir = std::env::current_dir().map_err(|e| e.to_string())?;
|
|
86
|
+
|
|
87
|
+
let (output_crate_name, out_dir, dependency_name, dependency_version_req, search_root) =
|
|
88
|
+
if let Some(dep) = args.dependency.as_ref() {
|
|
89
|
+
if let Some(out) = args.out_dir.as_ref() {
|
|
90
|
+
let root = args
|
|
91
|
+
.project_root
|
|
92
|
+
.clone()
|
|
93
|
+
.unwrap_or_else(|| out.parent().unwrap_or(out).to_path_buf());
|
|
94
|
+
(
|
|
95
|
+
args.crate_name
|
|
96
|
+
.clone()
|
|
97
|
+
.unwrap_or_else(|| "tish_serde_json".into()),
|
|
98
|
+
out.clone(),
|
|
99
|
+
dep.clone(),
|
|
100
|
+
args.dependency_version.clone(),
|
|
101
|
+
root,
|
|
102
|
+
)
|
|
103
|
+
} else {
|
|
104
|
+
let root = args
|
|
105
|
+
.project_root
|
|
106
|
+
.clone()
|
|
107
|
+
.unwrap_or_else(|| current_dir.clone());
|
|
108
|
+
let (crate_key, od) =
|
|
109
|
+
infer::infer_glue_paths_only(&root, args.crate_name.as_deref())?;
|
|
110
|
+
(
|
|
111
|
+
crate_key,
|
|
112
|
+
od,
|
|
113
|
+
dep.clone(),
|
|
114
|
+
args.dependency_version.clone(),
|
|
115
|
+
root,
|
|
116
|
+
)
|
|
117
|
+
}
|
|
118
|
+
} else {
|
|
119
|
+
let root = args
|
|
120
|
+
.project_root
|
|
121
|
+
.clone()
|
|
122
|
+
.unwrap_or_else(|| current_dir.clone());
|
|
123
|
+
let inf = infer::infer_from_project_root(&root, args.crate_name.as_deref())?;
|
|
124
|
+
(
|
|
125
|
+
inf.output_crate_name,
|
|
126
|
+
inf.out_dir,
|
|
127
|
+
inf.dependency_name,
|
|
128
|
+
inf.dependency_version_req,
|
|
129
|
+
root,
|
|
130
|
+
)
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
let tishlang_runtime = if let Some(req) = args.tishlang_runtime_version.clone() {
|
|
134
|
+
TishlangRuntimeDep::Version(req)
|
|
135
|
+
} else if let Some(p) = args.tishlang_runtime_path.as_ref() {
|
|
136
|
+
let abs = p
|
|
137
|
+
.canonicalize()
|
|
138
|
+
.map_err(|e| format!("tishlang_runtime path {}: {}", p.display(), e))?;
|
|
139
|
+
TishlangRuntimeDep::Path(runtime_path_relative_to_out_dir(&out_dir, &abs)?)
|
|
140
|
+
} else {
|
|
141
|
+
let runtime_abs = resolve_runtime_path_for_output(&search_root)?;
|
|
142
|
+
let rt_rel = runtime_path_relative_to_out_dir(&out_dir, &runtime_abs)?;
|
|
143
|
+
TishlangRuntimeDep::Path(rt_rel)
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
let cfg = BindgenConfig {
|
|
147
|
+
output_crate_name,
|
|
148
|
+
tishlang_runtime,
|
|
149
|
+
out_dir: out_dir.clone(),
|
|
150
|
+
dependency_name,
|
|
151
|
+
dependency_version_req,
|
|
152
|
+
exports,
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
if let Some(manifest) = args.manifest_path {
|
|
156
|
+
generate_from_manifest(&cfg, &manifest, &cfg.dependency_name)?;
|
|
157
|
+
} else {
|
|
158
|
+
generate_from_registry_dependency(&cfg)?;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
println!(
|
|
162
|
+
"Wrote {} (upstream `{}`, metadata + syn)",
|
|
163
|
+
out_dir.display(),
|
|
164
|
+
cfg.dependency_name
|
|
165
|
+
);
|
|
166
|
+
Ok(())
|
|
167
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
//! Resolve a crates.io (or registry) dependency to its on-disk source tree via `cargo metadata`.
|
|
2
|
+
|
|
3
|
+
use std::fs;
|
|
4
|
+
use std::path::{Path, PathBuf};
|
|
5
|
+
|
|
6
|
+
use cargo_metadata::{MetadataCommand, Package, TargetKind};
|
|
7
|
+
|
|
8
|
+
/// Root directory of the resolved dependency (contains `Cargo.toml` and usually `src/`).
|
|
9
|
+
#[derive(Debug, Clone)]
|
|
10
|
+
pub struct ResolvedDependency {
|
|
11
|
+
pub name: String,
|
|
12
|
+
pub version: String,
|
|
13
|
+
pub manifest_path: PathBuf,
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
impl ResolvedDependency {
|
|
17
|
+
pub fn source_root(&self) -> PathBuf {
|
|
18
|
+
self.manifest_path
|
|
19
|
+
.parent()
|
|
20
|
+
.expect("manifest has parent")
|
|
21
|
+
.to_path_buf()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
pub fn version(&self) -> &str {
|
|
25
|
+
&self.version
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
fn from_package(pkg: &Package) -> ResolvedDependency {
|
|
30
|
+
ResolvedDependency {
|
|
31
|
+
name: pkg.name.clone(),
|
|
32
|
+
version: pkg.version.to_string(),
|
|
33
|
+
manifest_path: pkg.manifest_path.as_std_path().to_path_buf(),
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/// Build a tiny probe crate, run `cargo metadata`, and return the named package.
|
|
38
|
+
pub fn resolve_registry_dependency(
|
|
39
|
+
name: &str,
|
|
40
|
+
version_req: &str,
|
|
41
|
+
) -> Result<ResolvedDependency, String> {
|
|
42
|
+
let probe_dir = tempfile::tempdir().map_err(|e| format!("tempdir: {}", e))?;
|
|
43
|
+
let probe_toml = format!(
|
|
44
|
+
r#"[package]
|
|
45
|
+
name = "_tishlang_bindgen_probe"
|
|
46
|
+
version = "0.0.0"
|
|
47
|
+
edition = "2021"
|
|
48
|
+
[dependencies]
|
|
49
|
+
{} = "{}"
|
|
50
|
+
"#,
|
|
51
|
+
name, version_req
|
|
52
|
+
);
|
|
53
|
+
let manifest = probe_dir.path().join("Cargo.toml");
|
|
54
|
+
fs::write(&manifest, probe_toml).map_err(|e| format!("write probe Cargo.toml: {}", e))?;
|
|
55
|
+
// `cargo metadata` requires at least one target in the root package.
|
|
56
|
+
let src_dir = probe_dir.path().join("src");
|
|
57
|
+
fs::create_dir_all(&src_dir).map_err(|e| format!("probe src dir: {}", e))?;
|
|
58
|
+
fs::write(src_dir.join("lib.rs"), "// probe only\n")
|
|
59
|
+
.map_err(|e| format!("write probe lib.rs: {}", e))?;
|
|
60
|
+
|
|
61
|
+
let meta = MetadataCommand::new()
|
|
62
|
+
.manifest_path(&manifest)
|
|
63
|
+
.exec()
|
|
64
|
+
.map_err(|e| format!("cargo metadata failed: {} (is `cargo` on PATH?)", e))?;
|
|
65
|
+
|
|
66
|
+
let pkg = meta
|
|
67
|
+
.packages
|
|
68
|
+
.iter()
|
|
69
|
+
.find(|p| p.name == name)
|
|
70
|
+
.ok_or_else(|| {
|
|
71
|
+
format!(
|
|
72
|
+
"dependency `{}` not found in metadata (check name and version req `{}`)",
|
|
73
|
+
name, version_req
|
|
74
|
+
)
|
|
75
|
+
})?;
|
|
76
|
+
|
|
77
|
+
let root = pkg.manifest_path.as_std_path().parent().unwrap();
|
|
78
|
+
if !root.join("src").is_dir()
|
|
79
|
+
&& !pkg
|
|
80
|
+
.targets
|
|
81
|
+
.iter()
|
|
82
|
+
.any(|t| t.kind.iter().any(|k| *k == TargetKind::Lib))
|
|
83
|
+
{
|
|
84
|
+
return Err(format!(
|
|
85
|
+
"package `{}` has no src/ or lib target at {}",
|
|
86
|
+
name,
|
|
87
|
+
root.display()
|
|
88
|
+
));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
Ok(from_package(pkg))
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/// Resolve using an existing workspace manifest (no temporary probe).
|
|
95
|
+
pub fn resolve_dependency_from_manifest(
|
|
96
|
+
manifest_path: &Path,
|
|
97
|
+
package_name: &str,
|
|
98
|
+
) -> Result<ResolvedDependency, String> {
|
|
99
|
+
let meta = MetadataCommand::new()
|
|
100
|
+
.manifest_path(manifest_path)
|
|
101
|
+
.exec()
|
|
102
|
+
.map_err(|e| format!("cargo metadata: {}", e))?;
|
|
103
|
+
|
|
104
|
+
let pkg = meta
|
|
105
|
+
.packages
|
|
106
|
+
.iter()
|
|
107
|
+
.find(|p| p.name == package_name)
|
|
108
|
+
.ok_or_else(|| {
|
|
109
|
+
format!(
|
|
110
|
+
"package `{}` not found in metadata for {}",
|
|
111
|
+
package_name,
|
|
112
|
+
manifest_path.display()
|
|
113
|
+
)
|
|
114
|
+
})?;
|
|
115
|
+
|
|
116
|
+
Ok(from_package(pkg))
|
|
117
|
+
}
|