dsu 2.4.1 → 2.4.3

Sign up to get free protection for your applications and to get access to all the features.
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}..."