dsu 2.1.4 → 2.2.0.rc.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 2f96d328d7aa210c34ec21066d11d393bfab30cc18575b404c65ee1c20b8594b
4
- data.tar.gz: da67601c29bee6c44d10d52561ccfd5196452539158dbf78137de54590654929
3
+ metadata.gz: cbc51c0376177977ba4f3c3d06ad2cfe42150c2122eaa826e19ec9074cc52889
4
+ data.tar.gz: 7647b24eb4ba59f8bfc78457a68935c9e469a893597de2e3930ba4f80e2173ba
5
5
  SHA512:
6
- metadata.gz: 13654ef47f6b35b8fcc3c27e0f7cdeca43791220001e24157dae4ab9d47964c3c3b08f8d961fbfd8458dd804e1e9e23e00306538b4f8ff5786abdde8d377465f
7
- data.tar.gz: '0233403599e4a3b82266b4fe68a4d31cfb4c539572e321d5fb79e8539d0eaa1864d9ea348bd40b3fb2c26c2d835d4050847a88ca4c2d201e84a7ff085408e429'
6
+ metadata.gz: 774f2bbfaf2bb32e6e5653f6c3e917b7b63e19f5d223e9e29cb3cc28d62e076694a58780644de2101b254d672cd8a89fce35ae61183756ca2e72d193ced41d02
7
+ data.tar.gz: c4359560a4589bc360239dbc8b53cef1405f444fec5dffcfba7c50be0b95607a48c84aaee7dd7b68f6c7ef5b519b664431399c7db222fa798a165dc5b8a4a35a
data/.env.test ADDED
@@ -0,0 +1 @@
1
+ DSU_ENV=test
data/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## [2.2.0.rc.1] 2023-12-23
2
+
3
+ Enhancements
4
+
5
+ - Added `dsu browse` command to interactively page through DSU entries.
6
+ - Added "light" theme for terminals with light backgrounds, see `dsu theme list` or `dsu theme show light` for more information.
7
+
8
+ Changes
9
+
10
+ - Refactors to use activesupport Time#in_time_zone, including tests.
11
+ - Various code refactors to support the aforementioned change.
12
+
1
13
  ## [2.1.4] 2023-12-19
2
14
 
3
15
  Changes
@@ -33,7 +45,6 @@ Bug fixes
33
45
 
34
46
  - Fix bug that did not included I18n locale files in yanked version 2.1.0.
35
47
 
36
-
37
48
  ## [2.1.0] 2023-12-16
38
49
 
39
50
  Enhancements
@@ -215,6 +226,7 @@ Changes
215
226
  - `dsu config info` now displays the default configuration being used if no configuration file is being used.
216
227
 
217
228
  Bug fixes
229
+
218
230
  - Fix bug that fails to load/use configuration file options when a config file exists.
219
231
 
220
232
  ## [0.1.0.alpha.1] 2023-05-06
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- dsu (2.1.4)
4
+ dsu (2.2.0.rc.1)
5
5
  activemodel (>= 7.0.8, < 8.0)
6
6
  activesupport (>= 7.0.8, < 8.0)
7
7
  colorize (>= 0.8.1, < 1.0)
@@ -122,6 +122,7 @@ GEM
122
122
  PLATFORMS
123
123
  x86_64-darwin-19
124
124
  x86_64-darwin-21
125
+ x86_64-linux
125
126
 
126
127
  DEPENDENCIES
127
128
  dotenv (~> 2.8, >= 2.8.1)
data/README.md CHANGED
@@ -37,6 +37,7 @@ After installation (`gem install dsu`), the first thing you may want to do is ru
37
37
  #=>
38
38
  Commands:
39
39
  dsu add|a [OPTIONS] DESCRIPTION # Adds a DSU entry...
40
+ dsu browse|b SUBCOMMAND # Browse DSU entries...
40
41
  dsu config|c SUBCOMMAND # Manage configuration...
41
42
  dsu delete|d SUBCOMMAND # Delete DSU entries...
42
43
  dsu edit|e SUBCOMMAND # Edit DSU entries...
@@ -176,6 +177,18 @@ This can be accomplished MUCH easier by using the `yesterday` mnemonic. This wil
176
177
  `$ dsu list dates --from yesterday --to -6`
177
178
  `$ dsu l dd -f y -t -6`
178
179
 
