Skip to content

Commit

Permalink
docdate etc. time-related attrs
Browse files Browse the repository at this point in the history
  • Loading branch information
jaredh159 committed Dec 26, 2024
1 parent 1057ddc commit 64fc7c4
Show file tree
Hide file tree
Showing 8 changed files with 179 additions and 8 deletions.
26 changes: 26 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 18 additions & 6 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@ use std::env;
use std::io::{Read, Write};
use std::process::{Command, Stdio};
use std::result::Result;
use std::time::{Duration, Instant};
use std::{error::Error, fs};
use std::time::{Duration, Instant, SystemTime};
use std::{error::Error, fs, time::UNIX_EPOCH};

use bumpalo::Bump;
use clap::Parser as ClapParser;
Expand All @@ -30,14 +30,20 @@ fn run(
mut stdout: impl Write,
mut stderr: impl Write,
) -> Result<(), Box<dyn Error>> {
let (src, src_file, base_dir) = {
let (src, src_file, base_dir, input_mtime) = {
if let Some(pathbuf) = &args.input {
let abspath = fs::canonicalize(pathbuf)?;
let mut file = fs::File::open(pathbuf.clone())?;
let mut input_mtime = None;
let mut src = file
.metadata()
.ok()
.map(|metadata| String::with_capacity(metadata.len() as usize))
.map(|metadata| {
if let Ok(mtime) = metadata.modified() {
input_mtime = Some(mtime.duration_since(UNIX_EPOCH).unwrap().as_secs());
}
String::with_capacity(metadata.len() as usize)
})
.unwrap_or_else(String::new);
// TODO: for perf, better to read the file straight into a BumpVec<u8>
// have an initializer on Parser that takes ownership of it
Expand All @@ -47,13 +53,13 @@ fn run(
.as_ref()
.cloned()
.or_else(|| abspath.parent().map(|p| p.to_path_buf()));
(src, SourceFile::Path(abspath.into()), base_dir)
(src, SourceFile::Path(abspath.into()), base_dir, input_mtime)
} else {
let mut src = String::new();
stdin.read_to_string(&mut src)?;
let cwd_buf = env::current_dir()?;
let cwd = Path::new(cwd_buf.to_str().unwrap_or(""));
(src, SourceFile::Stdin { cwd }, Some(cwd_buf))
(src, SourceFile::Stdin { cwd }, Some(cwd_buf), None)
}
};

Expand All @@ -63,6 +69,12 @@ fn run(
parser.apply_job_settings(args.clone().try_into()?);
parser.set_resolver(Box::new(CliResolver::new(base_dir)));

let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap()
.as_secs();
parser.provide_timestamps(now, input_mtime, None);

let result = parser.parse();
let parse_time = parse_start.elapsed();

Expand Down
2 changes: 0 additions & 2 deletions core/src/document_meta.rs
Original file line number Diff line number Diff line change
Expand Up @@ -244,8 +244,6 @@ lazy_static::lazy_static! {
"doc",
"docdir",
"docfile",
"docdate",
"docdatetime",
"docfilesuffix",
"docname",
])
Expand Down
1 change: 1 addition & 0 deletions parser/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ license = "MIT"
asciidork-ast = { path = "../ast", version = "0.11.1" }
asciidork-core = { path = "../core", version = "0.11.1" }
bumpalo = { version = "3.15.4", features = ["collections"] }
jiff = "0.1.15"
lazy_static = "1.4.0"
regex = { version = "1.10.2", features = ["std", "use_std"] }
smallvec = "1.13.1"
Expand Down
9 changes: 9 additions & 0 deletions parser/src/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,15 @@ impl<'arena> Parser<'arena> {
self.set_source_file_attrs();
}

pub fn provide_timestamps(
&mut self,
now: u64,
input_modified_time: Option<u64>,
reproducible_override: Option<u64>,
) {
self.set_datetime_attrs(now, input_modified_time, reproducible_override);
}

pub fn set_resolver(&mut self, resolver: Box<dyn IncludeResolver>) {
self.include_resolver = Some(resolver);
}
Expand Down
8 changes: 8 additions & 0 deletions parser/src/tasks/attr_refs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,12 @@ impl<'arena> Parser<'arena> {
.insert_job_attr(key, JobAttr::readonly(value))
.unwrap();
}

pub(crate) fn insert_doc_attr(
&mut self,
key: &str,
value: impl Into<AttrValue>,
) -> std::result::Result<(), String> {
self.document.meta.insert_doc_attr(key, value)
}
}
1 change: 1 addition & 0 deletions parser/src/tasks/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,4 @@ mod parse_revision_line;
pub mod parse_section;
mod section_id;
mod table;
mod time;
116 changes: 116 additions & 0 deletions parser/src/tasks/time.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
use std::convert::TryInto;

use jiff::civil::Date;
use jiff::{tz::TimeZone, Timestamp, Zoned};

use crate::internal::*;

impl Parser<'_> {
pub(crate) fn set_datetime_attrs(
&mut self,
now: u64,
input_mtime: Option<u64>,
reproducible: Option<u64>,
) {
let doc_ts = reproducible.unwrap_or(input_mtime.unwrap_or(now));
let now_ts = reproducible.unwrap_or(now);
let docdate = date_from_attr_or(self.document.meta.str("docdate"), doc_ts);
let doctime = time_from_attr_or(self.document.meta.str("doctime"), doc_ts);

let docyear_s = docdate.year().to_string();
let docdate_s = docdate.strftime("%Y-%m-%d").to_string();
let doctime_s = doctime.strftime("%H:%M:%S%z").to_string();
let docdatetime_s = format!("{} {}", docdate_s, doctime_s);
self.insert_doc_attr("docyear", docyear_s).unwrap();
self.insert_doc_attr("docdate", docdate_s).unwrap();
self.insert_doc_attr("doctime", doctime_s).unwrap();
self.insert_doc_attr("docdatetime", docdatetime_s).unwrap();

let nowdatetime = to_zoned(now_ts);
let nowyear = nowdatetime.year().to_string();
let nowdate = nowdatetime.strftime("%Y-%m-%d").to_string();
let nowtime = nowdatetime.strftime("%H:%M:%S%z").to_string();
let nowdatetime = nowdatetime.strftime("%Y-%m-%d %H:%M:%S%z").to_string();
self.insert_doc_attr("localyear", nowyear).unwrap();
self.insert_doc_attr("localdate", nowdate).unwrap();
self.insert_doc_attr("localtime", nowtime).unwrap();
self.insert_doc_attr("localdatetime", nowdatetime).unwrap();
}
}

fn to_zoned(seconds: u64) -> Zoned {
let seconds: i64 = seconds.try_into().expect("invalid timestamp");
let timestamp = Timestamp::from_second(seconds).expect("invalid timestamp");
timestamp.to_zoned(TimeZone::UTC)
}

fn date_from_attr_or(attr: Option<&str>, epoch_seconds: u64) -> Zoned {
attr
.and_then(|attr_str| Date::strptime("%Y-%m-%d", attr_str).ok())
.map(|date| date.to_zoned(TimeZone::UTC).expect("invalid date"))
.unwrap_or_else(|| to_zoned(epoch_seconds))
}

fn time_from_attr_or(attr: Option<&str>, epoch_seconds: u64) -> Zoned {
attr
.and_then(|s| Zoned::strptime("%Y-%m-%d %H:%M:%S%z", format!("2000-01-01 {s}")).ok())
.unwrap_or_else(|| to_zoned(epoch_seconds))
}

#[cfg(test)]
mod tests {
use super::*;
use test_utils::*;

#[test]
fn test_set_datetime_attrs_no_attr_overrides() {
let mut parser = test_parser!("");
parser.provide_timestamps(1734960586, Some(1734872889), None);
let meta = parser.document.meta;
assert_eq!(meta.str("docyear"), Some("2024"));
assert_eq!(meta.str("docdate"), Some("2024-12-22"));
assert_eq!(meta.str("doctime"), Some("13:08:09+0000"));
assert_eq!(meta.str("docdatetime"), Some("2024-12-22 13:08:09+0000"));
assert_eq!(meta.str("localyear"), Some("2024"));
assert_eq!(meta.str("localdate"), Some("2024-12-23"));
assert_eq!(meta.str("localtime"), Some("13:29:46+0000"));
assert_eq!(meta.str("localdatetime"), Some("2024-12-23 13:29:46+0000"));
}

#[test]
fn test_override_datetime_attrs() {
let mut parser = test_parser!("");
parser.insert_doc_attr("docdate", "2015-01-01").unwrap();
parser.insert_doc_attr("doctime", "10:00:00-0700").unwrap();
parser.provide_timestamps(1734960586, Some(1734872889), None);
let meta = parser.document.meta;
assert_eq!(meta.str("docyear"), Some("2015"));
assert_eq!(meta.str("docdate"), Some("2015-01-01"));
assert_eq!(meta.str("doctime"), Some("10:00:00-0700"));
assert_eq!(meta.str("docdatetime"), Some("2015-01-01 10:00:00-0700"));
assert_eq!(meta.str("localdatetime"), Some("2024-12-23 13:29:46+0000"));
}

#[test]
fn test_override_just_time_attrs() {
let mut parser = test_parser!("");
parser.insert_doc_attr("doctime", "10:00:00-0700").unwrap();
parser.provide_timestamps(1734960586, Some(1734872889), None);
let meta = parser.document.meta;
assert_eq!(meta.str("docyear"), Some("2024"));
assert_eq!(meta.str("docdate"), Some("2024-12-22"));
assert_eq!(meta.str("doctime"), Some("10:00:00-0700"));
assert_eq!(meta.str("docdatetime"), Some("2024-12-22 10:00:00-0700"));
assert_eq!(meta.str("localdatetime"), Some("2024-12-23 13:29:46+0000"));
}

#[test]
fn test_reproducible_override_wins() {
let mut parser = test_parser!("");
parser.provide_timestamps(1734960586, Some(1734872889), Some(1262304000));
let meta = parser.document.meta;
assert_eq!(meta.str("docyear"), Some("2010"));
assert_eq!(meta.str("doctime"), Some("00:00:00+0000"));
assert_eq!(meta.str("docdatetime"), Some("2010-01-01 00:00:00+0000"));
}
}

0 comments on commit 64fc7c4

Please sign in to comment.