Skip to content

Commit 6104efe

Browse files
committed
cleanup
1 parent 666f0d9 commit 6104efe

6 files changed

Lines changed: 163 additions & 229 deletions

File tree

src/topics/config.rs

Lines changed: 4 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ use super::{
77
self, FunctionSummary, TopicAutomationConfig, TopicAutomationConfigPatch,
88
TopicMapConfigPatch, TopicMapConfigUpdate, TopicMapGenerationSettings, TopicsConfigReport,
99
},
10+
formatting::{format_duration_compact, format_project_header},
1011
ConfigArgs, ConfigSetArgs, ResolvedContext, TopicMapSetArgs,
1112
};
1213

@@ -41,7 +42,7 @@ pub async fn run_set(ctx: &ResolvedContext, args: &ConfigSetArgs, json: bool) ->
4142

4243
print_command_status(CommandStatus::Success, "Updated Topics automation config");
4344
print_with_pager(&render_single_config(
44-
&report_header(&ctx.project.name, &ctx.project.id, ctx.client.org_name()),
45+
&format_project_header(&ctx.project.name, &ctx.project.id, ctx.client.org_name()),
4546
&updated,
4647
))?;
4748
Ok(())
@@ -66,14 +67,14 @@ pub async fn run_topic_map_set(
6667

6768
print_command_status(CommandStatus::Success, "Updated Topics topic map config");
6869
print_with_pager(&render_single_topic_map_update(
69-
&report_header(&ctx.project.name, &ctx.project.id, ctx.client.org_name()),
70+
&format_project_header(&ctx.project.name, &ctx.project.id, ctx.client.org_name()),
7071
&updated,
7172
))?;
7273
Ok(())
7374
}
7475