180
+ ## Browsing DSU Entries
181
+ You can browse DSU entries for the current week, month and year using any of the following commands. `dsu browse` somewhat similar to `dsu list` with added `week`, `month` and `year` convenience SUBCOMMANDs. `dsu browse` also pipes the output to the terminal, so you can conveniently scroll through the listed entries using your keyboard or mouse:
182
+
183
+ **NOTE:** Keyboard and/or mouse behavior while browsing (scrolling), is operating system dependent; `dsu browse` pipes its output to the terminal using `less` on nix systems, and `more` on Windows systems.
184
+
185
+ - `$ dsu browse week`
186
+ - `$ dsu b w` # Equivalent to the above, only using shortcuts
187
+ - `$ dsu browse month`
188
+ - `$ dsu b m` # Equivalent to the above, only using shortcuts
189
+ - `$ dsu browse year`
190
+ - `$ dsu b y` # Equivalent to the above, only using shortcuts
191
+
179
192
  ## Editing DSU Entries
180
193
 
181
194
  You can edit DSU entry groups by date. `dsu` will allow you to edit a DSU entry group using the `dsu edit SUBCOMMAND` date (`n|today|t|tomorrow|y|yesterday|date DATE`) you specify. `dsu edit` will open your DSU entry group entries in your editor, where you'll be able to perform editing functions against one or all of the entries.
data/lib/dsu/cli.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require 'fileutils'
4
4
  require 'time'
5
5
  require_relative 'base_cli'
6
+ require_relative 'subcommands/browse'
6
7
  require_relative 'subcommands/config'
7
8
  require_relative 'subcommands/delete'
8
9
  require_relative 'subcommands/edit'
@@ -13,6 +14,7 @@ module Dsu
13
14
  # The `dsu` command.
14
15
  class CLI < BaseCLI
15
16
  map I18n.t('commands.add.key_mappings') => :add
17
+ map I18n.t('commands.browse.key_mappings') => :browse
16
18
  map I18n.t('commands.config.key_mappings') => :config
17
19
  map I18n.t('commands.delete.key_mappings') => :delete
18
20
  map I18n.t('commands.edit.key_mappings') => :edit
@@ -43,6 +45,9 @@ module Dsu
43
45
  view_entry_group(time: time)
44
46
  end
45
47
 
48
+ desc I18n.t('commands.browse.desc'), I18n.t('commands.browse.usage')
49
+ subcommand :browse, Subcommands::Browse
50
+
46
51
  desc I18n.t('commands.list.desc'), I18n.t('commands.list.usage')
47
52
  subcommand :list, Subcommands::List
48
53
 
@@ -155,6 +155,18 @@ module Dsu
155
155
  superclass.exist?(file_path: entries_path_for(time: time))
156
156
  end
157
157
 
158
+ def entry_group_times(between: nil)
159
+ entry_files.filter_map do |file_path|
160
+ entry_file_name = File.basename(file_path)
161
+ next unless entry_file_name.match?(ENTRIES_FILE_NAME_REGEX)
162
+
163
+ time = File.basename(entry_file_name, '.*')
164
+ next if between && !Time.parse(time).between?(between.min, between.max)
165
+
166
+ time
167
+ end
168
+ end
169
+
158
170
  def find(time:)
159
171
  file_path = entries_path_for(time: time)
160
172
  entry_group_hash = read!(file_path: file_path)
@@ -200,7 +212,8 @@ module Dsu
200
212
  private
201
213
 
202
214
  def ensure_local_time(time)
203
- time.nil? ? Time.now : time.dup.localtime
215
+ time ||= Time.now
216
+ time.in_time_zone
204
217
  end
205
218
  end
206
219
  end
