mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-07 21:15:03 +00:00
feat: CLI compile documents with lock updates (#1218)
This commit is contained in:
parent
ebd811db13
commit
b541daf50e
10 changed files with 238 additions and 144 deletions
|
@ -22,11 +22,7 @@ pub enum DocCommands {
|
||||||
/// Project task commands.
|
/// Project task commands.
|
||||||
#[derive(Debug, Clone, clap::Subcommand)]
|
#[derive(Debug, Clone, clap::Subcommand)]
|
||||||
#[clap(rename_all = "kebab-case")]
|
#[clap(rename_all = "kebab-case")]
|
||||||
// clippy bug: TaskCompileArgs is evaluated as 0 bytes
|
|
||||||
#[allow(clippy::large_enum_variant)]
|
|
||||||
pub enum TaskCommands {
|
pub enum TaskCommands {
|
||||||
/// Declare a compile task (output).
|
|
||||||
Compile(TaskCompileArgs),
|
|
||||||
/// Declare a preview task.
|
/// Declare a preview task.
|
||||||
Preview(TaskPreviewArgs),
|
Preview(TaskPreviewArgs),
|
||||||
}
|
}
|
||||||
|
@ -48,6 +44,50 @@ pub struct DocNewArgs {
|
||||||
pub package: CompilePackageArgs,
|
pub package: CompilePackageArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl DocNewArgs {
|
||||||
|
/// Converts to project input.
|
||||||
|
pub fn to_input(&self) -> ProjectInput {
|
||||||
|
let id: Id = (&self.id).into();
|
||||||
|
|
||||||
|
let root = self
|
||||||
|
.root
|
||||||
|
.as_ref()
|
||||||
|
.map(|root| ResourcePath::from_user_sys(Path::new(root)));
|
||||||
|
let main = ResourcePath::from_user_sys(Path::new(&self.id.input));
|
||||||
|
|
||||||
|
let font_paths = self
|
||||||
|
.font
|
||||||
|
.font_paths
|
||||||
|
.iter()
|
||||||
|
.map(|p| ResourcePath::from_user_sys(p))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
let package_path = self
|
||||||
|
.package
|
||||||
|
.package_path
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| ResourcePath::from_user_sys(p));
|
||||||
|
|
||||||
|
let package_cache_path = self
|
||||||
|
.package
|
||||||
|
.package_cache_path
|
||||||
|
.as_ref()
|
||||||
|
.map(|p| ResourcePath::from_user_sys(p));
|
||||||
|
|
||||||
|
ProjectInput {
|
||||||
|
id: id.clone(),
|
||||||
|
root,
|
||||||
|
main,
|
||||||
|
// todo: inputs
|
||||||
|
inputs: vec![],
|
||||||
|
font_paths,
|
||||||
|
system_fonts: !self.font.ignore_system_fonts,
|
||||||
|
package_path,
|
||||||
|
package_cache_path,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// The id of a document.
|
/// The id of a document.
|
||||||
///
|
///
|
||||||
/// If an identifier is not provided, the document's path is used as the id.
|
/// If an identifier is not provided, the document's path is used as the id.
|
||||||
|
|
|
@ -37,7 +37,7 @@ pub struct ProjectInsId(EcoString);
|
||||||
///
|
///
|
||||||
/// Whether to export depends on the current state of the document and the user
|
/// Whether to export depends on the current state of the document and the user
|
||||||
/// settings.
|
/// settings.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy, Default)]
|
||||||
pub struct ExportSignal {
|
pub struct ExportSignal {
|
||||||
/// Whether the revision is annotated by memory events.
|
/// Whether the revision is annotated by memory events.
|
||||||
pub by_mem_events: bool,
|
pub by_mem_events: bool,
|
||||||
|
@ -62,6 +62,17 @@ pub struct CompileSnapshot<F: CompilerFeat> {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: CompilerFeat + 'static> CompileSnapshot<F> {
|
impl<F: CompilerFeat + 'static> CompileSnapshot<F> {
|
||||||
|
/// Creates a snapshot from the world.
|
||||||
|
pub fn from_world(world: CompilerWorld<F>) -> Self {
|
||||||
|
Self {
|
||||||
|
id: ProjectInsId("primary".into()),
|
||||||
|
signal: ExportSignal::default(),
|
||||||
|
env: CompileEnv::default(),
|
||||||
|
world,
|
||||||
|
success_doc: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Forks a new snapshot that compiles a different document.
|
/// Forks a new snapshot that compiles a different document.
|
||||||
///
|
///
|
||||||
/// Note: the resulting document should not be shared in system, because we
|
/// Note: the resulting document should not be shared in system, because we
|
||||||
|
|
|
@ -23,6 +23,10 @@ impl LockFile {
|
||||||
self.document.iter().find(|i| &i.id == id)
|
self.document.iter().find(|i| &i.id == id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn get_task(&self, id: &Id) -> Option<&ApplyProjectTask> {
|
||||||
|
self.task.iter().find(|i| &i.id == id)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn replace_document(&mut self, input: ProjectInput) {
|
pub fn replace_document(&mut self, input: ProjectInput) {
|
||||||
let id = input.id.clone();
|
let id = input.id.clone();
|
||||||
let index = self.document.iter().position(|i| i.id == id);
|
let index = self.document.iter().position(|i| i.id == id);
|
||||||
|
@ -263,10 +267,13 @@ impl LockFileUpdate {
|
||||||
let _ = package_cache_path;
|
let _ = package_cache_path;
|
||||||
let _ = package_path;
|
let _ = package_path;
|
||||||
|
|
||||||
|
// todo: freeze the sys.inputs
|
||||||
|
|
||||||
let input = ProjectInput {
|
let input = ProjectInput {
|
||||||
id: id.clone(),
|
id: id.clone(),
|
||||||
root: Some(root),
|
root: Some(root),
|
||||||
main: Some(main),
|
main,
|
||||||
|
inputs: vec![],
|
||||||
font_paths,
|
font_paths,
|
||||||
system_fonts: true, // !args.font.ignore_system_fonts,
|
system_fonts: true, // !args.font.ignore_system_fonts,
|
||||||
package_path: None,
|
package_path: None,
|
||||||
|
|
|
@ -407,13 +407,13 @@ impl ResourcePath {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Converts the resource path to an absolute file system path.
|
/// Converts the resource path to an absolute file system path.
|
||||||
pub fn to_abs_path(&self, rel: &Path) -> Option<PathBuf> {
|
pub fn to_abs_path(&self, base: &Path) -> Option<PathBuf> {
|
||||||
if self.0 == "file" {
|
if self.0 == "file" {
|
||||||
let path = Path::new(&self.1);
|
let path = Path::new(&self.1);
|
||||||
if path.is_absolute() {
|
if path.is_absolute() {
|
||||||
Some(path.to_owned())
|
Some(path.to_owned())
|
||||||
} else {
|
} else {
|
||||||
Some(rel.join(path))
|
Some(base.join(path))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
None
|
||||||
|
@ -481,12 +481,13 @@ pub struct LockFile {
|
||||||
pub struct ProjectInput {
|
pub struct ProjectInput {
|
||||||
/// The project's ID.
|
/// The project's ID.
|
||||||
pub id: Id,
|
pub id: Id,
|
||||||
/// The project's root directory.
|
/// The path to the root directory of the project.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub root: Option<ResourcePath>,
|
pub root: Option<ResourcePath>,
|
||||||
/// The project's main file.
|
/// The path to the main file of the project.
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
pub main: ResourcePath,
|
||||||
pub main: Option<ResourcePath>,
|
/// The key-value pairs visible through `sys.inputs`
|
||||||
|
pub inputs: Vec<(String, String)>,
|
||||||
/// The project's font paths.
|
/// The project's font paths.
|
||||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||||
pub font_paths: Vec<ResourcePath>,
|
pub font_paths: Vec<ResourcePath>,
|
||||||
|
|
|
@ -13,7 +13,7 @@ use std::path::Path;
|
||||||
use std::{borrow::Cow, sync::Arc};
|
use std::{borrow::Cow, sync::Arc};
|
||||||
|
|
||||||
use tinymist_std::error::prelude::*;
|
use tinymist_std::error::prelude::*;
|
||||||
use tinymist_std::ImmutPath;
|
use tinymist_std::{bail, ImmutPath};
|
||||||
use tinymist_world::font::system::SystemFontSearcher;
|
use tinymist_world::font::system::SystemFontSearcher;
|
||||||
use tinymist_world::package::{http::HttpRegistry, RegistryPathMapper};
|
use tinymist_world::package::{http::HttpRegistry, RegistryPathMapper};
|
||||||
use tinymist_world::vfs::{system::SystemAccessModel, Vfs};
|
use tinymist_world::vfs::{system::SystemAccessModel, Vfs};
|
||||||
|
@ -22,6 +22,7 @@ use typst::foundations::{Dict, Str, Value};
|
||||||
use typst::utils::LazyHash;
|
use typst::utils::LazyHash;
|
||||||
|
|
||||||
use crate::font::TinymistFontResolver;
|
use crate::font::TinymistFontResolver;
|
||||||
|
use crate::ProjectInput;
|
||||||
|
|
||||||
/// Compiler feature for LSP universe and worlds without typst.ts to implement
|
/// Compiler feature for LSP universe and worlds without typst.ts to implement
|
||||||
/// more for tinymist. type trait of [`CompilerUniverse`].
|
/// more for tinymist. type trait of [`CompilerUniverse`].
|
||||||
|
@ -114,6 +115,80 @@ impl WorldProvider for CompileOnceArgs {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// todo: merge me with the above impl
|
||||||
|
impl WorldProvider for (ProjectInput, ImmutPath) {
|
||||||
|
fn resolve(&self) -> Result<LspUniverse> {
|
||||||
|
let (proj, lock_dir) = self;
|
||||||
|
let entry = self.entry()?.try_into()?;
|
||||||
|
let inputs = proj
|
||||||
|
.inputs
|
||||||
|
.iter()
|
||||||
|
.map(|(k, v)| (Str::from(k.as_str()), Value::Str(Str::from(v.as_str()))))
|
||||||
|
.collect();
|
||||||
|
let fonts = LspUniverseBuilder::resolve_fonts(CompileFontArgs {
|
||||||
|
font_paths: {
|
||||||
|
proj.font_paths
|
||||||
|
.iter()
|
||||||
|
.flat_map(|p| p.to_abs_path(lock_dir))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
},
|
||||||
|
ignore_system_fonts: !proj.system_fonts,
|
||||||
|
})?;
|
||||||
|
let package = LspUniverseBuilder::resolve_package(
|
||||||
|
// todo: recover certificate path
|
||||||
|
None,
|
||||||
|
Some(&CompilePackageArgs {
|
||||||
|
package_path: proj
|
||||||
|
.package_path
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|p| p.to_abs_path(lock_dir)),
|
||||||
|
package_cache_path: proj
|
||||||
|
.package_cache_path
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|p| p.to_abs_path(lock_dir)),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
LspUniverseBuilder::build(
|
||||||
|
entry,
|
||||||
|
Arc::new(LazyHash::new(inputs)),
|
||||||
|
Arc::new(fonts),
|
||||||
|
package,
|
||||||
|
)
|
||||||
|
.context("failed to create universe")
|
||||||
|
}
|
||||||
|
|
||||||
|
fn entry(&self) -> Result<EntryOpts> {
|
||||||
|
let (proj, lock_dir) = self;
|
||||||
|
|
||||||
|
let entry = proj
|
||||||
|
.main
|
||||||
|
.to_abs_path(lock_dir)
|
||||||
|
.context("failed to resolve entry file")?;
|
||||||
|
|
||||||
|
let root = if let Some(root) = &proj.root {
|
||||||
|
root.to_abs_path(lock_dir)
|
||||||
|
.context("failed to resolve root")?
|
||||||
|
} else {
|
||||||
|
lock_dir.as_ref().to_owned()
|
||||||
|
};
|
||||||
|
|
||||||
|
if !entry.starts_with(&root) {
|
||||||
|
bail!("entry file must be in the root directory, {entry:?}, {root:?}");
|
||||||
|
}
|
||||||
|
|
||||||
|
let relative_entry = match entry.strip_prefix(&root) {
|
||||||
|
Ok(relative_entry) => relative_entry,
|
||||||
|
Err(_) => bail!("entry path must be inside the root: {}", entry.display()),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(EntryOpts::new_rooted(
|
||||||
|
root.clone(),
|
||||||
|
Some(relative_entry.to_owned()),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Builder for LSP universe.
|
/// Builder for LSP universe.
|
||||||
pub struct LspUniverseBuilder;
|
pub struct LspUniverseBuilder;
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,7 @@ use sync_lsp::transport::MirrorArgs;
|
||||||
|
|
||||||
use tinymist::{
|
use tinymist::{
|
||||||
project::{DocCommands, TaskCommands},
|
project::{DocCommands, TaskCommands},
|
||||||
|
tool::project::CompileArgs,
|
||||||
CompileFontArgs, CompileOnceArgs,
|
CompileFontArgs, CompileOnceArgs,
|
||||||
};
|
};
|
||||||
use tinymist_core::LONG_VERSION;
|
use tinymist_core::LONG_VERSION;
|
||||||
|
@ -32,8 +33,7 @@ pub enum Commands {
|
||||||
#[cfg(feature = "preview")]
|
#[cfg(feature = "preview")]
|
||||||
Preview(tinymist::tool::preview::PreviewCliArgs),
|
Preview(tinymist::tool::preview::PreviewCliArgs),
|
||||||
|
|
||||||
/// Runs compile commands
|
/// Runs compile command like `typst-cli compile`
|
||||||
#[clap(hide(true))] // still in development
|
|
||||||
Compile(CompileArgs),
|
Compile(CompileArgs),
|
||||||
/// Runs language query
|
/// Runs language query
|
||||||
#[clap(hide(true))] // still in development
|
#[clap(hide(true))] // still in development
|
||||||
|
@ -146,17 +146,6 @@ pub struct TraceLspArgs {
|
||||||
pub compile: CompileOnceArgs,
|
pub compile: CompileOnceArgs,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Common arguments of compile, watch, and query.
|
|
||||||
#[derive(Debug, Clone, Default, clap::Parser)]
|
|
||||||
pub struct CompileArgs {
|
|
||||||
#[clap(flatten)]
|
|
||||||
pub compile: CompileOnceArgs,
|
|
||||||
|
|
||||||
/// Path to output file
|
|
||||||
#[clap(value_name = "OUTPUT")]
|
|
||||||
pub output: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default, clap::Parser)]
|
#[derive(Debug, Clone, Default, clap::Parser)]
|
||||||
pub struct LspArgs {
|
pub struct LspArgs {
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
|
|
|
@ -16,18 +16,18 @@ use futures::future::MaybeDone;
|
||||||
use lsp_server::RequestId;
|
use lsp_server::RequestId;
|
||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use reflexo::ImmutPath;
|
use reflexo::ImmutPath;
|
||||||
use reflexo_typst::{package::PackageSpec, Compiler, TypstDict};
|
use reflexo_typst::{package::PackageSpec, TypstDict};
|
||||||
use serde_json::Value as JsonValue;
|
use serde_json::Value as JsonValue;
|
||||||
use sync_lsp::{
|
use sync_lsp::{
|
||||||
internal_error,
|
internal_error,
|
||||||
transport::{with_stdio_transport, MirrorArgs},
|
transport::{with_stdio_transport, MirrorArgs},
|
||||||
LspBuilder, LspClientRoot, LspResult,
|
LspBuilder, LspClientRoot, LspResult,
|
||||||
};
|
};
|
||||||
|
use tinymist::world::TaskInputs;
|
||||||
use tinymist::{
|
use tinymist::{
|
||||||
tool::project::{project_main, task_main},
|
tool::project::{compile_main, project_main, task_main},
|
||||||
CompileConfig, Config, RegularInit, ServerState, SuperInit, UserActionTask,
|
CompileConfig, Config, RegularInit, ServerState, SuperInit, UserActionTask,
|
||||||
};
|
};
|
||||||
use tinymist::{world::TaskInputs, world::WorldProvider};
|
|
||||||
use tinymist_core::LONG_VERSION;
|
use tinymist_core::LONG_VERSION;
|
||||||
use tinymist_project::EntryResolver;
|
use tinymist_project::EntryResolver;
|
||||||
use tinymist_query::package::PackageInfo;
|
use tinymist_query::package::PackageInfo;
|
||||||
|
@ -65,27 +65,29 @@ fn main() -> Result<()> {
|
||||||
#[cfg(feature = "dhat-heap")]
|
#[cfg(feature = "dhat-heap")]
|
||||||
let _profiler = dhat::Profiler::new_heap();
|
let _profiler = dhat::Profiler::new_heap();
|
||||||
|
|
||||||
|
// Parse command line arguments
|
||||||
|
let args = CliArguments::parse();
|
||||||
|
|
||||||
|
let is_transient_cmd = matches!(args.command, Some(Commands::Compile(..)));
|
||||||
|
|
||||||
// Start logging
|
// Start logging
|
||||||
let _ = {
|
let _ = {
|
||||||
use log::LevelFilter::*;
|
use log::LevelFilter::*;
|
||||||
|
let base_level = if is_transient_cmd { Warn } else { Info };
|
||||||
|
|
||||||
env_logger::builder()
|
env_logger::builder()
|
||||||
.filter_module("tinymist", Info)
|
.filter_module("tinymist", base_level)
|
||||||
.filter_module("typst_preview", Debug)
|
.filter_module("typst_preview", Debug)
|
||||||
.filter_module("typlite", Info)
|
.filter_module("typlite", base_level)
|
||||||
.filter_module("reflexo", Info)
|
.filter_module("reflexo", base_level)
|
||||||
.filter_module("sync_lsp", Info)
|
.filter_module("sync_lsp", base_level)
|
||||||
.filter_module("reflexo_typst::service::compile", Info)
|
|
||||||
.filter_module("reflexo_typst::service::watch", Info)
|
|
||||||
.filter_module("reflexo_typst::diag::console", Info)
|
.filter_module("reflexo_typst::diag::console", Info)
|
||||||
.try_init()
|
.try_init()
|
||||||
};
|
};
|
||||||
|
|
||||||
// Parse command line arguments
|
|
||||||
let args = CliArguments::parse();
|
|
||||||
|
|
||||||
match args.command.unwrap_or_default() {
|
match args.command.unwrap_or_default() {
|
||||||
Commands::Completion(args) => completion(args),
|
Commands::Completion(args) => completion(args),
|
||||||
Commands::Compile(args) => compile(args),
|
Commands::Compile(args) => RUNTIMES.tokio_runtime.block_on(compile_main(args)),
|
||||||
Commands::Query(query_cmds) => query_main(query_cmds),
|
Commands::Query(query_cmds) => query_main(query_cmds),
|
||||||
Commands::Lsp(args) => lsp_main(args),
|
Commands::Lsp(args) => lsp_main(args),
|
||||||
Commands::TraceLsp(args) => trace_lsp_main(args),
|
Commands::TraceLsp(args) => trace_lsp_main(args),
|
||||||
|
@ -114,41 +116,6 @@ pub fn completion(args: ShellCompletionArgs) -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs compilation
|
|
||||||
pub fn compile(args: CompileArgs) -> Result<()> {
|
|
||||||
use std::io::Write;
|
|
||||||
|
|
||||||
let input = args
|
|
||||||
.compile
|
|
||||||
.input
|
|
||||||
.as_ref()
|
|
||||||
.context("Missing required argument: INPUT")?;
|
|
||||||
let output = match args.output {
|
|
||||||
Some(stdout_path) if stdout_path == "-" => None,
|
|
||||||
Some(output_path) => Some(PathBuf::from(output_path)),
|
|
||||||
None => Some(Path::new(input).with_extension("pdf")),
|
|
||||||
};
|
|
||||||
|
|
||||||
let universe = args.compile.resolve()?;
|
|
||||||
let world = universe.snapshot();
|
|
||||||
|
|
||||||
let converter = std::marker::PhantomData.compile(&world, &mut Default::default());
|
|
||||||
let pdf = typst_pdf::pdf(&converter.unwrap().output, &Default::default());
|
|
||||||
|
|
||||||
match (pdf, output) {
|
|
||||||
(Ok(pdf), None) => {
|
|
||||||
std::io::stdout().write_all(&pdf).unwrap();
|
|
||||||
}
|
|
||||||
(Ok(pdf), Some(output)) => std::fs::write(output, pdf).unwrap(),
|
|
||||||
(Err(err), ..) => {
|
|
||||||
eprintln!("{err:?}");
|
|
||||||
std::process::exit(1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The main entry point for the language server.
|
/// The main entry point for the language server.
|
||||||
pub fn lsp_main(args: LspArgs) -> Result<()> {
|
pub fn lsp_main(args: LspArgs) -> Result<()> {
|
||||||
let pairs = LONG_VERSION.trim().split('\n');
|
let pairs = LONG_VERSION.trim().split('\n');
|
||||||
|
|
|
@ -263,8 +263,8 @@ impl ServerState {
|
||||||
.unwrap_or_else(|| lock_dir.clone());
|
.unwrap_or_else(|| lock_dir.clone());
|
||||||
let main = input
|
let main = input
|
||||||
.main
|
.main
|
||||||
.as_ref()
|
.to_abs_path(lock_dir)
|
||||||
.and_then(|main| Some(main.to_abs_path(lock_dir)?.as_path().into()))
|
.map(|path| path.as_path().into())
|
||||||
.unwrap_or_else(|| path.clone());
|
.unwrap_or_else(|| path.clone());
|
||||||
let entry = self
|
let entry = self
|
||||||
.entry_resolver()
|
.entry_resolver()
|
||||||
|
|
|
@ -1,76 +1,34 @@
|
||||||
//! Project management tools.
|
//! Project management tools.
|
||||||
|
|
||||||
use std::path::Path;
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
|
use reflexo::ImmutPath;
|
||||||
use tinymist_std::error::prelude::*;
|
use tinymist_std::error::prelude::*;
|
||||||
|
|
||||||
use crate::project::*;
|
use crate::{project::*, task::ExportTask};
|
||||||
|
|
||||||
|
/// Common arguments of compile, watch, and query.
|
||||||
|
#[derive(Debug, Clone, clap::Parser)]
|
||||||
|
pub struct CompileArgs {
|
||||||
|
/// Inherits the compile task arguments.
|
||||||
|
#[clap(flatten)]
|
||||||
|
pub compile: TaskCompileArgs,
|
||||||
|
|
||||||
|
/// Saves the compilation arguments to the lock file.
|
||||||
|
#[clap(long)]
|
||||||
|
pub save_lock: bool,
|
||||||
|
|
||||||
|
/// Specifies the path to the lock file. If the path is
|
||||||
|
/// set, the lock file will be saved.
|
||||||
|
#[clap(long)]
|
||||||
|
pub lockfile: Option<PathBuf>,
|
||||||
|
}
|
||||||
|
|
||||||
trait LockFileExt {
|
trait LockFileExt {
|
||||||
fn declare(&mut self, args: &DocNewArgs) -> Id;
|
|
||||||
fn preview(&mut self, doc_id: Id, args: &TaskPreviewArgs) -> Result<Id>;
|
fn preview(&mut self, doc_id: Id, args: &TaskPreviewArgs) -> Result<Id>;
|
||||||
fn compile(&mut self, args: TaskCompileArgs) -> Result<Id>;
|
|
||||||
fn export(&mut self, doc_id: Id, args: TaskCompileArgs) -> Result<Id>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LockFileExt for LockFile {
|
impl LockFileExt for LockFile {
|
||||||
fn declare(&mut self, args: &DocNewArgs) -> Id {
|
|
||||||
let id: Id = (&args.id).into();
|
|
||||||
|
|
||||||
let root = args
|
|
||||||
.root
|
|
||||||
.as_ref()
|
|
||||||
.map(|root| ResourcePath::from_user_sys(Path::new(root)));
|
|
||||||
let main = ResourcePath::from_user_sys(Path::new(&args.id.input));
|
|
||||||
|
|
||||||
let font_paths = args
|
|
||||||
.font
|
|
||||||
.font_paths
|
|
||||||
.iter()
|
|
||||||
.map(|p| ResourcePath::from_user_sys(p))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let package_path = args
|
|
||||||
.package
|
|
||||||
.package_path
|
|
||||||
.as_ref()
|
|
||||||
.map(|p| ResourcePath::from_user_sys(p));
|
|
||||||
|
|
||||||
let package_cache_path = args
|
|
||||||
.package
|
|
||||||
.package_cache_path
|
|
||||||
.as_ref()
|
|
||||||
.map(|p| ResourcePath::from_user_sys(p));
|
|
||||||
|
|
||||||
let input = ProjectInput {
|
|
||||||
id: id.clone(),
|
|
||||||
root,
|
|
||||||
main: Some(main),
|
|
||||||
font_paths,
|
|
||||||
system_fonts: !args.font.ignore_system_fonts,
|
|
||||||
package_path,
|
|
||||||
package_cache_path,
|
|
||||||
};
|
|
||||||
|
|
||||||
self.replace_document(input);
|
|
||||||
|
|
||||||
id
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compile(&mut self, args: TaskCompileArgs) -> Result<Id> {
|
|
||||||
let id = self.declare(&args.declare);
|
|
||||||
self.export(id, args)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn export(&mut self, doc_id: Id, args: TaskCompileArgs) -> Result<Id> {
|
|
||||||
let task = args.to_task(doc_id)?;
|
|
||||||
let task_id = task.id().clone();
|
|
||||||
|
|
||||||
self.replace_task(task);
|
|
||||||
|
|
||||||
Ok(task_id)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn preview(&mut self, doc_id: Id, args: &TaskPreviewArgs) -> Result<Id> {
|
fn preview(&mut self, doc_id: Id, args: &TaskPreviewArgs) -> Result<Id> {
|
||||||
let task_id = args
|
let task_id = args
|
||||||
.name
|
.name
|
||||||
|
@ -92,12 +50,51 @@ impl LockFileExt for LockFile {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Runs project compilation(s)
|
||||||
|
pub async fn compile_main(args: CompileArgs) -> Result<()> {
|
||||||
|
// Identifies the input and output
|
||||||
|
let input = args.compile.declare.to_input();
|
||||||
|
let output = args.compile.to_task(input.id.clone())?;
|
||||||
|
|
||||||
|
// Saves the lock file if the flags are set
|
||||||
|
let save_lock = args.save_lock || args.lockfile.is_some();
|
||||||
|
// todo: respect the name of the lock file
|
||||||
|
let lock_dir: ImmutPath = if let Some(lockfile) = args.lockfile {
|
||||||
|
lockfile.parent().context("no parent")?.into()
|
||||||
|
} else {
|
||||||
|
std::env::current_dir().context("lock directory")?.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
if save_lock {
|
||||||
|
LockFile::update(&lock_dir, |state| {
|
||||||
|
state.replace_document(input.clone());
|
||||||
|
state.replace_task(output.clone());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prepares for the compilation
|
||||||
|
let universe = (input, lock_dir.clone()).resolve()?;
|
||||||
|
let world = universe.snapshot();
|
||||||
|
let snap = CompileSnapshot::from_world(world);
|
||||||
|
|
||||||
|
// Compiles the project
|
||||||
|
let compiled = snap.compile();
|
||||||
|
|
||||||
|
// Exports the compiled project
|
||||||
|
let lock_dir = save_lock.then_some(lock_dir);
|
||||||
|
ExportTask::do_export(output.task, compiled, lock_dir).await?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Project document commands' main
|
/// Project document commands' main
|
||||||
pub fn project_main(args: DocCommands) -> Result<()> {
|
pub fn project_main(args: DocCommands) -> Result<()> {
|
||||||
LockFile::update(Path::new("."), |state| {
|
LockFile::update(Path::new("."), |state| {
|
||||||
match args {
|
match args {
|
||||||
DocCommands::New(args) => {
|
DocCommands::New(args) => {
|
||||||
state.declare(&args);
|
state.replace_document(args.to_input());
|
||||||
}
|
}
|
||||||
DocCommands::Configure(args) => {
|
DocCommands::Configure(args) => {
|
||||||
let id: Id = (&args.id).into();
|
let id: Id = (&args.id).into();
|
||||||
|
@ -117,11 +114,10 @@ pub fn project_main(args: DocCommands) -> Result<()> {
|
||||||
pub fn task_main(args: TaskCommands) -> Result<()> {
|
pub fn task_main(args: TaskCommands) -> Result<()> {
|
||||||
LockFile::update(Path::new("."), |state| {
|
LockFile::update(Path::new("."), |state| {
|
||||||
match args {
|
match args {
|
||||||
TaskCommands::Compile(args) => {
|
|
||||||
let _ = state.compile(args);
|
|
||||||
}
|
|
||||||
TaskCommands::Preview(args) => {
|
TaskCommands::Preview(args) => {
|
||||||
let id = state.declare(&args.declare);
|
let input = args.declare.to_input();
|
||||||
|
let id = input.id.clone();
|
||||||
|
state.replace_document(input);
|
||||||
let _ = state.preview(id, &args);
|
let _ = state.preview(id, &args);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,10 +4,16 @@ version = "0.1.0-beta0"
|
||||||
|
|
||||||
[[document]]
|
[[document]]
|
||||||
id = "file:docs/tinymist/book.typ"
|
id = "file:docs/tinymist/book.typ"
|
||||||
|
inputs = []
|
||||||
|
main = "file:docs/tinymist/book.typ"
|
||||||
|
root = "file:."
|
||||||
system-fonts = true
|
system-fonts = true
|
||||||
|
|
||||||
[[document]]
|
[[document]]
|
||||||
id = "file:docs/tinymist/ebook.typ"
|
id = "file:docs/tinymist/ebook.typ"
|
||||||
|
inputs = []
|
||||||
|
main = "file:docs/tinymist/ebook.typ"
|
||||||
|
root = "file:."
|
||||||
system-fonts = true
|
system-fonts = true
|
||||||
|
|
||||||
[[task]]
|
[[task]]
|
||||||
|
@ -23,7 +29,9 @@ type = "export-svg"
|
||||||
when = "never"
|
when = "never"
|
||||||
|
|
||||||
[[task.transform]]
|
[[task.transform]]
|
||||||
pages = ["1-1"]
|
|
||||||
|
[task.transform.pages]
|
||||||
|
ranges = ["1-1"]
|
||||||
|
|
||||||
[[task]]
|
[[task]]
|
||||||
document = "file:docs/tinymist/ebook.typ"
|
document = "file:docs/tinymist/ebook.typ"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue