summaryrefslogtreecommitdiff
path: root/crates/typst-cli
diff options
context:
space:
mode:
authorLuizAugustoPapa <112978478+LuizAugustoPapa@users.noreply.github.com>2024-06-06 14:22:54 -0300
committerGitHub <noreply@github.com>2024-06-06 17:22:54 +0000
commitcc3e9c86022c5ae5a82938b0b65e3d2dca93b1ed (patch)
tree9497e2e4a92fab3b2f3f757a613b75308f644819 /crates/typst-cli
parent753213c40a7967e2e425ad63da52aaac31681780 (diff)
Add more environment control parameters to CLI (#4227)
Co-authored-by: PgBiel <9021226+PgBiel@users.noreply.github.com> Co-authored-by: Tulio Martins <tulioml240@gmail.com> Co-authored-by: PepinhoJp <pepinho.jp@gmail.com>
Diffstat (limited to 'crates/typst-cli')
-rw-r--r--crates/typst-cli/src/args.rs64
-rw-r--r--crates/typst-cli/src/fonts.rs10
-rw-r--r--crates/typst-cli/src/init.rs7
-rw-r--r--crates/typst-cli/src/package.rs112
-rw-r--r--crates/typst-cli/src/update.rs27
-rw-r--r--crates/typst-cli/src/world.rs53
6 files changed, 187 insertions, 86 deletions
diff --git a/crates/typst-cli/src/args.rs b/crates/typst-cli/src/args.rs
index 36fb044f..cf8a2c6f 100644
--- a/crates/typst-cli/src/args.rs
+++ b/crates/typst-cli/src/args.rs
@@ -131,6 +131,10 @@ pub struct InitCommand {
/// The project directory, defaults to the template's name
pub dir: Option<String>,
+
+ /// Arguments related to storage of packages in the system
+ #[clap(flatten)]
+ pub package_storage_args: PackageStorageArgs,
}
/// Processes an input file to extract provided metadata
@@ -187,14 +191,9 @@ pub struct SharedArgs {
)]
pub inputs: Vec<(String, String)>,
- /// Adds additional directories to search for fonts
- #[clap(
- long = "font-path",
- env = "TYPST_FONT_PATHS",
- value_name = "DIR",
- value_delimiter = ENV_PATH_SEP,
- )]
- pub font_paths: Vec<PathBuf>,
+ /// Common font arguments
+ #[clap(flatten)]
+ pub font_args: FontArgs,
/// The document's creation date formatted as a UNIX timestamp.
///
@@ -214,6 +213,26 @@ pub struct SharedArgs {
value_parser = clap::value_parser!(DiagnosticFormat)
)]
pub diagnostic_format: DiagnosticFormat,
+
+ /// Arguments related to storage of packages in the system
+ #[clap(flatten)]
+ pub package_storage_args: PackageStorageArgs,
+}
+
+/// Arguments related to where packages are stored in the system.
+#[derive(Debug, Clone, Args)]
+pub struct PackageStorageArgs {
+ /// Custom path to local packages, defaults to system-dependent location
+ #[clap(long = "package-path", env = "TYPST_PACKAGE_PATH", value_name = "DIR")]
+ pub package_path: Option<PathBuf>,
+
+ /// Custom path to package cache, defaults to system-dependent location
+ #[clap(
+ long = "package-cache-path",
+ env = "TYPST_PACKAGE_CACHE_PATH",
+ value_name = "DIR"
+ )]
+ pub package_cache_path: Option<PathBuf>,
}
/// Parses a UNIX timestamp according to <https://reproducible-builds.org/specs/source-date-epoch/>
@@ -343,6 +362,18 @@ fn parse_page_number(value: &str) -> Result<NonZeroUsize, &'static str> {
/// Lists all discovered fonts in system and custom font paths
#[derive(Debug, Clone, Parser)]
pub struct FontsCommand {
+ /// Common font arguments
+ #[clap(flatten)]
+ pub font_args: FontArgs,
+
+ /// Also lists style variants of each font family
+ #[arg(long)]
+ pub variants: bool,
+}
+
+/// Common arguments to customize available fonts
+#[derive(Debug, Clone, Parser)]
+pub struct FontArgs {
/// Adds additional directories to search for fonts
#[clap(
long = "font-path",
@@ -352,9 +383,10 @@ pub struct FontsCommand {
)]
pub font_paths: Vec<PathBuf>,
- /// Also lists style variants of each font family
+ /// Ensures system fonts won't be searched, unless explicitly included via
+ /// `--font-path`
#[arg(long)]
- pub variants: bool,
+ pub ignore_system_fonts: bool,
}
/// Which format to use for diagnostics.
@@ -385,8 +417,18 @@ pub struct UpdateCommand {
/// Reverts to the version from before the last update (only possible if
/// `typst update` has previously ran)
- #[clap(long, default_value_t = false, exclusive = true)]
+ #[clap(
+ long,
+ default_value_t = false,
+ conflicts_with = "version",
+ conflicts_with = "force"
+ )]
pub revert: bool,
+
+ /// Custom path to the backup file created on update and used by `--revert`,
+ /// defaults to system-dependent location
+ #[clap(long = "backup-path", env = "TYPST_UPDATE_BACKUP_PATH", value_name = "FILE")]
+ pub backup_path: Option<PathBuf>,
}
/// Which format to use for the generated output file.
diff --git a/crates/typst-cli/src/fonts.rs b/crates/typst-cli/src/fonts.rs
index a5e454ed..de9d1fc1 100644
--- a/crates/typst-cli/src/fonts.rs
+++ b/crates/typst-cli/src/fonts.rs
@@ -12,7 +12,7 @@ use crate::args::FontsCommand;
/// Execute a font listing command.
pub fn fonts(command: &FontsCommand) -> StrResult<()> {
let mut searcher = FontSearcher::new();
- searcher.search(&command.font_paths);
+ searcher.search(&command.font_args.font_paths, command.font_args.ignore_system_fonts);
for (name, infos) in searcher.book.families() {
println!("{name}");
@@ -66,7 +66,7 @@ impl FontSearcher {
}
/// Search everything that is available.
- pub fn search(&mut self, font_paths: &[PathBuf]) {
+ pub fn search(&mut self, font_paths: &[PathBuf], ignore_system_fonts: bool) {
let mut db = Database::new();
// Font paths have highest priority.
@@ -74,8 +74,10 @@ impl FontSearcher {
db.load_fonts_dir(path);
}
- // System fonts have second priority.
- db.load_system_fonts();
+ if !ignore_system_fonts {
+ // System fonts have second priority.
+ db.load_system_fonts();
+ }
for face in db.faces() {
let path = match &face.source {
diff --git a/crates/typst-cli/src/init.rs b/crates/typst-cli/src/init.rs
index b0446bd1..cb6b6627 100644
--- a/crates/typst-cli/src/init.rs
+++ b/crates/typst-cli/src/init.rs
@@ -10,9 +10,12 @@ use typst::syntax::package::{
};
use crate::args::InitCommand;
+use crate::package::PackageStorage;
/// Execute an initialization command.
pub fn init(command: &InitCommand) -> StrResult<()> {
+ let package_storage = PackageStorage::from_args(&command.package_storage_args);
+
// Parse the package specification. If the user didn't specify the version,
// we try to figure it out automatically by downloading the package index
// or searching the disk.
@@ -20,12 +23,12 @@ pub fn init(command: &InitCommand) -> StrResult<()> {
// Try to parse without version, but prefer the error message of the
// normal package spec parsing if it fails.
let spec: VersionlessPackageSpec = command.template.parse().map_err(|_| err)?;
- let version = crate::package::determine_latest_version(&spec)?;
+ let version = package_storage.determine_latest_version(&spec)?;
StrResult::Ok(spec.at(version))
})?;
// Find or download the package.
- let package_path = crate::package::prepare_package(&spec)?;
+ let package_path = package_storage.prepare_package(&spec)?;
// Parse the manifest.
let manifest = parse_manifest(&package_path)?;
diff --git a/crates/typst-cli/src/package.rs b/crates/typst-cli/src/package.rs
index 7d3f2264..9f38cac0 100644
--- a/crates/typst-cli/src/package.rs
+++ b/crates/typst-cli/src/package.rs
@@ -2,6 +2,7 @@ use std::fs;
use std::io::{self, Write};
use std::path::{Path, PathBuf};
+use crate::args::PackageStorageArgs;
use codespan_reporting::term::{self, termcolor};
use ecow::eco_format;
use termcolor::WriteColor;
@@ -14,64 +15,83 @@ use crate::download::{download, download_with_progress};
use crate::terminal;
const HOST: &str = "https://packages.typst.org";
+const DEFAULT_PACKAGES_SUBDIR: &str = "typst/packages";
-/// Make a package available in the on-disk cache.
-pub fn prepare_package(spec: &PackageSpec) -> PackageResult<PathBuf> {
- let subdir =
- format!("typst/packages/{}/{}/{}", spec.namespace, spec.name, spec.version);
+/// Holds information about where packages should be stored.
+pub struct PackageStorage {
+ pub package_cache_path: Option<PathBuf>,
+ pub package_path: Option<PathBuf>,
+}
- if let Some(data_dir) = dirs::data_dir() {
- let dir = data_dir.join(&subdir);
- if dir.exists() {
- return Ok(dir);
- }
+impl PackageStorage {
+ pub fn from_args(args: &PackageStorageArgs) -> Self {
+ let package_cache_path = args.package_cache_path.clone().or_else(|| {
+ dirs::cache_dir().map(|cache_dir| cache_dir.join(DEFAULT_PACKAGES_SUBDIR))
+ });
+ let package_path = args.package_path.clone().or_else(|| {
+ dirs::data_dir().map(|data_dir| data_dir.join(DEFAULT_PACKAGES_SUBDIR))
+ });
+ Self { package_cache_path, package_path }
}
- if let Some(cache_dir) = dirs::cache_dir() {
- let dir = cache_dir.join(&subdir);
- if dir.exists() {
- return Ok(dir);
+ /// Make a package available in the on-disk cache.
+ pub fn prepare_package(&self, spec: &PackageSpec) -> PackageResult<PathBuf> {
+ let subdir = format!("{}/{}/{}", spec.namespace, spec.name, spec.version);
+
+ if let Some(packages_dir) = &self.package_path {
+ let dir = packages_dir.join(&subdir);
+ if dir.exists() {
+ return Ok(dir);
+ }
}
- // Download from network if it doesn't exist yet.
- if spec.namespace == "preview" {
- download_package(spec, &dir)?;
+ if let Some(cache_dir) = &self.package_cache_path {
+ let dir = cache_dir.join(&subdir);
if dir.exists() {
return Ok(dir);
}
+
+ // Download from network if it doesn't exist yet.
+ if spec.namespace == "preview" {
+ download_package(spec, &dir)?;
+ if dir.exists() {
+ return Ok(dir);
+ }
+ }
}
- }
- Err(PackageError::NotFound(spec.clone()))
-}
+ Err(PackageError::NotFound(spec.clone()))
+ }
-/// Try to determine the latest version of a package.
-pub fn determine_latest_version(
- spec: &VersionlessPackageSpec,
-) -> StrResult<PackageVersion> {
- if spec.namespace == "preview" {
- // For `@preview`, download the package index and find the latest
- // version.
- download_index()?
- .iter()
- .filter(|package| package.name == spec.name)
- .map(|package| package.version)
- .max()
- .ok_or_else(|| eco_format!("failed to find package {spec}"))
- } else {
- // For other namespaces, search locally. We only search in the data
- // directory and not the cache directory, because the latter is not
- // intended for storage of local packages.
- let subdir = format!("typst/packages/{}/{}", spec.namespace, spec.name);
- dirs::data_dir()
- .into_iter()
- .flat_map(|dir| std::fs::read_dir(dir.join(&subdir)).ok())
- .flatten()
- .filter_map(|entry| entry.ok())
- .map(|entry| entry.path())
- .filter_map(|path| path.file_name()?.to_string_lossy().parse().ok())
- .max()
- .ok_or_else(|| eco_format!("please specify the desired version"))
+ /// Try to determine the latest version of a package.
+ pub fn determine_latest_version(
+ &self,
+ spec: &VersionlessPackageSpec,
+ ) -> StrResult<PackageVersion> {
+ if spec.namespace == "preview" {
+ // For `@preview`, download the package index and find the latest
+ // version.
+ download_index()?
+ .iter()
+ .filter(|package| package.name == spec.name)
+ .map(|package| package.version)
+ .max()
+ .ok_or_else(|| eco_format!("failed to find package {spec}"))
+ } else {
+ // For other namespaces, search locally. We only search in the data
+ // directory and not the cache directory, because the latter is not
+ // intended for storage of local packages.
+ let subdir = format!("{}/{}", spec.namespace, spec.name);
+ self.package_path
+ .iter()
+ .flat_map(|dir| std::fs::read_dir(dir.join(&subdir)).ok())
+ .flatten()
+ .filter_map(|entry| entry.ok())
+ .map(|entry| entry.path())
+ .filter_map(|path| path.file_name()?.to_string_lossy().parse().ok())
+ .max()
+ .ok_or_else(|| eco_format!("please specify the desired version"))
+ }
}
}
diff --git a/crates/typst-cli/src/update.rs b/crates/typst-cli/src/update.rs
index b33e0519..fa7c3a34 100644
--- a/crates/typst-cli/src/update.rs
+++ b/crates/typst-cli/src/update.rs
@@ -40,7 +40,14 @@ pub fn update(command: &UpdateCommand) -> StrResult<()> {
}
}
- let backup_path = backup_path()?;
+ // Full path to the backup file.
+ let backup_path = command.backup_path.clone().map(Ok).unwrap_or_else(backup_path)?;
+
+ if let Some(backup_dir) = backup_path.parent() {
+ fs::create_dir_all(backup_dir)
+ .map_err(|err| eco_format!("failed to create backup directory ({err})"))?;
+ }
+
if command.revert {
if !backup_path.exists() {
bail!(
@@ -213,14 +220,19 @@ fn update_needed(release: &Release) -> StrResult<bool> {
Ok(new_tag > current_tag)
}
-/// Path to a potential backup file.
+/// Path to a potential backup file in the system.
///
-/// The backup will be placed in one of the following directories, depending on
-/// the platform:
+/// The backup will be placed as `typst_backup.part` in one of the following
+/// directories, depending on the platform:
/// - `$XDG_STATE_HOME` or `~/.local/state` on Linux
/// - `$XDG_DATA_HOME` or `~/.local/share` if the above path isn't available
/// - `~/Library/Application Support` on macOS
/// - `%APPDATA%` on Windows
+///
+/// If a custom backup path is provided via the environment variable
+/// `TYPST_UPDATE_BACKUP_PATH`, it will be used instead of the default
+/// directories determined by the platform. In that case, this function
+/// shouldn't be called.
fn backup_path() -> StrResult<PathBuf> {
#[cfg(target_os = "linux")]
let root_backup_dir = dirs::state_dir()
@@ -231,10 +243,5 @@ fn backup_path() -> StrResult<PathBuf> {
let root_backup_dir =
dirs::data_dir().ok_or("unable to locate local data directory")?;
- let backup_dir = root_backup_dir.join("typst");
-
- fs::create_dir_all(&backup_dir)
- .map_err(|err| eco_format!("failed to create backup directory ({err})"))?;
-
- Ok(backup_dir.join("typst_backup.part"))
+ Ok(root_backup_dir.join("typst").join("typst_backup.part"))
}
diff --git a/crates/typst-cli/src/world.rs b/crates/typst-cli/src/world.rs
index 81fece2a..9748d9c5 100644
--- a/crates/typst-cli/src/world.rs
+++ b/crates/typst-cli/src/world.rs
@@ -19,6 +19,7 @@ use typst_timing::{timed, TimingScope};
use crate::args::{Input, SharedArgs};
use crate::compile::ExportCache;
use crate::fonts::{FontSearcher, FontSlot};
+use crate::package::PackageStorage;
/// Static `FileId` allocated for stdin.
/// This is to ensure that a file is read in the correct way.
@@ -41,6 +42,8 @@ pub struct SystemWorld {
fonts: Vec<FontSlot>,
/// Maps file ids to source files and buffers.
slots: Mutex<HashMap<FileId, FileSlot>>,
+ /// Holds information about where packages are stored.
+ package_storage: PackageStorage,
/// The current datetime if requested. This is stored here to ensure it is
/// always the same within one compilation.
/// Reset between compilations if not [`Now::Fixed`].
@@ -103,13 +106,16 @@ impl SystemWorld {
};
let mut searcher = FontSearcher::new();
- searcher.search(&command.font_paths);
+ searcher
+ .search(&command.font_args.font_paths, command.font_args.ignore_system_fonts);
let now = match command.creation_timestamp {
Some(time) => Now::Fixed(time),
None => Now::System(OnceLock::new()),
};
+ let package_storage = PackageStorage::from_args(&command.package_storage_args);
+
Ok(Self {
workdir: std::env::current_dir().ok(),
root,
@@ -118,6 +124,7 @@ impl SystemWorld {
book: LazyHash::new(searcher.book),
fonts: searcher.fonts,
slots: Mutex::new(HashMap::new()),
+ package_storage,
now,
export_cache: ExportCache::new(),
})
@@ -144,7 +151,9 @@ impl SystemWorld {
.get_mut()
.values()
.filter(|slot| slot.accessed())
- .filter_map(|slot| system_path(&self.root, slot.id).ok())
+ .filter_map(|slot| {
+ system_path(&self.root, slot.id, &self.package_storage).ok()
+ })
}
/// Reset the compilation state in preparation of a new compilation.
@@ -183,11 +192,11 @@ impl World for SystemWorld {
}
fn source(&self, id: FileId) -> FileResult<Source> {
- self.slot(id, |slot| slot.source(&self.root))
+ self.slot(id, |slot| slot.source(&self.root, &self.package_storage))
}
fn file(&self, id: FileId) -> FileResult<Bytes> {
- self.slot(id, |slot| slot.file(&self.root))
+ self.slot(id, |slot| slot.file(&self.root, &self.package_storage))
}
fn font(&self, index: usize) -> Option<Font> {
@@ -259,9 +268,13 @@ impl FileSlot {
}
/// Retrieve the source for this file.
- fn source(&mut self, project_root: &Path) -> FileResult<Source> {
+ fn source(
+ &mut self,
+ project_root: &Path,
+ package_storage: &PackageStorage,
+ ) -> FileResult<Source> {
self.source.get_or_init(
- || read(self.id, project_root),
+ || read(self.id, project_root, package_storage),
|data, prev| {
let name = if prev.is_some() { "reparsing file" } else { "parsing file" };
let _scope = TimingScope::new(name, None);
@@ -277,9 +290,15 @@ impl FileSlot {
}
/// Retrieve the file's bytes.
- fn file(&mut self, project_root: &Path) -> FileResult<Bytes> {
- self.file
- .get_or_init(|| read(self.id, project_root), |data, _| Ok(data.into()))
+ fn file(
+ &mut self,
+ project_root: &Path,
+ package_storage: &PackageStorage,
+ ) -> FileResult<Bytes> {
+ self.file.get_or_init(
+ || read(self.id, project_root, package_storage),
+ |data, _| Ok(data.into()),
+ )
}
}
@@ -344,13 +363,17 @@ impl<T: Clone> SlotCell<T> {
/// Resolves the path of a file id on the system, downloading a package if
/// necessary.
-fn system_path(project_root: &Path, id: FileId) -> FileResult<PathBuf> {
+fn system_path(
+ project_root: &Path,
+ id: FileId,
+ package_storage: &PackageStorage,
+) -> FileResult<PathBuf> {
// Determine the root path relative to which the file path
// will be resolved.
let buf;
let mut root = project_root;
if let Some(spec) = id.package() {
- buf = crate::package::prepare_package(spec)?;
+ buf = package_storage.prepare_package(spec)?;
root = &buf;
}
@@ -363,11 +386,15 @@ fn system_path(project_root: &Path, id: FileId) -> FileResult<PathBuf> {
///
/// If the ID represents stdin it will read from standard input,
/// otherwise it gets the file path of the ID and reads the file from disk.
-fn read(id: FileId, project_root: &Path) -> FileResult<Vec<u8>> {
+fn read(
+ id: FileId,
+ project_root: &Path,
+ package_storage: &PackageStorage,
+) -> FileResult<Vec<u8>> {
if id == *STDIN_ID {
read_from_stdin()
} else {
- read_from_disk(&system_path(project_root, id)?)
+ read_from_disk(&system_path(project_root, id, package_storage)?)
}
}