forked from extern/nushell
add seq_date
command (#743)
* add `seq_date` command * fixed a reedline type-o * copy-n-paste error
This commit is contained in:
395
crates/nu-command/src/generators/cal.rs
Normal file
395
crates/nu-command/src/generators/cal.rs
Normal file
@ -0,0 +1,395 @@
|
||||
use chrono::{Datelike, Local, NaiveDate};
|
||||
use indexmap::IndexMap;
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Value,
|
||||
};
|
||||
use std::collections::VecDeque;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Cal;
|
||||
|
||||
struct Arguments {
|
||||
year: bool,
|
||||
quarter: bool,
|
||||
month: bool,
|
||||
month_names: bool,
|
||||
full_year: Option<Spanned<i64>>,
|
||||
week_start: Option<Spanned<String>>,
|
||||
}
|
||||
|
||||
impl Command for Cal {
|
||||
fn name(&self) -> &str {
|
||||
"cal"
|
||||
}
|
||||
|
||||
fn signature(&self) -> Signature {
|
||||
Signature::build("cal")
|
||||
.switch("year", "Display the year column", Some('y'))
|
||||
.switch("quarter", "Display the quarter column", Some('q'))
|
||||
.switch("month", "Display the month column", Some('m'))
|
||||
.named(
|
||||
"full-year",
|
||||
SyntaxShape::Int,
|
||||
"Display a year-long calendar for the specified year",
|
||||
None,
|
||||
)
|
||||
.named(
|
||||
"week-start",
|
||||
SyntaxShape::String,
|
||||
"Display the calendar with the specified day as the first day of the week",
|
||||
None,
|
||||
)
|
||||
.switch(
|
||||
"month-names",
|
||||
"Display the month names instead of integers",
|
||||
None,
|
||||
)
|
||||
.category(Category::Generators)
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"Display a calendar."
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
cal(engine_state, stack, call, input)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
vec![
|
||||
Example {
|
||||
description: "This month's calendar",
|
||||
example: "cal",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "The calendar for all of 2012",
|
||||
example: "cal --full-year 2012",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "This month's calendar with the week starting on monday",
|
||||
example: "cal --week-start monday",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn cal(
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let mut calendar_vec_deque = VecDeque::new();
|
||||
let tag = call.head;
|
||||
|
||||
let (current_year, current_month, current_day) = get_current_date();
|
||||
|
||||
let arguments = Arguments {
|
||||
year: call.has_flag("year"),
|
||||
month: call.has_flag("month"),
|
||||
month_names: call.has_flag("month-names"),
|
||||
quarter: call.has_flag("quarter"),
|
||||
full_year: call.get_flag(engine_state, stack, "full-year")?,
|
||||
week_start: call.get_flag(engine_state, stack, "week-start")?,
|
||||
};
|
||||
|
||||
let mut selected_year: i32 = current_year;
|
||||
let mut current_day_option: Option<u32> = Some(current_day);
|
||||
|
||||
let full_year_value = &arguments.full_year;
|
||||
let month_range = if let Some(full_year_value) = full_year_value {
|
||||
selected_year = full_year_value.item as i32;
|
||||
|
||||
if selected_year != current_year {
|
||||
current_day_option = None
|
||||
}
|
||||
(1, 12)
|
||||
} else {
|
||||
(current_month, current_month)
|
||||
};
|
||||
|
||||
add_months_of_year_to_table(
|
||||
&arguments,
|
||||
&mut calendar_vec_deque,
|
||||
tag,
|
||||
selected_year,
|
||||
month_range,
|
||||
current_month,
|
||||
current_day_option,
|
||||
)?;
|
||||
|
||||
Ok(Value::List {
|
||||
vals: calendar_vec_deque.into_iter().collect(),
|
||||
span: tag,
|
||||
}
|
||||
.into_pipeline_data())
|
||||
}
|
||||
|
||||
fn get_invalid_year_shell_error(head: Span) -> ShellError {
|
||||
ShellError::UnsupportedInput("The year is invalid".to_string(), head)
|
||||
}
|
||||
|
||||
struct MonthHelper {
|
||||
selected_year: i32,
|
||||
selected_month: u32,
|
||||
day_number_of_week_month_starts_on: u32,
|
||||
number_of_days_in_month: u32,
|
||||
quarter_number: u32,
|
||||
month_name: String,
|
||||
}
|
||||
|
||||
impl MonthHelper {
|
||||
pub fn new(selected_year: i32, selected_month: u32) -> Result<MonthHelper, ()> {
|
||||
let naive_date = NaiveDate::from_ymd_opt(selected_year, selected_month, 1).ok_or(())?;
|
||||
let number_of_days_in_month =
|
||||
MonthHelper::calculate_number_of_days_in_month(selected_year, selected_month)?;
|
||||
|
||||
Ok(MonthHelper {
|
||||
selected_year,
|
||||
selected_month,
|
||||
day_number_of_week_month_starts_on: naive_date.weekday().num_days_from_sunday(),
|
||||
number_of_days_in_month,
|
||||
quarter_number: ((selected_month - 1) / 3) + 1,
|
||||
month_name: naive_date.format("%B").to_string().to_ascii_lowercase(),
|
||||
})
|
||||
}
|
||||
|
||||
fn calculate_number_of_days_in_month(
|
||||
mut selected_year: i32,
|
||||
mut selected_month: u32,
|
||||
) -> Result<u32, ()> {
|
||||
// Chrono does not provide a method to output the amount of days in a month
|
||||
// This is a workaround taken from the example code from the Chrono docs here:
|
||||
// https://docs.rs/chrono/0.3.0/chrono/naive/date/struct.NaiveDate.html#example-30
|
||||
if selected_month == 12 {
|
||||
selected_year += 1;
|
||||
selected_month = 1;
|
||||
} else {
|
||||
selected_month += 1;
|
||||
};
|
||||
|
||||
let next_month_naive_date =
|
||||
NaiveDate::from_ymd_opt(selected_year, selected_month, 1).ok_or(())?;
|
||||
|
||||
Ok(next_month_naive_date.pred().day())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_date() -> (i32, u32, u32) {
|
||||
let local_now_date = Local::now().date();
|
||||
|
||||
let current_year: i32 = local_now_date.year();
|
||||
let current_month: u32 = local_now_date.month();
|
||||
let current_day: u32 = local_now_date.day();
|
||||
|
||||
(current_year, current_month, current_day)
|
||||
}
|
||||
|
||||
fn add_months_of_year_to_table(
|
||||
arguments: &Arguments,
|
||||
calendar_vec_deque: &mut VecDeque<Value>,
|
||||
tag: Span,
|
||||
selected_year: i32,
|
||||
(start_month, end_month): (u32, u32),
|
||||
current_month: u32,
|
||||
current_day_option: Option<u32>,
|
||||
) -> Result<(), ShellError> {
|
||||
for month_number in start_month..=end_month {
|
||||
let mut new_current_day_option: Option<u32> = None;
|
||||
|
||||
if let Some(current_day) = current_day_option {
|
||||
if month_number == current_month {
|
||||
new_current_day_option = Some(current_day)
|
||||
}
|
||||
}
|
||||
|
||||
let add_month_to_table_result = add_month_to_table(
|
||||
arguments,
|
||||
calendar_vec_deque,
|
||||
tag,
|
||||
selected_year,
|
||||
month_number,
|
||||
new_current_day_option,
|
||||
);
|
||||
|
||||
add_month_to_table_result?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn add_month_to_table(
|
||||
arguments: &Arguments,
|
||||
calendar_vec_deque: &mut VecDeque<Value>,
|
||||
tag: Span,
|
||||
selected_year: i32,
|
||||
current_month: u32,
|
||||
current_day_option: Option<u32>,
|
||||
) -> Result<(), ShellError> {
|
||||
let month_helper_result = MonthHelper::new(selected_year, current_month);
|
||||
|
||||
let full_year_value: &Option<Spanned<i64>> = &arguments.full_year;
|
||||
|
||||
let month_helper = match month_helper_result {
|
||||
Ok(month_helper) => month_helper,
|
||||
Err(()) => match full_year_value {
|
||||
Some(x) => return Err(get_invalid_year_shell_error(x.span)),
|
||||
None => {
|
||||
return Err(ShellError::UnknownOperator(
|
||||
"Issue parsing command, invalid command".to_string(),
|
||||
tag,
|
||||
))
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
let mut days_of_the_week = [
|
||||
"sunday",
|
||||
"monday",
|
||||
"tuesday",
|
||||
"wednesday",
|
||||
"thursday",
|
||||
"friday",
|
||||
"saturday",
|
||||
];
|
||||
|
||||
let mut week_start_day = days_of_the_week[0].to_string();
|
||||
if let Some(day) = &arguments.week_start {
|
||||
let s = &day.item;
|
||||
if days_of_the_week.contains(&s.as_str()) {
|
||||
week_start_day = s.to_string();
|
||||
} else {
|
||||
return Err(ShellError::UnsupportedInput(
|
||||
"The specified week start day is invalid".to_string(),
|
||||
day.span,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
let week_start_day_offset = days_of_the_week.len()
|
||||
- days_of_the_week
|
||||
.iter()
|
||||
.position(|day| *day == week_start_day)
|
||||
.unwrap_or(0);
|
||||
|
||||
days_of_the_week.rotate_right(week_start_day_offset);
|
||||
|
||||
let mut total_start_offset: u32 =
|
||||
month_helper.day_number_of_week_month_starts_on + week_start_day_offset as u32;
|
||||
total_start_offset %= days_of_the_week.len() as u32;
|
||||
|
||||
let mut day_number: u32 = 1;
|
||||
let day_limit: u32 = total_start_offset + month_helper.number_of_days_in_month;
|
||||
|
||||
let should_show_year_column = arguments.year;
|
||||
let should_show_quarter_column = arguments.quarter;
|
||||
let should_show_month_column = arguments.month;
|
||||
let should_show_month_names = arguments.month_names;
|
||||
|
||||
while day_number <= day_limit {
|
||||
let mut indexmap = IndexMap::new();
|
||||
|
||||
if should_show_year_column {
|
||||
indexmap.insert(
|
||||
"year".to_string(),
|
||||
Value::Int {
|
||||
val: month_helper.selected_year as i64,
|
||||
span: tag,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if should_show_quarter_column {
|
||||
indexmap.insert(
|
||||
"quarter".to_string(),
|
||||
Value::Int {
|
||||
val: month_helper.quarter_number as i64,
|
||||
span: tag,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
if should_show_month_column || should_show_month_names {
|
||||
let month_value = if should_show_month_names {
|
||||
Value::String {
|
||||
val: month_helper.month_name.clone(),
|
||||
span: tag,
|
||||
}
|
||||
} else {
|
||||
Value::Int {
|
||||
val: month_helper.selected_month as i64,
|
||||
span: tag,
|
||||
}
|
||||
};
|
||||
|
||||
indexmap.insert("month".to_string(), month_value);
|
||||
}
|
||||
|
||||
for day in &days_of_the_week {
|
||||
let should_add_day_number_to_table =
|
||||
(day_number > total_start_offset) && (day_number <= day_limit);
|
||||
|
||||
let mut value = Value::Nothing { span: tag };
|
||||
|
||||
if should_add_day_number_to_table {
|
||||
let adjusted_day_number = day_number - total_start_offset;
|
||||
|
||||
value = Value::Int {
|
||||
val: adjusted_day_number as i64,
|
||||
span: tag,
|
||||
};
|
||||
|
||||
if let Some(current_day) = current_day_option {
|
||||
if current_day == adjusted_day_number {
|
||||
// TODO: Update the value here with a color when color support is added
|
||||
// This colors the current day
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
indexmap.insert((*day).to_string(), value);
|
||||
|
||||
day_number += 1;
|
||||
}
|
||||
|
||||
let cols: Vec<String> = indexmap.keys().map(|f| f.to_string()).collect();
|
||||
let mut vals: Vec<Value> = Vec::new();
|
||||
for c in &cols {
|
||||
if let Some(x) = indexmap.get(c) {
|
||||
vals.push(x.to_owned())
|
||||
}
|
||||
}
|
||||
calendar_vec_deque.push_back(Value::Record {
|
||||
cols,
|
||||
vals,
|
||||
span: tag,
|
||||
})
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(Cal {})
|
||||
}
|
||||
}
|
5
crates/nu-command/src/generators/mod.rs
Normal file
5
crates/nu-command/src/generators/mod.rs
Normal file
@ -0,0 +1,5 @@
|
||||
mod cal;
|
||||
mod seq_date;
|
||||
|
||||
pub use cal::Cal;
|
||||
pub use seq_date::SeqDate;
|
370
crates/nu-command/src/generators/seq_date.rs
Normal file
370
crates/nu-command/src/generators/seq_date.rs
Normal file
@ -0,0 +1,370 @@
|
||||
use chrono::naive::NaiveDate;
|
||||
use chrono::{Duration, Local};
|
||||
use nu_engine::CallExt;
|
||||
use nu_protocol::ast::Call;
|
||||
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||
use nu_protocol::{
|
||||
Category, Example, IntoPipelineData, PipelineData, ShellError, Signature, Span, Spanned,
|
||||
SyntaxShape, Value,
|
||||
};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SeqDate;
|
||||
|
||||
impl Command for SeqDate {
|
||||
fn name(&self) -> &str {
|
||||
"seq date"
|
||||
}
|
||||
|
||||
fn usage(&self) -> &str {
|
||||
"print sequences of dates"
|
||||
}
|
||||
|
||||
fn signature(&self) -> nu_protocol::Signature {
|
||||
Signature::build("seq date")
|
||||
.named(
|
||||
"separator",
|
||||
SyntaxShape::String,
|
||||
"separator character (defaults to \\n)",
|
||||
Some('s'),
|
||||
)
|
||||
.named(
|
||||
"output-format",
|
||||
SyntaxShape::String,
|
||||
"prints dates in this format (defaults to %Y-%m-%d)",
|
||||
Some('o'),
|
||||
)
|
||||
.named(
|
||||
"input-format",
|
||||
SyntaxShape::String,
|
||||
"give argument dates in this format (defaults to %Y-%m-%d)",
|
||||
Some('i'),
|
||||
)
|
||||
.named(
|
||||
"begin-date",
|
||||
SyntaxShape::String,
|
||||
"beginning date range",
|
||||
Some('b'),
|
||||
)
|
||||
.named("end-date", SyntaxShape::String, "ending date", Some('e'))
|
||||
.named(
|
||||
"increment",
|
||||
SyntaxShape::Int,
|
||||
"increment dates by this number",
|
||||
Some('n'),
|
||||
)
|
||||
.named(
|
||||
"days",
|
||||
SyntaxShape::Int,
|
||||
"number of days to print",
|
||||
Some('d'),
|
||||
)
|
||||
.switch("reverse", "print dates in reverse", Some('r'))
|
||||
.category(Category::Generators)
|
||||
}
|
||||
|
||||
fn examples(&self) -> Vec<Example> {
|
||||
let span = Span::test_data();
|
||||
|
||||
vec![
|
||||
Example {
|
||||
description: "print the next 10 days in YYYY-MM-DD format with newline separator",
|
||||
example: "seq date --days 10",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "print the previous 10 days in YYYY-MM-DD format with newline separator",
|
||||
example: "seq date --days 10 -r",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "print the previous 10 days starting today in MM/DD/YYYY format with newline separator",
|
||||
example: "seq date --days 10 -o '%m/%d/%Y' -r",
|
||||
result: None,
|
||||
},
|
||||
Example {
|
||||
description: "print the first 10 days in January, 2020",
|
||||
example: "seq date -b '2020-01-01' -e '2020-01-10'",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::String { val: "2020-01-01".into(), span, },
|
||||
Value::String { val: "2020-01-02".into(), span, },
|
||||
Value::String { val: "2020-01-03".into(), span, },
|
||||
Value::String { val: "2020-01-04".into(), span, },
|
||||
Value::String { val: "2020-01-05".into(), span, },
|
||||
Value::String { val: "2020-01-06".into(), span, },
|
||||
Value::String { val: "2020-01-07".into(), span, },
|
||||
Value::String { val: "2020-01-08".into(), span, },
|
||||
Value::String { val: "2020-01-09".into(), span, },
|
||||
Value::String { val: "2020-01-10".into(), span, },
|
||||
],
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "print every fifth day between January 1st 2020 and January 31st 2020",
|
||||
example: "seq date -b '2020-01-01' -e '2020-01-31' -n 5",
|
||||
result: Some(Value::List {
|
||||
vals: vec![
|
||||
Value::String { val: "2020-01-01".into(), span, },
|
||||
Value::String { val: "2020-01-06".into(), span, },
|
||||
Value::String { val: "2020-01-11".into(), span, },
|
||||
Value::String { val: "2020-01-16".into(), span, },
|
||||
Value::String { val: "2020-01-21".into(), span, },
|
||||
Value::String { val: "2020-01-26".into(), span, },
|
||||
Value::String { val: "2020-01-31".into(), span, },
|
||||
],
|
||||
span,
|
||||
}),
|
||||
},
|
||||
Example {
|
||||
description: "starting on May 5th, 2020, print the next 10 days in your locale's date format, colon separated",
|
||||
example: "seq date -o %x -s ':' -d 10 -b '2020-05-01'",
|
||||
result: None,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
||||
fn run(
|
||||
&self,
|
||||
engine_state: &EngineState,
|
||||
stack: &mut Stack,
|
||||
call: &Call,
|
||||
_input: PipelineData,
|
||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||
let separator: Option<Spanned<String>> = call.get_flag(engine_state, stack, "separator")?;
|
||||
let output_format: Option<Spanned<String>> =
|
||||
call.get_flag(engine_state, stack, "output-format")?;
|
||||
let input_format: Option<Spanned<String>> =
|
||||
call.get_flag(engine_state, stack, "input-format")?;
|
||||
let begin_date: Option<Spanned<String>> =
|
||||
call.get_flag(engine_state, stack, "begin-date")?;
|
||||
let end_date: Option<Spanned<String>> = call.get_flag(engine_state, stack, "end-date")?;
|
||||
let increment: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "increment")?;
|
||||
let days: Option<Spanned<i64>> = call.get_flag(engine_state, stack, "days")?;
|
||||
let reverse = call.has_flag("reverse");
|
||||
|
||||
let sep: String = match separator {
|
||||
Some(s) => {
|
||||
if s.item == r"\t" {
|
||||
'\t'.to_string()
|
||||
} else if s.item == r"\n" {
|
||||
'\n'.to_string()
|
||||
} else if s.item == r"\r" {
|
||||
'\r'.to_string()
|
||||
} else {
|
||||
let vec_s: Vec<char> = s.item.chars().collect();
|
||||
if vec_s.is_empty() {
|
||||
return Err(ShellError::SpannedLabeledError(
|
||||
"Expected a single separator char from --separator".to_string(),
|
||||
"requires a single character string input".to_string(),
|
||||
s.span,
|
||||
));
|
||||
};
|
||||
vec_s.iter().collect()
|
||||
}
|
||||
}
|
||||
_ => '\n'.to_string(),
|
||||
};
|
||||
|
||||
let outformat = match output_format {
|
||||
Some(s) => Some(Value::string(s.item, s.span)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let informat = match input_format {
|
||||
Some(s) => Some(Value::string(s.item, s.span)),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let begin = match begin_date {
|
||||
Some(s) => Some(s.item),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let end = match end_date {
|
||||
Some(s) => Some(s.item),
|
||||
_ => None,
|
||||
};
|
||||
|
||||
let inc = match increment {
|
||||
Some(i) => Value::int(i.item, i.span),
|
||||
_ => Value::int(1_i64, Span::test_data()),
|
||||
};
|
||||
|
||||
let day_count = days.map(|i| Value::int(i.item, i.span));
|
||||
|
||||
let mut rev = false;
|
||||
if reverse {
|
||||
rev = reverse;
|
||||
}
|
||||
|
||||
Ok(
|
||||
run_seq_dates(sep, outformat, informat, begin, end, inc, day_count, rev)?
|
||||
.into_pipeline_data(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn parse_date_string(s: &str, format: &str) -> Result<NaiveDate, &'static str> {
|
||||
let d = match NaiveDate::parse_from_str(s, format) {
|
||||
Ok(d) => d,
|
||||
Err(_) => return Err("Failed to parse date."),
|
||||
};
|
||||
Ok(d)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn run_seq_dates(
|
||||
separator: String,
|
||||
output_format: Option<Value>,
|
||||
input_format: Option<Value>,
|
||||
beginning_date: Option<String>,
|
||||
ending_date: Option<String>,
|
||||
increment: Value,
|
||||
day_count: Option<Value>,
|
||||
reverse: bool,
|
||||
) -> Result<Value, ShellError> {
|
||||
let today = Local::today().naive_local();
|
||||
let mut step_size: i64 = increment
|
||||
.as_i64()
|
||||
.expect("unable to change increment to i64");
|
||||
|
||||
if step_size == 0 {
|
||||
return Err(ShellError::SpannedLabeledError(
|
||||
"increment cannot be 0".to_string(),
|
||||
"increment cannot be 0".to_string(),
|
||||
increment.span()?,
|
||||
));
|
||||
}
|
||||
|
||||
let in_format = match input_format {
|
||||
Some(i) => match i.as_string() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(ShellError::LabeledError(
|
||||
e.to_string(),
|
||||
"error with input_format as_string".to_string(),
|
||||
));
|
||||
}
|
||||
},
|
||||
_ => "%Y-%m-%d".to_string(),
|
||||
};
|
||||
|
||||
let out_format = match output_format {
|
||||
Some(i) => match i.as_string() {
|
||||
Ok(v) => v,
|
||||
Err(e) => {
|
||||
return Err(ShellError::LabeledError(
|
||||
e.to_string(),
|
||||
"error with output_format as_string".to_string(),
|
||||
));
|
||||
}
|
||||
},
|
||||
_ => "%Y-%m-%d".to_string(),
|
||||
};
|
||||
|
||||
let start_date = match beginning_date {
|
||||
Some(d) => match parse_date_string(&d, &in_format) {
|
||||
Ok(nd) => nd,
|
||||
Err(e) => {
|
||||
return Err(ShellError::SpannedLabeledError(
|
||||
e.to_string(),
|
||||
"Failed to parse date".to_string(),
|
||||
Span::test_data(),
|
||||
))
|
||||
}
|
||||
},
|
||||
_ => today,
|
||||
};
|
||||
|
||||
let mut end_date = match ending_date {
|
||||
Some(d) => match parse_date_string(&d, &in_format) {
|
||||
Ok(nd) => nd,
|
||||
Err(e) => {
|
||||
return Err(ShellError::SpannedLabeledError(
|
||||
e.to_string(),
|
||||
"Failed to parse date".to_string(),
|
||||
Span::test_data(),
|
||||
))
|
||||
}
|
||||
},
|
||||
_ => today,
|
||||
};
|
||||
|
||||
let mut days_to_output = match day_count {
|
||||
Some(d) => d.as_i64()?,
|
||||
None => 0i64,
|
||||
};
|
||||
|
||||
// Make the signs opposite if we're created dates in reverse direction
|
||||
if reverse {
|
||||
step_size *= -1;
|
||||
days_to_output *= -1;
|
||||
}
|
||||
|
||||
if days_to_output != 0 {
|
||||
end_date = match start_date.checked_add_signed(Duration::days(days_to_output)) {
|
||||
Some(date) => date,
|
||||
None => {
|
||||
return Err(ShellError::SpannedLabeledError(
|
||||
"integer value too large".to_string(),
|
||||
"integer value too large".to_string(),
|
||||
Span::test_data(),
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// conceptually counting down with a positive step or counting up with a negative step
|
||||
// makes no sense, attempt to do what one means by inverting the signs in those cases.
|
||||
if (start_date > end_date) && (step_size > 0) || (start_date < end_date) && step_size < 0 {
|
||||
step_size = -step_size;
|
||||
}
|
||||
|
||||
let is_out_of_range =
|
||||
|next| (step_size > 0 && next > end_date) || (step_size < 0 && next < end_date);
|
||||
|
||||
let mut next = start_date;
|
||||
if is_out_of_range(next) {
|
||||
return Err(ShellError::SpannedLabeledError(
|
||||
"date is out of range".to_string(),
|
||||
"date is out of range".to_string(),
|
||||
Span::test_data(),
|
||||
));
|
||||
}
|
||||
|
||||
let mut ret_str = String::from("");
|
||||
loop {
|
||||
ret_str.push_str(&next.format(&out_format).to_string());
|
||||
next += Duration::days(step_size);
|
||||
|
||||
if is_out_of_range(next) {
|
||||
break;
|
||||
}
|
||||
|
||||
ret_str.push_str(&separator);
|
||||
}
|
||||
|
||||
let rows: Vec<Value> = ret_str
|
||||
.lines()
|
||||
.map(|v| Value::string(v, Span::test_data()))
|
||||
.collect();
|
||||
|
||||
Ok(Value::List {
|
||||
vals: rows,
|
||||
span: Span::test_data(),
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_examples() {
|
||||
use crate::test_examples;
|
||||
|
||||
test_examples(SeqDate {})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user