dsu 1.0.0 → 1.1.0.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +5 -0
- data/Gemfile.lock +6 -15
- data/README.md +7 -8
- data/lib/dsu/base_cli.rb +13 -3
- data/lib/dsu/cli.rb +7 -9
- data/lib/dsu/core/ruby/not_today.rb +11 -0
- data/lib/dsu/models/entry.rb +0 -1
- data/lib/dsu/models/entry_group.rb +4 -13
- data/lib/dsu/services/configuration_loader_service.rb +18 -2
- data/lib/dsu/services/entry_group_editor_service.rb +2 -5
- data/lib/dsu/subcommands/list.rb +83 -15
- data/lib/dsu/support/command_options/dsu_times.rb +33 -0
- data/lib/dsu/support/command_options/time.rb +77 -0
- data/lib/dsu/support/command_options/time_mneumonic.rb +127 -0
- data/lib/dsu/support/command_options/time_mneumonics.rb +15 -0
- data/lib/dsu/support/configurable.rb +15 -0
- data/lib/dsu/support/configuration.rb +13 -1
- data/lib/dsu/support/entry_group_fileable.rb +31 -6
- data/lib/dsu/support/entry_group_loadable.rb +5 -6
- data/lib/dsu/support/entry_group_viewable.rb +26 -8
- data/lib/dsu/support/times_sortable.rb +1 -3
- data/lib/dsu/validators/entries_validator.rb +3 -3
- data/lib/dsu/version.rb +1 -1
- data/lib/dsu/views/entry_group/edit.rb +71 -16
- data/lib/dsu.rb +4 -2
- metadata +22 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: a0558e3688ef13d3969d4f42575742be25b847b18e822c7042ed2de38ff6450f
|
4
|
+
data.tar.gz: f72bd36cb559f2f99ba51687ffa2d13cb56e94dfe64a80f2634d8ad727fb3531
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bd93da3534ed0d6d11522a4a7e5b9adb9b4ec56eabc0633f5f314ccdaa900f98bb7f451bc86b083bd58b2aafe920ac214ce3b1bd4fee940713af39edcc09d9bd
|
7
|
+
data.tar.gz: fe1c030e83a19732cabe66389f9849125e5c72e4e5f23a30de2d2cda3b0a01ed26d9fc63077b67c7e10e7b3f54a9c9ce4d4bf893c7ed432c7b0e1b6f1d62e469
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,8 @@
|
|
1
|
+
## [1.1.0.alpha.1] - 2023-05-23
|
2
|
+
* Changes
|
3
|
+
- Added new configuration option `carry_over_entries_to_today` (`true|false`, default: `false`); if true, when editing DSU entries **for the first time** on any given day (e.g. `dsu edit today`), DSU entries from the previous day will be copied to the editing session. If there are no DSU entries from the previous day, `dsu` will search backwards up to 7 days to find a DSU date that has entries to copy. If after searching back 7 days, no DSU entries are found, the editor session will simply start with no previous DSU entries.
|
4
|
+
- Added new configuration option `include_all` (`true|false`, default: `false`); if true, when using dsu commands that list date ranges (e.g. `dsu list dates`), the displayed list will include dates that have no dsu entries. If false, the displayed list will only include dates that have dsu entries. For all other `dsu list` commands, if true, this option will behave in the aforementioned manner. If false, the displayed list will unconditionally display the first and last dates regardless of whether or not the DSU date has entries or not; all other dates will not be displayed if the DSU date has no entries.
|
5
|
+
- Changed the look of the editor template when editing entry group entries.
|
1
6
|
## [1.0.0] - 2023-05-18
|
2
7
|
* First official release.
|
3
8
|
* NOTE: If you have been using the alpha version of `dsu`, you will need to delete the `entries` folder (e.g. `/Users/<whoami>/dsu/entries` on a nix os) as the old entries .json files are incompatible with this official release.
|
data/Gemfile.lock
CHANGED
@@ -1,10 +1,10 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dsu (1.0.
|
4
|
+
dsu (1.1.0.alpha.1)
|
5
|
+
activemodel (~> 7.0, >= 7.0.4.3)
|
5
6
|
activesupport (~> 7.0, >= 7.0.4)
|
6
7
|
colorize (~> 0.8.1)
|
7
|
-
deco_lite (~> 1.3)
|
8
8
|
os (~> 1.1, >= 1.1.4)
|
9
9
|
thor (~> 1.2, >= 1.2.1)
|
10
10
|
thor_nested_subcommand (~> 1.0)
|
@@ -24,11 +24,6 @@ GEM
|
|
24
24
|
coderay (1.1.3)
|
25
25
|
colorize (0.8.1)
|
26
26
|
concurrent-ruby (1.2.2)
|
27
|
-
deco_lite (1.5.3)
|
28
|
-
activemodel (~> 7.0, >= 7.0.3.1)
|
29
|
-
activesupport (~> 7.0, >= 7.0.3.1)
|
30
|
-
immutable_struct_ex (~> 0.2.0)
|
31
|
-
mad_flatter (~> 2.0)
|
32
27
|
diff-lcs (1.5.0)
|
33
28
|
docile (1.4.0)
|
34
29
|
dotenv (2.8.1)
|
@@ -37,12 +32,8 @@ GEM
|
|
37
32
|
ffaker (2.21.0)
|
38
33
|
i18n (1.13.0)
|
39
34
|
concurrent-ruby (~> 1.0)
|
40
|
-
immutable_struct_ex (0.2.3)
|
41
35
|
json (2.6.3)
|
42
36
|
kwalify (0.7.2)
|
43
|
-
mad_flatter (2.0.0)
|
44
|
-
activesupport (~> 7.0, >= 7.0.3.1)
|
45
|
-
immutable_struct_ex (~> 0.2.0)
|
46
37
|
method_source (1.0.0)
|
47
38
|
minitest (5.18.0)
|
48
39
|
os (1.1.4)
|
@@ -76,7 +67,7 @@ GEM
|
|
76
67
|
diff-lcs (>= 1.2.0, < 2.0)
|
77
68
|
rspec-support (~> 3.12.0)
|
78
69
|
rspec-support (3.12.0)
|
79
|
-
rubocop (1.
|
70
|
+
rubocop (1.51.0)
|
80
71
|
json (~> 2.3)
|
81
72
|
parallel (~> 1.10)
|
82
73
|
parser (>= 3.2.0.0)
|
@@ -90,9 +81,9 @@ GEM
|
|
90
81
|
parser (>= 3.2.1.0)
|
91
82
|
rubocop-capybara (2.18.0)
|
92
83
|
rubocop (~> 1.41)
|
93
|
-
rubocop-factory_bot (2.
|
84
|
+
rubocop-factory_bot (2.23.1)
|
94
85
|
rubocop (~> 1.33)
|
95
|
-
rubocop-performance (1.
|
86
|
+
rubocop-performance (1.18.0)
|
96
87
|
rubocop (>= 1.7.0, < 2.0)
|
97
88
|
rubocop-ast (>= 0.4.0)
|
98
89
|
rubocop-rspec (2.22.0)
|
@@ -106,7 +97,7 @@ GEM
|
|
106
97
|
simplecov_json_formatter (~> 0.1)
|
107
98
|
simplecov-html (0.12.3)
|
108
99
|
simplecov_json_formatter (0.1.4)
|
109
|
-
thor (1.2.
|
100
|
+
thor (1.2.2)
|
110
101
|
thor_nested_subcommand (1.0.0)
|
111
102
|
tzinfo (2.0.6)
|
112
103
|
concurrent-ruby (~> 1.0)
|
data/README.md
CHANGED
@@ -71,12 +71,11 @@ The following displays the entries for "Today", where `Time.now == '2023-05-06 0
|
|
71
71
|
```shell
|
72
72
|
#=>
|
73
73
|
Saturday, (Today) 2023-05-06
|
74
|
-
1.
|
75
|
-
Hope to pair with John on it
|
74
|
+
1. Blocked for locally failing test IN-12345
|
76
75
|
|
77
76
|
Friday, (Yesterday) 2023-05-05
|
78
|
-
1.
|
79
|
-
2.
|
77
|
+
1. Pick up ticket IN-12345
|
78
|
+
2. Attend new hire meet & greet
|
80
79
|
```
|
81
80
|
|
82
81
|
`$ dsu list date "2023-05-06"`
|
@@ -86,12 +85,11 @@ See the [Dates](#dates) section for more information on acceptable DATE formats
|
|
86
85
|
```shell
|
87
86
|
#=>
|
88
87
|
Saturday, (Today) 2023-05-06
|
89
|
-
1.
|
90
|
-
Hope to pair with John on it
|
88
|
+
1. Blocked for locally failing test IN-12345
|
91
89
|
|
92
90
|
Friday, (Yesterday) 2023-05-05
|
93
|
-
1.
|
94
|
-
2.
|
91
|
+
1. Pick up ticket IN-12345
|
92
|
+
2. Attend new hire meet & greet
|
95
93
|
```
|
96
94
|
## Editing DSU Entries
|
97
95
|
|
@@ -115,6 +113,7 @@ The following will edit your DSU entry group entries for "Today", where `Time.no
|
|
115
113
|
#=> In your editor, you will see...
|
116
114
|
# Editing DSU Entries for Tuesday, (Today) 2023-05-09 EDT
|
117
115
|
# [ENTRY DESCRIPTION]
|
116
|
+
|
118
117
|
Interative planning meeting 11:00AM.
|
119
118
|
Pair with Chad on ticket 31211.
|
120
119
|
Investigate spike ticket 31255.
|
data/lib/dsu/base_cli.rb
CHANGED
@@ -30,9 +30,19 @@ module Dsu
|
|
30
30
|
end
|
31
31
|
|
32
32
|
def date_option_description
|
33
|
-
<<-
|
34
|
-
|
35
|
-
|
33
|
+
<<-OPTION_DESC
|
34
|
+
DATE:
|
35
|
+
\x5
|
36
|
+
This may be any date string that can be parsed using `Time.parse`. Consequently, you may use also use '/' as date separators, as well as omit thee year if the date you want to display is the current year (e.g. <month>/<day>, or 1/31). For example: `require 'time'; Time.parse('01/02/2023'); Time.parse('1/2') # etc.`
|
37
|
+
OPTION_DESC
|
38
|
+
end
|
39
|
+
|
40
|
+
def mneumonic_option_description
|
41
|
+
<<-OPTION_DESC
|
42
|
+
MNEUMONIC:
|
43
|
+
\x5
|
44
|
+
This may be any of the following: DATE (see DATE)|n|today|t|tomorrow|y|yesterday.
|
45
|
+
OPTION_DESC
|
36
46
|
end
|
37
47
|
end
|
38
48
|
|
data/lib/dsu/cli.rb
CHANGED
@@ -20,7 +20,7 @@ module Dsu
|
|
20
20
|
long_desc <<-LONG_DESC
|
21
21
|
NAME
|
22
22
|
\x5
|
23
|
-
`
|
23
|
+
`dsu add, -a [OPTIONS] DESCRIPTION` -- will add a DSU entry having DESCRIPTION to the date associated with the given OPTION.
|
24
24
|
|
25
25
|
SYNOPSIS
|
26
26
|
\x5
|
@@ -54,14 +54,12 @@ module Dsu
|
|
54
54
|
def add(description)
|
55
55
|
time = if options[:date].present?
|
56
56
|
Time.parse(options[:date])
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
Time.now
|
64
|
-
end
|
57
|
+
elsif options[:tomorrow].present?
|
58
|
+
Time.now.tomorrow
|
59
|
+
elsif options[:yesterday].present?
|
60
|
+
Time.now.yesterday
|
61
|
+
elsif options[:today].present?
|
62
|
+
Time.now
|
65
63
|
end
|
66
64
|
entry = Models::Entry.new(description: description)
|
67
65
|
CommandServices::AddEntryService.new(entry: entry, time: time).call
|
data/lib/dsu/models/entry.rb
CHANGED
@@ -51,19 +51,6 @@ module Dsu
|
|
51
51
|
def exists?(time:)
|
52
52
|
Dsu::Services::EntryGroupReaderService.entry_group_file_exists?(time: time)
|
53
53
|
end
|
54
|
-
|
55
|
-
# Loads the EntryGroup from the file system and returns an
|
56
|
-
# instantiated EntryGroup object.
|
57
|
-
def load(time: nil)
|
58
|
-
load_entry_group_file_for(time: time)
|
59
|
-
end
|
60
|
-
|
61
|
-
# This function returns a hash whose :time and :entries
|
62
|
-
# key values are hydrated with instantiated Time and Entry
|
63
|
-
# objects.
|
64
|
-
def load_entry_group_for(time:)
|
65
|
-
load_entry_group_file_for(time: time)
|
66
|
-
end
|
67
54
|
end
|
68
55
|
|
69
56
|
def valid_unique_entries
|
@@ -93,6 +80,10 @@ module Dsu
|
|
93
80
|
self
|
94
81
|
end
|
95
82
|
|
83
|
+
def time_formatted
|
84
|
+
formatted_time(time: time)
|
85
|
+
end
|
86
|
+
|
96
87
|
def save!
|
97
88
|
delete! and return if entries.empty?
|
98
89
|
|
@@ -30,9 +30,25 @@ module Dsu
|
|
30
30
|
attr_reader :default_options
|
31
31
|
|
32
32
|
def config_options
|
33
|
-
return
|
33
|
+
return default_config unless config_file?
|
34
34
|
|
35
|
-
@config_options ||=
|
35
|
+
@config_options ||= begin
|
36
|
+
loaded_config = YAML.safe_load(ERB.new(File.read(config_file)).result)
|
37
|
+
loaded_config = update_and_write_config_file!(loaded_config) unless loaded_config.keys == default_config.keys
|
38
|
+
loaded_config
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def update_and_write_config_file!(loaded_config)
|
43
|
+
loaded_config = default_config.merge(loaded_config)
|
44
|
+
# TODO: Make this into a configuration writer service.
|
45
|
+
# TODO: Test this
|
46
|
+
File.write(config_file, loaded_config.to_yaml)
|
47
|
+
loaded_config
|
48
|
+
end
|
49
|
+
|
50
|
+
def default_config
|
51
|
+
Support::Configuration::DEFAULT_DSU_OPTIONS
|
36
52
|
end
|
37
53
|
end
|
38
54
|
end
|
@@ -2,17 +2,18 @@
|
|
2
2
|
|
3
3
|
require_relative '../models/entry'
|
4
4
|
require_relative '../support/colorable'
|
5
|
+
require_relative '../support/configurable'
|
5
6
|
require_relative '../support/say'
|
6
7
|
require_relative '../support/time_formatable'
|
7
8
|
require_relative '../views/edited_entries/shared/errors'
|
8
9
|
require_relative '../views/shared/messages'
|
9
|
-
require_relative 'configuration_loader_service'
|
10
10
|
require_relative 'stdout_redirector_service'
|
11
11
|
|
12
12
|
module Dsu
|
13
13
|
module Services
|
14
14
|
class EntryGroupEditorService
|
15
15
|
include Support::Colorable
|
16
|
+
include Support::Configurable
|
16
17
|
include Support::Say
|
17
18
|
include Support::TimeFormatable
|
18
19
|
|
@@ -90,10 +91,6 @@ module Dsu
|
|
90
91
|
description = Models::Entry.clean_description(description)
|
91
92
|
!(description.blank? || description[0] == '#')
|
92
93
|
end
|
93
|
-
|
94
|
-
def configuration
|
95
|
-
@configuration ||= ConfigurationLoaderService.new.call
|
96
|
-
end
|
97
94
|
end
|
98
95
|
end
|
99
96
|
end
|
data/lib/dsu/subcommands/list.rb
CHANGED
@@ -1,11 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../base_cli'
|
4
|
+
require_relative '../support/command_options/dsu_times'
|
5
|
+
require_relative '../support/time_formatable'
|
4
6
|
|
5
7
|
module Dsu
|
6
8
|
module Subcommands
|
7
9
|
class List < Dsu::BaseCLI
|
10
|
+
include Support::CommandOptions::DsuTimes
|
11
|
+
include Support::TimeFormatable
|
12
|
+
|
8
13
|
map %w[d] => :date
|
14
|
+
map %w[dd] => :dates
|
9
15
|
map %w[n] => :today
|
10
16
|
map %w[t] => :tomorrow
|
11
17
|
map %w[y] => :yesterday
|
@@ -17,10 +23,8 @@ module Dsu
|
|
17
23
|
LONG_DESC
|
18
24
|
def today
|
19
25
|
time = Time.now
|
20
|
-
sorted_dsu_times_for(times: [time, time.yesterday])
|
21
|
-
|
22
|
-
puts
|
23
|
-
end
|
26
|
+
times = sorted_dsu_times_for(times: [time, time.yesterday])
|
27
|
+
view_list_for(times: times)
|
24
28
|
end
|
25
29
|
|
26
30
|
desc 'tomorrow, t',
|
@@ -30,10 +34,8 @@ module Dsu
|
|
30
34
|
LONG_DESC
|
31
35
|
def tomorrow
|
32
36
|
time = Time.now
|
33
|
-
sorted_dsu_times_for(times: [time.tomorrow, time])
|
34
|
-
|
35
|
-
puts
|
36
|
-
end
|
37
|
+
times = sorted_dsu_times_for(times: [time.tomorrow, time])
|
38
|
+
view_list_for(times: times)
|
37
39
|
end
|
38
40
|
|
39
41
|
desc 'yesterday, y',
|
@@ -43,10 +45,8 @@ module Dsu
|
|
43
45
|
LONG_DESC
|
44
46
|
def yesterday
|
45
47
|
time = Time.now
|
46
|
-
sorted_dsu_times_for(times: [time.yesterday, time.yesterday.yesterday])
|
47
|
-
|
48
|
-
puts
|
49
|
-
end
|
48
|
+
times = sorted_dsu_times_for(times: [time.yesterday, time.yesterday.yesterday])
|
49
|
+
view_list_for(times: times)
|
50
50
|
end
|
51
51
|
|
52
52
|
desc 'date, d DATE',
|
@@ -57,14 +57,82 @@ module Dsu
|
|
57
57
|
LONG_DESC
|
58
58
|
def date(date)
|
59
59
|
time = Time.parse(date)
|
60
|
-
sorted_dsu_times_for(times: [time, time.yesterday])
|
61
|
-
|
62
|
-
|
60
|
+
times = sorted_dsu_times_for(times: [time, time.yesterday])
|
61
|
+
view_list_for(times: times)
|
62
|
+
rescue ArgumentError => e
|
63
|
+
say "Error: #{e.message}", ERROR
|
64
|
+
exit 1
|
65
|
+
end
|
66
|
+
|
67
|
+
desc 'dates, dd OPTIONS',
|
68
|
+
'Displays the DSU entries for the OPTIONS provided'
|
69
|
+
long_desc <<~LONG_DESC
|
70
|
+
NAME
|
71
|
+
\x5
|
72
|
+
`dsu dates|dd OPTIONS` -- will display the DSU entries for the OPTIONS provided.
|
73
|
+
|
74
|
+
SYNOPSIS
|
75
|
+
\x5
|
76
|
+
`dsu dates|dd OPTIONS`
|
77
|
+
|
78
|
+
OPTIONS:
|
79
|
+
\x5
|
80
|
+
-f|--from DATE|MNEMONIC: ?.
|
81
|
+
|
82
|
+
\x5
|
83
|
+
-t|--to DATE|MNEMONIC: ?.
|
84
|
+
|
85
|
+
\x5
|
86
|
+
#{date_option_description}
|
87
|
+
|
88
|
+
\x5
|
89
|
+
#{mneumonic_option_description}
|
90
|
+
LONG_DESC
|
91
|
+
# -f, --from FROM [DATE|MNEMONIC] (e.g. -f, --from 1/1[/yyy]|n|t|y|today|tomorrow|yesterday)
|
92
|
+
option :from, type: :string, aliases: '-f', banner: 'DATE|MNEMONIC'
|
93
|
+
# -t, --to TO [DATE|MNEMONIC] (e.g. -t, --to 1/1[/yyy]|n|t|y|today|tomorrow|yesterday)
|
94
|
+
option :to, type: :string, aliases: '-t', banner: 'DATE|MNEMONIC'
|
95
|
+
|
96
|
+
# Include dates that have no DSU entries.
|
97
|
+
option :include_all, type: :boolean, aliases: '-a'
|
98
|
+
def dates
|
99
|
+
options = configuration.merge(self.options)
|
100
|
+
times = dsu_times_from!(from_command_option: options[:from], to_command_option: options[:to])
|
101
|
+
# Note special sort here, unlike the other commands where rules for
|
102
|
+
# displaying DSU entries are applied; this is more of a list command.
|
103
|
+
times = times_sort(times: times, entries_display_order: entries_display_order)
|
104
|
+
view_entry_groups(times: times, options: options) do |total_entry_groups|
|
105
|
+
nothing_to_display_banner_for(times) if total_entry_groups.zero?
|
63
106
|
end
|
64
107
|
rescue ArgumentError => e
|
65
108
|
say "Error: #{e.message}", ERROR
|
66
109
|
exit 1
|
67
110
|
end
|
111
|
+
|
112
|
+
private
|
113
|
+
|
114
|
+
def nothing_to_display_banner_for(entry_group_times)
|
115
|
+
entry_group_times.sort!
|
116
|
+
time_range = "#{formatted_time(time: entry_group_times.first)} " \
|
117
|
+
"through #{formatted_time(time: entry_group_times.last)}"
|
118
|
+
say "(nothing to display for #{time_range})", INFO
|
119
|
+
end
|
120
|
+
|
121
|
+
# This method will unconditionally display the FIRST and LAST entry groups
|
122
|
+
# associated with the times provided by the <times> argument. All other
|
123
|
+
# entry groups will be conditionally displayed based on the :include_all
|
124
|
+
# value in the <options> argument.
|
125
|
+
def view_list_for(times:)
|
126
|
+
options = configuration.merge(self.options)
|
127
|
+
times_first_and_last = [times.first, times.last]
|
128
|
+
times.each do |time|
|
129
|
+
view_options = options.dup
|
130
|
+
view_options[:include_all] = true if times_first_and_last.include?(time)
|
131
|
+
view_entry_group(time: time, options: view_options) do
|
132
|
+
puts
|
133
|
+
end
|
134
|
+
end
|
135
|
+
end
|
68
136
|
end
|
69
137
|
end
|
70
138
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'time'
|
4
|
+
require_relative 'time_mneumonic'
|
5
|
+
require_relative 'time_mneumonics'
|
6
|
+
|
7
|
+
module Dsu
|
8
|
+
module Support
|
9
|
+
module CommandOptions
|
10
|
+
module DsuTimes
|
11
|
+
include Time
|
12
|
+
include TimeMneumonic
|
13
|
+
include TimeMneumonics
|
14
|
+
|
15
|
+
# Returns an array of Time objects. The first element is the from time. The second element is the to time.
|
16
|
+
# Both arguments are expected to be command options that are time strings, time or relative time mneumonics.
|
17
|
+
def dsu_times_from!(from_command_option:, to_command_option:)
|
18
|
+
times = begin
|
19
|
+
from_time = time_from_mneumonic(command_option: from_command_option) if time_mneumonic?(from_command_option)
|
20
|
+
from_time ||= time_from_date_string(command_option: from_command_option)
|
21
|
+
|
22
|
+
to_time = time_from_mneumonic(command_option: to_command_option) if time_mneumonic?(to_command_option)
|
23
|
+
to_time ||= time_from_date_string(command_option: to_command_option)
|
24
|
+
|
25
|
+
[from_time, to_time].sort
|
26
|
+
end
|
27
|
+
|
28
|
+
(times.min.to_date..times.max.to_date).map(&:to_time)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dsu
|
4
|
+
module Support
|
5
|
+
module CommandOptions
|
6
|
+
# The purpose of this module is to take a command option that is a string and return a Time object.
|
7
|
+
# The command option is expected to be a date in the format of [M]M/[D]D[/YYYY]. MM and DD with
|
8
|
+
# leading zeroes is optional (i.e. only M and D are required), YYYY is optionl and will be replaced
|
9
|
+
# with the current year if not provided.
|
10
|
+
module Time
|
11
|
+
DATE_CAPTURE_REGEX = %r{\A(?<month>0?[1-9]|1[0-2])/(?<day>0?[1-9]|1\d|2\d|3[01])(?:/(?<year>\d{4}))?\z}
|
12
|
+
|
13
|
+
def time_from_date_string!(command_option:)
|
14
|
+
raise ArgumentError, 'command_option is nil.' if command_option.nil?
|
15
|
+
raise ArgumentError, 'command_option is blank.' if command_option.blank?
|
16
|
+
|
17
|
+
unless command_option.is_a?(String)
|
18
|
+
raise ArgumentError, "command_option is not a String: \"#{command_option}\"."
|
19
|
+
end
|
20
|
+
|
21
|
+
time_parts = time_parts_for(time_string: command_option)
|
22
|
+
return unless time_parts?(time_parts: time_parts)
|
23
|
+
|
24
|
+
valid_time!(time_parts: time_parts)
|
25
|
+
|
26
|
+
# This will rescue errors resulting from calling Date.strptime with an invalid date string,
|
27
|
+
# and return a more meaningful error message.
|
28
|
+
rescue DateTime::Error
|
29
|
+
raise ArgumentError, "command_option is not a valid date: \"#{command_option}\"."
|
30
|
+
end
|
31
|
+
|
32
|
+
def time_from_date_string(command_option:)
|
33
|
+
time_from_date_string!(command_option: command_option)
|
34
|
+
rescue ArgumentError
|
35
|
+
nil
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
# This method returns the time parts for the given time string in a hash
|
41
|
+
# (i.e. month, day, year) IF the time string matches the DATE_CAPTURE_REGEX
|
42
|
+
# regex. Otherwise, it returns an empty hash.
|
43
|
+
def time_parts_for(time_string:)
|
44
|
+
match_data = DATE_CAPTURE_REGEX.match(time_string)
|
45
|
+
return {} if match_data.nil?
|
46
|
+
|
47
|
+
{
|
48
|
+
month: match_data[:month],
|
49
|
+
day: match_data[:day],
|
50
|
+
year: match_data[:year]
|
51
|
+
}
|
52
|
+
end
|
53
|
+
|
54
|
+
# This method returns true if the date passes the DATE_CAPTURE_REGEX regex match
|
55
|
+
# in #date_parts_for and returns a non-nil hash. Otherwise, it returns false.
|
56
|
+
# A non-nil hash returned from #date_parts_for doesn necessarily mean the date
|
57
|
+
# parts will equate to a valid date when parsed, it just means the date string
|
58
|
+
# matched the regex. Calling #valid_date! will raise an ArgumentError if the
|
59
|
+
# date parts do not equate to a valid date.
|
60
|
+
def time_parts?(time_parts:)
|
61
|
+
!time_parts.empty?
|
62
|
+
end
|
63
|
+
|
64
|
+
def valid_time!(time_parts:)
|
65
|
+
time_string = time_string_for(time_parts: time_parts)
|
66
|
+
Date.strptime(time_string, '%Y/%m/%d').to_time
|
67
|
+
end
|
68
|
+
|
69
|
+
def time_string_for(time_parts:)
|
70
|
+
# Replace the year with the current year if it is nil.
|
71
|
+
time_parts[:year] = ::Time.now.year if time_parts[:year].nil?
|
72
|
+
"#{time_parts[:year]}/#{time_parts[:month]}/#{time_parts[:day]}"
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,127 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'time_mneumonics'
|
4
|
+
|
5
|
+
module Dsu
|
6
|
+
module Support
|
7
|
+
module CommandOptions
|
8
|
+
# The purpose of this module is to take a command option that is a string and return a Time object.
|
9
|
+
# The command option is expected to be a time mneumoic.
|
10
|
+
module TimeMneumonic
|
11
|
+
include TimeMneumonics
|
12
|
+
|
13
|
+
def time_from_mneumonic(command_option:, relative_time: nil)
|
14
|
+
time_from_mneumonic!(command_option: command_option, relative_time: relative_time)
|
15
|
+
rescue ArgumentError
|
16
|
+
nil
|
17
|
+
end
|
18
|
+
|
19
|
+
# command_option: is expected to me a time mneumonic. If relative_time is NOT nil, all
|
20
|
+
# time mneumonics are relative to relative_time. Otherwise, they are relative to Time.now.
|
21
|
+
# relative_time: is a Time object that is required IF command_option is expected to be
|
22
|
+
# a relative time mneumonic. Otherwise, it is optional.
|
23
|
+
def time_from_mneumonic!(command_option:, relative_time: nil)
|
24
|
+
validate_argument!(command_option: command_option, command_option_name: :command_option)
|
25
|
+
unless relative_time.nil?
|
26
|
+
validate_argument!(command_option: relative_time, command_option_name: :relative_time)
|
27
|
+
end
|
28
|
+
|
29
|
+
# if relative_time_mneumonic?(command_option) && !relative_time.nil?
|
30
|
+
# # If command_option is a relative time mneumonic, we need to get the time
|
31
|
+
# # relative to relative_time using ::Time.now, and use the command_option
|
32
|
+
# # as the relative time.
|
33
|
+
# return time_from_mneumonic!(command_option: relative_time, relative_time: command_option)
|
34
|
+
# end
|
35
|
+
|
36
|
+
time_for_mneumonic(mneumonic: command_option, relative_time: relative_time)
|
37
|
+
end
|
38
|
+
|
39
|
+
# This method returns true if mneumonic is a valid mneumonic OR
|
40
|
+
# a relative time mneumonic.
|
41
|
+
def time_mneumonic?(mneumonic)
|
42
|
+
mneumonic?(mneumonic) || relative_time_mneumonic?(mneumonic)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
# Returns a Time object from a mneumonic.
|
48
|
+
def time_for_mneumonic(mneumonic:, relative_time: nil)
|
49
|
+
# If relative_time is a relative time mneumonic, then we need to first
|
50
|
+
# convert mneumonic to a Time object first, so that we can calculate
|
51
|
+
# `relative_time.to_i.days.from_now(time)` to get the correct Time we
|
52
|
+
# need.
|
53
|
+
if relative_time_mneumonic?(relative_time)
|
54
|
+
time = time_for_mneumonic(mneumonic: mneumonic)
|
55
|
+
return relative_time_for(days_from_now: relative_time, time: time)
|
56
|
+
end
|
57
|
+
|
58
|
+
if mneumonic?(mneumonic) && mneumonic?(relative_time)
|
59
|
+
time = time_for_mneumonic(mneumonic: mneumonic)
|
60
|
+
|
61
|
+
# Simply return the time if relative_time is 'today'
|
62
|
+
# because 'today' relative to any time will always
|
63
|
+
# point to itself.
|
64
|
+
return time if today_mneumonic?(relative_time)
|
65
|
+
|
66
|
+
return time.public_send(relative_time)
|
67
|
+
end
|
68
|
+
|
69
|
+
time = ::Time.now
|
70
|
+
if today_mneumonic?(mneumonic)
|
71
|
+
time
|
72
|
+
elsif tomorrow_mneumonic?(mneumonic)
|
73
|
+
time.tomorrow
|
74
|
+
elsif yesterday_mneumonic?(mneumonic)
|
75
|
+
time.yesterday
|
76
|
+
elsif relative_time_mneumonic?(mneumonic)
|
77
|
+
relative_time_for(days_from_now: mneumonic, time: time)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def relative_time_for(days_from_now:, time:)
|
82
|
+
days_from_now.to_i.days.from_now(time)
|
83
|
+
end
|
84
|
+
|
85
|
+
# This method returns true if mneumonic is a valid time mneumonic.
|
86
|
+
# This method will return false if mneumonic is an invalid mneumonic
|
87
|
+
# OR if mneumonic is a relative time mneumonic.
|
88
|
+
def mneumonic?(mneumonic)
|
89
|
+
today_mneumonic?(mneumonic) ||
|
90
|
+
tomorrow_mneumonic?(mneumonic) ||
|
91
|
+
yesterday_mneumonic?(mneumonic)
|
92
|
+
end
|
93
|
+
|
94
|
+
# This method returns true if mneumonic is a valid relative
|
95
|
+
# time mneumonic.
|
96
|
+
def relative_time_mneumonic?(mneumonic)
|
97
|
+
return false unless mneumonic.is_a?(String)
|
98
|
+
|
99
|
+
mneumonic.match?(RELATIVE_REGEX)
|
100
|
+
end
|
101
|
+
|
102
|
+
def today_mneumonic?(mneumonic)
|
103
|
+
TODAY.include?(mneumonic)
|
104
|
+
end
|
105
|
+
|
106
|
+
def tomorrow_mneumonic?(mneumonic)
|
107
|
+
TOMORROW.include?(mneumonic)
|
108
|
+
end
|
109
|
+
|
110
|
+
def yesterday_mneumonic?(mneumonic)
|
111
|
+
YESERDAY.include?(mneumonic)
|
112
|
+
end
|
113
|
+
|
114
|
+
def validate_argument!(command_option:, command_option_name:)
|
115
|
+
raise ArgumentError, "#{command_option_name} cannot be nil." if command_option.nil?
|
116
|
+
raise ArgumentError, "#{command_option_name} cannot be blank." if command_option.blank?
|
117
|
+
unless command_option.is_a?(String)
|
118
|
+
raise ArgumentError, "#{command_option_name} must be a String: \"#{command_option}\""
|
119
|
+
end
|
120
|
+
unless time_mneumonic?(command_option)
|
121
|
+
raise ArgumentError, "#{command_option_name} is an invalid mneumonic: \"#{command_option}\"."
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dsu
|
4
|
+
module Support
|
5
|
+
module CommandOptions
|
6
|
+
module TimeMneumonics
|
7
|
+
TODAY = %w[n today].freeze
|
8
|
+
TOMORROW = %w[t tomorrow].freeze
|
9
|
+
YESERDAY = %w[y yesterday].freeze
|
10
|
+
|
11
|
+
RELATIVE_REGEX = /[+-]\d+/
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../services/configuration_loader_service'
|
4
|
+
|
5
|
+
module Dsu
|
6
|
+
module Support
|
7
|
+
# This module provides a way to configure a class, so that it can
|
8
|
+
# be used in a test environment.
|
9
|
+
module Configurable
|
10
|
+
def configuration
|
11
|
+
@configuration ||= Services::ConfigurationLoaderService.new.call
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -27,7 +27,19 @@ module Dsu
|
|
27
27
|
# asc or desc, ascending or descending, respectively.
|
28
28
|
'entries_display_order' => 'desc',
|
29
29
|
'entries_file_name' => '%Y-%m-%d.json',
|
30
|
-
'entries_folder' => "#{FolderLocations.root_folder}/dsu/entries"
|
30
|
+
'entries_folder' => "#{FolderLocations.root_folder}/dsu/entries",
|
31
|
+
'carry_over_entries_to_today' => false,
|
32
|
+
# If true, when using dsu commands that list date ranges (e.g.
|
33
|
+
# `dsu list dates`), the displayed list will include dates that
|
34
|
+
# have no dsu entries. If false, the displayed list will only
|
35
|
+
# include dates that have dsu entries.
|
36
|
+
# For all other `dsu list` commands, if true, this option will
|
37
|
+
# behave in the aforementioned manner. If false, the displayed
|
38
|
+
# list will unconditionally display the first and last dates
|
39
|
+
# regardless of whether or not the DSU date has entries or not;
|
40
|
+
# all other dates will not be displayed if the DSU date has no
|
41
|
+
# entries.
|
42
|
+
'include_all' => false
|
31
43
|
}.freeze
|
32
44
|
# rubocop:enable Style/StringHashKeys
|
33
45
|
|
@@ -1,14 +1,39 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../services/configuration_loader_service'
|
4
|
+
require_relative '../support/configurable'
|
4
5
|
|
5
6
|
module Dsu
|
6
7
|
module Support
|
8
|
+
# TODO: I hate this module; refactor it!!!
|
9
|
+
# This module expects the following attributes to be defined: :time, :options
|
7
10
|
module EntryGroupFileable
|
8
|
-
|
11
|
+
extend Support::Configurable
|
12
|
+
|
13
|
+
class << self
|
14
|
+
def entry_group_file_exists?(time:)
|
15
|
+
File.exist?(entry_group_file_path(time: time))
|
16
|
+
end
|
17
|
+
|
18
|
+
def entry_group_file_path(time:)
|
19
|
+
File.join(entries_folder, entries_file_name(time: time))
|
20
|
+
end
|
21
|
+
|
22
|
+
def entries_folder
|
23
|
+
configuration[:entries_folder]
|
24
|
+
end
|
25
|
+
|
26
|
+
def entries_file_name(time:)
|
27
|
+
time.strftime(configuration[:entries_file_name])
|
28
|
+
end
|
29
|
+
|
30
|
+
# def configuration
|
31
|
+
# Services::ConfigurationLoaderService.new.call
|
32
|
+
# end
|
33
|
+
end
|
9
34
|
|
10
35
|
def entry_group_file_exists?
|
11
|
-
|
36
|
+
EntryGroupFileable.entry_group_file_exists?(time: time)
|
12
37
|
end
|
13
38
|
|
14
39
|
def entry_group_path_exists?
|
@@ -16,15 +41,15 @@ module Dsu
|
|
16
41
|
end
|
17
42
|
|
18
43
|
def entry_group_file_path
|
19
|
-
|
44
|
+
EntryGroupFileable.entry_group_file_path(time: time)
|
20
45
|
end
|
21
46
|
|
22
47
|
def entries_folder
|
23
|
-
@entries_folder ||=
|
48
|
+
@entries_folder ||= EntryGroupFileable.entries_folder
|
24
49
|
end
|
25
50
|
|
26
51
|
def entries_file_name
|
27
|
-
@entries_file_name ||=
|
52
|
+
@entries_file_name ||= EntryGroupFileable.entries_file_name(time: time)
|
28
53
|
end
|
29
54
|
|
30
55
|
def create_entry_group_path_if!
|
@@ -34,7 +59,7 @@ module Dsu
|
|
34
59
|
private
|
35
60
|
|
36
61
|
def configuration
|
37
|
-
@configuration ||= options[:configuration] ||
|
62
|
+
@configuration ||= options[:configuration] || EntryGroupFileable.configuration
|
38
63
|
end
|
39
64
|
end
|
40
65
|
end
|
@@ -8,12 +8,9 @@ require_relative '../models/entry_group'
|
|
8
8
|
module Dsu
|
9
9
|
module Support
|
10
10
|
module EntryGroupLoadable
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
# where entries == an Array of Entry Hashes
|
15
|
-
# representing the JSON Entry objects for :time.
|
16
|
-
def load_entry_group_file_for(time:)
|
11
|
+
# returns an EntryGroup object loaded from
|
12
|
+
# the entry group json file.
|
13
|
+
def load(time:)
|
17
14
|
entry_group_json = Services::EntryGroupReaderService.new(time: time).call
|
18
15
|
hash = if entry_group_json.present?
|
19
16
|
JSON.parse(entry_group_json, symbolize_names: true).tap do |hash|
|
@@ -26,6 +23,8 @@ module Dsu
|
|
26
23
|
Models::EntryGroup.new(**hydrate_entry_group_hash(hash: hash, time: time))
|
27
24
|
end
|
28
25
|
|
26
|
+
module_function
|
27
|
+
|
29
28
|
# Accepts an entry group hash and returns a
|
30
29
|
# hydrated entry group hash:
|
31
30
|
#
|
@@ -3,17 +3,35 @@
|
|
3
3
|
module Dsu
|
4
4
|
module Support
|
5
5
|
module EntryGroupViewable
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
6
|
+
def view_entry_groups(times:, options: {})
|
7
|
+
total_viewable_entry_groups = 0
|
8
|
+
|
9
|
+
times.each do |time|
|
10
|
+
view_entry_group(time: time, options: options) do
|
11
|
+
total_viewable_entry_groups += 1
|
12
|
+
puts
|
13
|
+
end
|
14
14
|
end
|
15
|
+
|
16
|
+
yield total_viewable_entry_groups if block_given?
|
17
|
+
end
|
18
|
+
|
19
|
+
def view_entry_group(time:, options: {})
|
20
|
+
return unless show_entry_group?(time: time, options: options)
|
21
|
+
|
22
|
+
entry_group = Models::EntryGroup.load(time: time)
|
15
23
|
Views::EntryGroup::Show.new(entry_group: entry_group).render
|
24
|
+
|
25
|
+
yield if block_given?
|
16
26
|
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
def show_entry_group?(time:, options:)
|
31
|
+
Models::EntryGroup.exists?(time: time) || options[:include_all]
|
32
|
+
end
|
33
|
+
|
34
|
+
module_function :view_entry_group, :view_entry_groups, :show_entry_group?
|
17
35
|
end
|
18
36
|
end
|
19
37
|
end
|
@@ -18,13 +18,11 @@ module Dsu
|
|
18
18
|
end
|
19
19
|
end
|
20
20
|
|
21
|
+
# TODO: Do we have something else we can use here?
|
21
22
|
def times_for(times:)
|
22
23
|
start_date = times.max
|
23
24
|
return times unless start_date.monday? || start_date.on_weekend?
|
24
25
|
|
25
|
-
# (0..3).map { |num| start_date - num.days }
|
26
|
-
# (start_date..-start_date.friday?).map { |time| time }
|
27
|
-
# (0..3).map { |num| start_date - num.days if start_date.on_weekend? || start_date.monday? }
|
28
26
|
# If the start_date is a weekend or a Monday, then we need to include
|
29
27
|
# start_date along with all the dates up to and including the previous
|
30
28
|
# Monday.
|
@@ -12,7 +12,7 @@ module Dsu
|
|
12
12
|
def validate(record)
|
13
13
|
unless record.entries.is_a?(Array)
|
14
14
|
record.errors.add(:entries_entry, 'is the wrong object type. ' \
|
15
|
-
|
15
|
+
"\"Array\" was expected, but \"#{record.entries.class}\" was received.")
|
16
16
|
end
|
17
17
|
|
18
18
|
validate_entry_types record
|
@@ -27,7 +27,7 @@ module Dsu
|
|
27
27
|
next if entry.is_a? Dsu::Models::Entry
|
28
28
|
|
29
29
|
record.errors.add(:entries_entry, 'entry Array element is the wrong object type. ' \
|
30
|
-
|
30
|
+
"\"Entry\" was expected, but \"#{entry.class}\" was received.",
|
31
31
|
type: Support::FieldErrors::FIELD_TYPE_ERROR)
|
32
32
|
end
|
33
33
|
end
|
@@ -43,7 +43,7 @@ module Dsu
|
|
43
43
|
non_unique_descriptions = descriptions.select { |description| descriptions.count(description) > 1 }.uniq
|
44
44
|
if non_unique_descriptions.any?
|
45
45
|
record.errors.add(:entries_entry, 'contains a duplicate entry: ' \
|
46
|
-
|
46
|
+
"#{format_non_unique_descriptions(non_unique_descriptions)}.",
|
47
47
|
type: Support::FieldErrors::FIELD_DUPLICATE_ERROR)
|
48
48
|
end
|
49
49
|
end
|
data/lib/dsu/version.rb
CHANGED
@@ -1,17 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'time'
|
4
|
-
require 'active_support/core_ext/numeric/time'
|
5
3
|
require_relative '../../models/entry_group'
|
6
|
-
require_relative '../../support/
|
4
|
+
require_relative '../../support/configurable'
|
7
5
|
|
8
6
|
module Dsu
|
9
7
|
module Views
|
10
8
|
module EntryGroup
|
11
9
|
class Edit
|
12
|
-
include Support::
|
13
|
-
include Support::Say
|
14
|
-
include Support::TimeFormatable
|
10
|
+
include Support::Configurable
|
15
11
|
|
16
12
|
def initialize(entry_group:, options: {})
|
17
13
|
raise ArgumentError, 'entry_group is nil' if entry_group.nil?
|
@@ -31,18 +27,16 @@ module Dsu
|
|
31
27
|
# Just in case the entry group is invalid, we'll validate it before displaying it.
|
32
28
|
entry_group.validate!
|
33
29
|
|
34
|
-
# TODO: Display entry group entries from the previous DSU date so they can be
|
35
|
-
# easily copied over; or, add them to the current entry group entries below as
|
36
|
-
# a "# [+|a|add] <entry group from previous DSU entry description>"
|
37
|
-
# (e.g. commented out) by default?
|
38
|
-
|
39
30
|
<<~EDIT_VIEW
|
40
|
-
#
|
41
|
-
#
|
31
|
+
#{banner_line}
|
32
|
+
# Editing DSU Entries for #{entry_group.time_formatted}
|
33
|
+
#{banner_line}
|
42
34
|
|
43
|
-
#{
|
35
|
+
#{entry_group_view&.chomp}
|
44
36
|
|
45
|
-
#
|
37
|
+
#{banner_line}
|
38
|
+
# INSTRUCTIONS
|
39
|
+
#{banner_line}
|
46
40
|
# ADD a DSU entry: type an ENTRY DESCRIPTION on a new line.
|
47
41
|
# EDIT a DSU entry: change the existing ENTRY DESCRIPTION.
|
48
42
|
# DELETE a DSU entry: delete the ENTRY DESCRIPTION.
|
@@ -51,6 +45,7 @@ module Dsu
|
|
51
45
|
# REORDER a DSU entry: reorder the ENTRY DESCRIPTIONs in order preference.
|
52
46
|
#
|
53
47
|
# *** When you are done, save and close your editor ***
|
48
|
+
#{banner_line}
|
54
49
|
EDIT_VIEW
|
55
50
|
end
|
56
51
|
|
@@ -58,8 +53,68 @@ module Dsu
|
|
58
53
|
|
59
54
|
attr_reader :entry_group, :options
|
60
55
|
|
56
|
+
def time
|
57
|
+
@time ||= entry_group.time
|
58
|
+
end
|
59
|
+
|
60
|
+
def banner_line
|
61
|
+
'#' * 80
|
62
|
+
end
|
63
|
+
|
64
|
+
def entry_group_view
|
65
|
+
return entry_group_entry_lines if entry_group.entries.any?
|
66
|
+
return previous_entry_group_entry_lines if carry_over_entries_to_today? && previous_entry_group?
|
67
|
+
|
68
|
+
<<~EDIT_VIEW
|
69
|
+
#{banner_line}
|
70
|
+
# ENTER DSU ENTRIES BELOW
|
71
|
+
#{banner_line}
|
72
|
+
|
73
|
+
EDIT_VIEW
|
74
|
+
end
|
75
|
+
|
61
76
|
def entry_group_entry_lines
|
62
|
-
entry_group.entries.
|
77
|
+
raise 'No entries in entry group' if entry_group.entries.empty?
|
78
|
+
|
79
|
+
<<~EDIT_VIEW
|
80
|
+
#{banner_line}
|
81
|
+
# DSU ENTRIES
|
82
|
+
#{banner_line}
|
83
|
+
|
84
|
+
#{entry_group.entries.map(&:description).join("\n").chomp}
|
85
|
+
EDIT_VIEW
|
86
|
+
end
|
87
|
+
|
88
|
+
def previous_entry_group_entry_lines
|
89
|
+
raise 'carry_over_entries_to_today? is false' unless carry_over_entries_to_today?
|
90
|
+
raise 'Entries exist in entry_group' if entry_group.entries.any?
|
91
|
+
raise 'No previous entry group exists' unless previous_entry_group?
|
92
|
+
|
93
|
+
<<~EDIT_VIEW
|
94
|
+
#{banner_line}
|
95
|
+
# PREVIOUS DSU ENTRIES FROM #{previous_entry_group.time_formatted}
|
96
|
+
#{banner_line}
|
97
|
+
|
98
|
+
#{previous_entry_group.entries.map(&:description).join("\n").chomp}
|
99
|
+
EDIT_VIEW
|
100
|
+
end
|
101
|
+
|
102
|
+
def previous_entry_group?
|
103
|
+
previous_entry_group.present?
|
104
|
+
end
|
105
|
+
|
106
|
+
def previous_entry_group
|
107
|
+
# Go back a max of 7 days to find the previous entry group.
|
108
|
+
# TODO: Make this configurable or accept an option?
|
109
|
+
@previous_entry_group ||= (1..7).each do |days|
|
110
|
+
t = time.days_ago(days)
|
111
|
+
return Models::EntryGroup.load(time: t) if Support::EntryGroupFileable.entry_group_file_exists?(time: t)
|
112
|
+
end
|
113
|
+
nil
|
114
|
+
end
|
115
|
+
|
116
|
+
def carry_over_entries_to_today?
|
117
|
+
configuration[:carry_over_entries_to_today]
|
63
118
|
end
|
64
119
|
end
|
65
120
|
end
|
data/lib/dsu.rb
CHANGED
@@ -1,9 +1,11 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'time'
|
4
|
-
require 'active_support/core_ext/object/blank'
|
5
3
|
require 'active_support/core_ext/hash/indifferent_access'
|
6
4
|
require 'active_support/core_ext/numeric/time'
|
5
|
+
require 'active_support/core_ext/object/blank'
|
6
|
+
require 'pry-byebug' if ENV['DEV_ENV']
|
7
|
+
require 'thor'
|
8
|
+
require 'time'
|
7
9
|
|
8
10
|
Dir.glob("#{__dir__}/lib/core/**/*.rb").each do |file|
|
9
11
|
require file
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dsu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
4
|
+
version: 1.1.0.alpha.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gene M. Angelo, Jr.
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-05-
|
11
|
+
date: 2023-05-23 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -31,33 +31,39 @@ dependencies:
|
|
31
31
|
- !ruby/object:Gem::Version
|
32
32
|
version: 7.0.4
|
33
33
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
34
|
+
name: activemodel
|
35
35
|
requirement: !ruby/object:Gem::Requirement
|
36
36
|
requirements:
|
37
37
|
- - "~>"
|
38
38
|
- !ruby/object:Gem::Version
|
39
|
-
version: 0
|
39
|
+
version: '7.0'
|
40
|
+
- - ">="
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 7.0.4.3
|
40
43
|
type: :runtime
|
41
44
|
prerelease: false
|
42
45
|
version_requirements: !ruby/object:Gem::Requirement
|
43
46
|
requirements:
|
44
47
|
- - "~>"
|
45
48
|
- !ruby/object:Gem::Version
|
46
|
-
version: 0
|
49
|
+
version: '7.0'
|
50
|
+
- - ">="
|
51
|
+
- !ruby/object:Gem::Version
|
52
|
+
version: 7.0.4.3
|
47
53
|
- !ruby/object:Gem::Dependency
|
48
|
-
name:
|
54
|
+
name: colorize
|
49
55
|
requirement: !ruby/object:Gem::Requirement
|
50
56
|
requirements:
|
51
57
|
- - "~>"
|
52
58
|
- !ruby/object:Gem::Version
|
53
|
-
version:
|
59
|
+
version: 0.8.1
|
54
60
|
type: :runtime
|
55
61
|
prerelease: false
|
56
62
|
version_requirements: !ruby/object:Gem::Requirement
|
57
63
|
requirements:
|
58
64
|
- - "~>"
|
59
65
|
- !ruby/object:Gem::Version
|
60
|
-
version:
|
66
|
+
version: 0.8.1
|
61
67
|
- !ruby/object:Gem::Dependency
|
62
68
|
name: os
|
63
69
|
requirement: !ruby/object:Gem::Requirement
|
@@ -149,6 +155,7 @@ files:
|
|
149
155
|
- lib/dsu/base_cli.rb
|
150
156
|
- lib/dsu/cli.rb
|
151
157
|
- lib/dsu/command_services/add_entry_service.rb
|
158
|
+
- lib/dsu/core/ruby/not_today.rb
|
152
159
|
- lib/dsu/models/entry.rb
|
153
160
|
- lib/dsu/models/entry_group.rb
|
154
161
|
- lib/dsu/services/configuration_loader_service.rb
|
@@ -166,6 +173,11 @@ files:
|
|
166
173
|
- lib/dsu/subcommands/list.rb
|
167
174
|
- lib/dsu/support/ask.rb
|
168
175
|
- lib/dsu/support/colorable.rb
|
176
|
+
- lib/dsu/support/command_options/dsu_times.rb
|
177
|
+
- lib/dsu/support/command_options/time.rb
|
178
|
+
- lib/dsu/support/command_options/time_mneumonic.rb
|
179
|
+
- lib/dsu/support/command_options/time_mneumonics.rb
|
180
|
+
- lib/dsu/support/configurable.rb
|
169
181
|
- lib/dsu/support/configuration.rb
|
170
182
|
- lib/dsu/support/descriptable.rb
|
171
183
|
- lib/dsu/support/entry_group_fileable.rb
|
@@ -204,9 +216,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
204
216
|
version: 3.0.1
|
205
217
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
206
218
|
requirements:
|
207
|
-
- - "
|
219
|
+
- - ">"
|
208
220
|
- !ruby/object:Gem::Version
|
209
|
-
version:
|
221
|
+
version: 1.3.1
|
210
222
|
requirements: []
|
211
223
|
rubygems_version: 3.3.22
|
212
224
|
signing_key:
|