Skip to content

Commit eb4322b

Browse files
committed
Added document metadata
1 parent 41168a9 commit eb4322b

2 files changed

Lines changed: 154 additions & 0 deletions

File tree

crates/docmodel/src/document.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,13 @@ pub struct Document {
4949
/// this will be a subdirectory of `src_dir` named `build`.
5050
build_dir: PathBuf,
5151

52+
/// Arbitrary document metadata.
53+
/// This has no effect on tectonic's build process.
54+
/// Rather, allows users to add easily-accessible information to their documents.
55+
///
56+
/// Tectonic's `show metadata` command displays this information.
57+
pub metadata: Option<toml::Value>,
58+
5259
/// The document name. This will be used to name build artifacts and the
5360
/// like, and so should be relatively filesystem-friendly. It does not
5461
/// need to be the same as the document title.
@@ -103,6 +110,7 @@ impl Document {
103110
build_dir: build_dir.into(),
104111
name: doc.doc.name,
105112
bundle_loc: doc.doc.bundle,
113+
metadata: doc.doc.metadata,
106114
outputs,
107115
})
108116
}
@@ -124,6 +132,7 @@ impl Document {
124132
doc: syntax::DocSection {
125133
name: self.name.clone(),
126134
bundle: self.bundle_loc.clone(),
135+
metadata: None,
127136
},
128137
outputs,
129138
};
@@ -288,6 +297,7 @@ impl Document {
288297
name,
289298
bundle_loc,
290299
outputs: crate::document::default_outputs(),
300+
metadata: None,
291301
})
292302
}
293303
}
@@ -329,6 +339,7 @@ mod syntax {
329339
pub struct DocSection {
330340
pub name: String,
331341
pub bundle: String,
342+
pub metadata: Option<toml::Value>,
332343
}
333344

334345
#[derive(Debug, Deserialize, Serialize)]

src/bin/tectonic/v2cli.rs

Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -726,18 +726,23 @@ enum ShowCommands {
726726
#[structopt(name = "user-cache-dir")]
727727
/// Print the location of the default per-user cache directory
728728
UserCacheDir(ShowUserCacheDirCommand),
729+
730+
/// Show metadata from the current environment
731+
Metadata(ShowMetadataCommand),
729732
}
730733

731734
impl ShowCommand {
732735
fn customize(&self, cc: &mut CommandCustomizations) {
733736
match &self.command {
734737
ShowCommands::UserCacheDir(c) => c.customize(cc),
738+
ShowCommands::Metadata(c) => c.customize(cc),
735739
}
736740
}
737741

738742
fn execute(self, config: PersistentConfig, status: &mut dyn StatusBackend) -> Result<i32> {
739743
match self.command {
740744
ShowCommands::UserCacheDir(c) => c.execute(config, status),
745+
ShowCommands::Metadata(c) => c.execute(config, status),
741746
}
742747
}
743748
}
@@ -757,3 +762,141 @@ impl ShowUserCacheDirCommand {
757762
Ok(0)
758763
}
759764
}
765+
766+
#[derive(Debug, Eq, PartialEq, StructOpt)]
767+
struct ShowMetadataCommand {
768+
#[structopt(help = "The metadata key to get")]
769+
key: Option<String>,
770+
}
771+
772+
773+
// If parts is none, print this value as a string.
774+
// This is used to recursively stringify arrays and tables.
775+
fn get_metadata(meta: &toml::Value, mut parts: Option<core::str::Split<&str>>) -> Option<String> {
776+
match meta {
777+
toml::Value::String(_)
778+
| toml::Value::Boolean(_)
779+
| toml::Value::Integer(_)
780+
| toml::Value::Float(_)
781+
| toml::Value::Datetime(_) => {
782+
if parts.is_some() && parts.unwrap().next().is_some() {
783+
// None of these types may be indexed further
784+
return None;
785+
}
786+
787+
return Some(match meta {
788+
toml::Value::String(s) => s.clone(),
789+
toml::Value::Datetime(d) => format!("{}", d),
790+
_ => format!("{}", meta),
791+
});
792+
}
793+
794+
toml::Value::Array(a) => {
795+
let idx = {
796+
if parts.is_some() {
797+
parts.as_mut().unwrap().next()
798+
} else {
799+
None
800+
}
801+
};
802+
803+
match idx {
804+
None => {
805+
// Print the whole array
806+
let s = a
807+
.iter()
808+
.map(|x| get_metadata(x, None).unwrap())
809+
.collect::<Vec<String>>()
810+
.join(", ");
811+
return Some(format!("[{}]", s));
812+
}
813+
Some(s) => {
814+
if s == "len" {
815+
// Magic key to get array length
816+
return Some(format!("{}", a.len()));
817+
} else {
818+
let i: usize = match s.parse() {
819+
Ok(i) => i,
820+
Err(_) => return None,
821+
};
822+
823+
let meta = match a.get(i) {
824+
Some(v) => v,
825+
None => return None,
826+
};
827+
828+
return get_metadata(meta, parts);
829+
}
830+
}
831+
}
832+
}
833+
834+
toml::Value::Table(t) => {
835+
let idx = {
836+
if parts.is_some() {
837+
parts.as_mut().unwrap().next()
838+
} else {
839+
None
840+
}
841+
};
842+
843+
match idx {
844+
None => return None,
845+
Some(s) => {
846+
let meta = match t.get(s) {
847+
Some(v) => v,
848+
None => return None,
849+
};
850+
851+
return get_metadata(meta, parts);
852+
}
853+
}
854+
}
855+
};
856+
}
857+
858+
859+
860+
impl ShowMetadataCommand {
861+
fn customize(&self, cc: &mut CommandCustomizations) {
862+
cc.always_stderr = true;
863+
}
864+
865+
fn execute(mut self, _config: PersistentConfig, status: &mut dyn StatusBackend) -> Result<i32> {
866+
// `tectonic show metadata` should be shell-script-friendly.
867+
// Namely,it should either print a metadata value or return error code 1,
868+
// with NO OTHER OUTPUT.
869+
870+
let meta = match Workspace::open_from_environment() {
871+
Ok(ws) => {
872+
let doc = ws.first_document();
873+
let meta = doc.metadata.clone();
874+
if meta.is_none() {
875+
// This workspace has no metadata.
876+
return Ok(1);
877+
}
878+
meta.unwrap()
879+
}
880+
881+
Err(_) => {
882+
//tt_error!(status, "could not load metadata. Are you in a workspace?");
883+
return Ok(1);
884+
}
885+
};
886+
887+
let parts;
888+
if self.key.is_none() {
889+
parts = None;
890+
} else {
891+
parts = Some(self.key.as_mut().unwrap().split("."));
892+
}
893+
894+
let meta = get_metadata(&meta, parts);
895+
if meta.is_none() {
896+
return Ok(1);
897+
}
898+
899+
println!("{}", meta.unwrap());
900+
Ok(0)
901+
}
902+
}

0 commit comments

Comments
 (0)