@@ -0,0 +1,100 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../models/entry_group'
4
+ require_relative '../../support/time_formatable'
5
+ require_relative '../../support/times_sortable'
6
+
7
+ module Dsu
8
+ module Services
9
+ module EntryGroup
10
+ # This service is responsible for returning an array of
11
+ # sorted entry group dates as Time objects. The Time objects
12
+ # returned, and the sort order are determined by the options
13
+ # passed in.
14
+ class BrowseService
15
+ include Support::TimeFormatable
16
+ include Support::TimesSortable
17
+
18
+ def initialize(time:, options: {})
19
+ raise ArgumentError, 'Argument time is nil' if time.nil?
20
+ raise ArgumentError, 'Argument options is nil' if options.nil?
21
+
22
+ @time = time
23
+ @options = options
24
+ end
25
+
26
+ def call
27
+ return [] if entry_group_times.empty?
28
+
29
+ times_sort(times: entry_group_times, entries_display_order: entries_display_order)
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :time, :options
35
+
36
+ def entry_group_times
37
+ @entry_group_times ||= (min_time.to_i..max_time.to_i).step(1.day).each_with_object([]) do |time_step, times|
38
+ time = Time.at(time_step)
39
+ next unless include_all? || entry_group_count(time).positive?
40
+
41
+ times << time
42
+ end
43
+ end
44
+
45
+ def entry_group_count(time)
46
+ entry_group = Models::EntryGroup.find_or_initialize(time: time)
47
+ entry_group.persisted? ? entry_group.entries.count : 0
48
+ end
49
+
50
+ def min_time
51
+ @min_time ||= if week?
52
+ time.beginning_of_week
53
+ elsif month?
54
+ time.beginning_of_month
55
+ elsif year?
56
+ time.beginning_of_year
57
+ end
58
+ end
59
+
60
+ def max_time
61
+ @max_time ||= if week?
62
+ time.end_of_week
63
+ elsif month?
64
+ time.end_of_month
65
+ elsif year?
66
+ time.end_of_year
67
+ end
68
+ end
69
+
70
+ def entries_display_order
71
+ options[:entries_display_order] || default_entries_display_order
72
+ end
73
+
74
+ def default_entries_display_order
75
+ :asc
76
+ end
77
+
78
+ def week?
79
+ options.fetch(:browse, default_browse) == :week
80
+ end
81
+
82
+ def month?
83
+ options[:browse] == :month
84
+ end
85
+
86
+ def year?
87
+ options[:browse] == :year
88
+ end
89
+
90
+ def default_browse
91
+ :week
92
+ end
93
+
94
+ def include_all?
95
+ options.fetch(:include_all, false)
96
+ end
97
+ end
98
+ end
99
+ end
100
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'io/console'
4
+
5
+ require_relative '../services/stdout_redirector_service'
6
+ require_relative '../support/command_options/dsu_times'
7
+ require_relative '../support/command_options/time_mnemonic'
8
+ require_relative '../support/entry_group_browsable'
9
+ require_relative '../support/time_formatable'
10
+ require_relative '../views/entry_group/shared/no_entries_to_display'
11
+ require_relative '../views/shared/error'
12
+ require_relative 'base_subcommand'
13
+
14
+ module Dsu
15
+ module Subcommands
16
+ class Browse < BaseSubcommand
17
+ include Support::EntryGroupBrowsable
18
+ include Support::CommandOptions::TimeMnemonic
19
+ include Support::TimeFormatable
20
+
21
+ # TODO: I18n.
22
+ map %w[w] => :week
23
+ map %w[m] => :month
24
+ map %w[y] => :year
25
+
26
+ class_option :include_all, default: nil, type: :boolean, aliases: '-a',
27
+ desc: I18n.t('options.include_all')
28
+
29
+ desc I18n.t('subcommands.browse.week.desc'), I18n.t('subcommands.browse.week.usage')
30
+ long_desc I18n.t('subcommands.browse.week.long_desc')
31
+ def week
32
+ browse_entry_groups time: Time.now, options: options.merge({ browse: :week })
33
+ end
34
+
35
+ desc I18n.t('subcommands.browse.month.desc'), I18n.t('subcommands.browse.month.usage')
36
+ long_desc I18n.t('subcommands.browse.month.long_desc')
37
+ def month
38
+ browse_entry_groups time: Time.now, options: options.merge({ browse: :month })
39
+ end
40
+
41
+ desc I18n.t('subcommands.browse.year.desc'), I18n.t('subcommands.browse.year.usage')
42
+ long_desc I18n.t('subcommands.browse.year.long_desc')
43
+ def year
44
+ browse_entry_groups time: Time.now, options: options.merge({ browse: :year })
45
+ end
46
+ end
47
+ end
48
+ end
@@ -15,6 +15,7 @@ module Dsu
15
15
  include Support::CommandOptions::TimeMnemonic
16
16
  include Support::TimeFormatable
17
17
 
18
+ # TODO: I18n.
18
19
  map %w[d] => :date
19
20
  map %w[dd] => :dates
20
21
  map %w[n] => :today
@@ -7,6 +7,7 @@ require_relative '../views/entry_group/show'
7
7
  module Dsu
8
8
  module Subcommands
9
9
  class Edit < BaseSubcommand
10
+ # TODO: I18n.
10
11
  map %w[d] => :date
11
12
  map %w[n] => :today
12
13
  map %w[t] => :tomorrow
@@ -79,7 +79,7 @@ module Dsu
79
79
  # NOTE: special sort here, unlike the other commands where rules for
80
80
  # displaying DSU entries are applied; this is more of a list command.
81
81
  times = times_sort(times: times, entries_display_order: options[:entries_display_order])
