| //! binary codegen tool. |
| //! |
| //! Takes a path to a template file as input, and writes the output to stdout |
| |
| use std::path::{Path, PathBuf}; |
| |
| use font_codegen::{ErrorReport, Mode}; |
| |
| use log::{debug, error}; |
| use miette::miette; |
| use rayon::prelude::*; |
| use serde::Deserialize; |
| |
| fn main() -> miette::Result<()> { |
| env_logger::init(); |
| match flags::Args::from_env() { |
| Ok(args) => match args.subcommand { |
| flags::ArgsCmd::Plan(plan) => run_plan(&plan.path), |
| flags::ArgsCmd::File(args) => { |
| let generated_code = run_for_path(&args.path, args.mode)?; |
| print!("{generated_code}"); |
| Ok(()) |
| } |
| }, |
| Err(e) => { |
| error!("{e}"); |
| std::process::exit(1); |
| } |
| } |
| } |
| |
| fn run_plan(path: &Path) -> miette::Result<()> { |
| ensure_correct_working_directory()?; |
| let contents = read_contents(path)?; |
| let plan: CodegenPlan = |
| toml::from_str(&contents).map_err(|e| miette!("failed to parse plan: '{}'", e))?; |
| |
| for path in &plan.clean { |
| if path.exists() { |
| debug!("removing {}", path.display()); |
| if path.is_dir() { |
| std::fs::remove_dir_all(path) |
| .map_err(|e| miette!("failed to clean dir '{}': {e}", path.display()))?; |
| debug!("creating {}", path.display()); |
| std::fs::create_dir_all(path) |
| .map_err(|e| miette!("failed to create directory '{}': {e}", path.display()))?; |
| } else { |
| std::fs::remove_file(path) |
| .map_err(|e| miette!("failed to clean path '{}': {e}", path.display()))?; |
| } |
| } |
| } |
| |
| let results = plan |
| .generate |
| .par_iter() |
| .map(|op| run_for_path(&op.source, op.mode)) |
| .collect::<Result<Vec<_>, _>>()?; |
| |
| for (op, generated) in plan.generate.iter().zip(results.iter()) { |
| debug!( |
| "writing {} bytes to {}", |
| generated.len(), |
| op.target.display() |
| ); |
| std::fs::write(&op.target, generated) |
| .map_err(|e| miette!("error writing '{}': {}", op.target.display(), e))?; |
| } |
| Ok(()) |
| } |
| |
| fn ensure_correct_working_directory() -> miette::Result<()> { |
| if !(Path::new("read-fonts").is_dir() && Path::new("resources").is_dir()) { |
| return Err(miette!( |
| "codegen tool must be run from the root of the workspace" |
| )); |
| } |
| Ok(()) |
| } |
| |
| #[derive(Deserialize)] |
| struct CodegenPlan { |
| generate: Vec<CodegenOp>, |
| clean: Vec<PathBuf>, |
| } |
| |
| #[derive(Deserialize)] |
| struct CodegenOp { |
| mode: Mode, |
| source: PathBuf, |
| target: PathBuf, |
| } |
| |
| fn read_contents(path: &Path) -> miette::Result<String> { |
| std::fs::read_to_string(path).map_err(|e| { |
| { ErrorReport::message(format!("error reading '{}': {}", path.display(), e)) }.into() |
| }) |
| } |
| |
| fn run_for_path(path: &Path, mode: Mode) -> miette::Result<String> { |
| let contents = read_contents(path)?; |
| font_codegen::generate_code(&contents, mode) |
| .map_err(|e| ErrorReport::from_error_src(&e, path, contents).into()) |
| } |
| |
| mod flags { |
| use font_codegen::Mode; |
| use std::path::PathBuf; |
| |
| xflags::xflags! { |
| /// Generate font table representations |
| cmd args { |
| cmd file { |
| /// Code to generate; either 'parse' or 'compile'. |
| required mode: Mode |
| /// Path to the input file |
| required path: PathBuf |
| } |
| default cmd plan { |
| /// plan path |
| required path: PathBuf |
| } |
| } |
| } |
| } |