Compare commits
2 Commits
3cec3953c0
...
88ec0a9de2
| Author | SHA1 | Date |
|---|---|---|
|
|
88ec0a9de2 | |
|
|
5f4b97d54d |
|
|
@ -158,6 +158,15 @@ version = "1.0.4"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
checksum = "b05b61dc5112cbb17e4b6cd61790d9845d13888356391624cbe7e41efeac1e75"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colored"
|
||||||
|
version = "3.1.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "core-foundation-sys"
|
name = "core-foundation-sys"
|
||||||
version = "0.8.7"
|
version = "0.8.7"
|
||||||
|
|
@ -327,6 +336,7 @@ dependencies = [
|
||||||
"chrono",
|
"chrono",
|
||||||
"chrono-tz",
|
"chrono-tz",
|
||||||
"clap",
|
"clap",
|
||||||
|
"colored",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,4 @@ edition = "2024"
|
||||||
chrono = "0.4.43"
|
chrono = "0.4.43"
|
||||||
chrono-tz = "0.10.4"
|
chrono-tz = "0.10.4"
|
||||||
clap = { version = "4.5.57", features = ["derive"] }
|
clap = { version = "4.5.57", features = ["derive"] }
|
||||||
|
colored = "3.1.1"
|
||||||
|
|
|
||||||
196
src/calendar.rs
196
src/calendar.rs
|
|
@ -5,16 +5,12 @@ use std::fmt;
|
||||||
|
|
||||||
#[derive(PartialEq, Debug)]
|
#[derive(PartialEq, Debug)]
|
||||||
pub enum CalendarError {
|
pub enum CalendarError {
|
||||||
UnknownTimezone(String),
|
|
||||||
WrongTimeFormat,
|
|
||||||
TimezoneConversionFailed(String),
|
TimezoneConversionFailed(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for CalendarError {
|
impl fmt::Display for CalendarError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let message = match *self {
|
let message = match *self {
|
||||||
Self::UnknownTimezone(ref tz) => format!("Unknown timezone: {}", tz),
|
|
||||||
Self::WrongTimeFormat => "Wrong time format".to_string(),
|
|
||||||
Self::TimezoneConversionFailed(ref tz) => {
|
Self::TimezoneConversionFailed(ref tz) => {
|
||||||
format!("Could not convert time to timezone: {}", tz)
|
format!("Could not convert time to timezone: {}", tz)
|
||||||
}
|
}
|
||||||
|
|
@ -26,80 +22,33 @@ impl fmt::Display for CalendarError {
|
||||||
|
|
||||||
impl Error for CalendarError {}
|
impl Error for CalendarError {}
|
||||||
|
|
||||||
pub struct SimpleTimezone {
|
fn create_timetable_vecs(time: &NaiveTime, padding_hrs: i8) -> Vec<NaiveTime> {
|
||||||
pub name: String,
|
|
||||||
pub offset: FixedOffset,
|
|
||||||
}
|
|
||||||
|
|
||||||
fn create_calendar_strings(time: &NaiveTime, padding_hrs: i8) -> Vec<String> {
|
|
||||||
let start = -padding_hrs;
|
let start = -padding_hrs;
|
||||||
let end = padding_hrs;
|
let end = padding_hrs;
|
||||||
(start..=end)
|
(start..=end)
|
||||||
.map(|v| time.overflowing_add_signed(TimeDelta::hours(v.into())).0)
|
.map(|v| time.overflowing_add_signed(TimeDelta::hours(v.into())).0)
|
||||||
.map(|h| h.format("%H:%M").to_string())
|
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_local_timezone_offset(
|
|
||||||
time: &NaiveTime,
|
|
||||||
timezone: &String,
|
|
||||||
) -> Result<SimpleTimezone, CalendarError> {
|
|
||||||
if timezone == "Local" {
|
|
||||||
Ok(SimpleTimezone {
|
|
||||||
name: "Local".to_string(),
|
|
||||||
offset: Local::now()
|
|
||||||
.with_time(time.clone())
|
|
||||||
.single()
|
|
||||||
.ok_or(CalendarError::WrongTimeFormat)?
|
|
||||||
.offset()
|
|
||||||
.clone(),
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
let offset = timezone
|
|
||||||
.parse::<Tz>()
|
|
||||||
.map_err(|_| CalendarError::UnknownTimezone(timezone.to_string()))?
|
|
||||||
.offset_from_utc_datetime(&Utc::now().naive_utc());
|
|
||||||
|
|
||||||
Ok(SimpleTimezone {
|
|
||||||
name: timezone.to_string(),
|
|
||||||
offset: offset.fix(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_timetable(
|
pub fn create_timetable(
|
||||||
target_time: &NaiveTime,
|
target_time: &NaiveTime,
|
||||||
target_date: &NaiveDate,
|
target_date: &NaiveDate,
|
||||||
local_timezone: &FixedOffset,
|
local_timezone: &Tz,
|
||||||
in_timezone: &String,
|
target_timezone: &Tz,
|
||||||
padding_hrs: i8,
|
padding_hrs: i8,
|
||||||
) -> Result<Vec<String>, CalendarError> {
|
) -> Result<Vec<NaiveTime>, CalendarError> {
|
||||||
let datetime = local_timezone.from_local_datetime(&NaiveDateTime::new(
|
let datetime = local_timezone
|
||||||
target_date.clone(),
|
.from_local_datetime(&NaiveDateTime::new(
|
||||||
target_time.clone(),
|
target_date.clone(),
|
||||||
));
|
target_time.clone(),
|
||||||
let time_in_timezone = if in_timezone == "Local" {
|
))
|
||||||
Local::now()
|
.single()
|
||||||
.with_time(target_time.clone())
|
.ok_or_else(|| {
|
||||||
.single()
|
CalendarError::TimezoneConversionFailed(local_timezone.name().to_string())
|
||||||
.ok_or(CalendarError::WrongTimeFormat)?
|
})?;
|
||||||
.naive_local()
|
let time_in_timezone = datetime.with_timezone(target_timezone);
|
||||||
} else {
|
|
||||||
let parsed_timezone = in_timezone
|
|
||||||
.parse::<Tz>()
|
|
||||||
.map_err(|_| CalendarError::UnknownTimezone(in_timezone.clone()))?;
|
|
||||||
|
|
||||||
datetime
|
Ok(create_timetable_vecs(&time_in_timezone.time(), padding_hrs))
|
||||||
.single()
|
|
||||||
.ok_or(CalendarError::TimezoneConversionFailed(in_timezone.clone()))?
|
|
||||||
.with_timezone(&parsed_timezone)
|
|
||||||
.naive_local()
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(create_calendar_strings(
|
|
||||||
&time_in_timezone.time(),
|
|
||||||
padding_hrs,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_todays_date() -> String {
|
pub fn get_todays_date() -> String {
|
||||||
|
|
@ -109,33 +58,58 @@ pub fn get_todays_date() -> String {
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
use chrono_tz::Europe::Prague;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calendar_string() {
|
fn test_timetable_vecs() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
create_calendar_strings(&NaiveTime::from_hms_opt(10, 0, 0).unwrap(), 4),
|
create_timetable_vecs(&NaiveTime::from_hms_opt(10, 0, 0).unwrap(), 4),
|
||||||
vec![
|
vec![
|
||||||
"06:00", "07:00", "08:00", "09:00", "10:00", "11:00", "12:00", "13:00", "14:00"
|
NaiveTime::from_hms_opt(06, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(07, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(08, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(09, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(11, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(12, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(13, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(14, 0, 0).unwrap(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calendar_string_overflow() {
|
fn test_timetable_vecs_overflow() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
create_calendar_strings(&NaiveTime::from_hms_opt(3, 0, 0).unwrap(), 4),
|
create_timetable_vecs(&NaiveTime::from_hms_opt(3, 0, 0).unwrap(), 4),
|
||||||
vec![
|
vec![
|
||||||
"23:00", "00:00", "01:00", "02:00", "03:00", "04:00", "05:00", "06:00", "07:00"
|
NaiveTime::from_hms_opt(23, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(00, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(01, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(02, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(03, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(04, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(05, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(06, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(07, 0, 0).unwrap(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_calendar_string_underflow() {
|
fn test_timetable_vecs_underflow() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
create_calendar_strings(&NaiveTime::from_hms_opt(22, 0, 0).unwrap(), 4),
|
create_timetable_vecs(&NaiveTime::from_hms_opt(22, 0, 0).unwrap(), 4),
|
||||||
vec![
|
vec![
|
||||||
"18:00", "19:00", "20:00", "21:00", "22:00", "23:00", "00:00", "01:00", "02:00"
|
NaiveTime::from_hms_opt(18, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(19, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(20, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(21, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(22, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(23, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(00, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(01, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(02, 0, 0).unwrap(),
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -144,13 +118,20 @@ mod tests {
|
||||||
fn test_create_timezone() {
|
fn test_create_timezone() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
create_timetable(
|
create_timetable(
|
||||||
&"10".to_string(),
|
&NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
|
||||||
&"1-1-2000".to_string(),
|
&NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(),
|
||||||
&"Europe/Prague".to_string(),
|
&Prague,
|
||||||
|
&Prague,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
vec!["08:00", "09:00", "10:00", "11:00", "12:00"]
|
vec![
|
||||||
|
NaiveTime::from_hms_opt(08, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(09, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(10, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(11, 0, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(12, 0, 0).unwrap()
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -158,55 +139,20 @@ mod tests {
|
||||||
fn test_create_timezone_nonzero_minutes() {
|
fn test_create_timezone_nonzero_minutes() {
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
create_timetable(
|
create_timetable(
|
||||||
&"10:30".to_string(),
|
&NaiveTime::from_hms_opt(10, 30, 0).unwrap(),
|
||||||
&"1-1-2000".to_string(),
|
&NaiveDate::from_ymd_opt(2000, 1, 1).unwrap(),
|
||||||
&"Europe/Prague".to_string(),
|
&Prague,
|
||||||
|
&Prague,
|
||||||
2
|
2
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
vec!["08:30", "09:30", "10:30", "11:30", "12:30"]
|
vec![
|
||||||
);
|
NaiveTime::from_hms_opt(08, 30, 0).unwrap(),
|
||||||
}
|
NaiveTime::from_hms_opt(09, 30, 0).unwrap(),
|
||||||
|
NaiveTime::from_hms_opt(10, 30, 0).unwrap(),
|
||||||
#[test]
|
NaiveTime::from_hms_opt(11, 30, 0).unwrap(),
|
||||||
fn test_create_timezone_wrong_time() {
|
NaiveTime::from_hms_opt(12, 30, 0).unwrap()
|
||||||
assert_eq!(
|
]
|
||||||
create_timetable(
|
|
||||||
&"hello".to_string(),
|
|
||||||
&"1-1-2000".to_string(),
|
|
||||||
&"Europe/Prague".to_string(),
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
.unwrap_err(),
|
|
||||||
CalendarError::WrongTimeFormat
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_create_timezone_wrong_date() {
|
|
||||||
assert_eq!(
|
|
||||||
create_timetable(
|
|
||||||
&"10:00".to_string(),
|
|
||||||
&"hello".to_string(),
|
|
||||||
&"Europe/Prague".to_string(),
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
.unwrap_err(),
|
|
||||||
CalendarError::WrongDateFormat
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_create_timezone_wrong_zone_name() {
|
|
||||||
assert_eq!(
|
|
||||||
create_timetable(
|
|
||||||
&"10:00".to_string(),
|
|
||||||
&"1-1-2000".to_string(),
|
|
||||||
&"hello".to_string(),
|
|
||||||
2,
|
|
||||||
)
|
|
||||||
.unwrap_err(),
|
|
||||||
CalendarError::UnknownTimezone("hello".to_string())
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
87
src/cli.rs
87
src/cli.rs
|
|
@ -1,10 +1,11 @@
|
||||||
use crate::calendar::CalendarError;
|
use super::calendar::{create_timetable, get_todays_date};
|
||||||
|
|
||||||
use super::calendar::{create_timetable, get_local_timezone_offset, get_todays_date};
|
|
||||||
|
|
||||||
use chrono::NaiveDate;
|
use chrono::NaiveDate;
|
||||||
use chrono::NaiveTime;
|
use chrono::NaiveTime;
|
||||||
|
use chrono_tz::Etc::UTC;
|
||||||
|
use chrono_tz::Tz;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
|
use colored::Colorize;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::iter;
|
use std::iter;
|
||||||
|
|
@ -28,7 +29,8 @@ pub struct Args {
|
||||||
pub enum CliError {
|
pub enum CliError {
|
||||||
CouldNotParseTime,
|
CouldNotParseTime,
|
||||||
CouldNotParseDate,
|
CouldNotParseDate,
|
||||||
CouldNoGenerateTimetable(CalendarError),
|
CouldNotParseTimezone(String),
|
||||||
|
CouldNoGenerateTimetable(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for CliError {
|
impl fmt::Display for CliError {
|
||||||
|
|
@ -36,6 +38,9 @@ impl fmt::Display for CliError {
|
||||||
let message = match *self {
|
let message = match *self {
|
||||||
Self::CouldNotParseTime => "Couldn't parse time".to_string(),
|
Self::CouldNotParseTime => "Couldn't parse time".to_string(),
|
||||||
Self::CouldNotParseDate => "Couldn't parse date".to_string(),
|
Self::CouldNotParseDate => "Couldn't parse date".to_string(),
|
||||||
|
Self::CouldNotParseTimezone(ref tz) => {
|
||||||
|
format!("Couldn't parse timezone {}", tz).to_string()
|
||||||
|
}
|
||||||
Self::CouldNoGenerateTimetable(ref er) => {
|
Self::CouldNoGenerateTimetable(ref er) => {
|
||||||
format!("Could not generate timetable: {}", er)
|
format!("Could not generate timetable: {}", er)
|
||||||
}
|
}
|
||||||
|
|
@ -66,30 +71,80 @@ pub fn print_timezones(
|
||||||
.or_else(|_| NaiveDate::parse_from_str(date, "%d-%m"))
|
.or_else(|_| NaiveDate::parse_from_str(date, "%d-%m"))
|
||||||
.map_err(|_| CliError::CouldNotParseDate)?;
|
.map_err(|_| CliError::CouldNotParseDate)?;
|
||||||
|
|
||||||
let local_timezone_offset = &get_local_timezone_offset(&parsed_time, local_timezone)
|
let parsed_local_timezone: Tz = if local_timezone == "Local" {
|
||||||
.map_err(|e| CliError::CouldNoGenerateTimetable(e))?;
|
UTC // FIXME: use local timezone instead
|
||||||
|
} else {
|
||||||
|
local_timezone
|
||||||
|
.parse::<Tz>()
|
||||||
|
.map_err(|_| CliError::CouldNoGenerateTimetable(local_timezone.to_string()))?
|
||||||
|
};
|
||||||
|
|
||||||
let all_timezones: Vec<&String> = iter::once(&local_timezone_offset.name)
|
let parsed_timezones: Vec<Tz> = timezones
|
||||||
.chain(timezones)
|
.iter()
|
||||||
|
.map(|tz| {
|
||||||
|
tz.parse::<Tz>()
|
||||||
|
.map_err(|_| CliError::CouldNotParseTimezone(tz.to_string()))
|
||||||
|
})
|
||||||
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
|
|
||||||
|
let all_timezones: Vec<Tz> = iter::once(parsed_local_timezone)
|
||||||
|
.chain(parsed_timezones)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let max_len = all_timezones.iter().map(|t| t.len()).max().unwrap_or(0) as u32;
|
let max_len = all_timezones
|
||||||
|
.iter()
|
||||||
|
.map(|t| t.name().len())
|
||||||
|
.max()
|
||||||
|
.unwrap_or(0) as u32;
|
||||||
|
|
||||||
for timezone in all_timezones {
|
for timezone in all_timezones {
|
||||||
let name_padding = (0..(max_len - timezone.len() as u32))
|
let timezone_name = timezone.name();
|
||||||
.map(|_| " ".to_string())
|
let name_padding_len = max_len - timezone_name.len() as u32;
|
||||||
.collect::<Vec<String>>()
|
|
||||||
.join("");
|
|
||||||
match create_timetable(
|
match create_timetable(
|
||||||
&parsed_time,
|
&parsed_time,
|
||||||
&parsed_date,
|
&parsed_date,
|
||||||
&local_timezone_offset.offset,
|
&parsed_local_timezone,
|
||||||
timezone,
|
&timezone,
|
||||||
5,
|
5,
|
||||||
) {
|
) {
|
||||||
Ok(timetable) => println!("{}:{}\t{}", timezone, name_padding, timetable.join("\t")),
|
Ok(timetable) => {
|
||||||
|
print_timetable(&timezone_name.to_string(), name_padding_len, &timetable)
|
||||||
|
}
|
||||||
Err(error) => panic!("Failed: {}", error),
|
Err(error) => panic!("Failed: {}", error),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn print_timetable(name: &String, name_padding_len: u32, timetable: &Vec<NaiveTime>) {
|
||||||
|
let len = timetable.len();
|
||||||
|
let mid = (len - 1) / 2;
|
||||||
|
let acceptable_time_from = NaiveTime::from_hms_opt(8, 0, 0).unwrap();
|
||||||
|
let acceptable_time_to = NaiveTime::from_hms_opt(20, 0, 0).unwrap();
|
||||||
|
let formatted: Vec<String> = timetable
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.map(|(i, t)| {
|
||||||
|
let stringified = t.format("%H:%M").to_string();
|
||||||
|
|
||||||
|
let acceptable_hours_formatted = if t < &acceptable_time_from || t > &acceptable_time_to
|
||||||
|
{
|
||||||
|
stringified.color("#fea201")
|
||||||
|
} else {
|
||||||
|
stringified.green()
|
||||||
|
};
|
||||||
|
|
||||||
|
if i == mid {
|
||||||
|
acceptable_hours_formatted.reversed().to_string()
|
||||||
|
} else {
|
||||||
|
acceptable_hours_formatted.to_string()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let name_padding = (0..name_padding_len)
|
||||||
|
.map(|_| " ".to_string())
|
||||||
|
.collect::<Vec<String>>()
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
println!("{}:{}\t{}", name, name_padding, formatted.join("\t"));
|
||||||
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue