use std::fs::File; use std::io::prelude::*; use std::path::{Path, PathBuf}; use std::process; use anyhow::Result; use pico_args::Arguments; #[cfg(feature = "native")] use uw8::MicroW8; #[cfg(feature = "browser")] use uw8::RunWebServer; #[cfg(any(feature = "native", feature = "browser"))] use uw8::Runtime; fn main() -> Result<()> { env_logger::Builder::from_env(env_logger::Env::default()).init(); let mut args = Arguments::from_env(); // try to enable ansi support in win10 cmd shell #[cfg(target_os = "windows")] let _ = ansi_term::enable_ansi_support(); match args.subcommand()?.as_deref() { Some("version") => { println!("{}", env!("CARGO_PKG_VERSION")); Ok(()) } #[cfg(any(feature = "native", feature = "browser"))] Some("run") => run(args), Some("pack") => pack(args), Some("unpack") => unpack(args), Some("compile") => compile(args), Some("filter-exports") => filter_exports(args), Some("help") | None => { println!("uw8 {}", env!("CARGO_PKG_VERSION")); println!(); println!("Usage:"); #[cfg(any(feature = "native", feature = "browser"))] println!(" uw8 run [-t/--timeout ] [--b/--browser] [-w/--watch] [-p/--pack] [-u/--uncompressed] [-l/--level] [-o/--output ] "); println!(" uw8 pack [-u/--uncompressed] [-l/--level] "); println!(" uw8 unpack "); println!(" uw8 compile [-d/--debug] "); println!(" uw8 filter-exports "); Ok(()) } Some(other) => { eprintln!("Unknown command '{}'", other); process::exit(1); } } } #[cfg(any(feature = "native", feature = "browser"))] fn run(mut args: Arguments) -> Result<()> { let watch_mode = args.contains(["-w", "--watch"]); #[allow(unused)] let timeout: Option = args.opt_value_from_str(["-t", "--timeout"])?; let mut config = Config::default(); if args.contains(["-p", "--pack"]) { let mut pack = uw8_tool::PackConfig::default(); if args.contains(["-u", "--uncompressed"]) { pack = pack.uncompressed(); } if let Some(level) = args.opt_value_from_str(["-l", "--level"])? { pack = pack.with_compression_level(level); } config.pack = Some(pack); } if let Some(path) = args.opt_value_from_os_str::<_, _, bool>(["-o", "--output"], |s| Ok(s.into()))? { config.output_path = Some(path); } #[cfg(feature = "native")] let run_browser = args.contains(["-b", "--browser"]); #[cfg(not(feature = "native"))] let run_browser = args.contains(["-b", "--browser"]) || true; #[allow(unused)] let disable_audio = args.contains(["-m", "--no-audio"]); #[cfg(feature = "native")] let window_config = { let mut config = uw8_window::WindowConfig::default(); if !run_browser { config.parse_arguments(&mut args); } config }; let filename = args.free_from_os_str::(|s| Ok(s.into()))?; let mut watcher = uw8::FileWatcher::new()?; use std::process::exit; let mut runtime: Box = if !run_browser { #[cfg(not(feature = "native"))] unimplemented!(); #[cfg(feature = "native")] { let mut microw8 = MicroW8::new(timeout, window_config)?; if disable_audio { microw8.disable_audio(); } Box::new(microw8) } } else { #[cfg(not(feature = "browser"))] unimplemented!(); #[cfg(feature = "browser")] Box::new(RunWebServer::new()) }; let mut first_run = true; while runtime.is_open() { if first_run || watcher.poll_changed_file()?.is_some() { let (result, dependencies) = start_cart(&filename, &mut *runtime, &config); if watch_mode { for dep in dependencies { watcher.add_file(dep)?; } } if let Err(err) = result { eprintln!("Load error: {}", err); if !watch_mode { exit(1); } } first_run = false; } if let Err(err) = runtime.run_frame() { eprintln!("Runtime error: {}", err); if !watch_mode { exit(1); } } } Ok(()) } #[derive(Default)] struct Config { pack: Option, output_path: Option, } fn load_cart(filename: &Path, config: &Config) -> (Result>, Vec) { let mut dependencies = Vec::new(); fn inner(filename: &Path, config: &Config, dependencies: &mut Vec) -> Result> { let mut cart = match SourceType::of_file(filename)? { SourceType::Binary => { let mut cart = vec![]; File::open(filename)?.read_to_end(&mut cart)?; cart } SourceType::Wat => { let cart = wat::parse_file(filename)?; cart } SourceType::CurlyWas => { let (module, deps) = curlywas::compile_file(filename, curlywas::Options::default()); *dependencies = deps; module? } }; if let Some(ref pack_config) = config.pack { cart = uw8_tool::pack(&cart, pack_config)?; println!( "\npacked size: {} bytes ({:.2})", cart.len(), uw8_tool::compressed_size(&cart) ); } if let Some(ref path) = config.output_path { File::create(path)?.write_all(&cart)?; } Ok(cart) } let result = inner(filename, config, &mut dependencies); if dependencies.is_empty() { dependencies.push(filename.to_path_buf()); } (result, dependencies) } enum SourceType { Binary, Wat, CurlyWas, } impl SourceType { fn of_file(filename: &Path) -> Result { if let Some(extension) = filename.extension() { if extension == "uw8" || extension == "wasm" { return Ok(SourceType::Binary); } else if extension == "wat" || extension == "wast" { return Ok(SourceType::Wat); } else if extension == "cwa" { return Ok(SourceType::CurlyWas); } } let mut cart = vec![]; File::open(filename)?.read_to_end(&mut cart)?; let ty = if cart[0] < 10 { SourceType::Binary } else { let src = String::from_utf8(cart)?; if src.chars().find(|&c| !c.is_whitespace() && c != ';') == Some('(') { SourceType::Wat } else { SourceType::CurlyWas } }; Ok(ty) } } #[cfg(any(feature = "native", feature = "browser"))] fn start_cart( filename: &Path, runtime: &mut dyn Runtime, config: &Config, ) -> (Result<()>, Vec) { let (cart, dependencies) = load_cart(filename, config); let cart = match cart { Ok(cart) => cart, Err(err) => return (Err(err), dependencies), }; if let Err(err) = runtime.load(&cart) { eprintln!("Load error: {}", err); (Err(err), dependencies) } else { (Ok(()), dependencies) } } fn pack(mut args: Arguments) -> Result<()> { let mut pack_config = uw8_tool::PackConfig::default(); if args.contains(["-u", "--uncompressed"]) { pack_config = pack_config.uncompressed(); } if let Some(level) = args.opt_value_from_str(["-l", "--level"])? { pack_config = pack_config.with_compression_level(level); } let in_file = args.free_from_os_str::(|s| Ok(s.into()))?; let out_file = args.free_from_os_str::(|s| Ok(s.into()))?; let cart = load_cart( &in_file, &Config { pack: Some(pack_config), output_path: None, }, ) .0?; File::create(out_file)?.write_all(&cart)?; Ok(()) } fn unpack(mut args: Arguments) -> Result<()> { let in_file = args.free_from_os_str::(|s| Ok(s.into()))?; let out_file = args.free_from_os_str::(|s| Ok(s.into()))?; uw8_tool::unpack_file(&in_file, &out_file) } fn compile(mut args: Arguments) -> Result<()> { let mut options = curlywas::Options::default(); if args.contains(["-d", "--debug"]) { options = options.with_debug(); } let in_file = args.free_from_os_str::(|s| Ok(s.into()))?; let out_file = args.free_from_os_str::(|s| Ok(s.into()))?; let module = curlywas::compile_file(in_file, options).0?; File::create(out_file)?.write_all(&module)?; Ok(()) } fn filter_exports(mut args: Arguments) -> Result<()> { let in_file = args.free_from_os_str::(|s| Ok(s.into()))?; let out_file = args.free_from_os_str::(|s| Ok(s.into()))?; uw8_tool::filter_exports(&in_file, &out_file)?; Ok(()) }