82
- view_entry_groups(times: times, options: options) do |_total_entry_groups, _total_entry_groups_not_shown|
82
+ view_entry_groups(times: times, options: options) do
83
83
  if Services::EntryGroup::CounterService.new(times: times).call.zero?
84
84
  Views::EntryGroup::Shared::NoEntriesToDisplay.new(times: times, options: options).render
85
85
  end
@@ -11,6 +11,7 @@ require_relative 'base_subcommand'
11
11
  module Dsu
12
12
  module Subcommands
13
13
  class Theme < BaseSubcommand
14
+ # TODO: I18n.
14
15
  map %w[c] => :create if Dsu.env.local?
15
16
  map %w[d] => :delete if Dsu.env.local?
16
17
  map %w[l] => :list
@@ -30,6 +30,13 @@ module Dsu
30
30
  puts
31
31
  end
32
32
 
33
+ def display_dsu_footer
34
+ puts apply_theme('_' * 35, theme_color: color_theme.dsu_footer)
35
+ # TODO: I18n.
36
+ footer = apply_theme("Theme: #{color_theme.theme_name}", theme_color: color_theme.dsu_footer)
37
+ puts footer
38
+ end
39
+
33
40
  private
34
41
 
35
42
  def suspend_header?(args, _options)
@@ -39,13 +46,6 @@ module Dsu
39
46
  true if args[0] == 'theme' && %w[use delete].include?(args[1])
40
47
  end
41
48
 
42
- def display_dsu_footer
43
- puts apply_theme('_' * 35, theme_color: color_theme.dsu_footer)
44
- # TODO: I18n.
45
- footer = apply_theme("Theme: #{color_theme.theme_name}", theme_color: color_theme.dsu_footer)
46
- puts footer
47
- end
48
-
49
49
  def display_errors_if(stderror_string)
50
50
  stderror_string = stderror_string.strip
51
51
  return unless stderror_string.present?
@@ -0,0 +1,101 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../models/configuration'
4
+ require_relative '../services/entry_group/browse_service'
5
+ require_relative '../services/entry_group/counter_service'
6
+
7
+ module Dsu
8
+ module Support
9
+ module EntryGroupBrowsable
10
+ def browse_entry_groups(time:, options: {})
11
+ raise ArgumentError, 'time must be a Time object' unless time.is_a?(Time)
12
+ raise ArgumentError, 'options must be a Hash' unless options.is_a?(Hash)
13
+
14
+ options = configuration.to_h.merge(options).with_indifferent_access
15
+ times = browse_service(time: time, options: options).call
16
+ if times.empty? || (options.fetch(:include_all, false) && no_entries_for?(times: times, options: options))
17
+ display_no_entries_to_display_message time: time, options: options
18
+ return
19
+ end
20
+
21
+ output = Services::StdoutRedirectorService.call do
22
+ self.class.display_dsu_header
23
+ header = browse_header_for(time: time, options: options)
24
+ Views::Shared::Info.new(messages: header).render
25
+ puts
26
+ view_entry_groups(times: times, options: options)
27
+ self.class.display_dsu_footer
28
+ end
29
+ output_with_pager output: output, options: options
30
+ end
31
+
32
+ private
33
+
34
+ def no_entries_for?(times:, options:)
35
+ Services::EntryGroup::CounterService.new(times: times, options: options).call.zero?
36
+ end
37
+
38
+ def browse_header_for(time:, options:)
39
+ of, times = case options[:browse]
40
+ when :week
41
+ [
42
+ I18n.t('subcommands.browse.headers.week_of', week: time.beginning_of_week.to_date),
43
+ [time.beginning_of_week, time.end_of_week]
44
+ ]
45
+ when :month
46
+ [
47
+ I18n.t('subcommands.browse.headers.month_of', month: I18n.l(time, format: '%B')),
48
+ [time.beginning_of_month, time.end_of_month]
49
+ ]
50
+ when :year
51
+ [
52
+ I18n.t('subcommands.browse.headers.year_of', year: time.to_date.year),
53
+ [time.beginning_of_year, time.end_of_year]
54
+ ]
55
+ end
56
+
57
+ I18n.t('subcommands.browse.headers.browsing', of: of, from: times.min.to_date.to_s, to: times.max.to_date.to_s)
58
+ end
59
+
60
+ def output_with_pager(output:, options:)
61
+ if options[:pager] == :no_pager
62
+ puts output
63
+ return
64
+ end
65
+
66
+ pager_command = if RUBY_PLATFORM.match?(/win32|windows/i)
67
+ 'more' # Windows command
68
+ else
69
+ 'less' # Unix-like command
70
+ end
71
+
72
+ IO.popen(pager_command, 'w') do |pipe|
73
+ pipe.puts output
74
+ pipe.close_write
75
+ end
76
+ rescue Errno::ENOENT
77
+ message = "Operating system pager command (#{pager_command}) not found. Falling back to direct output."
78
+ Views::Shared::Error.new(messages: message).render
79
+ puts output
80
+ end
81
+
82
+ def display_no_entries_to_display_message(time:, options:)
83
+ case options[:browse]
84
+ when :week
85
+ Views::EntryGroup::Shared::NoEntriesToDisplayForWeekOf.new(time: time, options: options).render
86
+ when :month
87
+ Views::EntryGroup::Shared::NoEntriesToDisplayForMonthOf.new(time: time, options: options).render
88
+ when :year
89
+ Views::EntryGroup::Shared::NoEntriesToDisplayForYearOf.new(time: time, options: options).render
90
+ else
91
+ raise NotImplementedError, 'Unhandled option; ' \
92
+ "expected :week, :month, or :year but received #{options[:browse]}"
93
+ end
94
+ end
95
+
96
+ def browse_service(time:, options: {})
97
+ Services::EntryGroup::BrowseService.new(time: time, options: options)
98
+ end
99
+ end
100
+ end
101
+ end
@@ -10,7 +10,7 @@ module Dsu
10
10
  end
