dsu 2.1.4 → 2.2.0.rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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: