nushell/crates/nu-command/src/generators/cal.rs

386 lines
11 KiB
Rust
Raw Normal View History

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, Type, 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,
)
.input_output_types(vec![(Type::Nothing, Type::Table(vec![]))])
.allow_variants_without_examples(true) // TODO: supply exhaustive examples
.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(())?;
chore: chrono_update (#7132) chrono version update # Description upgrade chrono to 0.4.23 # Major Changes If you're considering making any major change to nushell, before starting work on it, seek feedback from regular contributors and get approval for the idea from the core team either on [Discord](https://discordapp.com/invite/NtAbbGn) or [GitHub issue](https://github.com/nushell/nushell/issues/new/choose). Making sure we're all on board with the change saves everybody's time. Thanks! # Tests + Formatting Make sure you've done the following, if applicable: - Add tests that cover your changes (either in the command examples, the crate/tests folder, or in the /tests folder) - Try to think about corner cases and various ways how your changes could break. Cover those in the tests Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace --features=extra` to check that all tests pass # After Submitting * Help us keep the docs up to date: If your PR affects the user experience of Nushell (adding/removing a command, changing an input/output type, etc.), make sure the changes are reflected in the documentation (https://github.com/nushell/nushell.github.io) after the PR is merged. Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-11-26 18:19:02 +00:00
Ok(next_month_naive_date.pred_opt().unwrap_or_default().day())
}
}
fn get_current_date() -> (i32, u32, u32) {
chore: chrono_update (#7132) chrono version update # Description upgrade chrono to 0.4.23 # Major Changes If you're considering making any major change to nushell, before starting work on it, seek feedback from regular contributors and get approval for the idea from the core team either on [Discord](https://discordapp.com/invite/NtAbbGn) or [GitHub issue](https://github.com/nushell/nushell/issues/new/choose). Making sure we're all on board with the change saves everybody's time. Thanks! # Tests + Formatting Make sure you've done the following, if applicable: - Add tests that cover your changes (either in the command examples, the crate/tests folder, or in the /tests folder) - Try to think about corner cases and various ways how your changes could break. Cover those in the tests Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace --features=extra -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace --features=extra` to check that all tests pass # After Submitting * Help us keep the docs up to date: If your PR affects the user experience of Nushell (adding/removing a command, changing an input/output type, etc.), make sure the changes are reflected in the documentation (https://github.com/nushell/nushell.github.io) after the PR is merged. Co-authored-by: Darren Schroeder <343840+fdncred@users.noreply.github.com>
2022-11-26 18:19:02 +00:00
let local_now_date = Local::now().date_naive();
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(),
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412) # Description While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` constructors, which seem designed to make it easier to construct various Values, but which aren't used often at all in the codebase. So, using a few find-replaces regexes, I increased their usage. This reduces overall LOC because structures like this: ``` Value::Int { val: a, span: head } ``` are changed into ``` Value::int(a, head) ``` and are respected as such by the project's formatter. There are little readability concerns because the second argument to all of these is `span`, and it's almost always extremely obvious which is the span at every callsite. # User-Facing Changes None. # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 16:37:51 +00:00
Value::int(month_helper.selected_year as i64, tag),
);
}
if should_show_quarter_column {
indexmap.insert(
"quarter".to_string(),
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412) # Description While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` constructors, which seem designed to make it easier to construct various Values, but which aren't used often at all in the codebase. So, using a few find-replaces regexes, I increased their usage. This reduces overall LOC because structures like this: ``` Value::Int { val: a, span: head } ``` are changed into ``` Value::int(a, head) ``` and are respected as such by the project's formatter. There are little readability concerns because the second argument to all of these is `span`, and it's almost always extremely obvious which is the span at every callsite. # User-Facing Changes None. # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 16:37:51 +00:00
Value::int(month_helper.quarter_number as i64, 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 {
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412) # Description While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` constructors, which seem designed to make it easier to construct various Values, but which aren't used often at all in the codebase. So, using a few find-replaces regexes, I increased their usage. This reduces overall LOC because structures like this: ``` Value::Int { val: a, span: head } ``` are changed into ``` Value::int(a, head) ``` and are respected as such by the project's formatter. There are little readability concerns because the second argument to all of these is `span`, and it's almost always extremely obvious which is the span at every callsite. # User-Facing Changes None. # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 16:37:51 +00:00
Value::int(month_helper.selected_month as i64, 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;
Reduced LOC by replacing several instances of `Value::Int {}`, `Value::Float{}`, `Value::Bool {}`, and `Value::String {}` with `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` (#7412) # Description While perusing Value.rs, I noticed the `Value::int()`, `Value::float()`, `Value::boolean()` and `Value::string()` constructors, which seem designed to make it easier to construct various Values, but which aren't used often at all in the codebase. So, using a few find-replaces regexes, I increased their usage. This reduces overall LOC because structures like this: ``` Value::Int { val: a, span: head } ``` are changed into ``` Value::int(a, head) ``` and are respected as such by the project's formatter. There are little readability concerns because the second argument to all of these is `span`, and it's almost always extremely obvious which is the span at every callsite. # User-Facing Changes None. # Tests + Formatting Don't forget to add tests that cover your changes. Make sure you've run and fixed any issues with these commands: - `cargo fmt --all -- --check` to check standard code formatting (`cargo fmt --all` applies these changes) - `cargo clippy --workspace -- -D warnings -D clippy::unwrap_used -A clippy::needless_collect` to check that you're using the standard code style - `cargo test --workspace` to check that all tests pass # After Submitting If your PR had any user-facing changes, update [the documentation](https://github.com/nushell/nushell.github.io) after the PR is merged, if necessary. This will help us keep the docs up to date.
2022-12-09 16:37:51 +00:00
value = Value::int(adjusted_day_number as i64, 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 {})
}
}