11
11
 
12
12
  def time_equal_compare_string_for(time:)
13
- time = time.localtime if time.utc?
13
+ time = time.in_time_zone
14
14
 
15
15
  time.strftime(TIME_COMPARABLE_FORMAT_SPECIFIER)
16
16
  end
@@ -12,7 +12,7 @@ module Dsu
12
12
 
13
13
  # TODO: I18n.
14
14
  def formatted_time(time:)
15
- time = time.localtime if time.utc?
15
+ time = time.in_time_zone
16
16
 
17
17
  today_yesterday_or_tomorrow = if time.today?
18
18
  'Today'
@@ -39,6 +39,10 @@ module Dsu
39
39
  time.strftime("%m#{separator}%d#{separator}%Y")
40
40
  end
41
41
 
42
+ def dd_mm_yyyy(time:, separator: '/')
43
+ time.strftime("%d#{separator}%m#{separator}%Y")
44
+ end
45
+
42
46
  def timezone_for(time:)
43
47
  time.zone
44
48
  end
@@ -23,10 +23,9 @@ module Dsu
23
23
  # in ascending order. If the sort is descending, then in order to
24
24
  # properly reverse the times array, it needs to first be sorted in
25
25
  # ascending order before being reversed.
26
- times.sort!
27
- times.reverse! if entries_display_order == :desc
26
+ return times.sort if entries_display_order == :asc
28
27
 
29
- times
28
+ times.sort_by { |time| -time.to_i }
30
29
  end
31
30
 
32
31
  def times_for(times:)
@@ -18,7 +18,7 @@ module Dsu
18
18
  return
19
19
  end
20
20
 
21
- record.errors.add(:time, 'is not in localtime format.') if time.utc?
21
+ record.errors.add(:time, 'is not in localtime format.') unless time == time.in_time_zone
22
22
  end
23
23
  end
24
24
  end
data/lib/dsu/version.rb CHANGED
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Dsu
4
4
  VERSION_REGEX = /\A\d+\.\d+\.\d+(\.alpha\.\d+)?\z/
5
- VERSION = '2.1.4'
5
+ VERSION = '2.2.0.rc.1'
6
6
  end
@@ -23,10 +23,6 @@ module Dsu
23
23
 
24
24
  # TODO: I18n.
25
25
  def render
26
- times.sort!
27
- time_range = "#{formatted_time(time: times.first)} " \
28
- "through #{formatted_time(time: times.last)}"
29
- message = "(nothing to display for #{time_range})"
30
26
  puts apply_theme(message, theme_color: color_theme.info)
31
27
  end
32
28
 
@@ -34,6 +30,15 @@ module Dsu
34
30
 
35
31
  attr_reader :times, :options
36
32
 
33
+ def message
34
+ "(nothing to display for #{time_range})"
35
+ end
36
+
37
+ def time_range
38
+ "#{formatted_time(time: times.min)} " \
39
+ "through #{formatted_time(time: times.max)}"
40
+ end
41
+
37
42
  def color_theme
38
43
  @color_theme ||= Models::ColorTheme.current_or_default
39
44
  end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'no_entries_to_display'
