From caa72f4ec2401c275ddd3d8794dcf0bfdf9697a8 Mon Sep 17 00:00:00 2001 From: Laurenz Date: Thu, 5 Dec 2024 16:25:18 +0100 Subject: [PATCH] Put HTTP server behind on-by-default feature flag (#5532) --- .github/workflows/ci.yml | 1 + crates/typst-cli/Cargo.toml | 7 +++- crates/typst-cli/src/args.rs | 40 ++++++++++++------- crates/typst-cli/src/compile.rs | 70 +++++++++++++++++++-------------- crates/typst-cli/src/main.rs | 1 + crates/typst-cli/src/server.rs | 7 ++-- crates/typst-cli/src/watch.rs | 1 + 7 files changed, 78 insertions(+), 49 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9ddb309bb..268fd93c0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,6 +64,7 @@ jobs: components: clippy, rustfmt - uses: Swatinem/rust-cache@v2 - run: cargo clippy --workspace --all-targets --all-features + - run: cargo clippy --workspace --all-targets --no-default-features - run: cargo fmt --check --all - run: cargo doc --workspace --no-deps diff --git a/crates/typst-cli/Cargo.toml b/crates/typst-cli/Cargo.toml index c859f043c..7e9b93f93 100644 --- a/crates/typst-cli/Cargo.toml +++ b/crates/typst-cli/Cargo.toml @@ -50,7 +50,7 @@ shell-escape = { workspace = true } sigpipe = { workspace = true } tar = { workspace = true } tempfile = { workspace = true } -tiny_http = { workspace = true } +tiny_http = { workspace = true, optional = true } toml = { workspace = true } ureq = { workspace = true } xz2 = { workspace = true, optional = true } @@ -65,11 +65,14 @@ color-print = { workspace = true } semver = { workspace = true } [features] -default = ["embed-fonts"] +default = ["embed-fonts", "http-server"] # Embeds some fonts into the binary, see typst-kit embed-fonts = ["typst-kit/embed-fonts"] +# Enables the built-in HTTP server for `typst watch` and HTML export. +http-server = ["dep:tiny_http"] + # Permits the CLI to update itself without a package manager. self-update = ["dep:self-replace", "dep:xz2", "dep:zip"] diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs index ead932362..83c4c8f9e 100644 --- a/crates/typst-cli/src/args.rs +++ b/crates/typst-cli/src/args.rs @@ -98,20 +98,10 @@ pub struct WatchCommand { #[clap(flatten)] pub args: CompileArgs, - /// Disables the built-in HTTP server for HTML export. - #[clap(long)] - pub no_serve: bool, - - /// Disables the injected live reload script for HTML export. The HTML that - /// is written to disk isn't affected either way. - #[clap(long)] - pub no_reload: bool, - - /// The port where HTML is served. - /// - /// Defaults to the first free port in the range 3000-3005. - #[clap(long)] - pub port: Option, + /// Arguments for the HTTP server. + #[cfg(feature = "http-server")] + #[clap(flatten)] + pub server: ServerArgs, } /// Initializes a new project from a template. @@ -354,7 +344,7 @@ pub struct PackageArgs { pub package_cache_path: Option, } -/// Common arguments to customize available fonts +/// Common arguments to customize available fonts. #[derive(Debug, Clone, Parser)] pub struct FontArgs { /// Adds additional directories that are recursively searched for fonts. @@ -375,6 +365,26 @@ pub struct FontArgs { pub ignore_system_fonts: bool, } +/// Arguments for the HTTP server. +#[cfg(feature = "http-server")] +#[derive(Debug, Clone, Parser)] +pub struct ServerArgs { + /// Disables the built-in HTTP server for HTML export. + #[clap(long)] + pub no_serve: bool, + + /// Disables the injected live reload script for HTML export. The HTML that + /// is written to disk isn't affected either way. + #[clap(long)] + pub no_reload: bool, + + /// The port where HTML is served. + /// + /// Defaults to the first free port in the range 3000-3005. + #[clap(long)] + pub port: Option, +} + macro_rules! display_possible_values { ($ty:ty) => { impl Display for $ty { diff --git a/crates/typst-cli/src/compile.rs b/crates/typst-cli/src/compile.rs index 01a6de1bc..3aa3aa3b9 100644 --- a/crates/typst-cli/src/compile.rs +++ b/crates/typst-cli/src/compile.rs @@ -1,4 +1,4 @@ -use std::ffi::OsString; +use std::ffi::OsStr; use std::fs::{self, File}; use std::io::{self, Write}; use std::path::{Path, PathBuf}; @@ -23,6 +23,7 @@ use crate::args::{ CompileArgs, CompileCommand, DiagnosticFormat, Input, Output, OutputFormat, PdfStandard, WatchCommand, }; +#[cfg(feature = "http-server")] use crate::server::HtmlServer; use crate::timings::Timer; @@ -72,6 +73,7 @@ pub struct CompileConfig { /// watch` sessions with images. pub export_cache: ExportCache, /// Server for `typst watch` to HTML. + #[cfg(feature = "http-server")] pub server: Option, } @@ -139,17 +141,18 @@ impl CompileConfig { PdfStandards::new(&list)? }; - let mut server = None; - let mut watching = false; - if let Some(command) = watch { - watching = true; - if output_format == OutputFormat::Html && !command.no_serve { - server = Some(HtmlServer::new(&input, command.port, !command.no_reload)?); + #[cfg(feature = "http-server")] + let server = match watch { + Some(command) + if output_format == OutputFormat::Html && !command.server.no_serve => + { + Some(HtmlServer::new(&input, &command.server)?) } - } + _ => None, + }; Ok(Self { - watching, + watching: watch.is_some(), input, output, output_format, @@ -161,6 +164,7 @@ impl CompileConfig { diagnostic_format: args.process.diagnostic_format, open: args.open.clone(), export_cache: ExportCache::new(), + #[cfg(feature = "http-server")] server, }) } @@ -241,6 +245,7 @@ fn export_html(document: &HtmlDocument, config: &CompileConfig) -> SourceResult< let html = typst_html::html(document)?; let result = config.output.write(html.as_bytes()); + #[cfg(feature = "http-server")] if let Some(server) = &config.server { server.update(html); } @@ -556,30 +561,37 @@ fn write_make_deps(world: &mut SystemWorld, config: &CompileConfig) -> StrResult }) } -/// Opens the output if desired, with: -/// - The default file viewer if `open` is `None`. -/// - The given viewer provided by `open` if it is `Some`. -/// -/// If the file could not be opened, an error is returned. +/// Opens the output if desired. fn open_output(config: &mut CompileConfig) -> StrResult<()> { - let Some(open) = config.open.take() else { return Ok(()) }; + let Some(viewer) = config.open.take() else { return Ok(()) }; - let path = if let Some(server) = &config.server { - OsString::from(format!("http://{}", server.addr())) - } else if let Output::Path(path) = &config.output { - // Some resource openers require the path to be canonicalized. - path.canonicalize() - .map_err(|err| eco_format!("failed to canonicalize path ({err})"))? - .into_os_string() - } else { - return Ok(()); - }; + #[cfg(feature = "http-server")] + if let Some(server) = &config.server { + let url = format!("http://{}", server.addr()); + return open_path(OsStr::new(&url), viewer.as_deref()); + } - if let Some(app) = &open { - open::with_detached(&path, app) - .map_err(|err| eco_format!("failed to open file with {} ({})", app, err)) + // Can't open stdout. + let Output::Path(path) = &config.output else { return Ok(()) }; + + // Some resource openers require the path to be canonicalized. + let path = path + .canonicalize() + .map_err(|err| eco_format!("failed to canonicalize path ({err})"))?; + + open_path(path.as_os_str(), viewer.as_deref()) +} + +/// Opens the given file using: +/// +/// - The default file viewer if `app` is `None`. +/// - The given viewer provided by `app` if it is `Some`. +fn open_path(path: &OsStr, viewer: Option<&str>) -> StrResult<()> { + if let Some(viewer) = viewer { + open::with_detached(path, viewer) + .map_err(|err| eco_format!("failed to open file with {} ({})", viewer, err)) } else { - open::that_detached(&path).map_err(|err| { + open::that_detached(path).map_err(|err| { let openers = open::commands(path) .iter() .map(|command| command.get_program().to_string_lossy()) diff --git a/crates/typst-cli/src/main.rs b/crates/typst-cli/src/main.rs index 610f89c03..14f8a665d 100644 --- a/crates/typst-cli/src/main.rs +++ b/crates/typst-cli/src/main.rs @@ -6,6 +6,7 @@ mod greet; mod init; mod package; mod query; +#[cfg(feature = "http-server")] mod server; mod terminal; mod timings; diff --git a/crates/typst-cli/src/server.rs b/crates/typst-cli/src/server.rs index b3ce83f86..8910e0323 100644 --- a/crates/typst-cli/src/server.rs +++ b/crates/typst-cli/src/server.rs @@ -7,7 +7,7 @@ use parking_lot::{Condvar, Mutex, MutexGuard}; use tiny_http::{Header, Request, Response, StatusCode}; use typst::diag::{bail, StrResult}; -use crate::args::Input; +use crate::args::{Input, ServerArgs}; /// Serves HTML with live reload. pub struct HtmlServer { @@ -17,8 +17,9 @@ pub struct HtmlServer { impl HtmlServer { /// Create a new HTTP server that serves live HTML. - pub fn new(input: &Input, port: Option, reload: bool) -> StrResult { - let (addr, server) = start_server(port)?; + pub fn new(input: &Input, args: &ServerArgs) -> StrResult { + let reload = !args.no_reload; + let (addr, server) = start_server(args.port)?; let placeholder = PLACEHOLDER_HTML.replace("{INPUT}", &input.to_string()); let bucket = Arc::new(Bucket::new(placeholder)); diff --git a/crates/typst-cli/src/watch.rs b/crates/typst-cli/src/watch.rs index e62746dfb..91132fc30 100644 --- a/crates/typst-cli/src/watch.rs +++ b/crates/typst-cli/src/watch.rs @@ -293,6 +293,7 @@ impl Status { out.reset()?; writeln!(out, " {}", config.output)?; + #[cfg(feature = "http-server")] if let Some(server) = &config.server { out.set_color(&color)?; write!(out, "serving at")?;