Put HTTP server behind on-by-default feature flag (#5532)

This commit is contained in:
Laurenz 2024-12-05 16:25:18 +01:00 committed by GitHub
parent 8e4f5f21e0
commit caa72f4ec2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 78 additions and 49 deletions

View File

@ -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

View File

@ -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"]

View File

@ -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<u16>,
/// 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<PathBuf>,
}
/// 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<u16>,
}
macro_rules! display_possible_values {
($ty:ty) => {
impl Display for $ty {

View File

@ -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<HtmlServer>,
}
@ -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(()) };
#[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());
}
// Can't open stdout.
let Output::Path(path) = &config.output 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(());
};
let path = path
.canonicalize()
.map_err(|err| eco_format!("failed to canonicalize path ({err})"))?;
if let Some(app) = &open {
open::with_detached(&path, app)
.map_err(|err| eco_format!("failed to open file with {} ({})", app, 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())

View File

@ -6,6 +6,7 @@ mod greet;
mod init;
mod package;
mod query;
#[cfg(feature = "http-server")]
mod server;
mod terminal;
mod timings;

View File

@ -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<u16>, reload: bool) -> StrResult<Self> {
let (addr, server) = start_server(port)?;
pub fn new(input: &Input, args: &ServerArgs) -> StrResult<Self> {
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));

View File

@ -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")?;