4
+
5
+ module Dsu
6
+ module Views
7
+ module EntryGroup
8
+ module Shared
9
+ class NoEntriesToDisplayForMonthOf < NoEntriesToDisplay
10
+ def initialize(time:, options: {})
11
+ super(times: [time.beginning_of_month, time.end_of_month], options: options)
12
+
13
+ @time = time
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :time
19
+
20
+ # TODO: I18n.
21
+ def message
22
+ "(nothing to display for the month of #{month_string}, #{time_range})"
23
+ end
24
+
25
+ def month_string
26
+ I18n.l(time, format: '%B')
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'no_entries_to_display'
4
+
5
+ module Dsu
6
+ module Views
7
+ module EntryGroup
8
+ module Shared
9
+ class NoEntriesToDisplayForWeekOf < NoEntriesToDisplay
10
+ def initialize(time:, options: {})
11
+ super(times: [time.beginning_of_week, time.end_of_week], options: options)
12
+
13
+ @time = time
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :time
19
+
20
+ # TODO: I18n.
21
+ def message
22
+ "(nothing to display for week of #{week_of_string}, #{time_range})"
23
+ end
24
+
25
+ # TODO: I18n.
26
+ def week_of_string
27
+ time.to_date
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'no_entries_to_display'
4
+
5
+ module Dsu
6
+ module Views
7
+ module EntryGroup
8
+ module Shared
9
+ class NoEntriesToDisplayForYearOf < NoEntriesToDisplay
10
+ def initialize(time:, options: {})
11
+ super(times: [time.beginning_of_year, time.end_of_year], options: options)
12
+
13
+ @time = time
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :time
19
+
20
+ # TODO: I18n.
21
+ def message
22
+ "(nothing to display for the year of #{year_string}, #{time_range})"
23
+ end
24
+
25
+ # TODO: I18n.
26
+ def year_string
27
+ time.year
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
data/lib/dsu.rb CHANGED
@@ -24,11 +24,22 @@ Dir.glob("#{__dir__}/dsu/**/*.rb").each do |file|
24
24
  require file
25
25
  end
26
26
 
27
- if !(Dsu.env.test? || Dsu.env.development?) && Dsu::Migration::Service.run_migrations?
28
- begin
29
- Dsu::Migration::Service.new.call
30
- rescue StandardError => e
31
- puts I18n.t('errors.migration.error', message: e.message)
32
- exit 1
27
+ unless Dsu.env.test? || Dsu.env.development?
28
+ if Dsu::Migration::Service.run_migrations?
29
+ begin
30
+ Dsu::Migration::Service.new.call
31
+ rescue StandardError => e
32
+ puts I18n.t('migrations.error.failed', message: e.message)
33
+ exit 1
34
+ end
35
+ end
36
+ # TODO: Hack. Integrate this into the migration service
37
+ # so that this runs only if the migration version changes.
38
+ theme_file = 'light.json'
39
+ destination_theme_file_path = File.join(Dsu::Support::Fileable.themes_folder, theme_file)
40
+ unless File.exist?(destination_theme_file_path)
41
+ source_theme_file_path = File.join(Dsu::Support::Fileable.seed_data_folder, 'themes', theme_file)
42
+ FileUtils.cp(source_theme_file_path, destination_theme_file_path)
43
+ puts I18n.t('migrations.information.theme_copied', from: source_theme_file_path, to: destination_theme_file_path)
33
44
  end
34
45
  end
@@ -49,6 +49,10 @@ en:
49
49
  DESCRIPTION
50
50
 
51
51
  Must be be between 2 and 256 characters (inclusive) in length.
52
+ browse:
53
+ key_mappings: b
54
+ desc: browse|b SUBCOMMAND
55
+ usage: Browse DSU entries by the given SUBCOMMAND
52
56
  config:
53
57
  key_mappings: c
54
58
  desc: config|c SUBCOMMAND
@@ -115,11 +119,18 @@ en:
115
119
  date_option_description: |
116
120
  DATE
117
121
 
118
- This may be any date string that can be parsed using `Time.parse`.
119
- Consequently, you may use also use '/' as date separators,
122
+ In the format of: [d]d-|/[m]m-|/[-|/yyyy].
123
+
124
+ This may be any date string that can be parsed using ruby's `Time.parse`.
125
+ Consequently, you may use use '-' or '/' as date separators,
120
126
  as well as omit the year if the date you want to display is the
121
- current year (e.g. <month>/<day>, or 1/31). For example: `require 'time';
122
- Time.parse('01/02/2023'); Time.parse('1/2') # etc.`
127
+ current year (e.g. <month>/<day>, or 1/31). Leading zeroes are optional.
128
+ For example: `require 'time'; Time.parse('1/2') # etc.`
129
+
130
+ IMPORTANT: If you include the year as part of your date string, the format of
131
+ the date must be <day>/<month>/<year>, where <day> and <month> may include optional
132
+ leading zeroes, and <year> must be in the format of yyyy. For example
133
+ `require 'time'; Time.parse('31/1/2023') # etc.`
123
134
  mnemonic_option_description: |
