dsu 2.4.1 → 2.4.3
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 +4 -4
- data/CHANGELOG.md +16 -0
- data/Gemfile.lock +8 -8
- data/README.md +23 -530
- data/lib/dsu/base_cli.rb +0 -2
- data/lib/dsu/cli.rb +5 -1
- data/lib/dsu/crud/json_file.rb +30 -16
- data/lib/dsu/env.rb +20 -1
- data/lib/dsu/models/color_theme.rb +1 -1
- data/lib/dsu/models/configuration.rb +2 -0
- data/lib/dsu/models/entry_group.rb +1 -1
- data/lib/dsu/models/migration_version.rb +2 -0
- data/lib/dsu/presenters/base_presenter_ex.rb +1 -1
- data/lib/dsu/presenters/configuration_presenter.rb +6 -1
- data/lib/dsu/presenters/entry_group/list/date_presenter.rb +77 -0
- data/lib/dsu/presenters/entry_group/list/dates_presenter.rb +60 -0
- data/lib/dsu/presenters/entry_group/list/messages.rb +15 -0
- data/lib/dsu/presenters/entry_group/list/nothing_to_list.rb +15 -0
- data/lib/dsu/services/entry_group/editor_service.rb +1 -1
- data/lib/dsu/subcommands/list.rb +13 -10
- data/lib/dsu/support/command_hookable.rb +5 -0
- data/lib/dsu/support/entry_group_browsable.rb +4 -1
- data/lib/dsu/version.rb +1 -1
- data/lib/dsu/views/entry_group/list.rb +23 -0
- data/lib/dsu/views/entry_group/show.rb +5 -1
- data/lib/locales/en/services.yml +2 -2
- metadata +38 -30
- data/lib/dsu/crud/raw_json_file.rb +0 -51
- data/lib/dsu/support/entry_group_viewable.rb +0 -61
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 'presenters/entry_group/list/date_presenter'
|
6
7
|
require_relative 'subcommands/browse'
|
7
8
|
require_relative 'subcommands/config'
|
8
9
|
require_relative 'subcommands/delete'
|
@@ -11,6 +12,7 @@ require_relative 'subcommands/export'
|
|
11
12
|
require_relative 'subcommands/import'
|
12
13
|
require_relative 'subcommands/list'
|
13
14
|
require_relative 'subcommands/theme'
|
15
|
+
require_relative 'views/entry_group/list'
|
14
16
|
|
15
17
|
module Dsu
|
16
18
|
# The `dsu` command.
|
@@ -46,7 +48,9 @@ module Dsu
|
|
46
48
|
end
|
47
49
|
entry = Models::Entry.new(description: description)
|
48
50
|
CommandServices::AddEntryService.new(entry: entry, time: time).call
|
49
|
-
|
51
|
+
presenter = Presenters::EntryGroup::List::DatePresenter.new(times: [time], options: options)
|
52
|
+
# TODO: Refactor View::EntryGroup::Show to accept a presenter and use it here
|
53
|
+
Views::EntryGroup::List.new(presenter: presenter).render
|
50
54
|
end
|
51
55
|
|
52
56
|
desc I18n.t('commands.browse.desc'), I18n.t('commands.browse.usage')
|
data/lib/dsu/crud/json_file.rb
CHANGED
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'active_model'
|
4
4
|
require 'json'
|
5
|
-
require_relative 'raw_json_file'
|
6
5
|
|
7
6
|
module Dsu
|
8
7
|
module Crud
|
@@ -23,12 +22,12 @@ module Dsu
|
|
23
22
|
self.class.delete!(file_path: file_path)
|
24
23
|
end
|
25
24
|
|
26
|
-
def
|
27
|
-
self.class.
|
25
|
+
def file_exist?
|
26
|
+
self.class.file_exist?(file_path: file_path)
|
28
27
|
end
|
29
28
|
|
30
29
|
def persisted?
|
31
|
-
|
30
|
+
file_exist?
|
32
31
|
end
|
33
32
|
|
34
33
|
# Override this method to reload data from the file
|
@@ -67,16 +66,20 @@ module Dsu
|
|
67
66
|
alias save! write!
|
68
67
|
|
69
68
|
class << self
|
70
|
-
def
|
71
|
-
|
69
|
+
def file_exist?(file_path:)
|
70
|
+
File.exist?(file_path)
|
72
71
|
end
|
73
72
|
|
74
73
|
def delete(file_path:)
|
75
|
-
|
74
|
+
return false unless file_exist?(file_path: file_path)
|
75
|
+
|
76
|
+
File.delete(file_path)
|
77
|
+
|
78
|
+
true
|
76
79
|
end
|
77
80
|
|
78
81
|
def delete!(file_path:)
|
79
|
-
|
82
|
+
raise file_does_not_exist_message(file_path: file_path) unless delete(file_path: file_path)
|
80
83
|
end
|
81
84
|
|
82
85
|
def parse(json)
|
@@ -86,54 +89,65 @@ module Dsu
|
|
86
89
|
end
|
87
90
|
|
88
91
|
def read(file_path:)
|
89
|
-
|
92
|
+
json = File.read(file_path) if file_exist?(file_path: file_path)
|
93
|
+
hash = parse(json)
|
90
94
|
return yield hash if hash && block_given?
|
91
95
|
|
92
96
|
hash
|
93
97
|
end
|
94
98
|
|
95
99
|
def read!(file_path:)
|
96
|
-
|
100
|
+
raise file_does_not_exist_message(file_path: file_path) unless file_exist?(file_path: file_path)
|
101
|
+
|
102
|
+
hash = read(file_path: file_path)
|
97
103
|
return yield hash if hash && block_given?
|
98
104
|
|
99
105
|
hash
|
100
106
|
end
|
101
107
|
|
102
108
|
def write(file_data:, file_path:)
|
103
|
-
|
109
|
+
raise ArgumentError, 'file_data is nil' if file_data.nil?
|
110
|
+
raise ArgumentError, "file_data is the wrong object type:\"#{file_data}\"" unless file_data.is_a?(Hash)
|
111
|
+
|
112
|
+
file_data = JSON.pretty_generate(file_data)
|
113
|
+
File.write(file_path, file_data)
|
104
114
|
end
|
105
115
|
|
106
116
|
def write!(file_data:, file_path:)
|
107
117
|
write(file_data: file_data, file_path: file_path)
|
108
118
|
end
|
119
|
+
|
120
|
+
def file_does_not_exist_message(file_path:)
|
121
|
+
"File \"#{file_path}\" does not exist"
|
122
|
+
end
|
109
123
|
end
|
110
124
|
|
111
125
|
private
|
112
126
|
|
127
|
+
attr_writer :file_path, :version
|
128
|
+
|
113
129
|
def read
|
114
|
-
hash = self.class.
|
130
|
+
hash = self.class.read(file_path: file_path)
|
115
131
|
return yield hash if block_given?
|
116
132
|
|
117
133
|
hash
|
118
134
|
end
|
119
135
|
|
120
136
|
def read!
|
121
|
-
hash = self.class.
|
137
|
+
hash = self.class.read!(file_path: file_path)
|
122
138
|
return yield hash if hash && block_given?
|
123
139
|
|
124
140
|
hash
|
125
141
|
end
|
126
142
|
|
127
143
|
def read_version
|
128
|
-
return 0 unless
|
144
|
+
return 0 unless file_exist?
|
129
145
|
|
130
146
|
hash = read
|
131
147
|
return 0 if hash.nil?
|
132
148
|
|
133
149
|
hash.fetch(:version, 0).to_i
|
134
150
|
end
|
135
|
-
|
136
|
-
attr_writer :file_path, :version
|
137
151
|
end
|
138
152
|
end
|
139
153
|
end
|
data/lib/dsu/env.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
module Dsu
|
4
4
|
class << self
|
5
|
-
def env
|
5
|
+
def env # rubocop:disable Metrics/MethodLength
|
6
6
|
@env ||= Struct.new(:env) do
|
7
7
|
def test?
|
8
8
|
env.fetch('DSU_ENV', nil) == 'test'
|
@@ -19,6 +19,25 @@ module Dsu
|
|
19
19
|
def production?
|
20
20
|
env.fetch('DSU_ENV', 'production') == 'production'
|
21
21
|
end
|
22
|
+
|
23
|
+
def screen_shot_mode?
|
24
|
+
development? && (env.fetch('SCREEN_SHOT_USERNAME', '').present? ||
|
25
|
+
env.fetch('SCREEN_SHOT_HOSTNAME', '').present?)
|
26
|
+
end
|
27
|
+
|
28
|
+
def screen_shot_prompt
|
29
|
+
username = screen_shot_username
|
30
|
+
hostname = screen_shot_hostname
|
31
|
+
"#{username}@#{hostname}:~ $"
|
32
|
+
end
|
33
|
+
|
34
|
+
def screen_shot_username
|
35
|
+
env.fetch('SCREEN_SHOT_USERNAME', 'username')
|
36
|
+
end
|
37
|
+
|
38
|
+
def screen_shot_hostname
|
39
|
+
env.fetch('SCREEN_SHOT_HOSTNAME', 'hostname')
|
40
|
+
end
|
22
41
|
end.new(ENV)
|
23
42
|
end
|
24
43
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../env'
|
3
4
|
require_relative '../support/color_themable'
|
4
5
|
require_relative 'base_presenter'
|
5
6
|
|
@@ -28,7 +29,11 @@ module Dsu
|
|
28
29
|
private
|
29
30
|
|
30
31
|
def config_path
|
31
|
-
@config_path ||=
|
32
|
+
@config_path ||= if Dsu.env.screen_shot_mode?
|
33
|
+
"/Users/#{Dsu.env.screen_shot_username}/.dsu"
|
34
|
+
else
|
35
|
+
config.file_path
|
36
|
+
end
|
32
37
|
end
|
33
38
|
|
34
39
|
def formatted_config_entry_with_index(config_entry, index:, theme_color:)
|
@@ -0,0 +1,77 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../../models/configuration'
|
4
|
+
require_relative '../../../models/entry_group'
|
5
|
+
require_relative '../../../views/entry_group/shared/no_entries_to_display'
|
6
|
+
require_relative '../../base_presenter_ex'
|
7
|
+
require_relative 'messages'
|
8
|
+
require_relative 'nothing_to_list'
|
9
|
+
|
10
|
+
module Dsu
|
11
|
+
module Presenters
|
12
|
+
module EntryGroup
|
13
|
+
module List
|
14
|
+
class DatePresenter < BasePresenterEx
|
15
|
+
include Messages
|
16
|
+
include NothingToList
|
17
|
+
|
18
|
+
def initialize(times:, options: {})
|
19
|
+
raise ArgumentError, 'times must be an Array' unless times.is_a?(Array)
|
20
|
+
raise ArgumentError, 'options must be a Hash' unless options.is_a?(Hash)
|
21
|
+
|
22
|
+
super(options: options)
|
23
|
+
|
24
|
+
@times = times
|
25
|
+
end
|
26
|
+
|
27
|
+
def render
|
28
|
+
return if nothing_to_list?
|
29
|
+
|
30
|
+
entry_groups.each do |entry_group|
|
31
|
+
Views::EntryGroup::Show.new(entry_group: entry_group).render
|
32
|
+
puts
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def display_nothing_to_list_message
|
37
|
+
# This presenter will ALWAYS have something to list (display) since the first
|
38
|
+
# and last (if different) entry groups will always be displayed.
|
39
|
+
raise 'display_nothing_to_list_message called when there are entries to display'
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_reader :times
|
45
|
+
|
46
|
+
def entry_groups
|
47
|
+
@entry_groups ||= begin
|
48
|
+
options = configuration.to_h.merge(self.options).with_indifferent_access
|
49
|
+
|
50
|
+
times.filter_map do |time|
|
51
|
+
view_options = options.dup
|
52
|
+
# Always show the first and last entry groups.
|
53
|
+
view_options[:include_all] = true if times_min_max.include?(time)
|
54
|
+
|
55
|
+
next unless show_entry_group?(time: time, options: view_options)
|
56
|
+
|
57
|
+
Models::EntryGroup.find_or_initialize(time: time)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def times_min_max
|
63
|
+
@times_min_max ||= times.minmax
|
64
|
+
end
|
65
|
+
|
66
|
+
def configuration
|
67
|
+
@configuration ||= Models::Configuration.new
|
68
|
+
end
|
69
|
+
|
70
|
+
def show_entry_group?(time:, options:)
|
71
|
+
Models::EntryGroup.exist?(time: time) || options[:include_all]
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../../models/entry_group'
|
4
|
+
require_relative '../../../views/entry_group/shared/no_entries_to_display'
|
5
|
+
require_relative '../../base_presenter_ex'
|
6
|
+
require_relative 'messages'
|
7
|
+
require_relative 'nothing_to_list'
|
8
|
+
|
9
|
+
module Dsu
|
10
|
+
module Presenters
|
11
|
+
module EntryGroup
|
12
|
+
module List
|
13
|
+
class DatesPresenter < BasePresenterEx
|
14
|
+
include Messages
|
15
|
+
include NothingToList
|
16
|
+
|
17
|
+
def initialize(times:, options: {})
|
18
|
+
raise ArgumentError, 'times must be an Array' unless times.is_a?(Array)
|
19
|
+
raise ArgumentError, 'options must be a Hash' unless options.is_a?(Hash)
|
20
|
+
|
21
|
+
super(options: options)
|
22
|
+
|
23
|
+
@times = times
|
24
|
+
end
|
25
|
+
|
26
|
+
def render
|
27
|
+
return if nothing_to_list?
|
28
|
+
|
29
|
+
entry_groups.each do |entry_group|
|
30
|
+
Views::EntryGroup::Show.new(entry_group: entry_group).render
|
31
|
+
puts
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def display_nothing_to_list_message
|
36
|
+
raise 'display_nothing_to_list_message called when there are entries to display' unless nothing_to_list?
|
37
|
+
|
38
|
+
Views::EntryGroup::Shared::NoEntriesToDisplay.new(times: times, options: options).render
|
39
|
+
end
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
attr_reader :times
|
44
|
+
|
45
|
+
def entry_groups
|
46
|
+
@entry_groups ||= times.filter_map do |time|
|
47
|
+
next unless show_entry_group?(time: time, options: options)
|
48
|
+
|
49
|
+
Models::EntryGroup.find_or_initialize(time: time)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def show_entry_group?(time:, options:)
|
54
|
+
Models::EntryGroup.exist?(time: time) || options[:include_all]
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -65,7 +65,7 @@ module Dsu
|
|
65
65
|
process_entry_group!(entry_group_with_edits)
|
66
66
|
else
|
67
67
|
message_array = I18n.t('services.editor_service.errors.temp_file_error',
|
68
|
-
editor: configuration.editor,
|
68
|
+
editor: ENV.fetch('EDITOR', configuration.editor),
|
69
69
|
status: $CHILD_STATUS).split("\n")
|
70
70
|
puts apply_theme(message_array, theme_color: color_theme.error)
|
71
71
|
end
|
data/lib/dsu/subcommands/list.rb
CHANGED
@@ -1,10 +1,12 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../presenters/entry_group/list/date_presenter'
|
4
|
+
require_relative '../presenters/entry_group/list/dates_presenter'
|
3
5
|
require_relative '../services/entry_group/counter_service'
|
4
6
|
require_relative '../support/command_options/dsu_times'
|
5
7
|
require_relative '../support/command_options/time_mnemonic'
|
6
8
|
require_relative '../support/time_formatable'
|
7
|
-
require_relative '../views/entry_group/
|
9
|
+
require_relative '../views/entry_group/list'
|
8
10
|
require_relative '../views/shared/error'
|
9
11
|
require_relative 'base_subcommand'
|
10
12
|
|
@@ -25,7 +27,8 @@ module Dsu
|
|
25
27
|
def today
|
26
28
|
time = Time.now
|
27
29
|
times = sorted_dsu_times_for(times: [time.yesterday, time])
|
28
|
-
|
30
|
+
presenter = Presenters::EntryGroup::List::DatePresenter.new(times: times, options: options)
|
31
|
+
Views::EntryGroup::List.new(presenter: presenter).render
|
29
32
|
end
|
30
33
|
|
31
34
|
desc I18n.t('subcommands.list.tomorrow.desc'), I18n.t('subcommands.list.tomorrow.usage')
|
@@ -33,7 +36,8 @@ module Dsu
|
|
33
36
|
def tomorrow
|
34
37
|
time = Time.now
|
35
38
|
times = sorted_dsu_times_for(times: [time, time.tomorrow])
|
36
|
-
|
39
|
+
presenter = Presenters::EntryGroup::List::DatePresenter.new(times: times, options: options)
|
40
|
+
Views::EntryGroup::List.new(presenter: presenter).render
|
37
41
|
end
|
38
42
|
|
39
43
|
desc I18n.t('subcommands.list.yesterday.desc'), I18n.t('subcommands.list.yesterday.usage')
|
@@ -41,7 +45,8 @@ module Dsu
|
|
41
45
|
def yesterday
|
42
46
|
time = Time.now
|
43
47
|
times = sorted_dsu_times_for(times: [time.yesterday, time.yesterday.yesterday])
|
44
|
-
|
48
|
+
presenter = Presenters::EntryGroup::List::DatePresenter.new(times: times, options: options)
|
49
|
+
Views::EntryGroup::List.new(presenter: presenter).render
|
45
50
|
end
|
46
51
|
|
47
52
|
desc I18n.t('subcommands.list.date.desc'), I18n.t('subcommands.list.date.usage')
|
@@ -55,7 +60,8 @@ module Dsu
|
|
55
60
|
Time.parse(date_or_mnemonic)
|
56
61
|
end
|
57
62
|
times = sorted_dsu_times_for(times: [time, time.yesterday])
|
58
|
-
|
63
|
+
presenter = Presenters::EntryGroup::List::DatePresenter.new(times: times, options: options)
|
64
|
+
Views::EntryGroup::List.new(presenter: presenter).render
|
59
65
|
rescue ArgumentError => e
|
60
66
|
Views::Shared::Error.new(messages: e.message).render
|
61
67
|
end
|
@@ -79,11 +85,8 @@ module Dsu
|
|
79
85
|
# NOTE: special sort here, unlike the other commands where rules for
|
80
86
|
# displaying DSU entries are applied; this is more of a list command.
|
81
87
|
times = times_sort(times: times, entries_display_order: options[:entries_display_order])
|
82
|
-
|
83
|
-
|
84
|
-
Views::EntryGroup::Shared::NoEntriesToDisplay.new(times: times, options: options).render
|
85
|
-
end
|
86
|
-
end
|
88
|
+
presenter = Presenters::EntryGroup::List::DatesPresenter.new(times: times, options: options)
|
89
|
+
Views::EntryGroup::List.new(presenter: presenter).render
|
87
90
|
rescue ArgumentError => e
|
88
91
|
Views::Shared::Error.new(messages: e.message).render
|
89
92
|
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../env'
|
3
4
|
require_relative '../models/color_theme'
|
4
5
|
require_relative '../services/stderr_redirector_service'
|
5
6
|
require_relative '../views/shared/error'
|
@@ -26,6 +27,10 @@ module Dsu
|
|
26
27
|
end
|
27
28
|
|
28
29
|
def display_dsu_header
|
30
|
+
if Dsu.env.screen_shot_mode?
|
31
|
+
puts apply_theme('Running screen shot mode!', theme_color: color_theme.warning)
|
32
|
+
puts "#{Dsu.env.screen_shot_prompt} dsu #{ARGV.join(' ')}"
|
33
|
+
end
|
29
34
|
end
|
30
35
|
|
31
36
|
def display_dsu_footer
|
@@ -1,8 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../models/configuration'
|
4
|
+
require_relative '../presenters/entry_group/list/dates_presenter'
|
4
5
|
require_relative '../services/entry_group/browse_service'
|
5
6
|
require_relative '../services/entry_group/counter_service'
|
7
|
+
require_relative '../views/entry_group/list'
|
6
8
|
|
7
9
|
module Dsu
|
8
10
|
module Support
|
@@ -23,7 +25,8 @@ module Dsu
|
|
23
25
|
header = browse_header_for(time: time, options: options)
|
24
26
|
Views::Shared::Info.new(messages: header).render
|
25
27
|
puts
|
26
|
-
|
28
|
+
presenter = Presenters::EntryGroup::List::DatesPresenter.new(times: times, options: options)
|
29
|
+
Views::EntryGroup::List.new(presenter: presenter).render
|
27
30
|
self.class.display_dsu_footer
|
28
31
|
end
|
29
32
|
output_with_pager output: output, options: options
|
data/lib/dsu/version.rb
CHANGED
@@ -0,0 +1,23 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dsu
|
4
|
+
module Views
|
5
|
+
module EntryGroup
|
6
|
+
class List
|
7
|
+
def initialize(presenter:)
|
8
|
+
@presenter = presenter
|
9
|
+
end
|
10
|
+
|
11
|
+
def render
|
12
|
+
return presenter.display_nothing_to_list_message if presenter.nothing_to_list?
|
13
|
+
|
14
|
+
presenter.render
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
attr_reader :presenter
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -36,7 +36,11 @@ module Dsu
|
|
36
36
|
puts presenter.formatted_time
|
37
37
|
|
38
38
|
entry_group.validate!
|
39
|
-
|
39
|
+
if entry_group.entries.empty?
|
40
|
+
puts presenter.no_entries_available
|
41
|
+
|
42
|
+
return
|
43
|
+
end
|
40
44
|
|
41
45
|
entry_group.entries.each_with_index do |entry, index|
|
42
46
|
entry_presenter = entry.presenter
|
data/lib/locales/en/services.yml
CHANGED
@@ -4,7 +4,7 @@ en:
|
|
4
4
|
errors:
|
5
5
|
temp_file_error: |
|
6
6
|
Failed to open temporary file in editor '%{editor}'; the system error returned was: '%{status}'.
|
7
|
-
|
8
|
-
Run
|
7
|
+
Check your EDITOR environment variable and the dsu editor configuration option (run `dsu config info`).
|
8
|
+
Run `dsu help config` for more information.
|
9
9
|
messages:
|
10
10
|
editing: "Editing entry group %{formatted_time}..."
|