seam

Symbolic-Expressions As Markup.
git clone git://git.knutsen.co/seam
Log | Files | Refs | README | LICENSE

commit 1c652410814e5b69b9e09c12180e06343e2fee53
parent a0660bbdd206cc0596ca38acd4149fc2658501c6
Author: Demonstrandum <samuel@knutsen.co>
Date:   Mon,  9 Dec 2024 21:57:08 +0000

Added :timezone offset to %date macro.

Diffstat:
MCargo.lock | 47+++++++++++++++++++++++++++++++++++++++++++++++
Mcrates/seam/Cargo.toml | 1+
Mcrates/seam/src/parse/expander.rs | 20++++++++++++++++++++
Mcrates/seam/src/parse/lexer.rs | 2+-
Mcrates/seam/src/tests.rs | 16++++++++++++++++
5 files changed, 85 insertions(+), 1 deletion(-)

diff --git a/Cargo.lock b/Cargo.lock @@ -51,12 +51,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] +name = "az" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b7e4c2464d97fe331d41de9d5db0def0a96f4d823b8b32a2efd503578988973" + +[[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] +name = "bytemuck" +version = "1.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a" + +[[package]] name = "cc" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -102,6 +114,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" [[package]] +name = "crunchy" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" + +[[package]] name = "descape" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -123,6 +141,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] +name = "fixed" +version = "1.28.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85c6e0b89bf864acd20590dbdbad56f69aeb898abfc9443008fd7bd48b2cc85a" +dependencies = [ + "az", + "bytemuck", + "half", + "typenum", +] + +[[package]] name = "formatx" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -135,6 +165,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" [[package]] +name = "half" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" +dependencies = [ + "cfg-if", + "crunchy", +] + +[[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -302,6 +342,7 @@ dependencies = [ "chrono", "colored", "descape", + "fixed", "formatx", "glob", "markdown", @@ -402,6 +443,12 @@ dependencies = [ ] [[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + +[[package]] name = "unicode-id" version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" diff --git a/crates/seam/Cargo.toml b/crates/seam/Cargo.toml @@ -25,6 +25,7 @@ path = "src/bin.rs" seam_argparse_proc_macro = { version = "0.1.1", path = "../seam_argparse_proc_macro" } colored = "2.1" chrono = "0.4" +fixed = "1.28.0" unicode-width = "0.2.0" descape = "2.0.3" formatx = "0.2.2" diff --git a/crates/seam/src/parse/expander.rs b/crates/seam/src/parse/expander.rs @@ -15,6 +15,7 @@ use std::{ }; use colored::*; +use fixed; use formatx; use glob::glob; use unicode_width::UnicodeWidthStr; @@ -878,7 +879,18 @@ impl<'a> Expander<'a> { let (_, args) = arguments! { [&params] mandatory(1): literal, optional(2): number, + optional("timezone"): number, }?; + let timezone_offset: i32 = match args.timezone { + Some(ref offset) => match offset.value.parse::<fixed::types::I32F32>() { + Ok(offset) => (offset * 60 * 60).round().to_num(), + Err(_) => return Err(ExpansionError::new( + "Invalid (decimal) timezone offset in hours.", + &offset.site, + )), + }, + None => 0, + }; let date_format = args.number.1.value; let time = if let Some(time) = args.number.2 { let Ok(secs) = time.value.parse::<i64>() else { @@ -894,6 +906,14 @@ impl<'a> Expander<'a> { } else { chrono::Utc::now() }; + + let Some(timezone) = chrono::FixedOffset::east_opt(timezone_offset) else { + return Err(ExpansionError( + format!("Failed to compute UTC+(east) offset of {} seconds", timezone_offset), + args.timezone.map_or(node.owned_site(), |node| node.site), + )); + }; + let time = time.with_timezone(&timezone); let formatted = time.format(&date_format).to_string(); let date_string_node = ParseNode::String(Node { value: formatted, diff --git a/crates/seam/src/parse/lexer.rs b/crates/seam/src/parse/lexer.rs @@ -36,7 +36,7 @@ fn character_kind(character: char) '(' => Some(tokens::Kind::LParen), ')' => Some(tokens::Kind::RParen), '0'..='9' => Some(tokens::Kind::Number), - '-' => Some(tokens::Kind::Number), + '+' | '-' => Some(tokens::Kind::Number), ':' => Some(tokens::Kind::Keyword), '"' => Some(tokens::Kind::String), _ => Some(tokens::Kind::Symbol) diff --git a/crates/seam/src/tests.rs b/crates/seam/src/tests.rs @@ -64,4 +64,20 @@ mod tests { "\"2024-12-09 20:42:46\"" ); } + + #[test] + fn test_date_timezones() { + assert_output_eq( + "(%date \"%Y-%m-%d %H:%M:%S%Z\" 999999999 :timezone +1)", + "\"2001-09-09 02:46:39+01:00\"" + ); + assert_output_eq( + "(%date \"%Y-%m-%d %H:%M:%S%Z\" 999999999 :timezone +3.5)", + "\"2001-09-09 05:16:39+03:30\"" + ); + assert_output_eq( + "(%date \"%Y-%m-%d %H:%M:%S%Z\" 999999999 :timezone -2.25)", + "\"2001-09-08 23:31:39-02:15\"" + ); + } }