124
135
  MNEMONIC
125
136
 
@@ -6,8 +6,6 @@ en:
6
6
  error: "Error: %{message}"
7
7
  from_option_invalid: Option -f, [--from=DATE|MNEMONIC] value is invalid ["%{from_option}"]
8
8
  to_option_invalid: Option -t, [--to=DATE|MNEMONIC] value is invalid ["%{to_option}"]
9
- migration:
10
- error: "Error running migrations: %{message}"
11
9
  headers:
12
10
  entry:
13
11
  could_not_be_added: "An error was encountered; the entry could not be added:"
@@ -21,3 +19,8 @@ en:
21
19
  information:
22
20
  dates:
23
21
  through: "%{from} thru %{to}"
22
+ migrations:
23
+ error:
24
+ failed: "Error running migrations: %{message}"
25
+ information:
26
+ theme_copied: Theme copied from %{from} to %{to}.
@@ -1,6 +1,39 @@
1
1
  # lib/dsu/subcommands
2
2
  en:
3
3
  subcommands:
4
+ browse:
5
+ week:
6
+ desc: week|w
7
+ usage: Browse DSU entries for the current week
8
+ long_desc: |
9
+ Browse DSU entries for the current week.
10
+
11
+ $ dsu browse w
12
+
13
+ $ dsu browse w
14
+ month:
15
+ desc: month|m
16
+ usage: Browse DSU entries for the current month
17
+ long_desc: |
18
+ Browse DSU entries for the current month.
19
+
20
+ $ dsu browse month
21
+
22
+ $ dsu browse m
23
+ year:
24
+ desc: year|y
25
+ usage: Browse DSU entries for the current year
26
+ long_desc: |
27
+ Browse DSU entries for the current year.
28
+
29
+ $ dsu browse year
30
+
31
+ $ dsu browse y
32
+ headers:
33
+ browsing: Browsing DSU entries for %{of} (%{from} thru %{to})
34
+ week_of: Week of %{week}
35
+ month_of: Month of %{month}
36
+ year_of: Year of %{year}
4
37
  config:
5
38
  delete:
6
39
  desc: delete
@@ -0,0 +1,79 @@
1
+ {
2
+ "version": 20230613121411,
3
+ "description": "Theme for Light backgrounds",
4
+ "help": {
5
+ "color": "default",
6
+ "mode": "default",
7
+ "background": "default"
8
+ },
9
+ "dsu_header": {
10
+ "color": "white",
11
+ "mode": "bold",
12
+ "background": "black"
13
+ },
14
+ "dsu_footer": {
15
+ "color": "default",
16
+ "mode": "default",
17
+ "background": "default"
18
+ },
19
+ "header": {
20
+ "color": "default",
21
+ "mode": "bold",
22
+ "background": "default"
23
+ },
24
+ "subheader": {
25
+ "color": "default",
26
+ "mode": "underline",
27
+ "background": "default"
28
+ },
29
+ "body": {
30
+ "color": "default",
31
+ "mode": "default",
32
+ "background": "default"
33
+ },
34
+ "footer": {
35
+ "color": "light_default",
36
+ "mode": "default",
37
+ "background": "default"
38
+ },
39
+ "date": {
40
+ "color": "default",
41
+ "mode": "bold",
42
+ "background": "default"
43
+ },
44
+ "index": {
45
+ "color": "light_default",
46
+ "mode": "italic",
47
+ "background": "default"
48
+ },
49
+ "info": {
50
+ "color": "white",
51
+ "mode": "bold",
52
+ "background": "blue"
53
+ },
54
+ "success": {
55
+ "color": "white",
56
+ "mode": "bold",
57
+ "background": "green"
58
+ },
59
+ "warning": {
60
+ "color": "default",
61
+ "mode": "bold",
62
+ "background": "yellow"
63
+ },
64
+ "error": {
65
+ "color": "light_yellow",
66
+ "mode": "default",
67
+ "background": "red"
68
+ },
69
+ "prompt": {
70
+ "color": "default",
71
+ "mode": "bold",
72
+ "background": "default"
73
+ },
74
+ "prompt_options": {
75
+ "color": "default",
76
+ "mode": "bold",
77
+ "background": "default"
78
+ }
79
+ }
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: 2.1.4
4
+ version: 2.2.0.rc.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-12-20 00:00:00.000000000 Z
11
+ date: 2023-12-23 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -145,6 +145,7 @@ executables:
145
145
  extensions: []
