Skip to content

Commit dd96739

Browse files
Alan Baldassarreclaude
authored andcommitted
feat: add now line rendering to Gantt charts (RFC-0017)
Add vertical "now line" markers to Gantt chart renderers showing the status date and/or today's date as visual reference points. Features: - NowLineConfig struct for configuring now line rendering - HTML/SVG: Red solid line for status date, green dashed for today - MermaidJS: todayMarker directive support - PlantUML: Date coloring with status_date - WASM Playground: Toggle checkbox (default: enabled) - CLI flags: --as-of, --no-now-line, --show-today - TJP parser: Handle `now` attribute -> project.status_date - MS Project import: Extract status_date from project properties Tests: 10 unit tests, 5 WASM tests, 6 E2E tests Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 85cf1a0 commit dd96739

19 files changed

Lines changed: 1235 additions & 30 deletions

File tree

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ This project follows **Semantic Versioning** (SemVer):
2121

2222
Version is set in `Cargo.toml` under `[workspace.package]`:
2323
```toml
24-
version = "0.12.2"
24+
version = "0.13.0"
2525
```
2626

2727
All crates inherit this version via `version.workspace = true`.

Cargo.lock

Lines changed: 7 additions & 7 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ members = [
1414
]
1515

1616
[workspace.package]
17-
version = "0.12.2"
17+
version = "0.13.0"
1818
edition = "2021"
1919
rust-version = "1.75"
2020
license = "MIT OR Apache-2.0"
@@ -24,10 +24,10 @@ categories = ["command-line-utilities", "development-tools"]
2424

2525
[workspace.dependencies]
2626
# Internal crates (version required for crates.io publishing)
27-
utf8proj-core = { version = "0.12.0", path = "crates/utf8proj-core" }
28-
utf8proj-parser = { version = "0.12.0", path = "crates/utf8proj-parser" }
29-
utf8proj-solver = { version = "0.12.0", path = "crates/utf8proj-solver" }
30-
utf8proj-render = { version = "0.12.0", path = "crates/utf8proj-render" }
27+
utf8proj-core = { version = "0.13.0", path = "crates/utf8proj-core" }
28+
utf8proj-parser = { version = "0.13.0", path = "crates/utf8proj-parser" }
29+
utf8proj-solver = { version = "0.13.0", path = "crates/utf8proj-solver" }
30+
utf8proj-render = { version = "0.13.0", path = "crates/utf8proj-render" }
3131

3232
# Date/Time
3333
chrono = { version = "0.4", features = ["serde"] }

crates/utf8proj-cli/src/main.rs

Lines changed: 45 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,19 @@ enum Commands {
204204
/// Number of days to show in daily Excel schedule (default: 60)
205205
#[arg(long, default_value = "60")]
206206
days: u32,
207+
208+
/// Status date for now line (RFC-0017). Format: YYYY-MM-DD.
209+
/// Defaults to project.status_date or today if not specified.
210+
#[arg(long, value_name = "DATE")]
211+
as_of: Option<String>,
212+
213+
/// Disable now line rendering on Gantt chart (RFC-0017)
214+
#[arg(long)]
215+
no_now_line: bool,
216+
217+
/// Show today line separately when status_date differs from today (RFC-0017)
218+
#[arg(long)]
219+
show_today: bool,
207220
},
208221

209222
/// Run performance benchmarks
@@ -455,6 +468,9 @@ fn main() -> Result<()> {
455468
context_depth,
456469
daily,
457470
days,
471+
as_of,
472+
no_now_line,
473+
show_today,
458474
}) => cmd_gantt(
459475
&file,
460476
&output,
@@ -470,6 +486,9 @@ fn main() -> Result<()> {
470486
context_depth,
471487
daily,
472488
days,
489+
as_of.as_deref(),
490+
no_now_line,
491+
show_today,
473492
),
474493
Some(Commands::Benchmark {
475494
topology,
@@ -1040,6 +1059,9 @@ fn cmd_gantt(
10401059
context_depth: usize,
10411060
daily: bool,
10421061
days: u32,
1062+
as_of: Option<&str>,
1063+
no_now_line: bool,
1064+
show_today: bool,
10431065
) -> Result<()> {
10441066
use utf8proj_render::DisplayMode;
10451067
// Parse the file
@@ -1175,8 +1197,8 @@ fn cmd_gantt(
11751197
.with_context(|| "Failed to render SVG Gantt chart")?
11761198
}
11771199
"html" => {
1178-
// HTML format with focus view support
1179-
use utf8proj_render::gantt::{FocusConfig, HtmlGanttRenderer};
1200+
// HTML format with focus view and now line support
1201+
use utf8proj_render::gantt::{FocusConfig, HtmlGanttRenderer, NowLineConfig};
11801202

11811203
let mut renderer = HtmlGanttRenderer::new();
11821204
renderer.label_width = width as u32;
@@ -1198,6 +1220,27 @@ fn cmd_gantt(
11981220
}
11991221
}
12001222

1223+
// Configure now line (RFC-0017)
1224+
if no_now_line {
1225+
renderer.now_line = NowLineConfig::disabled();
1226+
} else {
1227+
// Resolve status date: --as-of > project.status_date > today
1228+
let status_date = if let Some(date_str) = as_of {
1229+
chrono::NaiveDate::parse_from_str(date_str, "%Y-%m-%d")
1230+
.with_context(|| format!("Invalid date format '{}', expected YYYY-MM-DD", date_str))?
1231+
} else if let Some(date) = project.status_date {
1232+
date
1233+
} else {
1234+
chrono::Local::now().date_naive()
1235+
};
1236+
1237+
let mut now_line_config = NowLineConfig::with_status_date(status_date);
1238+
if show_today {
1239+
now_line_config = now_line_config.with_today();
1240+
}
1241+
renderer.now_line = now_line_config;
1242+
}
1243+
12011244
renderer
12021245
.render(&project, &schedule)
12031246
.with_context(|| "Failed to render HTML Gantt chart")?

crates/utf8proj-parser/src/tjp/mod.rs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ fn parse_project_decl(
113113
Rule::workinghours_attr => {
114114
// Could configure calendar
115115
}
116+
Rule::now_attr => {
117+
// RFC-0017: Parse 'now' attribute as status_date
118+
let mut inner = actual_attr.into_inner();
119+
if let Some(date_pair) = inner.next() {
120+
project.status_date = Some(parse_date(date_pair.as_str())?);
121+
}
122+
}
116123
_ => {}
117124
}
118125
}
@@ -690,4 +697,22 @@ mod tests {
690697
utf8proj_core::DependencyType::FinishToFinish
691698
);
692699
}
700+
701+
#[test]
702+
fn parse_project_now_attribute() {
703+
// RFC-0017: 'now' attribute sets status_date
704+
let input = r#"
705+
project test "Test" 2025-01-01 - 2025-12-31 {
706+
now 2025-03-15
707+
}
708+
task t1 "Task 1" { duration 5d }
709+
"#;
710+
711+
let project = parse(input).unwrap();
712+
assert!(project.status_date.is_some(), "status_date should be set from 'now'");
713+
assert_eq!(
714+
project.status_date.unwrap(),
715+
chrono::NaiveDate::from_ymd_opt(2025, 3, 15).unwrap()
716+
);
717+
}
693718
}

0 commit comments

Comments
 (0)