pub mod build;
pub mod bundle;
pub mod icon;
use std::path::{Path, PathBuf};
use std::sync::{Arc, RwLock};
pub use bundle::{Bundle, Entry};
static INSTALLED: RwLock<Option<Arc<Bundle>>> = RwLock::new(None);
pub const MACOS_PAYLOAD: &str = "app.nykpak";
pub fn install(bundle: Option<Bundle>) {
let mut slot = INSTALLED
.write()
.unwrap_or_else(|poison| poison.into_inner());
*slot = bundle.map(Arc::new);
}
pub fn installed() -> Option<Arc<Bundle>> {
INSTALLED
.read()
.unwrap_or_else(|poison| poison.into_inner())
.clone()
}
pub fn read(path: &str) -> Option<std::io::Result<Vec<u8>>> {
installed()?.read(path)
}
pub fn entry(path: &str) -> Option<Entry> {
installed()?.entry(path)
}
pub fn check_writable(path: &str) -> Result<(), String> {
match entry(path) {
Some(_) => Err(format!("`{}` is bundled with the app and read-only", path)),
None => Ok(()),
}
}
pub fn macos_payload_path(exe: &Path) -> Option<PathBuf> {
let macos_dir = exe.parent()?;
let contents = macos_dir.parent()?;
if macos_dir.file_name()? != "MacOS" || contents.file_name()? != "Contents" {
return None;
}
Some(contents.join("Resources").join(MACOS_PAYLOAD))
}
pub fn find_own_bundle() -> Result<Option<Bundle>, String> {
let Ok(exe) = std::env::current_exe() else {
return Ok(None);
};
if let Some(bundle) = Bundle::from_executable(&exe)? {
return Ok(Some(bundle));
}
match macos_payload_path(&exe) {
Some(payload) if payload.is_file() => Bundle::from_payload_file(&payload).map(Some),
_ => Ok(None),
}
}
pub fn run_installed() -> Result<(), String> {
let bundle = installed().ok_or_else(|| "no app bundle is installed".to_string())?;
let entry = bundle
.meta("entry")
.ok_or_else(|| "the app bundle names no entry script".to_string())?
.to_string();
let bytes = bundle
.read(&entry)
.ok_or_else(|| {
format!(
"the app bundle does not contain its entry script '{}'",
entry
)
})?
.map_err(|err| format!("cannot read {}: {}", entry, err))?;
crate::vm::run_bytes(&entry, bytes, crate::compiler::Options::RUN)
}
pub fn report_error(message: &str) {
eprintln!("{}", message);
#[cfg(windows)]
if let Some(bundle) = installed()
&& bundle.meta("console") != Some("true")
{
show_error_box(bundle.meta("name"), message);
}
}
#[cfg(windows)]
fn show_error_box(title: Option<&str>, message: &str) {
use windows_sys::Win32::UI::WindowsAndMessaging::{MB_ICONERROR, MB_OK, MessageBoxW};
let wide = |s: &str| {
s.encode_utf16()
.chain(std::iter::once(0))
.collect::<Vec<u16>>()
};
let text = wide(message);
let caption = wide(title.unwrap_or("Neyuki"));
unsafe {
MessageBoxW(
std::ptr::null_mut(),
text.as_ptr(),
caption.as_ptr(),
MB_OK | MB_ICONERROR,
);
}
}
#[cfg(test)]
mod tests;