mirror of
https://github.com/typst/typst
synced 2025-05-18 11:05:28 +08:00
parent
756bdb623c
commit
afc95ed19d
9
Cargo.lock
generated
9
Cargo.lock
generated
@ -1468,7 +1468,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "d16814a067484415fda653868c9be0ac5f2abd2ef5d951082a5f2fe1b3662944"
|
checksum = "d16814a067484415fda653868c9be0ac5f2abd2ef5d951082a5f2fe1b3662944"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"is-wsl",
|
"is-wsl",
|
||||||
"pathdiff",
|
"pathdiff 0.2.1",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1523,6 +1523,12 @@ version = "1.0.12"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
|
checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pathdiff"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a3bf70094d203e07844da868b634207e71bfab254fe713171fae9a6e751ccf31"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "pathdiff"
|
name = "pathdiff"
|
||||||
version = "0.2.1"
|
version = "0.2.1"
|
||||||
@ -2631,6 +2637,7 @@ dependencies = [
|
|||||||
"notify",
|
"notify",
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"open",
|
"open",
|
||||||
|
"pathdiff 0.1.0",
|
||||||
"same-file",
|
"same-file",
|
||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
|
@ -33,6 +33,7 @@ memmap2 = "0.5"
|
|||||||
notify = "5"
|
notify = "5"
|
||||||
once_cell = "1"
|
once_cell = "1"
|
||||||
open = "4.0.2"
|
open = "4.0.2"
|
||||||
|
pathdiff = "0.1"
|
||||||
same-file = "1"
|
same-file = "1"
|
||||||
serde = "1.0.184"
|
serde = "1.0.184"
|
||||||
serde_json = "1"
|
serde_json = "1"
|
||||||
|
@ -231,11 +231,23 @@ pub fn print_diagnostics(
|
|||||||
|
|
||||||
impl<'a> codespan_reporting::files::Files<'a> for SystemWorld {
|
impl<'a> codespan_reporting::files::Files<'a> for SystemWorld {
|
||||||
type FileId = FileId;
|
type FileId = FileId;
|
||||||
type Name = FileId;
|
type Name = String;
|
||||||
type Source = Source;
|
type Source = Source;
|
||||||
|
|
||||||
fn name(&'a self, id: FileId) -> CodespanResult<Self::Name> {
|
fn name(&'a self, id: FileId) -> CodespanResult<Self::Name> {
|
||||||
Ok(id)
|
let vpath = id.vpath();
|
||||||
|
Ok(if let Some(package) = id.package() {
|
||||||
|
format!("{package}{}", vpath.as_rooted_path().display())
|
||||||
|
} else {
|
||||||
|
// Try to express the path relative to the working directory.
|
||||||
|
vpath
|
||||||
|
.resolve(self.root())
|
||||||
|
.and_then(|abs| pathdiff::diff_paths(&abs, self.workdir()))
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or_else(|| vpath.as_rootless_path())
|
||||||
|
.to_string_lossy()
|
||||||
|
.into()
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn source(&'a self, id: FileId) -> CodespanResult<Self::Source> {
|
fn source(&'a self, id: FileId) -> CodespanResult<Self::Source> {
|
||||||
|
@ -11,8 +11,7 @@ use siphasher::sip128::{Hasher128, SipHasher13};
|
|||||||
use typst::diag::{FileError, FileResult, StrResult};
|
use typst::diag::{FileError, FileResult, StrResult};
|
||||||
use typst::eval::{eco_format, Bytes, Datetime, Library};
|
use typst::eval::{eco_format, Bytes, Datetime, Library};
|
||||||
use typst::font::{Font, FontBook};
|
use typst::font::{Font, FontBook};
|
||||||
use typst::syntax::{FileId, Source};
|
use typst::syntax::{FileId, Source, VirtualPath};
|
||||||
use typst::util::PathExt;
|
|
||||||
use typst::World;
|
use typst::World;
|
||||||
|
|
||||||
use crate::args::SharedArgs;
|
use crate::args::SharedArgs;
|
||||||
@ -21,6 +20,8 @@ use crate::package::prepare_package;
|
|||||||
|
|
||||||
/// A world that provides access to the operating system.
|
/// A world that provides access to the operating system.
|
||||||
pub struct SystemWorld {
|
pub struct SystemWorld {
|
||||||
|
/// The working directory.
|
||||||
|
workdir: Option<PathBuf>,
|
||||||
/// The root relative to which absolute paths are resolved.
|
/// The root relative to which absolute paths are resolved.
|
||||||
root: PathBuf,
|
root: PathBuf,
|
||||||
/// The input path.
|
/// The input path.
|
||||||
@ -49,7 +50,7 @@ impl SystemWorld {
|
|||||||
searcher.search(&command.font_paths);
|
searcher.search(&command.font_paths);
|
||||||
|
|
||||||
// Resolve the system-global input path.
|
// Resolve the system-global input path.
|
||||||
let system_input = command.input.canonicalize().map_err(|_| {
|
let input = command.input.canonicalize().map_err(|_| {
|
||||||
eco_format!("input file not found (searched at {})", command.input.display())
|
eco_format!("input file not found (searched at {})", command.input.display())
|
||||||
})?;
|
})?;
|
||||||
|
|
||||||
@ -58,22 +59,21 @@ impl SystemWorld {
|
|||||||
let path = command
|
let path = command
|
||||||
.root
|
.root
|
||||||
.as_deref()
|
.as_deref()
|
||||||
.or_else(|| system_input.parent())
|
.or_else(|| input.parent())
|
||||||
.unwrap_or(Path::new("."));
|
.unwrap_or(Path::new("."));
|
||||||
path.canonicalize().map_err(|_| {
|
path.canonicalize().map_err(|_| {
|
||||||
eco_format!("root directory not found (searched at {})", path.display())
|
eco_format!("root directory not found (searched at {})", path.display())
|
||||||
})?
|
})?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Resolve the input path within the project.
|
// Resolve the virtual path of the main file within the project root.
|
||||||
let project_input = system_input
|
let main_path = VirtualPath::within_root(&input, &root)
|
||||||
.strip_prefix(&root)
|
.ok_or("input file must be contained in project root")?;
|
||||||
.map(|path| Path::new("/").join(path))
|
|
||||||
.map_err(|_| "input file must be contained in project root")?;
|
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
|
workdir: std::env::current_dir().ok(),
|
||||||
root,
|
root,
|
||||||
main: FileId::new(None, &project_input),
|
main: FileId::new(None, main_path),
|
||||||
library: Prehashed::new(typst_library::build()),
|
library: Prehashed::new(typst_library::build()),
|
||||||
book: Prehashed::new(searcher.book),
|
book: Prehashed::new(searcher.book),
|
||||||
fonts: searcher.fonts,
|
fonts: searcher.fonts,
|
||||||
@ -88,6 +88,16 @@ impl SystemWorld {
|
|||||||
self.main
|
self.main
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The root relative to which absolute paths are resolved.
|
||||||
|
pub fn root(&self) -> &Path {
|
||||||
|
&self.root
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The current working directory.
|
||||||
|
pub fn workdir(&self) -> &Path {
|
||||||
|
self.workdir.as_deref().unwrap_or(Path::new("."))
|
||||||
|
}
|
||||||
|
|
||||||
/// Return all paths the last compilation depended on.
|
/// Return all paths the last compilation depended on.
|
||||||
pub fn dependencies(&mut self) -> impl Iterator<Item = &Path> {
|
pub fn dependencies(&mut self) -> impl Iterator<Item = &Path> {
|
||||||
self.paths.get_mut().values().map(|slot| slot.system_path.as_path())
|
self.paths.get_mut().values().map(|slot| slot.system_path.as_path())
|
||||||
@ -160,15 +170,16 @@ impl SystemWorld {
|
|||||||
.or_insert_with(|| {
|
.or_insert_with(|| {
|
||||||
// Determine the root path relative to which the file path
|
// Determine the root path relative to which the file path
|
||||||
// will be resolved.
|
// will be resolved.
|
||||||
let root = match id.package() {
|
let buf;
|
||||||
Some(spec) => prepare_package(spec)?,
|
let mut root = &self.root;
|
||||||
None => self.root.clone(),
|
if let Some(spec) = id.package() {
|
||||||
};
|
buf = prepare_package(spec)?;
|
||||||
|
root = &buf;
|
||||||
|
}
|
||||||
|
|
||||||
// Join the path to the root. If it tries to escape, deny
|
// Join the path to the root. If it tries to escape, deny
|
||||||
// access. Note: It can still escape via symlinks.
|
// access. Note: It can still escape via symlinks.
|
||||||
system_path =
|
system_path = id.vpath().resolve(root).ok_or(FileError::AccessDenied)?;
|
||||||
root.join_rooted(id.path()).ok_or(FileError::AccessDenied)?;
|
|
||||||
|
|
||||||
PathHash::new(&system_path)
|
PathHash::new(&system_path)
|
||||||
})
|
})
|
||||||
|
@ -7,7 +7,7 @@ use typst::diag::FileResult;
|
|||||||
use typst::eval::{Bytes, Datetime, Tracer};
|
use typst::eval::{Bytes, Datetime, Tracer};
|
||||||
use typst::font::{Font, FontBook};
|
use typst::font::{Font, FontBook};
|
||||||
use typst::geom::{Point, Size};
|
use typst::geom::{Point, Size};
|
||||||
use typst::syntax::{FileId, Source};
|
use typst::syntax::{FileId, Source, VirtualPath};
|
||||||
use typst::World;
|
use typst::World;
|
||||||
use yaml_front_matter::YamlFrontMatter;
|
use yaml_front_matter::YamlFrontMatter;
|
||||||
|
|
||||||
@ -424,7 +424,7 @@ fn code_block(resolver: &dyn Resolver, lang: &str, text: &str) -> Html {
|
|||||||
return Html::new(format!("<pre>{}</pre>", highlighted.as_str()));
|
return Html::new(format!("<pre>{}</pre>", highlighted.as_str()));
|
||||||
}
|
}
|
||||||
|
|
||||||
let id = FileId::new(None, Path::new("/main.typ"));
|
let id = FileId::new(None, VirtualPath::new("main.typ"));
|
||||||
let source = Source::new(id, compile);
|
let source = Source::new(id, compile);
|
||||||
let world = DocWorld(source);
|
let world = DocWorld(source);
|
||||||
let mut tracer = Tracer::default();
|
let mut tracer = Tracer::default();
|
||||||
@ -498,8 +498,8 @@ impl World for DocWorld {
|
|||||||
fn file(&self, id: FileId) -> FileResult<Bytes> {
|
fn file(&self, id: FileId) -> FileResult<Bytes> {
|
||||||
assert!(id.package().is_none());
|
assert!(id.package().is_none());
|
||||||
Ok(FILES
|
Ok(FILES
|
||||||
.get_file(id.path().strip_prefix("/").unwrap())
|
.get_file(id.vpath().as_rootless_path())
|
||||||
.unwrap_or_else(|| panic!("failed to load {:?}", id.path().display()))
|
.unwrap_or_else(|| panic!("failed to load {:?}", id.vpath()))
|
||||||
.contents()
|
.contents()
|
||||||
.into())
|
.into())
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,9 @@ use super::is_ident;
|
|||||||
static INTERNER: Lazy<RwLock<Interner>> =
|
static INTERNER: Lazy<RwLock<Interner>> =
|
||||||
Lazy::new(|| RwLock::new(Interner { to_id: HashMap::new(), from_id: Vec::new() }));
|
Lazy::new(|| RwLock::new(Interner { to_id: HashMap::new(), from_id: Vec::new() }));
|
||||||
|
|
||||||
|
/// The path that we use for detached file ids.
|
||||||
|
static DETACHED_PATH: Lazy<VirtualPath> = Lazy::new(|| VirtualPath::new("/unknown"));
|
||||||
|
|
||||||
/// A package-path interner.
|
/// A package-path interner.
|
||||||
struct Interner {
|
struct Interner {
|
||||||
to_id: HashMap<Pair, FileId>,
|
to_id: HashMap<Pair, FileId>,
|
||||||
@ -23,7 +26,7 @@ struct Interner {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An interned pair of a package specification and a path.
|
/// An interned pair of a package specification and a path.
|
||||||
type Pair = &'static (Option<PackageSpec>, PathBuf);
|
type Pair = &'static (Option<PackageSpec>, VirtualPath);
|
||||||
|
|
||||||
/// Identifies a file in a project or package.
|
/// Identifies a file in a project or package.
|
||||||
///
|
///
|
||||||
@ -37,16 +40,9 @@ impl FileId {
|
|||||||
/// The path must start with a `/` or this function will panic.
|
/// The path must start with a `/` or this function will panic.
|
||||||
/// Note that the path is normalized before interning.
|
/// Note that the path is normalized before interning.
|
||||||
#[track_caller]
|
#[track_caller]
|
||||||
pub fn new(package: Option<PackageSpec>, path: &Path) -> Self {
|
pub fn new(package: Option<PackageSpec>, path: VirtualPath) -> Self {
|
||||||
assert_eq!(
|
|
||||||
path.components().next(),
|
|
||||||
Some(std::path::Component::RootDir),
|
|
||||||
"file path must be absolute within project or package: {}",
|
|
||||||
path.display(),
|
|
||||||
);
|
|
||||||
|
|
||||||
// Try to find an existing entry that we can reuse.
|
// Try to find an existing entry that we can reuse.
|
||||||
let pair = (package, normalize_path(path));
|
let pair = (package, path);
|
||||||
if let Some(&id) = INTERNER.read().unwrap().to_id.get(&pair) {
|
if let Some(&id) = INTERNER.read().unwrap().to_id.get(&pair) {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@ -88,9 +84,9 @@ impl FileId {
|
|||||||
|
|
||||||
/// The absolute and normalized path to the file _within_ the project or
|
/// The absolute and normalized path to the file _within_ the project or
|
||||||
/// package.
|
/// package.
|
||||||
pub fn path(&self) -> &'static Path {
|
pub fn vpath(&self) -> &'static VirtualPath {
|
||||||
if self.is_detached() {
|
if self.is_detached() {
|
||||||
Path::new("/detached.typ")
|
&DETACHED_PATH
|
||||||
} else {
|
} else {
|
||||||
&self.pair().1
|
&self.pair().1
|
||||||
}
|
}
|
||||||
@ -102,13 +98,7 @@ impl FileId {
|
|||||||
Err("cannot access file system from here")?;
|
Err("cannot access file system from here")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
let package = self.package().cloned();
|
Ok(Self::new(self.package().cloned(), self.vpath().join(path)))
|
||||||
let base = self.path();
|
|
||||||
Ok(if let Some(parent) = base.parent() {
|
|
||||||
Self::new(package, &parent.join(path))
|
|
||||||
} else {
|
|
||||||
Self::new(package, Path::new(path))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Construct from a raw number.
|
/// Construct from a raw number.
|
||||||
@ -127,27 +117,35 @@ impl FileId {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Display for FileId {
|
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
|
||||||
let path = self.path().display();
|
|
||||||
match self.package() {
|
|
||||||
Some(package) => write!(f, "{package}{path}"),
|
|
||||||
None => write!(f, "{path}"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Debug for FileId {
|
impl Debug for FileId {
|
||||||
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
Display::fmt(self, f)
|
let vpath = self.vpath();
|
||||||
|
match self.package() {
|
||||||
|
Some(package) => write!(f, "{package:?}{vpath:?}"),
|
||||||
|
None => write!(f, "{vpath:?}"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lexically normalize a path.
|
/// An absolute path in the virtual file system of a project or package.
|
||||||
fn normalize_path(path: &Path) -> PathBuf {
|
#[derive(Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
let mut out = PathBuf::new();
|
pub struct VirtualPath(PathBuf);
|
||||||
|
|
||||||
|
impl VirtualPath {
|
||||||
|
/// Create a new virtual path.
|
||||||
|
///
|
||||||
|
/// Even if it doesn't start with `/` or `\`, it is still interpreted as
|
||||||
|
/// starting from the root.
|
||||||
|
pub fn new(path: impl AsRef<Path>) -> Self {
|
||||||
|
Self::new_impl(path.as_ref())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Non generic new implementation.
|
||||||
|
fn new_impl(path: &Path) -> Self {
|
||||||
|
let mut out = Path::new(&Component::RootDir).to_path_buf();
|
||||||
for component in path.components() {
|
for component in path.components() {
|
||||||
match component {
|
match component {
|
||||||
|
Component::Prefix(_) | Component::RootDir => {}
|
||||||
Component::CurDir => {}
|
Component::CurDir => {}
|
||||||
Component::ParentDir => match out.components().next_back() {
|
Component::ParentDir => match out.components().next_back() {
|
||||||
Some(Component::Normal(_)) => {
|
Some(Component::Normal(_)) => {
|
||||||
@ -155,19 +153,74 @@ fn normalize_path(path: &Path) -> PathBuf {
|
|||||||
}
|
}
|
||||||
_ => out.push(component),
|
_ => out.push(component),
|
||||||
},
|
},
|
||||||
Component::Prefix(_) | Component::RootDir | Component::Normal(_) => {
|
Component::Normal(_) => out.push(component),
|
||||||
out.push(component)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Self(out)
|
||||||
}
|
}
|
||||||
if out.as_os_str().is_empty() {
|
|
||||||
out.push(Component::CurDir);
|
/// Create a virtual path from a real path and a real root.
|
||||||
|
///
|
||||||
|
/// Returns `None` if the file path is not contained in the root (i.e. if
|
||||||
|
/// `root` is not a lexical prefix of `path`). No file system operations are
|
||||||
|
/// performed.
|
||||||
|
pub fn within_root(path: &Path, root: &Path) -> Option<Self> {
|
||||||
|
path.strip_prefix(root).ok().map(Self::new)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the underlying path with a leading `/` or `\`.
|
||||||
|
pub fn as_rooted_path(&self) -> &Path {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the underlying path without a leading `/` or `\`.
|
||||||
|
pub fn as_rootless_path(&self) -> &Path {
|
||||||
|
self.0.strip_prefix(Component::RootDir).unwrap_or(&self.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve the virtual path relative to an actual file system root
|
||||||
|
/// (where the project or package resides).
|
||||||
|
///
|
||||||
|
/// Returns `None` if the path lexically escapes the root. The path might
|
||||||
|
/// still escape through symlinks.
|
||||||
|
pub fn resolve(&self, root: &Path) -> Option<PathBuf> {
|
||||||
|
let root_len = root.as_os_str().len();
|
||||||
|
let mut out = root.to_path_buf();
|
||||||
|
for component in self.0.components() {
|
||||||
|
match component {
|
||||||
|
Component::Prefix(_) => {}
|
||||||
|
Component::RootDir => {}
|
||||||
|
Component::CurDir => {}
|
||||||
|
Component::ParentDir => {
|
||||||
|
out.pop();
|
||||||
|
if out.as_os_str().len() < root_len {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Component::Normal(_) => out.push(component),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resolve a path relative to this virtual path.
|
||||||
|
pub fn join(&self, path: impl AsRef<Path>) -> Self {
|
||||||
|
if let Some(parent) = self.0.parent() {
|
||||||
|
Self::new(parent.join(path))
|
||||||
|
} else {
|
||||||
|
Self::new(path)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Debug for VirtualPath {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
Display::fmt(&self.0.display(), f)
|
||||||
}
|
}
|
||||||
out
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Identifies a package.
|
/// Identifies a package.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Clone, Eq, PartialEq, Hash)]
|
||||||
pub struct PackageSpec {
|
pub struct PackageSpec {
|
||||||
/// The namespace the package lives in.
|
/// The namespace the package lives in.
|
||||||
pub namespace: EcoString,
|
pub namespace: EcoString,
|
||||||
@ -217,6 +270,12 @@ impl FromStr for PackageSpec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for PackageSpec {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
Display::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for PackageSpec {
|
impl Display for PackageSpec {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "@{}/{}:{}", self.namespace, self.name, self.version)
|
write!(f, "@{}/{}:{}", self.namespace, self.name, self.version)
|
||||||
@ -224,7 +283,7 @@ impl Display for PackageSpec {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A package's version.
|
/// A package's version.
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
|
||||||
pub struct PackageVersion {
|
pub struct PackageVersion {
|
||||||
/// The package's major version.
|
/// The package's major version.
|
||||||
pub major: u32,
|
pub major: u32,
|
||||||
@ -259,6 +318,12 @@ impl FromStr for PackageVersion {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Debug for PackageVersion {
|
||||||
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
|
Display::fmt(self, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Display for PackageVersion {
|
impl Display for PackageVersion {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
|
write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
|
||||||
|
@ -11,7 +11,7 @@ mod reparser;
|
|||||||
mod source;
|
mod source;
|
||||||
mod span;
|
mod span;
|
||||||
|
|
||||||
pub use self::file::{FileId, PackageSpec, PackageVersion};
|
pub use self::file::{FileId, PackageSpec, PackageVersion, VirtualPath};
|
||||||
pub use self::kind::SyntaxKind;
|
pub use self::kind::SyntaxKind;
|
||||||
pub use self::lexer::{is_id_continue, is_id_start, is_ident, is_newline};
|
pub use self::lexer::{is_id_continue, is_id_start, is_ident, is_newline};
|
||||||
pub use self::node::{LinkedChildren, LinkedNode, SyntaxError, SyntaxNode};
|
pub use self::node::{LinkedChildren, LinkedNode, SyntaxError, SyntaxNode};
|
||||||
|
@ -241,7 +241,7 @@ impl Source {
|
|||||||
|
|
||||||
impl Debug for Source {
|
impl Debug for Source {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "Source({})", self.id().path().display())
|
write!(f, "Source({:?})", self.id().vpath())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,7 +63,6 @@ pub use self::value::{Dynamic, Type, Value};
|
|||||||
|
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::path::Path;
|
|
||||||
|
|
||||||
use comemo::{Track, Tracked, TrackedMut, Validate};
|
use comemo::{Track, Tracked, TrackedMut, Validate};
|
||||||
use ecow::{EcoString, EcoVec};
|
use ecow::{EcoString, EcoVec};
|
||||||
@ -82,7 +81,7 @@ use crate::model::{
|
|||||||
use crate::syntax::ast::{self, AstNode};
|
use crate::syntax::ast::{self, AstNode};
|
||||||
use crate::syntax::{
|
use crate::syntax::{
|
||||||
parse, parse_code, parse_math, FileId, PackageSpec, PackageVersion, Source, Span,
|
parse, parse_code, parse_math, FileId, PackageSpec, PackageVersion, Source, Span,
|
||||||
Spanned, SyntaxKind, SyntaxNode,
|
Spanned, SyntaxKind, SyntaxNode, VirtualPath,
|
||||||
};
|
};
|
||||||
use crate::World;
|
use crate::World;
|
||||||
|
|
||||||
@ -101,7 +100,7 @@ pub fn eval(
|
|||||||
// Prevent cyclic evaluation.
|
// Prevent cyclic evaluation.
|
||||||
let id = source.id();
|
let id = source.id();
|
||||||
if route.contains(id) {
|
if route.contains(id) {
|
||||||
panic!("Tried to cyclicly evaluate {}", id.path().display());
|
panic!("Tried to cyclicly evaluate {:?}", id.vpath());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hook up the lang items.
|
// Hook up the lang items.
|
||||||
@ -141,7 +140,13 @@ pub fn eval(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Assemble the module.
|
// Assemble the module.
|
||||||
let name = id.path().file_stem().unwrap_or_default().to_string_lossy();
|
let name = id
|
||||||
|
.vpath()
|
||||||
|
.as_rootless_path()
|
||||||
|
.file_stem()
|
||||||
|
.unwrap_or_default()
|
||||||
|
.to_string_lossy();
|
||||||
|
|
||||||
Ok(Module::new(name).with_scope(vm.scopes.top).with_content(output))
|
Ok(Module::new(name).with_scope(vm.scopes.top).with_content(output))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1798,7 +1803,7 @@ fn import(
|
|||||||
/// Import an external package.
|
/// Import an external package.
|
||||||
fn import_package(vm: &mut Vm, spec: PackageSpec, span: Span) -> SourceResult<Module> {
|
fn import_package(vm: &mut Vm, spec: PackageSpec, span: Span) -> SourceResult<Module> {
|
||||||
// Evaluate the manifest.
|
// Evaluate the manifest.
|
||||||
let manifest_id = FileId::new(Some(spec.clone()), Path::new("/typst.toml"));
|
let manifest_id = FileId::new(Some(spec.clone()), VirtualPath::new("typst.toml"));
|
||||||
let bytes = vm.world().file(manifest_id).at(span)?;
|
let bytes = vm.world().file(manifest_id).at(span)?;
|
||||||
let manifest = PackageManifest::parse(&bytes).at(span)?;
|
let manifest = PackageManifest::parse(&bytes).at(span)?;
|
||||||
manifest.validate(&spec).at(span)?;
|
manifest.validate(&spec).at(span)?;
|
||||||
|
@ -5,7 +5,6 @@ pub mod fat;
|
|||||||
use std::fmt::{self, Debug, Formatter};
|
use std::fmt::{self, Debug, Formatter};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::path::{Component, Path, PathBuf};
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use siphasher::sip128::{Hasher128, SipHasher13};
|
use siphasher::sip128::{Hasher128, SipHasher13};
|
||||||
@ -104,40 +103,6 @@ where
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extra methods for [`Path`].
|
|
||||||
pub trait PathExt {
|
|
||||||
/// Treat `self` as a virtual root relative to which the `path` is resolved.
|
|
||||||
///
|
|
||||||
/// Returns `None` if the path lexically escapes the root. The path
|
|
||||||
/// might still escape through symlinks.
|
|
||||||
fn join_rooted(&self, path: &Path) -> Option<PathBuf>;
|
|
||||||
}
|
|
||||||
|
|
||||||
impl PathExt for Path {
|
|
||||||
fn join_rooted(&self, path: &Path) -> Option<PathBuf> {
|
|
||||||
let mut parts: Vec<_> = self.components().collect();
|
|
||||||
let root = parts.len();
|
|
||||||
for component in path.components() {
|
|
||||||
match component {
|
|
||||||
Component::Prefix(_) => return None,
|
|
||||||
Component::RootDir => parts.truncate(root),
|
|
||||||
Component::CurDir => {}
|
|
||||||
Component::ParentDir => {
|
|
||||||
if parts.len() <= root {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
parts.pop();
|
|
||||||
}
|
|
||||||
Component::Normal(_) => parts.push(component),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if parts.len() < root {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
Some(parts.into_iter().collect())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Format pieces separated with commas and a final "and" or "or".
|
/// Format pieces separated with commas and a final "and" or "or".
|
||||||
pub fn separated_list(pieces: &[impl AsRef<str>], last: &str) -> String {
|
pub fn separated_list(pieces: &[impl AsRef<str>], last: &str) -> String {
|
||||||
let mut buf = String::new();
|
let mut buf = String::new();
|
||||||
|
@ -25,8 +25,7 @@ use typst::doc::{Document, Frame, FrameItem, Meta};
|
|||||||
use typst::eval::{eco_format, func, Bytes, Datetime, Library, NoneValue, Tracer, Value};
|
use typst::eval::{eco_format, func, Bytes, Datetime, Library, NoneValue, Tracer, Value};
|
||||||
use typst::font::{Font, FontBook};
|
use typst::font::{Font, FontBook};
|
||||||
use typst::geom::{Abs, Color, RgbaColor, Smart};
|
use typst::geom::{Abs, Color, RgbaColor, Smart};
|
||||||
use typst::syntax::{FileId, Source, Span, SyntaxNode};
|
use typst::syntax::{FileId, Source, Span, SyntaxNode, VirtualPath};
|
||||||
use typst::util::PathExt;
|
|
||||||
use typst::World;
|
use typst::World;
|
||||||
use typst_library::layout::{Margin, PageElem};
|
use typst_library::layout::{Margin, PageElem};
|
||||||
use typst_library::text::{TextElem, TextSize};
|
use typst_library::text::{TextElem, TextSize};
|
||||||
@ -289,7 +288,7 @@ impl World for TestWorld {
|
|||||||
|
|
||||||
impl TestWorld {
|
impl TestWorld {
|
||||||
fn set(&mut self, path: &Path, text: String) -> Source {
|
fn set(&mut self, path: &Path, text: String) -> Source {
|
||||||
self.main = FileId::new(None, &Path::new("/").join(path));
|
self.main = FileId::new(None, VirtualPath::new(path));
|
||||||
let mut slot = self.slot(self.main).unwrap();
|
let mut slot = self.slot(self.main).unwrap();
|
||||||
let source = Source::new(self.main, text);
|
let source = Source::new(self.main, text);
|
||||||
slot.source = OnceCell::from(Ok(source.clone()));
|
slot.source = OnceCell::from(Ok(source.clone()));
|
||||||
@ -302,7 +301,7 @@ impl TestWorld {
|
|||||||
None => PathBuf::new(),
|
None => PathBuf::new(),
|
||||||
};
|
};
|
||||||
|
|
||||||
let system_path = root.join_rooted(id.path()).ok_or(FileError::AccessDenied)?;
|
let system_path = id.vpath().resolve(&root).ok_or(FileError::AccessDenied)?;
|
||||||
|
|
||||||
Ok(RefMut::map(self.paths.borrow_mut(), |paths| {
|
Ok(RefMut::map(self.paths.borrow_mut(), |paths| {
|
||||||
paths.entry(system_path.clone()).or_insert_with(|| PathSlot {
|
paths.entry(system_path.clone()).or_insert_with(|| PathSlot {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user