mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Change the signature of World::main
(#4531)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
parent
4d8976b619
commit
a3f3a1a833
@ -5,16 +5,16 @@ use std::path::{Path, PathBuf};
|
|||||||
use chrono::{Datelike, Timelike};
|
use chrono::{Datelike, Timelike};
|
||||||
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
use codespan_reporting::diagnostic::{Diagnostic, Label};
|
||||||
use codespan_reporting::term;
|
use codespan_reporting::term;
|
||||||
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
|
use ecow::{eco_format, EcoString};
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
use rayon::iter::{IntoParallelRefIterator, ParallelIterator};
|
||||||
use typst::diag::{bail, FileError, Severity, SourceDiagnostic, StrResult, Warned};
|
use typst::diag::{bail, Severity, SourceDiagnostic, StrResult, Warned};
|
||||||
use typst::foundations::{Datetime, Smart};
|
use typst::foundations::{Datetime, Smart};
|
||||||
use typst::layout::{Frame, PageRanges};
|
use typst::layout::{Frame, PageRanges};
|
||||||
use typst::model::Document;
|
use typst::model::Document;
|
||||||
use typst::syntax::{FileId, Source, Span};
|
use typst::syntax::{FileId, Source, Span};
|
||||||
use typst::visualize::Color;
|
use typst::visualize::Color;
|
||||||
use typst::{World, WorldExt};
|
use typst::WorldExt;
|
||||||
|
|
||||||
use crate::args::{
|
use crate::args::{
|
||||||
CompileCommand, DiagnosticFormat, Input, Output, OutputFormat, PageRangeArgument,
|
CompileCommand, DiagnosticFormat, Input, Output, OutputFormat, PageRangeArgument,
|
||||||
@ -96,21 +96,6 @@ pub fn compile_once(
|
|||||||
Status::Compiling.print(command).unwrap();
|
Status::Compiling.print(command).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(errors) = world
|
|
||||||
.source(world.main())
|
|
||||||
.map_err(|err| hint_invalid_main_file(err, &command.common.input))
|
|
||||||
{
|
|
||||||
set_failed();
|
|
||||||
if watching {
|
|
||||||
Status::Error.print(command).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
print_diagnostics(world, &errors, &[], command.common.diagnostic_format)
|
|
||||||
.map_err(|err| eco_format!("failed to print diagnostics ({err})"))?;
|
|
||||||
|
|
||||||
return Ok(());
|
|
||||||
}
|
|
||||||
|
|
||||||
let Warned { output, warnings } = typst::compile(world);
|
let Warned { output, warnings } = typst::compile(world);
|
||||||
|
|
||||||
match output {
|
match output {
|
||||||
@ -498,52 +483,6 @@ fn open_file(open: Option<&str>, path: &Path) -> StrResult<()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Adds useful hints when the main source file couldn't be read
|
|
||||||
/// and returns the final diagnostic.
|
|
||||||
fn hint_invalid_main_file(
|
|
||||||
file_error: FileError,
|
|
||||||
input: &Input,
|
|
||||||
) -> EcoVec<SourceDiagnostic> {
|
|
||||||
let is_utf8_error = matches!(file_error, FileError::InvalidUtf8);
|
|
||||||
let mut diagnostic =
|
|
||||||
SourceDiagnostic::error(Span::detached(), EcoString::from(file_error));
|
|
||||||
|
|
||||||
// Attempt to provide helpful hints for UTF-8 errors.
|
|
||||||
// Perhaps the user mistyped the filename.
|
|
||||||
// For example, they could have written "file.pdf" instead of
|
|
||||||
// "file.typ".
|
|
||||||
if is_utf8_error {
|
|
||||||
if let Input::Path(path) = input {
|
|
||||||
let extension = path.extension();
|
|
||||||
if extension.is_some_and(|extension| extension == "typ") {
|
|
||||||
// No hints if the file is already a .typ file.
|
|
||||||
// The file is indeed just invalid.
|
|
||||||
return eco_vec![diagnostic];
|
|
||||||
}
|
|
||||||
|
|
||||||
match extension {
|
|
||||||
Some(extension) => {
|
|
||||||
diagnostic.hint(eco_format!(
|
|
||||||
"a file with the `.{}` extension is not usually a Typst file",
|
|
||||||
extension.to_string_lossy()
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
None => {
|
|
||||||
diagnostic
|
|
||||||
.hint("a file without an extension is not usually a Typst file");
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if path.with_extension("typ").exists() {
|
|
||||||
diagnostic.hint("check if you meant to use the `.typ` extension instead");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
eco_vec![diagnostic]
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Print diagnostic messages to the terminal.
|
/// Print diagnostic messages to the terminal.
|
||||||
pub fn print_diagnostics(
|
pub fn print_diagnostics(
|
||||||
world: &SystemWorld,
|
world: &SystemWorld,
|
||||||
|
@ -192,8 +192,8 @@ impl World for SystemWorld {
|
|||||||
&self.book
|
&self.book
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(&self) -> Source {
|
fn main(&self) -> FileId {
|
||||||
self.source(self.main).unwrap()
|
self.main
|
||||||
}
|
}
|
||||||
|
|
||||||
fn source(&self, id: FileId) -> FileResult<Source> {
|
fn source(&self, id: FileId) -> FileResult<Source> {
|
||||||
|
@ -139,8 +139,8 @@ mod tests {
|
|||||||
&self.base.book
|
&self.base.book
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(&self) -> Source {
|
fn main(&self) -> FileId {
|
||||||
self.main.clone()
|
self.main.id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn source(&self, id: FileId) -> FileResult<Source> {
|
fn source(&self, id: FileId) -> FileResult<Source> {
|
||||||
|
@ -91,6 +91,11 @@ impl FileId {
|
|||||||
Self::new(self.package().cloned(), self.vpath().join(path))
|
Self::new(self.package().cloned(), self.vpath().join(path))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The same file location, but with a different extension.
|
||||||
|
pub fn with_extension(&self, extension: &str) -> Self {
|
||||||
|
Self::new(self.package().cloned(), self.vpath().with_extension(extension))
|
||||||
|
}
|
||||||
|
|
||||||
/// Construct from a raw number.
|
/// Construct from a raw number.
|
||||||
pub(crate) const fn from_raw(v: u16) -> Self {
|
pub(crate) const fn from_raw(v: u16) -> Self {
|
||||||
Self(v)
|
Self(v)
|
||||||
|
@ -85,6 +85,11 @@ impl VirtualPath {
|
|||||||
Self::new(path)
|
Self::new(path)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The same path, but with a different extension.
|
||||||
|
pub fn with_extension(&self, extension: &str) -> Self {
|
||||||
|
Self(self.0.with_extension(extension))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Debug for VirtualPath {
|
impl Debug for VirtualPath {
|
||||||
|
@ -60,10 +60,12 @@ use std::collections::HashSet;
|
|||||||
use std::ops::{Deref, Range};
|
use std::ops::{Deref, Range};
|
||||||
|
|
||||||
use comemo::{Track, Tracked, Validate};
|
use comemo::{Track, Tracked, Validate};
|
||||||
use ecow::{EcoString, EcoVec};
|
use ecow::{eco_format, eco_vec, EcoString, EcoVec};
|
||||||
use typst_timing::{timed, TimingScope};
|
use typst_timing::{timed, TimingScope};
|
||||||
|
|
||||||
use crate::diag::{warning, FileResult, SourceDiagnostic, SourceResult, Warned};
|
use crate::diag::{
|
||||||
|
warning, FileError, FileResult, SourceDiagnostic, SourceResult, Warned,
|
||||||
|
};
|
||||||
use crate::engine::{Engine, Route, Sink, Traced};
|
use crate::engine::{Engine, Route, Sink, Traced};
|
||||||
use crate::foundations::{
|
use crate::foundations::{
|
||||||
Array, Bytes, Datetime, Dict, Module, Scope, StyleChain, Styles, Value,
|
Array, Bytes, Datetime, Dict, Module, Scope, StyleChain, Styles, Value,
|
||||||
@ -108,13 +110,19 @@ fn compile_inner(
|
|||||||
let library = world.library();
|
let library = world.library();
|
||||||
let styles = StyleChain::new(&library.styles);
|
let styles = StyleChain::new(&library.styles);
|
||||||
|
|
||||||
|
// Fetch the main source file once.
|
||||||
|
let main = world.main();
|
||||||
|
let main = world
|
||||||
|
.source(main)
|
||||||
|
.map_err(|err| hint_invalid_main_file(world, err, main))?;
|
||||||
|
|
||||||
// First evaluate the main source file into a module.
|
// First evaluate the main source file into a module.
|
||||||
let content = crate::eval::eval(
|
let content = crate::eval::eval(
|
||||||
world,
|
world,
|
||||||
traced,
|
traced,
|
||||||
sink.track_mut(),
|
sink.track_mut(),
|
||||||
Route::default().track(),
|
Route::default().track(),
|
||||||
&world.main(),
|
&main,
|
||||||
)?
|
)?
|
||||||
.content();
|
.content();
|
||||||
|
|
||||||
@ -203,8 +211,8 @@ pub trait World: Send + Sync {
|
|||||||
/// Metadata about all known fonts.
|
/// Metadata about all known fonts.
|
||||||
fn book(&self) -> &LazyHash<FontBook>;
|
fn book(&self) -> &LazyHash<FontBook>;
|
||||||
|
|
||||||
/// Access the main source file.
|
/// Get the file id of the main source file.
|
||||||
fn main(&self) -> Source;
|
fn main(&self) -> FileId;
|
||||||
|
|
||||||
/// Try to access the specified source file.
|
/// Try to access the specified source file.
|
||||||
fn source(&self, id: FileId) -> FileResult<Source>;
|
fn source(&self, id: FileId) -> FileResult<Source>;
|
||||||
@ -246,7 +254,7 @@ macro_rules! delegate_for_ptr {
|
|||||||
self.deref().book()
|
self.deref().book()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(&self) -> Source {
|
fn main(&self) -> FileId {
|
||||||
self.deref().main()
|
self.deref().main()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -402,3 +410,48 @@ fn prelude(global: &mut Scope) {
|
|||||||
global.define("horizon", Alignment::HORIZON);
|
global.define("horizon", Alignment::HORIZON);
|
||||||
global.define("bottom", Alignment::BOTTOM);
|
global.define("bottom", Alignment::BOTTOM);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Adds useful hints when the main source file couldn't be read
|
||||||
|
/// and returns the final diagnostic.
|
||||||
|
fn hint_invalid_main_file(
|
||||||
|
world: Tracked<dyn World + '_>,
|
||||||
|
file_error: FileError,
|
||||||
|
input: FileId,
|
||||||
|
) -> EcoVec<SourceDiagnostic> {
|
||||||
|
let is_utf8_error = matches!(file_error, FileError::InvalidUtf8);
|
||||||
|
let mut diagnostic =
|
||||||
|
SourceDiagnostic::error(Span::detached(), EcoString::from(file_error));
|
||||||
|
|
||||||
|
// Attempt to provide helpful hints for UTF-8 errors. Perhaps the user
|
||||||
|
// mistyped the filename. For example, they could have written "file.pdf"
|
||||||
|
// instead of "file.typ".
|
||||||
|
if is_utf8_error {
|
||||||
|
let path = input.vpath();
|
||||||
|
let extension = path.as_rootless_path().extension();
|
||||||
|
if extension.is_some_and(|extension| extension == "typ") {
|
||||||
|
// No hints if the file is already a .typ file.
|
||||||
|
// The file is indeed just invalid.
|
||||||
|
return eco_vec![diagnostic];
|
||||||
|
}
|
||||||
|
|
||||||
|
match extension {
|
||||||
|
Some(extension) => {
|
||||||
|
diagnostic.hint(eco_format!(
|
||||||
|
"a file with the `.{}` extension is not usually a Typst file",
|
||||||
|
extension.to_string_lossy()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
None => {
|
||||||
|
diagnostic
|
||||||
|
.hint("a file without an extension is not usually a Typst file");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if world.source(input.with_extension("typ")).is_ok() {
|
||||||
|
diagnostic.hint("check if you meant to use the `.typ` extension instead");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
eco_vec![diagnostic]
|
||||||
|
}
|
||||||
|
@ -6,7 +6,7 @@ use heck::{ToKebabCase, ToTitleCase};
|
|||||||
use pulldown_cmark as md;
|
use pulldown_cmark as md;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use typed_arena::Arena;
|
use typed_arena::Arena;
|
||||||
use typst::diag::{FileResult, StrResult};
|
use typst::diag::{FileError, FileResult, StrResult};
|
||||||
use typst::foundations::{Bytes, Datetime};
|
use typst::foundations::{Bytes, Datetime};
|
||||||
use typst::layout::{Abs, Point, Size};
|
use typst::layout::{Abs, Point, Size};
|
||||||
use typst::syntax::{FileId, Source, VirtualPath};
|
use typst::syntax::{FileId, Source, VirtualPath};
|
||||||
@ -463,12 +463,16 @@ impl World for DocWorld {
|
|||||||
&FONTS.0
|
&FONTS.0
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(&self) -> Source {
|
fn main(&self) -> FileId {
|
||||||
self.0.clone()
|
self.0.id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn source(&self, _: FileId) -> FileResult<Source> {
|
fn source(&self, id: FileId) -> FileResult<Source> {
|
||||||
|
if id == self.0.id() {
|
||||||
Ok(self.0.clone())
|
Ok(self.0.clone())
|
||||||
|
} else {
|
||||||
|
Err(FileError::NotFound(id.vpath().as_rootless_path().into()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file(&self, id: FileId) -> FileResult<Bytes> {
|
fn file(&self, id: FileId) -> FileResult<Bytes> {
|
||||||
|
@ -39,16 +39,20 @@ impl World for FuzzWorld {
|
|||||||
&self.book
|
&self.book
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(&self) -> Source {
|
fn main(&self) -> FileId {
|
||||||
self.source.clone()
|
self.source.id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn source(&self, src: FileId) -> FileResult<Source> {
|
fn source(&self, id: FileId) -> FileResult<Source> {
|
||||||
Err(FileError::NotFound(src.vpath().as_rootless_path().into()))
|
if id == self.source.id() {
|
||||||
|
Ok(self.source.clone())
|
||||||
|
} else {
|
||||||
|
Err(FileError::NotFound(id.vpath().as_rootless_path().into()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn file(&self, src: FileId) -> FileResult<Bytes> {
|
fn file(&self, id: FileId) -> FileResult<Bytes> {
|
||||||
Err(FileError::NotFound(src.vpath().as_rootless_path().into()))
|
Err(FileError::NotFound(id.vpath().as_rootless_path().into()))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn font(&self, _: usize) -> Option<Font> {
|
fn font(&self, _: usize) -> Option<Font> {
|
||||||
|
@ -43,8 +43,8 @@ impl World for TestWorld {
|
|||||||
&self.base.book
|
&self.base.book
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main(&self) -> Source {
|
fn main(&self) -> FileId {
|
||||||
self.main.clone()
|
self.main.id()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn source(&self, id: FileId) -> FileResult<Source> {
|
fn source(&self, id: FileId) -> FileResult<Source> {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user