146
146
  extra_rdoc_files: []
147
147
  files:
148
+ - ".env.test"
148
149
  - ".reek.yml"
149
150
  - ".rspec"
150
151
  - ".rubocop.yml"
@@ -186,6 +187,7 @@ files:
186
187
  - lib/dsu/services/color_theme/hydrator_service.rb
187
188
  - lib/dsu/services/configuration/hydrator_service.rb
188
189
  - lib/dsu/services/entry/hydrator_service.rb
190
+ - lib/dsu/services/entry_group/browse_service.rb
189
191
  - lib/dsu/services/entry_group/counter_service.rb
190
192
  - lib/dsu/services/entry_group/deleter_service.rb
191
193
  - lib/dsu/services/entry_group/editor_service.rb
@@ -196,6 +198,7 @@ files:
196
198
  - lib/dsu/services/temp_file/reader_service.rb
197
199
  - lib/dsu/services/temp_file/writer_service.rb
198
200
  - lib/dsu/subcommands/base_subcommand.rb
201
+ - lib/dsu/subcommands/browse.rb
199
202
  - lib/dsu/subcommands/config.rb
200
203
  - lib/dsu/subcommands/delete.rb
201
204
  - lib/dsu/subcommands/edit.rb
@@ -210,6 +213,7 @@ files:
210
213
  - lib/dsu/support/command_options/time_mnemonic.rb
211
214
  - lib/dsu/support/command_options/time_mnemonics.rb
212
215
  - lib/dsu/support/descriptable.rb
216
+ - lib/dsu/support/entry_group_browsable.rb
213
217
  - lib/dsu/support/entry_group_viewable.rb
214
218
  - lib/dsu/support/field_errors.rb
215
219
  - lib/dsu/support/fileable.rb
@@ -229,6 +233,9 @@ files:
229
233
  - lib/dsu/views/configuration/show.rb
230
234
  - lib/dsu/views/entry_group/edit.rb
231
235
  - lib/dsu/views/entry_group/shared/no_entries_to_display.rb
236
+ - lib/dsu/views/entry_group/shared/no_entries_to_display_for_month_of.rb
237
+ - lib/dsu/views/entry_group/shared/no_entries_to_display_for_week_of.rb
238
+ - lib/dsu/views/entry_group/shared/no_entries_to_display_for_year_of.rb
232
239
  - lib/dsu/views/entry_group/show.rb
233
240
  - lib/dsu/views/shared/error.rb
234
241
  - lib/dsu/views/shared/info.rb
@@ -245,6 +252,7 @@ files:
245
252
  - lib/seed_data/themes/cherry.json
246
253
  - lib/seed_data/themes/default.json
247
254
  - lib/seed_data/themes/lemon.json
255
+ - lib/seed_data/themes/light.json
248
256
  - lib/seed_data/themes/matrix.json
249
257
  - lib/seed_data/themes/whiteout.json
250
258
  - sig/dsu.rbs
@@ -264,11 +272,26 @@ post_install_message: |
264
272
  View the dsu README.md here: https://github.com/gangelo/dsu
265
273
  View the dsu CHANGELOG.md: https://github.com/gangelo/dsu/blob/main/CHANGELOG.md
266
274
 
275
+ Dsu now has a browse command! Try it out by running `dsu browse help`.
276
+
277
+ Dsu now has a "light" theme for light background terminals! Try it out by running `dsu theme use light`.
278
+
267
279
  Dsu now has a delete command! Try it out by running `dsu delete help`.
268
280
 
269
281
  Try a dsu theme by running `dsu theme list` and then `dsu theme use THEME_NAME` where THEME_NAME is the name of the theme you want to try.
270
282
 
271
283
  :)
284
+
285
+ Merry CHRISTmas, New Years and Happy holidays from dsu!
286
+ *
287
+ /*\
288
+ */*|\*
289
+ /*/|\*\
290
+ */**|\*\*
291
+ *//*|*\*\*\
292
+ |||
293
+ |||
294
+ |||
272
295
  rdoc_options: []
273
296
  require_paths:
274
297
  - lib
@@ -279,9 +302,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
279
302
  version: 3.0.1
280
303
  required_rubygems_version: !ruby/object:Gem::Requirement
281
304
  requirements:
282
- - - ">="
305
+ - - ">"
283
306
  - !ruby/object:Gem::Version
284
- version: '0'
307
+ version: 1.3.1
285
308
  requirements: []
286
309
  rubygems_version: 3.2.15
287
310
  signing_key: