feat: make month week start configurable
Add a Monday-default --week-start option for every weekday and carry it through domain layout, full and compact rendering, event query windows, print mode, and the interactive TUI. Preserve byte-compatible Monday output and cover rotated headers, spill dates, invalid input, sanitizer behavior, and live terminal restoration.
This commit is contained in:
41
src/main.cpp
41
src/main.cpp
@@ -26,6 +26,7 @@ struct Options {
|
||||
std::filesystem::path calendar_path;
|
||||
std::optional<std::filesystem::path> import_path;
|
||||
std::optional<std::filesystem::path> export_path;
|
||||
std::chrono::weekday week_start{std::chrono::Monday};
|
||||
bool demo{false};
|
||||
bool print{false};
|
||||
bool restore_backup{false};
|
||||
@@ -50,6 +51,7 @@ void print_help(std::ostream& output)
|
||||
"Usage: nocal [OPTIONS] [CALENDAR.ics]\n\n"
|
||||
"A theme-native terminal month calendar.\n\n"
|
||||
" -c, --calendar PATH read events from PATH\n"
|
||||
" --week-start DAY set first weekday: monday through sunday\n"
|
||||
" --demo add sample appointments without saving them\n"
|
||||
" --print print one plain-text frame and exit\n"
|
||||
" --restore-backup restore PATH.bak over PATH and exit\n"
|
||||
@@ -68,18 +70,32 @@ void print_help(std::ostream& output)
|
||||
" t jumps to today, ? toggles help, q quits.\n";
|
||||
}
|
||||
|
||||
std::optional<std::chrono::weekday> parse_week_start(const std::string_view value)
|
||||
{
|
||||
if (value == "monday") return std::chrono::Monday;
|
||||
if (value == "tuesday") return std::chrono::Tuesday;
|
||||
if (value == "wednesday") return std::chrono::Wednesday;
|
||||
if (value == "thursday") return std::chrono::Thursday;
|
||||
if (value == "friday") return std::chrono::Friday;
|
||||
if (value == "saturday") return std::chrono::Saturday;
|
||||
if (value == "sunday") return std::chrono::Sunday;
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
Options parse_options(int argc, char** argv)
|
||||
{
|
||||
Options options{
|
||||
.calendar_path = default_calendar_path(),
|
||||
.import_path = std::nullopt,
|
||||
.export_path = std::nullopt,
|
||||
.week_start = std::chrono::Monday,
|
||||
.demo = false,
|
||||
.print = false,
|
||||
.restore_backup = false,
|
||||
.no_color = false,
|
||||
};
|
||||
bool positional_seen = false;
|
||||
bool week_start_seen = false;
|
||||
for (int index = 1; index < argc; ++index) {
|
||||
const std::string_view argument{argv[index]};
|
||||
if (argument == "-h" || argument == "--help") {
|
||||
@@ -98,6 +114,22 @@ Options parse_options(int argc, char** argv)
|
||||
options.restore_backup = true;
|
||||
} else if (argument == "--no-color") {
|
||||
options.no_color = true;
|
||||
} else if (argument == "--week-start") {
|
||||
if (week_start_seen) {
|
||||
throw std::invalid_argument("--week-start may only be supplied once");
|
||||
}
|
||||
if (++index >= argc) {
|
||||
throw std::invalid_argument("--week-start requires a day");
|
||||
}
|
||||
const std::string_view value{argv[index]};
|
||||
const auto parsed = parse_week_start(value);
|
||||
if (!parsed) {
|
||||
throw std::invalid_argument(
|
||||
"invalid --week-start day: " + std::string{value} +
|
||||
" (expected monday through sunday)");
|
||||
}
|
||||
options.week_start = *parsed;
|
||||
week_start_seen = true;
|
||||
} else if (argument == "--import" || argument == "--export") {
|
||||
if (++index >= argc) {
|
||||
throw std::invalid_argument(std::string{argument} + " requires a path");
|
||||
@@ -182,7 +214,8 @@ std::vector<nocal::Event> demo_events()
|
||||
return {std::move(planning), std::move(release), std::move(coffee)};
|
||||
}
|
||||
|
||||
int print_frame(std::span<const nocal::Event> events, bool no_color)
|
||||
int print_frame(std::span<const nocal::Event> events, bool no_color,
|
||||
const std::chrono::weekday week_start)
|
||||
{
|
||||
const auto day = nocal::today();
|
||||
const nocal::tui::ScreenState state{
|
||||
@@ -205,6 +238,7 @@ int print_frame(std::span<const nocal::Event> events, bool no_color)
|
||||
.agenda_start_day = day,
|
||||
.agenda_items = {},
|
||||
.agenda_index = 0,
|
||||
.week_start = week_start,
|
||||
};
|
||||
std::cout << nocal::tui::render_month(events, state) << '\n';
|
||||
return std::cout ? EXIT_SUCCESS : EXIT_FAILURE;
|
||||
@@ -304,7 +338,7 @@ int main(int argc, char** argv)
|
||||
const bool non_interactive = ::isatty(STDIN_FILENO) == 0 ||
|
||||
::isatty(STDOUT_FILENO) == 0;
|
||||
if (options.print || non_interactive) {
|
||||
return print_frame(loaded.events, true);
|
||||
return print_frame(loaded.events, true, options.week_start);
|
||||
}
|
||||
if (options.no_color) {
|
||||
::setenv("NO_COLOR", "1", 1);
|
||||
@@ -325,7 +359,8 @@ int main(int argc, char** argv)
|
||||
};
|
||||
}
|
||||
}
|
||||
nocal::tui::TuiApp app{loaded.events, std::move(saver)};
|
||||
nocal::tui::TuiApp app{
|
||||
loaded.events, std::move(saver), options.week_start};
|
||||
return app.run() == nocal::tui::ExitReason::io_error ? EXIT_FAILURE : EXIT_SUCCESS;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << "nocal: " << error.what() << '\n';
|
||||
|
||||
Reference in New Issue
Block a user