From c7a37d2accc1824b9e219364fac22d37e6f27171 Mon Sep 17 00:00:00 2001 From: gangelo Date: Thu, 8 Feb 2024 09:58:53 -0500 Subject: [PATCH] Enable dsu add --date to accept relative date mnemonics --- CHANGELOG.md | 1 + lib/dsu/cli.rb | 22 ++++-- spec/dsu/features/dsu_add_features_spec.rb | 90 +++++++++++++++------- 3 files changed, 80 insertions(+), 33 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ea51c57..d73aef5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ All notable changes to this project will be documented in this file. ### Enhancements [x] Added `dsu project` command to manage DSU projects. See `dsu help project` or the [dsu wiki](https://github.com/gangelo/dsu/wiki) for more information. +[x] The `dsu add` command how allows you to add entries using mnemonics and relative date mnemonics (RDMs) in addition to absolute dates. For example, `dsu add -d -7 "My entry"` will add an entry one week in the past from the current day. See `dsu help add` for more information. ### Changes [x] Update ruby gems. diff --git a/lib/dsu/cli.rb b/lib/dsu/cli.rb index 182b4421..03529dba 100644 --- a/lib/dsu/cli.rb +++ b/lib/dsu/cli.rb @@ -13,11 +13,16 @@ require_relative 'subcommands/list' require_relative 'subcommands/project' require_relative 'subcommands/theme' +require_relative 'support/command_options/time_mnemonic' +require_relative 'support/time_formatable' require_relative 'views/entry_group/list' module Dsu # The `dsu` command. class CLI < BaseCLI + include Support::CommandOptions::TimeMnemonic + include Support::TimeFormatable + map I18n.t('commands.add.key_mappings') => :add map I18n.t('commands.browse.key_mappings') => :browse map I18n.t('commands.config.key_mappings') => :config @@ -39,20 +44,27 @@ class CLI < BaseCLI option I18n.t('options.yesterday.name'), aliases: I18n.t('options.yesterday.aliases'), type: :boolean option I18n.t('options.today.name'), aliases: I18n.t('options.today.aliases'), type: :boolean, default: true def add(description) - time = if options[I18n.t('options.date.name')].present? - Time.parse(options[I18n.t('options.date.name')]) + date_or_mnemonic = if options[I18n.t('options.date.name')].present? + options[I18n.t('options.date.name')] elsif options[I18n.t('options.tomorrow.name')].present? - Time.now.tomorrow + I18n.t('options.tomorrow.name') elsif options[I18n.t('options.yesterday.name')].present? - Time.now.yesterday + I18n.t('options.yesterday.name') elsif options[I18n.t('options.today.name')].present? - Time.now + I18n.t('options.today.name') + end + time = if time_mnemonic?(date_or_mnemonic) + time_from_mnemonic(command_option: date_or_mnemonic) + else + Time.parse(date_or_mnemonic) end entry = Models::Entry.new(description: description) CommandServices::AddEntryService.new(entry: entry, time: time).call presenter = Presenters::EntryGroup::List::DatePresenter.new(times: [time], options: options) # TODO: Refactor View::EntryGroup::Show to accept a presenter and use it here Views::EntryGroup::List.new(presenter: presenter).render + rescue ArgumentError => e + Views::Shared::Error.new(messages: e.message).render end desc I18n.t('commands.browse.desc'), I18n.t('commands.browse.usage') diff --git a/spec/dsu/features/dsu_add_features_spec.rb b/spec/dsu/features/dsu_add_features_spec.rb index 1191eac4..64fe48ad 100644 --- a/spec/dsu/features/dsu_add_features_spec.rb +++ b/spec/dsu/features/dsu_add_features_spec.rb @@ -2,7 +2,7 @@ RSpec.describe 'Dsu add features', type: :feature do subject(:cli) do - strip_escapes(Dsu::Services::StdoutRedirectorService.call { Dsu::CLI.start(args) }) + capture_stdout_and_strip_escapes { Dsu::CLI.start(args) } end let(:args) { %w[add] } @@ -43,16 +43,17 @@ end end - context "when 'dsu add --date=DATE' is called" do + context "when 'dsu add --tomorrow' is called" do before do with_entries + + freeze_time_at(time_string: '2023-06-16') end - let(:args) { ['add', '--date', entry_date, entry_description] } - let(:entry_date) { '2023-06-16' } + let(:args) { ['add', '--tomorrow', entry_description] } let(:entry_description) { 'This is a test' } let(:expected_date) do - Dsu::Support::TimeFormatable.formatted_time(time: Time.parse(entry_date)) + Dsu::Support::TimeFormatable.formatted_time(time: Time.now.tomorrow) end it 'displays the entry group date' do @@ -60,7 +61,7 @@ end it 'displays the entries already existing in the group' do - expected_entry_descriptions = ['20230616 description 0', '20230616 description 1'] + expected_entry_descriptions = ['20230617 description 0', '20230617 description 1'] expected_entry_descriptions.each do |entry_description| expect(cli).to include(entry_description) end @@ -71,17 +72,17 @@ end end - context "when 'dsu add --tomorrow' is called" do + context "when 'dsu add --yesterday' is called" do before do with_entries freeze_time_at(time_string: '2023-06-16') end - let(:args) { ['add', '--tomorrow', entry_description] } + let(:args) { ['add', '--yesterday', entry_description] } let(:entry_description) { 'This is a test' } let(:expected_date) do - Dsu::Support::TimeFormatable.formatted_time(time: Time.now.tomorrow) + Dsu::Support::TimeFormatable.formatted_time(time: Time.now.yesterday) end it 'displays the entry group date' do @@ -89,7 +90,7 @@ end it 'displays the entries already existing in the group' do - expected_entry_descriptions = ['20230617 description 0', '20230617 description 1'] + expected_entry_descriptions = ['20230615 description 0', '20230615 description 1'] expected_entry_descriptions.each do |entry_description| expect(cli).to include(entry_description) end @@ -100,17 +101,17 @@ end end - context "when 'dsu add --yesterday' is called" do + context "when 'dsu add --today' is called" do before do with_entries freeze_time_at(time_string: '2023-06-16') end - let(:args) { ['add', '--yesterday', entry_description] } + let(:args) { ['add', '--today', entry_description] } let(:entry_description) { 'This is a test' } let(:expected_date) do - Dsu::Support::TimeFormatable.formatted_time(time: Time.now.yesterday) + Dsu::Support::TimeFormatable.formatted_time(time: Time.now) end it 'displays the entry group date' do @@ -118,7 +119,7 @@ end it 'displays the entries already existing in the group' do - expected_entry_descriptions = ['20230615 description 0', '20230615 description 1'] + expected_entry_descriptions = ['20230616 description 0', '20230616 description 1'] expected_entry_descriptions.each do |entry_description| expect(cli).to include(entry_description) end @@ -129,32 +130,65 @@ end end - context "when 'dsu add --today' is called" do + context "when 'dsu add --date=DATE' is called" do before do with_entries + end - freeze_time_at(time_string: '2023-06-16') + shared_examples 'the expected output is displayed' do + it 'displays the expected output' do + expect(cli.split("\n")[0..4].map(&:squish).reject(&:blank?)).to eq(expected_output.split("\n")) + end end - let(:args) { ['add', '--today', entry_description] } - let(:entry_description) { 'This is a test' } - let(:expected_date) do - Dsu::Support::TimeFormatable.formatted_time(time: Time.now) + let(:expected_output) do + <<~OUTPUT + #{expected_date} + 1. #{entry_date.tr('-', '')} description 0 + 2. #{entry_date.tr('-', '')} description 1 + 3. #{entry_description} + OUTPUT end - it 'displays the entry group date' do - expect(cli).to include(expected_date) + context 'when using option --date' do + let(:args) { ['add', '--date', entry_date, entry_description] } + let(:entry_date) { '2023-06-16' } + let(:entry_description) { 'This is a test' } + let(:expected_date) do + Dsu::Support::TimeFormatable.formatted_time(time: Time.parse(entry_date)) + end + + it_behaves_like 'the expected output is displayed' end - it 'displays the entries already existing in the group' do - expected_entry_descriptions = ['20230616 description 0', '20230616 description 1'] - expected_entry_descriptions.each do |entry_description| - expect(cli).to include(entry_description) + context 'when using --date with a positive relative date mnemonic' do + before do + freeze_time_at(time_string: '2023-06-16') end + + let(:args) { ['add', '--date', '+1', entry_description] } + let(:entry_date) { '2023-06-17' } + let(:entry_description) { 'This is a test' } + let(:expected_date) do + Dsu::Support::TimeFormatable.formatted_time(time: Time.parse(entry_date)) + end + + it_behaves_like 'the expected output is displayed' end - it 'displays the description that was added' do - expect(cli).to include(entry_description) + context 'when using --date with a negative relative date mnemonic' do + before do + freeze_time_at(time_string: '2023-06-16') + end + + let(:args) { ['add', '--date', '-1', entry_description] } + let(:entry_date) { '2023-06-15' } + let(:entry_description) { 'This is a test' } + let(:expected_date) do + Dsu::Support::TimeFormatable.formatted_time(time: Time.parse(entry_date)) + end + + it_behaves_like 'the expected output is displayed' end end end