use num_traits::AsPrimitive;
use std::sync::Arc;
use anyhow::{Result, bail};
use parking_lot::Mutex;
use super::json_bridge::{json_to_pvalue, pvalue_to_json};
use super::native::Native;
use super::native_methods::value_to_bytes;
use super::std_bridge::make_path;
use super::value::{StructData, Value};
fn opt_path(p: Option<std::path::PathBuf>) -> Value {
match p {
Some(p) => Value::some(make_path(p.display().to_string())),
None => Value::none(),
}
}
pub(super) fn bytes_to_vec(b: &[u8]) -> Value {
Value::vec(b.iter().map(|x| Value::Int(i64::from(*x))).collect())
}
pub(super) fn crate_bridge(module: &str, func: &str, args: &[Value]) -> Result<Option<Value>> {
let s0 = || args.first().map(Value::display).unwrap_or_default();
Ok(Some(match (module, func) {
("dirs", "home_dir") => opt_path(dirs::home_dir()),
("dirs", "cache_dir") => opt_path(dirs::cache_dir()),
("dirs", "config_dir") => opt_path(dirs::config_dir()),
("dirs", "config_local_dir") => opt_path(dirs::config_local_dir()),
("dirs", "data_dir") => opt_path(dirs::data_dir()),
("dirs", "data_local_dir") => opt_path(dirs::data_local_dir()),
("dirs", "executable_dir") => opt_path(dirs::executable_dir()),
("dirs", "runtime_dir") => opt_path(dirs::runtime_dir()),
("dirs", "desktop_dir") => opt_path(dirs::desktop_dir()),
("dirs", "download_dir") => opt_path(dirs::download_dir()),
("dirs", "document_dir") => opt_path(dirs::document_dir()),
("which", "which") => match which::which(s0()) {
Ok(p) => Value::ok(make_path(p.display().to_string())),
Err(e) => Value::err(Value::str(e.to_string())),
},
("glob", "glob") => match glob::glob(&s0()) {
Ok(paths) => Value::ok(Value::vec(
paths
.map(|r| match r {
Ok(p) => Value::ok(make_path(p.display().to_string())),
Err(e) => Value::err(Value::str(e.to_string())),
})
.collect(),
)),
Err(e) => Value::err(Value::str(e.to_string())),
},
("Sha256", "new" | "default") => {
use sha2::Digest;
Native::Sha256(sha2::Sha256::new()).wrap()
}
("Sha256", "digest") => {
use sha2::Digest;
bytes_to_vec(&sha2::Sha256::digest(value_to_bytes(args.first())))
}
("regex", "escape") => Value::str(regex::escape(&s0())),
("hex", "encode") => Value::str(hex::encode(value_to_bytes(args.first()))),
("hex", "decode") => match hex::decode(s0()) {
Ok(b) => Value::ok(bytes_to_vec(&b)),
Err(e) => Value::err(Value::str(e.to_string())),
},
("toml", "from_str") => match toml::from_str::<serde_json::Value>(&s0()) {
Ok(j) => Value::ok(json_to_pvalue(j)),
Err(e) => Value::err(Value::str(e.to_string())),
},
("toml", "to_string" | "to_string_pretty") => {
match toml::to_string(&pvalue_to_json(args.first().unwrap_or(&Value::Unit))?) {
Ok(s) => Value::ok(Value::str(s)),
Err(e) => Value::err(Value::str(e.to_string())),
}
}
("serde_yaml", "from_str") => match serde_yaml::from_str::<serde_json::Value>(&s0()) {
Ok(j) => Value::ok(json_to_pvalue(j)),
Err(e) => Value::err(Value::str(e.to_string())),
},
("serde_yaml", "to_string") => {
match serde_yaml::to_string(&pvalue_to_json(args.first().unwrap_or(&Value::Unit))?) {
Ok(s) => Value::ok(Value::str(s)),
Err(e) => Value::err(Value::str(e.to_string())),
}
}
("rand", "rng" | "thread_rng") => Value::struct_of("Rng", []),
("rand", "random") => Value::Float(rand::random::<f64>()),
("jsonwebtoken", "encode") => super::jwt_bridge::jwt_encode(args)?,
("tempfile", "tempdir") => match tempfile::tempdir() {
Ok(d) => Value::ok(Native::TempDir(d).wrap()),
Err(e) => Value::err(Value::str(e.to_string())),
},
("tempfile", "tempfile") => match tempfile::tempfile() {
Ok(f) => Value::ok(Native::File(std::io::BufReader::new(f)).wrap()),
Err(e) => Value::err(Value::str(e.to_string())),
},
("NamedTempFile", "new") => match tempfile::NamedTempFile::new() {
Ok(f) => Value::ok(Native::NamedTempFile(f).wrap()),
Err(e) => Value::err(Value::str(e.to_string())),
},
("RegKey", "predef") => super::winreg_bridge::predef(args),
("ServiceManager", "local_computer") => super::service_bridge::local_computer(args),
("WMIConnection", "new") => super::wmi_bridge::connection(args, true),
("WMIConnection", "with_namespace_path") => super::wmi_bridge::connection(args, false),
("terminal", "size") => terminal_size(),
("terminal_light", "luma") => terminal_luma(),
_ => return Ok(None),
}))
}
fn terminal_size() -> Value {
match crossterm::terminal::size() {
Ok((cols, rows)) => Value::ok(Value::tuple(vec![
Value::Int(i64::from(cols)),
Value::Int(i64::from(rows)),
])),
Err(e) => Value::err(Value::str(e.to_string())),
}
}
fn terminal_luma() -> Value {
match terminal_light::luma() {
Ok(luma) => Value::ok(Value::F32(luma)),
Err(e) => Value::err(Value::str(e.to_string())),
}
}
pub(super) fn base64_engine(name: &str) -> Option<Value> {
let kind = match name {
"STANDARD" | "BASE64_STANDARD" => "standard",
"STANDARD_NO_PAD" | "BASE64_STANDARD_NO_PAD" => "standard_no_pad",
"URL_SAFE" | "BASE64_URL_SAFE" => "url_safe",
"URL_SAFE_NO_PAD" | "BASE64_URL_SAFE_NO_PAD" => "url_safe_no_pad",
_ => return None,
};
Some(Value::struct_of(
"Base64Engine",
[("kind".into(), Value::str(kind))],
))
}
pub(super) fn base64_method(s: &StructData, method: &str, args: &[Value]) -> Result<Value> {
use base64::Engine;
use base64::engine::general_purpose::{STANDARD, STANDARD_NO_PAD, URL_SAFE, URL_SAFE_NO_PAD};
let kind = s.get("kind").map(|v| v.display()).unwrap_or_default();
macro_rules! pick {
($m:ident, $($a:tt)*) => {
match kind.as_str() {
"standard_no_pad" => STANDARD_NO_PAD.$m($($a)*),
"url_safe" => URL_SAFE.$m($($a)*),
"url_safe_no_pad" => URL_SAFE_NO_PAD.$m($($a)*),
_ => STANDARD.$m($($a)*),
}
};
}
Ok(match method {
"encode" => Value::str(pick!(encode, value_to_bytes(args.first()))),
"decode" => {
let input = args.first().map(Value::display).unwrap_or_default();
match pick!(decode, &input) {
Ok(b) => Value::ok(bytes_to_vec(&b)),
Err(e) => Value::err(Value::str(e.to_string())),
}
}
_ => bail!("unknown method `{method}` on a base64 engine"),
})
}
pub(super) fn rng_method(name: &str, args: &[Value]) -> Result<Value> {
use rand::RngExt;
let mut rng = rand::rng();
Ok(match name {
"random_range" | "gen_range" => match args.first() {
Some(Value::Range {
start,
end,
inclusive,
}) => {
let hi = if *inclusive { end + 1 } else { *end };
if hi > *start {
Value::Int(rng.random_range(*start..hi))
} else {
Value::Int(*start)
}
}
_ => bail!("random_range needs a range"),
},
"random_bool" | "gen_bool" => {
let p = match args.first() {
Some(Value::Float(f)) => *f,
Some(Value::Int(i)) => AsPrimitive::<f64>::as_(*i),
_ => 0.5,
};
Value::Bool(rng.random_bool(p.clamp(0.0, 1.0)))
}
"random" | "r#gen" | "gen" => Value::Float(rng.random::<f64>()),
"fill_bytes" | "fill" => {
if let Some(Value::Vec(v)) = args.first() {
let mut buf = v.lock();
for slot in buf.iter_mut() {
*slot = Value::Int(i64::from(rng.random::<u8>()));
}
}
Value::Unit
}
_ => bail!("unknown method `{name}` on Rng"),
})
}
pub(super) fn sha256_method(
handle: &Arc<Mutex<Native>>,
method: &str,
args: &[Value],
) -> Result<Option<Value>> {
use sha2::Digest;
let mut h = handle.lock();
let Native::Sha256(hasher) = &mut *h else {
return Ok(None);
};
Ok(Some(match method {
"update" => {
hasher.update(value_to_bytes(args.first()));
Value::Unit
}
"chain_update" => {
hasher.update(value_to_bytes(args.first()));
Value::Native(handle.clone())
}
"finalize" => bytes_to_vec(&hasher.clone().finalize()),
_ => return Ok(None),
}))
}