7576
fn render_report(report: &TopicsConfigReport) -> String {
76-
let mut output = report_header(
77+
let mut output = format_project_header(
7778
&report.project.name,
7879
&report.project.id,
7980
&report.project.org_name,
@@ -119,10 +120,6 @@ fn render_single_topic_map_update(header: &str, updated: &TopicMapConfigUpdate)
119120
output
120121
}
121122

122-
fn report_header(project_name: &str, project_id: &str, org_name: &str) -> String {
123-
format!("Project: {org_name} / {project_name} ({project_id})")
124-
}
125-
126123
fn render_config_block(automation: &TopicAutomationConfig) -> String {
127124
let mut lines = vec![format!("{} ({})", automation.name, automation.id)];
128125
lines.push(format!(
@@ -382,26 +379,6 @@ fn format_sampling_percent(value: f64) -> String {
382379
}
383380
}
384381

385-
fn format_duration_compact(seconds: Option<i64>) -> String {
386-
let Some(seconds) = seconds else {
387-
return "n/a".to_string();
388-
};
389-
390-
let units = [
391-
("w", 7 * 24 * 60 * 60),
392-
("d", 24 * 60 * 60),
393-
("h", 60 * 60),
394-
("m", 60),
395-
("s", 1),
396-
];
397-
for (suffix, scale) in units {
398-
if seconds >= scale && seconds % scale == 0 {
399-
return format!("{}{}", seconds / scale, suffix);
400-
}
401-
}
402-
format!("{seconds}s")
403-
}
404-
405382
fn format_float_compact(value: f64) -> String {
406383
let rounded = value.round();
407384
if (value - rounded).abs() < 0.000_001 {

src/topics/formatting.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
use chrono::{DateTime, SecondsFormat, Utc};
2+
3+
pub(crate) fn format_project_header(
4+
project_name: &str,
5+
project_id: &str,
6+
org_name: &str,
7+
) -> String {
8+
format!("Project: {org_name} / {project_name} ({project_id})")
9+
}
10+
11+
pub(crate) fn format_duration_compact(seconds: Option<i64>) -> String {
12+
let Some(seconds) = seconds else {
13+
return "n/a".to_string();
14+
};
15+
16+
let units = [
17+
("w", 7 * 24 * 60 * 60),
18+
("d", 24 * 60 * 60),
19+
("h", 60 * 60),
20+
("m", 60),
21+
("s", 1),
22+
];
23+
for (suffix, scale) in units {
24+
if seconds >= scale && seconds % scale == 0 {
25+
return format!("{}{}", seconds / scale, suffix);
26+
}
27+
}
28+
format!("{seconds}s")
29+
}
30+
31+
pub(crate) fn format_timestamp_with_relative(value: &str) -> String {
32+
let Ok(parsed) = DateTime::parse_from_rfc3339(value) else {
33+
return value.to_string();
34+
};
35+
format_datetime_with_relative(parsed.with_timezone(&Utc))
36+
}
37+
38+
pub(crate) fn format_datetime_with_relative(timestamp: DateTime<Utc>) -> String {
39+
format!(
40+
"{} ({})",
41+
timestamp.to_rfc3339_opts(SecondsFormat::Secs, true),
42+
relative_time(timestamp)
43+
)
44+
}
45+
46+
pub(crate) fn format_relative_duration_seconds(
47+
delta_seconds: i64,
48+
include_direction: bool,
49+
) -> String {
50+
let absolute_seconds = delta_seconds.abs();
51+
if absolute_seconds < 5 {
52+
return if include_direction {
53+
"now".to_string()
54+
} else {
55+
"0s".to_string()
56+
};
57+
}
58+
59+
let units = [
60+
("w", 7 * 24 * 60 * 60),
61+
("d", 24 * 60 * 60),
62+
("h", 60 * 60),
63+
("m", 60),
64+
("s", 1),
65+
];
66+
for (suffix, scale) in units {
67+
if absolute_seconds < scale {
68+
continue;
69+
}
70+
let rounded = absolute_seconds / scale;
71+
if !include_direction {
72+
return format!("{rounded}{suffix}");
73+
}
74+
if delta_seconds > 0 {
75+
return format!("in {rounded}{suffix}");
76+
}
77+
return format!("{rounded}{suffix} ago");
78+
}
79+
"0s".to_string()
80+
}
81+
82+
pub(crate) fn format_count(value: usize) -> String {
83+
let digits = value.to_string();
84+
let mut out = String::with_capacity(digits.len() + digits.len() / 3);
85+
86+
for (idx, ch) in digits.chars().rev().enumerate() {
87+
if idx > 0 && idx % 3 == 0 {
88+
out.push(',');
89+
}
90+
out.push(ch);
91+
}
92+
93+
out.chars().rev().collect()
94+
}
95+
96+
fn relative_time(timestamp: DateTime<Utc>) -> String {
97+
let delta = timestamp.signed_duration_since(Utc::now());
98+
let seconds = delta.num_seconds();
99+
100+
if seconds.abs() < 5 {
101+
return "now".to_string();
102+
}
103+
104+
let text = human_interval(seconds.abs());
105+
if seconds >= 0 {
106+
format!("in {text}")
107+
} else {
108+
format!("{text} ago")
109+
}
110+
}
111+
112+
fn human_interval(seconds: i64) -> String {
113+
let units = [("d", 24 * 60 * 60), ("h", 60 * 60), ("m", 60), ("s", 1)];
114+
let mut remaining = seconds;
115+
let mut parts = Vec::new();
116+
117+
for (suffix, scale) in units {
118+
if remaining < scale {
119+
continue;
120+
}
121+
let value = remaining / scale;
122+
remaining %= scale;
123+
parts.push(format!("{value}{suffix}"));
124+
if parts.len() == 2 {
125+
break;
126+
}
127+
}
128+
129+
if parts.is_empty() {
130+
"0s".to_string()
131+
} else {
132+
parts.join(" ")
133+
}
134+
}

src/topics/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use crate::{args::BaseArgs, projects::context::resolve_project_context};
55

66
pub(crate) mod api;
77
mod config;
8+
mod formatting;
89
mod open;
910
mod poke;
1011
mod rewind;

src/topics/poke.rs

Lines changed: 9 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
use anyhow::Result;
2-
use chrono::{DateTime, SecondsFormat, Utc};
32

43
use crate::ui::{print_with_pager, with_spinner};
54

6-
use super::{api, ResolvedContext};
5+
use super::{
6+
api,
7+
formatting::{format_project_header, format_timestamp_with_relative},
8+
ResolvedContext,
9+
};
710

811
pub async fn run(ctx: &ResolvedContext, json: bool) -> Result<()> {
912
let report = with_spinner("Queueing Topics run...", api::poke_topic_automations(ctx)).await?;
@@ -18,9 +21,10 @@ pub async fn run(ctx: &ResolvedContext, json: bool) -> Result<()> {
1821
}
1922

2023
fn render_report(report: &api::TopicsPokeReport) -> String {
21-
let mut lines = vec![format!(
22-
"Project: {} / {} ({})",
23-
report.project.org_name, report.project.name, report.project.id
24+
let mut lines = vec![format_project_header(
25+
&report.project.name,
26+
&report.project.id,
27+
&report.project.org_name,
2428
)];
2529

2630
if report.queued.is_empty() {
@@ -72,58 +76,6 @@ fn render_report(report: &api::TopicsPokeReport) -> String {
7276
lines.join("\n")
7377
}
7478

75-
fn format_timestamp_with_relative(value: &str) -> String {
76-
let Ok(parsed) = DateTime::parse_from_rfc3339(value) else {
77-
return value.to_string();
78-
};
79-
let utc = parsed.with_timezone(&Utc);
80-
format!(
81-
"{} ({})",
82-
utc.to_rfc3339_opts(SecondsFormat::Secs, true),
83-
relative_time(utc)
84-
)
85-
}
86-
87-
fn relative_time(timestamp: DateTime<Utc>) -> String {
88-
let delta = timestamp.signed_duration_since(Utc::now());
89-
let seconds = delta.num_seconds();
90-
91-
if seconds.abs() < 5 {
92-
return "now".to_string();
93-
}
94-
95-
let text = human_interval(seconds.abs());
96-
if seconds >= 0 {
97-
format!("in {text}")
98-
} else {
99-
format!("{text} ago")
100-
}
101-
}
102-
103-
fn human_interval(seconds: i64) -> String {
104-
let units = [("d", 24 * 60 * 60), ("h", 60 * 60), ("m", 60), ("s", 1)];
105-
let mut remaining = seconds;
106-
let mut parts = Vec::new();
107-
108-
for (suffix, scale) in units {
109-
if remaining < scale {
110-
continue;
111-
}
112-
let value = remaining / scale;
113-
remaining %= scale;
114-
parts.push(format!("{value}{suffix}"));
115-
if parts.len() == 2 {
116-
break;
117-
}
118-
}
119-
120-
if parts.is_empty() {
121-
"0s".to_string()
122-
} else {
123-
parts.join(" ")
124-
}
125-
}
126-
12779
#[cfg(test)]
12880
mod tests {
12981
use super::*;

src/topics/rewind.rs

Lines changed: 10 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,11 @@ use anyhow::{bail, Result};
22

33
use crate::ui::{print_with_pager, with_spinner};
44

5-
use super::{api, ResolvedContext, RewindArgs};
5+
use super::{
6+
api,
7+
formatting::{format_duration_compact, format_project_header},
8+
ResolvedContext, RewindArgs,
9+
};
610

711
pub async fn run(ctx: &ResolvedContext, args: &RewindArgs, json: bool) -> Result<()> {
812
let window_seconds = parse_topic_window(&args.topic_window)?;
@@ -22,9 +26,10 @@ pub async fn run(ctx: &ResolvedContext, args: &RewindArgs, json: bool) -> Result
2226
}
2327

2428
fn render_report(report: &api::TopicsRewindReport) -> String {
25-
let mut lines = vec![format!(
26-
"Project: {} / {} ({})",
27-
report.project.org_name, report.project.name, report.project.id
29+
let mut lines = vec![format_project_header(
30+
&report.project.name,
31+
&report.project.id,
32+
&report.project.org_name,
2833
)];
2934

3035
if report.rewound.is_empty() {
@@ -43,7 +48,7 @@ fn render_report(report: &api::TopicsRewindReport) -> String {
4348
lines.push(format!(" object: {}", item.object_id));
4449
lines.push(format!(
4550
" rewind window: {}",
46-
format_duration_compact(item.window_seconds)
51+
format_duration_compact(Some(item.window_seconds))
4752
));
4853
lines.push(format!(" rewind start xact: {}", item.start_xact_id));
4954
lines.push(" next check: now".to_string());
@@ -81,23 +86,6 @@ fn parse_topic_window(value: &str) -> Result<i64> {
8186
};
8287
Ok(amount * multiplier)
8388
}
84-
85-
fn format_duration_compact(seconds: i64) -> String {
86-
let units = [
87-
("w", 7 * 24 * 60 * 60),
88-
("d", 24 * 60 * 60),
89-
("h", 60 * 60),
90-
("m", 60),
91-
("s", 1),
92-
];
93-
for (suffix, scale) in units {
94-
if seconds >= scale && seconds % scale == 0 {
95-
return format!("{}{}", seconds / scale, suffix);
96-
}
97-
}
98-
format!("{seconds}s")
99-
}
100-
10189
#[cfg(test)]
10290
mod tests {
10391
use super::*;

0 commit comments

Comments
 (0)