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.
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
- view_entry_group(time: time)
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')
@@ -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 exist?
27
- self.class.exist?(file_path: file_path)
25
+ def file_exist?
26
+ self.class.file_exist?(file_path: file_path)
28
27
  end
29
28
 
30
29
  def persisted?
31
- exist?
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 exist?(file_path:)
71
- RawJsonFile.exist?(file_path: file_path)
69
+ def file_exist?(file_path:)
70
+ File.exist?(file_path)
72
71
  end
73
72
 
74
73
  def delete(file_path:)
75
- RawJsonFile.delete(file_path: file_path)
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
- RawJsonFile.delete!(file_path: file_path)
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
- hash = parse(RawJsonFile.read(file_path: file_path))
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
- hash = parse(RawJsonFile.read!(file_path: file_path))
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
- Crud::RawJsonFile.write(file_data: file_data, file_path: file_path)
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.parse(RawJsonFile.read(file_path: file_path))
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.parse(RawJsonFile.read!(file_path: file_path))
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 exist?
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
@@ -160,7 +160,7 @@ module Dsu
160
160
  end
161
161
 
162
162
  def exist?(theme_name:)
163
- superclass.exist?(file_path: themes_path_for(theme_name: theme_name))
163
+ superclass.file_exist?(file_path: themes_path_for(theme_name: theme_name))
164
164
  end
165
165
 
166
166
  def find(theme_name:)
@@ -63,6 +63,8 @@ module Dsu
63
63
 
64
64
  attr_reader :options
65
65
 
66
+ alias exist? file_exist?
67
+
66
68
  def initialize(options: {})
67
69
  super(config_path)
68
70
 
@@ -150,7 +150,7 @@ module Dsu
150
150
  end
151
151
 
152
152
  def exist?(time:)
153
- superclass.exist?(file_path: entries_path_for(time: time))
153
+ superclass.file_exist?(file_path: entries_path_for(time: time))
154
154
  end
155
155
 
156
156
  def entry_group_times(between: nil)
@@ -13,6 +13,8 @@ module Dsu
13
13
 
14
14
  attr_reader :options
15
15
 
16
+ alias exist? file_exist?
17
+
16
18
  def initialize(version: nil, options: {})
17
19
  super(migration_version_path)
18
20
 
@@ -10,7 +10,7 @@ module Dsu
10
10
  include Support::ColorThemable
11
11
 
12
12
  def initialize(options: {})
13
- @options = options || {}
13
+ @options = options&.dup || {}
14
14
  @color_theme = Models::ColorTheme.find(theme_name: theme_name)
15
15
  end
16
16
 
@@ -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 ||= config.file_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
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsu
4
+ module Presenters
5
+ module EntryGroup
6
+ module List
7
+ module Messages
8
+ def display_nothing_to_list_message
9
+ raise NotImplementedError
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsu
4
+ module Presenters
5
+ module EntryGroup
6
+ module List
7
+ module NothingToList
8
+ def nothing_to_list?
9
+ entry_groups.none?
10
+ end
11
+ end
12
+ end
13
+ end
14
+ end
15
+ 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
@@ -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/shared/no_entries_to_display'
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
- view_list_for(times: times, options: options)
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
- view_list_for(times: times, options: options)
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
- view_list_for(times: times, options: options)
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
- view_list_for(times: times, options: options)
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
- view_entry_groups(times: times, options: options) do
83
- if Services::EntryGroup::CounterService.new(times: times).call.zero?
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
- view_entry_groups(times: times, options: options)
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Dsu
4
4
  VERSION_REGEX = /\A\d+\.\d+\.\d+(\.(alpha|rc)\.\d+)?\z/
5
- VERSION = '2.4.1'
5
+ VERSION = '2.4.3'
6
6
  end
@@ -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
- puts presenter.no_entries_available and return if entry_group.entries.empty?
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
@@ -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
- Either set the EDITOR environment variable or set the dsu editor configuration option (`$ dsu config info`).
8
- Run `$ dsu help config` for more information.
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}..."