summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLaurenz <laurmaedje@gmail.com>2022-05-20 10:05:04 +0200
committerLaurenz <laurmaedje@gmail.com>2022-05-20 10:31:44 +0200
commit6ff553612765d932975eb6b81f0eb420dc0afd55 (patch)
tree074533cb0aecd8d5a39d271e49b44919c7cd83b2 /src
parentc9b72aaa84bdd5f67283889a907c8416a853b73c (diff)
Expand command line interface
Diffstat (limited to 'src')
-rw-r--r--src/main.rs263
1 files changed, 190 insertions, 73 deletions
diff --git a/src/main.rs b/src/main.rs
index 59ad5a71..c017e735 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -5,34 +5,95 @@ use std::process;
use codespan_reporting::diagnostic::{Diagnostic, Label};
use codespan_reporting::term::{self, termcolor, Config, Styles};
+use pico_args::Arguments;
use same_file::is_same_file;
use termcolor::{ColorChoice, StandardStream, WriteColor};
-use typst::diag::Error;
+use typst::diag::{Error, StrResult};
use typst::export;
+use typst::font::{FaceInfo, FontStore};
+use typst::library::text::THEME;
use typst::loading::FsLoader;
+use typst::parse::TokenMode;
use typst::source::SourceStore;
+use typst::syntax;
use typst::Context;
+/// What to do.
+enum Command {
+ Typeset(TypesetCommand),
+ Highlight(HighlightCommand),
+ Fonts(FontsCommand),
+}
+
+/// Typeset a .typ file into a PDF file.
+struct TypesetCommand {
+ input: PathBuf,
+ output: PathBuf,
+ root: Option<PathBuf>,
+}
+
const HELP: &'static str = "\
typst creates PDF files from .typ files
USAGE:
typst [OPTIONS] <input.typ> [output.pdf]
+ typst [SUBCOMMAND] ...
+
+ARGS:
+ <input.typ> Path to input Typst file
+ [output.pdf] Path to output PDF file
OPTIONS:
-h, --help Print this help
--root <dir> Configure the root for absolute paths
+SUBCOMMANDS:
+ --highlight Highlight .typ files to HTML
+ --fonts List all discovered system fonts
+";
+
+/// Highlight a .typ file into a HTML file.
+struct HighlightCommand {
+ input: PathBuf,
+ output: PathBuf,
+}
+
+const HELP_HIGHLIGHT: &'static str = "\
+typst --highlight creates highlighted HTML from .typ files
+
+USAGE:
+ typst --highlight [OPTIONS] <input.typ> [output.html]
+
ARGS:
<input.typ> Path to input Typst file
- [output.pdf] Path to output PDF
+ [output.html] Path to output HTML file
+
+OPTIONS:
+ -h, --help Print this help
+";
+
+/// List discovered fonts.
+struct FontsCommand {
+ variants: bool,
+}
+
+const HELP_FONTS: &'static str = "\
+typst --fonts lists all discovered system fonts
+
+USAGE:
+ typst --fonts [OPTIONS]
+
+OPTIONS:
+ -h, --help Print this help
+ --variants Also list style variants of each font family
";
+/// Entry point.
fn main() {
- let args = parse_args();
- let ok = args.is_ok();
- if let Err(msg) = args.and_then(try_main) {
+ let command = parse_args();
+ let ok = command.is_ok();
+ if let Err(msg) = command.and_then(dispatch) {
print_error(&msg).unwrap();
if !ok {
println!("\nfor more information, try --help");
@@ -41,95 +102,71 @@ fn main() {
}
}
-/// The main compiler logic.
-fn try_main(args: Args) -> Result<(), String> {
- // Create a loader for fonts and files.
- let mut loader = FsLoader::new();
- let mut builder = Context::builder();
- if let Some(root) = &args.root {
- builder.root(root);
- }
+/// Parse command line arguments.
+fn parse_args() -> StrResult<Command> {
+ let mut args = Arguments::from_env();
+ let help = args.contains(["-h", "--help"]);
- // Search for fonts in the project directory.
- if let Some(dir) = args.input.parent() {
- if args.root.is_none() {
- builder.root(dir);
+ let command = if args.contains("--highlight") {
+ if help {
+ print_help(HELP_HIGHLIGHT);
}
- if dir.as_os_str().is_empty() {
- // Just a filename, so directory is current directory.
- loader.search_path(".");
- } else {
- loader.search_path(dir);
+ let (input, output) = parse_input_output(&mut args, "html")?;
+ Command::Highlight(HighlightCommand { input, output })
+ } else if args.contains("--fonts") {
+ if help {
+ print_help(HELP_FONTS);
}
- }
-
- // Search system fonts only now to give local fonts priority.
- loader.search_system();
-
- // Create the context which holds loaded source files, fonts, images and
- // cached artifacts.
- let mut ctx = builder.build(loader.wrap());
-
- // Ensure that the source file is not overwritten.
- if is_same_file(&args.input, &args.output).unwrap_or(false) {
- Err("source and destination files are the same")?;
- }
- // Load the source file.
- let id = ctx
- .sources
- .load(&args.input)
- .map_err(|_| "failed to load source file")?;
-
- // Typeset.
- match ctx.typeset(id) {
- // Export the PDF.
- Ok(frames) => {
- let buffer = export::pdf(&ctx, &frames);
- fs::write(&args.output, buffer).map_err(|_| "failed to write PDF file")?;
+ Command::Fonts(FontsCommand { variants: args.contains("--variants") })
+ } else {
+ if help {
+ print_help(HELP);
}
- // Print diagnostics.
- Err(errors) => {
- print_diagnostics(&ctx.sources, *errors)
- .map_err(|_| "failed to print diagnostics")?;
- }
- }
+ let root = args.opt_value_from_str("--root").map_err(|_| "missing root path")?;
+ let (input, output) = parse_input_output(&mut args, "pdf")?;
+ Command::Typeset(TypesetCommand { input, output, root })
+ };
- Ok(())
-}
+ // Don't allow excess arguments.
+ let rest = args.finish();
+ if !rest.is_empty() {
+ Err(format!(
+ "unexpected argument{}",
+ if rest.len() > 1 { "s" } else { "" }
+ ))?;
+ }
-struct Args {
- input: PathBuf,
- output: PathBuf,
- root: Option<PathBuf>,
+ Ok(command)
}
-/// Parse command line arguments.
-fn parse_args() -> Result<Args, String> {
- let mut args = pico_args::Arguments::from_env();
- if args.contains(["-h", "--help"]) {
- print!("{}", HELP);
- std::process::exit(0);
- }
-
- let root = args.opt_value_from_str("--root").map_err(|_| "malformed root")?;
+/// Parse two freestanding path arguments, with the output path being optional.
+/// If it is omitted, it is determined from the input path's filename with the
+/// given extension.
+fn parse_input_output(args: &mut Arguments, ext: &str) -> StrResult<(PathBuf, PathBuf)> {
let input: PathBuf = args.free_from_str().map_err(|_| "missing input file")?;
let output = match args.opt_free_from_str().ok().flatten() {
Some(output) => output,
None => {
let name = input.file_name().ok_or("source path does not point to a file")?;
- Path::new(name).with_extension("pdf")
+ Path::new(name).with_extension(ext)
}
};
- // Don't allow excess arguments.
- if !args.finish().is_empty() {
- Err("too many arguments")?;
+ // Ensure that the source file is not overwritten.
+ if is_same_file(&input, &output).unwrap_or(false) {
+ Err("source and destination files are the same")?;
}
- Ok(Args { input, output, root })
+ Ok((input, output))
+}
+
+/// Print a help string and quit.
+fn print_help(help: &'static str) {
+ print!("{help}");
+ std::process::exit(0);
}
/// Print an application-level error (independent from a source file).
@@ -144,6 +181,55 @@ fn print_error(msg: &str) -> io::Result<()> {
writeln!(w, ": {msg}.")
}
+/// Dispatch a command.
+fn dispatch(command: Command) -> StrResult<()> {
+ match command {
+ Command::Typeset(command) => typeset(command),
+ Command::Highlight(command) => highlight(command),
+ Command::Fonts(command) => fonts(command),
+ }
+}
+
+/// Execute a typesetting command.
+fn typeset(command: TypesetCommand) -> StrResult<()> {
+ let mut builder = Context::builder();
+ if let Some(root) = &command.root {
+ builder.root(root);
+ } else if let Some(dir) = command.input.parent() {
+ builder.root(dir);
+ }
+
+ // Create a loader for fonts and files.
+ let loader = FsLoader::new().with_system();
+
+ // Create the context which holds loaded source files, fonts, images and
+ // cached artifacts.
+ let mut ctx = builder.build(loader.wrap());
+
+ // Load the source file.
+ let id = ctx
+ .sources
+ .load(&command.input)
+ .map_err(|_| "failed to load source file")?;
+
+ // Typeset.
+ match ctx.typeset(id) {
+ // Export the PDF.
+ Ok(frames) => {
+ let buffer = export::pdf(&ctx, &frames);
+ fs::write(&command.output, buffer).map_err(|_| "failed to write PDF file")?;
+ }
+
+ // Print diagnostics.
+ Err(errors) => {
+ print_diagnostics(&ctx.sources, *errors)
+ .map_err(|_| "failed to print diagnostics")?;
+ }
+ }
+
+ Ok(())
+}
+
/// Print diagnostics messages to the terminal.
fn print_diagnostics(
sources: &SourceStore,
@@ -173,3 +259,34 @@ fn print_diagnostics(
Ok(())
}
+
+/// Execute a highlighting command.
+fn highlight(command: HighlightCommand) -> StrResult<()> {
+ let input = std::fs::read_to_string(&command.input)
+ .map_err(|_| "failed to load source file")?;
+
+ let html = syntax::highlight_html(&input, TokenMode::Markup, &THEME);
+ fs::write(&command.output, html).map_err(|_| "failed to write HTML file")?;
+
+ Ok(())
+}
+
+/// Execute a font listing command.
+fn fonts(command: FontsCommand) -> StrResult<()> {
+ let loader = FsLoader::new().with_system();
+ let fonts = FontStore::new(loader.wrap());
+
+ for (name, infos) in fonts.families() {
+ println!("{name}");
+ if command.variants {
+ for &FaceInfo { variant, .. } in infos {
+ println!(
+ "- Style: {:?}, Weight: {:?}, Stretch: {:?}",
+ variant.style, variant.weight, variant.stretch,
+ );
+ }
+ }
+ }
+
+ Ok(())
+}