dsu 1.2.0 → 2.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +57 -20
  3. data/Gemfile.lock +7 -7
  4. data/README.md +28 -35
  5. data/bin/console +23 -1
  6. data/bin/dsu +3 -0
  7. data/bin/setup +14 -3
  8. data/exe/dsu +23 -1
  9. data/exe/dsu_migrate.rb +43 -0
  10. data/lib/core/ruby/color_theme_colors.rb +16 -0
  11. data/lib/core/ruby/color_theme_mode.rb +42 -0
  12. data/lib/core/ruby/not_today.rb +7 -0
  13. data/lib/core/ruby/wrap_and_join.rb +31 -0
  14. data/lib/dsu/base_cli.rb +19 -23
  15. data/lib/dsu/cli.rb +47 -37
  16. data/lib/dsu/command_services/add_entry_service.rb +10 -21
  17. data/lib/dsu/crud/json_file.rb +139 -0
  18. data/lib/dsu/crud/raw_json_file.rb +51 -0
  19. data/lib/dsu/env.rb +21 -0
  20. data/lib/dsu/migration/service.rb +196 -0
  21. data/lib/dsu/migration/version.rb +7 -0
  22. data/lib/dsu/models/color_theme.rb +270 -0
  23. data/lib/dsu/models/configuration.rb +160 -0
  24. data/lib/dsu/models/entry.rb +6 -2
  25. data/lib/dsu/models/entry_group.rb +143 -42
  26. data/lib/dsu/models/migration_version.rb +48 -0
  27. data/lib/dsu/presenters/base_presenter.rb +32 -0
  28. data/lib/dsu/presenters/color_theme_presenter.rb +50 -0
  29. data/lib/dsu/presenters/color_theme_show_presenter.rb +49 -0
  30. data/lib/dsu/presenters/configuration_presenter.rb +45 -0
  31. data/lib/dsu/presenters/entry_group_presenter.rb +35 -0
  32. data/lib/dsu/presenters/entry_presenter.rb +25 -0
  33. data/lib/dsu/services/color_theme/hydrator_service.rb +42 -0
  34. data/lib/dsu/services/configuration/hydrator_service.rb +42 -0
  35. data/lib/dsu/services/entry/hydrator_service.rb +33 -0
  36. data/lib/dsu/services/entry_group/editor_service.rb +107 -0
  37. data/lib/dsu/services/entry_group/hydrator_service.rb +37 -0
  38. data/lib/dsu/services/migration_version/hydrator_service.rb +36 -0
  39. data/lib/dsu/services/stderr_redirector_service.rb +27 -0
  40. data/lib/dsu/services/temp_file/reader_service.rb +33 -0
  41. data/lib/dsu/services/temp_file/writer_service.rb +35 -0
  42. data/lib/dsu/subcommands/base_subcommand.rb +14 -0
  43. data/lib/dsu/subcommands/config.rb +92 -32
  44. data/lib/dsu/subcommands/edit.rb +3 -3
  45. data/lib/dsu/subcommands/list.rb +70 -93
  46. data/lib/dsu/subcommands/theme.rb +159 -0
  47. data/lib/dsu/support/ask.rb +14 -19
  48. data/lib/dsu/support/color_themable.rb +34 -0
  49. data/lib/dsu/support/command_help_colorizeable.rb +27 -0
  50. data/lib/dsu/support/command_hookable.rb +60 -0
  51. data/lib/dsu/support/command_options/dsu_times.rb +32 -21
  52. data/lib/dsu/support/command_options/time.rb +7 -1
  53. data/lib/dsu/support/command_options/time_mneumonic.rb +7 -1
  54. data/lib/dsu/support/descriptable.rb +6 -4
  55. data/lib/dsu/support/entry_group_viewable.rb +28 -4
  56. data/lib/dsu/support/fileable.rb +94 -0
  57. data/lib/dsu/support/presentable.rb +11 -0
  58. data/lib/dsu/support/subcommand_help_colorizeable.rb +27 -0
  59. data/lib/dsu/support/time_comparable.rb +19 -0
  60. data/lib/dsu/support/time_formatable.rb +12 -0
  61. data/lib/dsu/support/times_sortable.rb +48 -14
  62. data/lib/dsu/support/utils.rb +11 -0
  63. data/lib/dsu/validators/color_theme_validator.rb +74 -0
  64. data/lib/dsu/validators/entries_validator.rb +4 -8
  65. data/lib/dsu/validators/version_validator.rb +29 -0
  66. data/lib/dsu/version.rb +2 -1
  67. data/lib/dsu/views/color_theme/index.rb +62 -0
  68. data/lib/dsu/views/color_theme/show.rb +106 -0
  69. data/lib/dsu/views/configuration/show.rb +41 -0
  70. data/lib/dsu/views/entry_group/edit.rb +4 -6
  71. data/lib/dsu/views/entry_group/shared/no_entries_to_display.rb +41 -0
  72. data/lib/dsu/views/entry_group/show.rb +16 -15
  73. data/lib/dsu/views/shared/error.rb +17 -0
  74. data/lib/dsu/views/shared/info.rb +17 -0
  75. data/lib/dsu/views/shared/message.rb +85 -0
  76. data/lib/dsu/views/shared/model_errors.rb +31 -0
  77. data/lib/dsu/views/shared/success.rb +17 -0
  78. data/lib/dsu/views/shared/warning.rb +17 -0
  79. data/lib/dsu.rb +22 -1
  80. data/lib/seed_data/themes/cherry.json +79 -0
  81. data/lib/seed_data/themes/default.json +79 -0
  82. data/lib/seed_data/themes/lemon.json +79 -0
  83. data/lib/seed_data/themes/matrix.json +79 -0
  84. data/lib/seed_data/themes/whiteout.json +79 -0
  85. metadata +70 -25
  86. data/lib/dsu/core/ruby/not_today.rb +0 -11
  87. data/lib/dsu/services/ai/tense_translator_service.rb +0 -63
  88. data/lib/dsu/services/configuration_loader_service.rb +0 -55
  89. data/lib/dsu/services/entry_group_deleter_service.rb +0 -31
  90. data/lib/dsu/services/entry_group_editor_service.rb +0 -96
  91. data/lib/dsu/services/entry_group_hydrator_service.rb +0 -43
  92. data/lib/dsu/services/entry_group_reader_service.rb +0 -36
  93. data/lib/dsu/services/entry_group_writer_service.rb +0 -46
  94. data/lib/dsu/services/entry_hydrator_service.rb +0 -35
  95. data/lib/dsu/services/temp_file_reader_service.rb +0 -31
  96. data/lib/dsu/services/temp_file_writer_service.rb +0 -33
  97. data/lib/dsu/support/colorable.rb +0 -14
  98. data/lib/dsu/support/configurable.rb +0 -15
  99. data/lib/dsu/support/configuration.rb +0 -112
  100. data/lib/dsu/support/entry_group_fileable.rb +0 -44
  101. data/lib/dsu/support/entry_group_loadable.rb +0 -49
  102. data/lib/dsu/support/folder_locations.rb +0 -21
  103. data/lib/dsu/support/say.rb +0 -40
  104. data/lib/dsu/views/edited_entries/shared/errors.rb +0 -39
  105. data/lib/dsu/views/shared/messages.rb +0 -56
@@ -0,0 +1,74 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+
5
+ # rubocop:disable Layout/LineLength
6
+ module Dsu
7
+ module Validators
8
+ class ColorThemeValidator < ActiveModel::Validator
9
+ def validate(record)
10
+ default_theme_colors = record.class::DEFAULT_THEME_COLORS
11
+
12
+ # return unless validate_color_theme_keys!(record, default_theme_colors)
13
+
14
+ default_theme_colors.each_key do |theme_color_key|
15
+ theme_colors_hash = record.public_send(theme_color_key)
16
+
17
+ next unless validate_theme_color_type!(record, theme_color_key, theme_colors_hash)
18
+
19
+ if theme_colors_hash.empty?
20
+ record.errors.add(:base, ":#{theme_color_key} colors Hash is empty")
21
+ next
22
+ end
23
+
24
+ validate_theme_colors!(record, theme_colors_hash)
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def colors
31
+ @colors ||= String.colors
32
+ end
33
+
34
+ def modes
35
+ @modes ||= String.modes
36
+ end
37
+
38
+ def validate_theme_color_type!(record, theme_color_key, theme_colors_hash)
39
+ return true if theme_colors_hash.is_a?(Hash)
40
+
41
+ record.errors.add(:base, ":#{theme_color_key} value is the wrong object type. " \
42
+ "\"Hash\" was expected, but \"#{theme_colors_hash.class}\" was received.")
43
+ false
44
+ end
45
+
46
+ def validate_theme_colors!(record, theme_colors_hash)
47
+ unless colors.include?(theme_colors_hash[:color])
48
+ value = theme_color_value_to_s(theme_colors_hash[:color])
49
+ record.errors.add(:base, ":color key value #{value} in theme color Hash #{theme_colors_hash} is not a valid color. " \
50
+ "One of #{colors.wrap_and_join(wrapper: [':', ''])} was expected, but #{value} was received.")
51
+ end
52
+
53
+ unless theme_colors_hash[:mode].nil? || modes.include?(theme_colors_hash[:mode])
54
+ value = theme_color_value_to_s(theme_colors_hash[:mode])
55
+ record.errors.add(:base, ":mode key value #{value} in theme color Hash #{theme_colors_hash} is not a valid mode value. " \
56
+ "One of #{modes.wrap_and_join(wrapper: [':', ''])} was expected, but #{value} was received.")
57
+ end
58
+
59
+ unless theme_colors_hash[:background].nil? || colors.include?(theme_colors_hash[:background])
60
+ value = theme_color_value_to_s(theme_colors_hash[:background])
61
+ record.errors.add(:base, ":background key value #{value} in theme color Hash #{theme_colors_hash} is not a valid color. " \
62
+ "One of #{colors.wrap_and_join(wrapper: [':', ''])} was expected, but #{value} was received.")
63
+ end
64
+ end
65
+
66
+ def theme_color_value_to_s(theme_color_value)
67
+ return ":#{theme_color_value}" if theme_color_value.is_a?(Symbol)
68
+
69
+ "'#{theme_color_value}'"
70
+ end
71
+ end
72
+ end
73
+ end
74
+ # rubocop:enable Layout/LineLength
@@ -17,8 +17,8 @@ module Dsu
17
17
  end
18
18
 
19
19
  return if validate_entry_types record
20
- return if validate_unique_entry record
21
20
 
21
+ validate_unique_entry record
22
22
  validate_entries record
23
23
  end
24
24
 
@@ -45,9 +45,9 @@ module Dsu
45
45
  return if descriptions.uniq.length == descriptions.length
46
46
 
47
47
  non_unique_descriptions = descriptions.select { |description| descriptions.count(description) > 1 }.uniq
48
- if non_unique_descriptions.any?
49
- record.errors.add(:entries, 'Array contains a duplicate entry: ' \
50
- "#{format_non_unique_descriptions(non_unique_descriptions)}.",
48
+ non_unique_descriptions.each do |non_unique_description|
49
+ record.errors.add(:entries,
50
+ "array contains duplicate entry: \"#{short_description(non_unique_description)}\".",
51
51
  type: Support::FieldErrors::FIELD_DUPLICATE_ERROR)
52
52
  end
53
53
 
@@ -67,10 +67,6 @@ module Dsu
67
67
  end
68
68
  end
69
69
 
70
- def format_non_unique_descriptions(non_unique_descriptions)
71
- non_unique_descriptions.map { |description| "\"#{short_description(description)}\"" }.join(', ')
72
- end
73
-
74
70
  def short_description(description)
75
71
  Models::Entry.short_description(string: description)
76
72
  end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ # https://guides.rubyonrails.org/active_record_validations.html#validates-with
4
+ module Dsu
5
+ module Validators
6
+ class VersionValidator < ActiveModel::Validator
7
+ def validate(record)
8
+ version = record.version
9
+
10
+ if version.nil?
11
+ record.errors.add(:version, 'is nil')
12
+ return
13
+ end
14
+
15
+ unless version.is_a?(Integer)
16
+ record.errors.add(:version, 'is the wrong object type. ' \
17
+ "\"Integer\" was expected, but \"#{version.class}\" was received.")
18
+ nil
19
+ end
20
+
21
+ # TODO: This validation should check the configuration version
22
+ # against the current migration version and they should match.
23
+ # unless version == record.class::VERSION
24
+ # record.errors.add(:version, "\"#{version}\" is not the correct version: \"#{record.class::VERSION}\"")
25
+ # end
26
+ end
27
+ end
28
+ end
29
+ end
data/lib/dsu/version.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dsu
4
- VERSION = '1.2.0'
4
+ VERSION_REGEX = /\A\d+\.\d+\.\d+(\.alpha\.\d+)?\z/
5
+ VERSION = '2.0.0.alpha.1'
5
6
  end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../models/configuration'
4
+ require_relative '../../models/color_theme'
5
+
6
+ module Dsu
7
+ module Views
8
+ module ColorTheme
9
+ class Index
10
+ include Support::ColorThemable
11
+
12
+ def render
13
+ render!
14
+ end
15
+
16
+ private
17
+
18
+ def render!
19
+ presenter = color_theme.presenter
20
+ puts presenter.header
21
+ puts
22
+ render_theme_details
23
+ puts
24
+ puts presenter.footer
25
+ end
26
+
27
+ def render_theme_details
28
+ themes_folder = Models::Configuration.new.themes_folder
29
+ theme_file_names = Dir.glob("#{themes_folder}/*").map { |theme_path| File.basename(theme_path, '.*') }
30
+ theme_file_names << default_theme_name unless theme_file_names.include?(default_theme_name)
31
+ theme_file_names.sort.each_with_index do |theme_file, index|
32
+ color_theme = if theme_file == default_theme_name
33
+ default_theme
34
+ else
35
+ Models::ColorTheme.find(theme_name: theme_file)
36
+ end
37
+ presenter = color_theme.presenter
38
+ puts presenter.detail_with_index(index: index)
39
+ end
40
+ end
41
+
42
+ # When getting the default theme, if it exists on disk, use that; otherwise,
43
+ # use the in-memory default theme.
44
+ def default_theme
45
+ if Models::ColorTheme.exist?(theme_name: default_theme_name)
46
+ Models::ColorTheme.find(theme_name: default_theme_name)
47
+ else
48
+ Models::ColorTheme.default
49
+ end
50
+ end
51
+
52
+ def default_theme_name
53
+ Dsu::Models::ColorTheme::DEFAULT_THEME_NAME
54
+ end
55
+
56
+ def color_theme
57
+ @color_theme ||= Models::ColorTheme.current_or_default
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,106 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../models/color_theme'
4
+ require_relative '../../models/configuration'
5
+ require_relative '../entry_group/show'
6
+ require_relative '../shared/error'
7
+ require_relative '../shared/info'
8
+ require_relative '../shared/success'
9
+ require_relative '../shared/warning'
10
+
11
+ module Dsu
12
+ module Views
13
+ module ColorTheme
14
+ class Show
15
+ include Support::ColorThemable
16
+
17
+ def initialize(theme_name:, options: {})
18
+ @theme_name = theme_name
19
+ @options = options || {}
20
+ end
21
+
22
+ def render
23
+ render!
24
+ end
25
+
26
+ private
27
+
28
+ attr_reader :theme_name, :options
29
+
30
+ def color_theme
31
+ @color_theme ||= Models::ColorTheme.find_or_initialize(theme_name: theme_name)
32
+ end
33
+
34
+ def presenter
35
+ @presenter ||= Dsu::Presenters::ColorThemeShowPresenter.new(color_theme, options: options)
36
+ end
37
+
38
+ def render!
39
+ puts presenter.header
40
+ puts
41
+
42
+ presenter.detail
43
+ puts
44
+
45
+ display_entry_group_example
46
+ puts
47
+
48
+ display_configuration_example
49
+ puts
50
+
51
+ display_messages_example
52
+ puts
53
+
54
+ puts presenter.footer
55
+ end
56
+
57
+ def display_entry_group_example
58
+ puts apply_theme('`dsu list` example', theme_color: color_theme.subheader)
59
+ puts
60
+
61
+ options = custom_options
62
+ entry_group = mock_entry_group(options)
63
+ EntryGroup::Show.new(entry_group: entry_group, options: options).render
64
+ end
65
+
66
+ def display_configuration_example
67
+ puts apply_theme('`dsu config info` example', theme_color: color_theme.subheader)
68
+ puts
69
+
70
+ Views::Configuration::Show.new(config: Models::Configuration.new(options: custom_options)).render
71
+ end
72
+
73
+ def display_messages_example
74
+ options = custom_options
75
+ messages = ['Example 1', 'Example 2', 'Example 3']
76
+
77
+ puts apply_theme('Message examples', theme_color: color_theme.subheader)
78
+ puts
79
+
80
+ Shared::Error.new(messages: messages, header: 'Errors example', options: options).render
81
+ puts
82
+
83
+ Shared::Info.new(messages: messages, header: 'Info example', options: options).render
84
+ puts
85
+
86
+ Shared::Success.new(messages: messages, header: 'Success example', options: options).render
87
+ puts
88
+
89
+ Shared::Warning.new(messages: messages, header: 'Warning example', options: options).render
90
+ end
91
+
92
+ def mock_entry_group(options)
93
+ @mock_entry_group ||= Dsu::Models::EntryGroup.new(time: Time.now, entries: [
94
+ Dsu::Models::Entry.new(description: 'Dsu entry 1', options: options),
95
+ Dsu::Models::Entry.new(description: 'Dsu entry 2', options: options),
96
+ Dsu::Models::Entry.new(description: 'Dsu entry 3', options: options)
97
+ ], options: options)
98
+ end
99
+
100
+ def custom_options
101
+ options.merge(theme_name: color_theme.theme_name, output_stream: $stdout)
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'active_support/core_ext/numeric/time'
5
+ require_relative '../../models/configuration'
6
+ require_relative '../../support/color_themable'
7
+
8
+ module Dsu
9
+ module Views
10
+ module Configuration
11
+ class Show
12
+ include Support::ColorThemable
13
+
14
+ def initialize(config:, options: {})
15
+ raise ArgumentError, 'config is nil' if config.nil?
16
+ raise ArgumentError, 'config is the wrong object type' unless config.is_a?(Models::Configuration)
17
+ raise ArgumentError, 'options is nil' if options.nil?
18
+ raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
19
+
20
+ @config = config
21
+ @options = options || {}
22
+ end
23
+
24
+ def call
25
+ render!
26
+ end
27
+ alias render call
28
+
29
+ private
30
+
31
+ attr_reader :config, :options
32
+
33
+ def render!
34
+ presenter = config.presenter
35
+ puts presenter.configuration_header
36
+ puts presenter.configuration_details
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -1,14 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../../models/configuration'
3
4
  require_relative '../../models/entry_group'
4
- require_relative '../../support/configurable'
5
5
 
6
6
  module Dsu
7
7
  module Views
8
8
  module EntryGroup
9
9
  class Edit
10
- include Support::Configurable
11
-
12
10
  def initialize(entry_group:, options: {})
13
11
  raise ArgumentError, 'entry_group is nil' if entry_group.nil?
14
12
  raise ArgumentError, 'entry_group is the wrong object type' unless entry_group.is_a?(Models::EntryGroup)
@@ -100,7 +98,7 @@ module Dsu
100
98
  end
101
99
 
102
100
  def previous_entry_group?
103
- previous_entry_group.present?
101
+ previous_entry_group.entries.present?
104
102
  end
105
103
 
106
104
  def previous_entry_group
@@ -108,13 +106,13 @@ module Dsu
108
106
  # TODO: Make this configurable or accept an option?
109
107
  @previous_entry_group ||= (1..7).each do |days|
110
108
  t = time.days_ago(days)
111
- return Models::EntryGroup.load(time: t) if Support::EntryGroupFileable.entry_group_file_exists?(time: t)
109
+ return Models::EntryGroup.find(time: t) if Models::EntryGroup.exist?(time: t)
112
110
  end
113
111
  nil
114
112
  end
115
113
 
116
114
  def carry_over_entries_to_today?
117
- configuration[:carry_over_entries_to_today]
115
+ Models::Configuration.new.merge(options).carry_over_entries_to_today?
118
116
  end
119
117
  end
120
118
  end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../models/color_theme'
4
+ require_relative '../../../support/color_themable'
5
+ require_relative '../../../support/time_formatable'
6
+
7
+ module Dsu
8
+ module Views
9
+ module EntryGroup
10
+ module Shared
11
+ class NoEntriesToDisplay
12
+ include Support::ColorThemable
13
+ include Support::TimeFormatable
14
+
15
+ def initialize(times:, options: {})
16
+ raise ArgumentError, 'times must be an Array' unless times.is_a?(Array)
17
+ raise ArgumentError, 'times must contain Time objects' unless times.all?(Time)
18
+ raise ArgumentError, 'options must be a Hash' unless options.is_a?(Hash) || options.nil?
19
+
20
+ @times = times
21
+ @options = options || {}
22
+ end
23
+
24
+ def render
25
+ entry_group_times.sort!
26
+ time_range = "#{formatted_time(time: times.first)} " \
27
+ "through #{formatted_time(time: times.last)}"
28
+ message = "(nothing to display for #{time_range})"
29
+ puts apply_theme(message, theme_color: color_theme.info)
30
+ end
31
+
32
+ private
33
+
34
+ def color_theme
35
+ @color_theme ||= Models::ColorTheme.current_or_default
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -3,16 +3,14 @@
3
3
  require 'time'
4
4
  require 'active_support/core_ext/numeric/time'
5
5
  require_relative '../../models/entry_group'
6
- require_relative '../../support/colorable'
7
- require_relative '../../support/say'
6
+ require_relative '../../support/color_themable'
8
7
  require_relative '../../support/time_formatable'
9
8
 
10
9
  module Dsu
11
10
  module Views
12
11
  module EntryGroup
13
12
  class Show
14
- include Support::Colorable
15
- include Support::Say
13
+ include Support::ColorThemable
16
14
  include Support::TimeFormatable
17
15
 
18
16
  def initialize(entry_group:, options: {})
@@ -35,22 +33,25 @@ module Dsu
35
33
  attr_reader :entry_group, :options
36
34
 
37
35
  def render!
38
- say formatted_time(time: entry_group.time), HIGHLIGHT
39
- say('(no entries available for this day)') and return if entry_group.entries.empty?
36
+ puts presenter.formatted_time
37
+
38
+ entry_group.validate!
39
+ puts presenter.no_entries_available and return if entry_group.entries.empty?
40
40
 
41
41
  entry_group.entries.each_with_index do |entry, index|
42
- prefix = "#{format('%03s', index + 1)}. "
43
- description = colorize_string(string: entry.description, mode: :bold)
44
- entry_info = "#{prefix} #{description}"
45
- unless entry.valid?
46
- entry_info = "#{entry_info} (validation failed: #{entry_errors(entry_group_deleter_service)})"
47
- end
48
- say entry_info
42
+ entry_presenter = entry.presenter
43
+ puts entry_presenter.formatted_description_with_index(index: index)
49
44
  end
45
+ rescue ActiveModel::ValidationError
46
+ puts apply_theme(errors(entry_group), theme_color: presenter.color_theme.error)
47
+ end
48
+
49
+ def errors(model)
50
+ model.errors.full_messages.join(', ')
50
51
  end
51
52
 
52
- def entry_errors(entry)
53
- entry.errors.full_messages.join(', ')
53
+ def presenter
54
+ @presenter ||= entry_group.presenter
54
55
  end
55
56
  end
56
57
  end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'message'
4
+
5
+ module Dsu
6
+ module Views
7
+ module Shared
8
+ class Error < Message
9
+ def initialize(messages:, header: nil, options: {})
10
+ options = { header: header, output_stream: $stderr }.merge(options)
11
+
12
+ super(messages: messages, message_type: :error, options: options)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'message'
4
+
5
+ module Dsu
6
+ module Views
7
+ module Shared
8
+ class Info < Message
9
+ def initialize(messages:, header: nil, options: {})
10
+ options = { header: header, output_stream: $stdout }.merge(options)
11
+
12
+ super(messages: messages, message_type: :info, options: options)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../models/color_theme'
4
+
5
+ module Dsu
6
+ module Views
7
+ module Shared
8
+ class Message
9
+ include Support::ColorThemable
10
+
11
+ MESSAGE_TYPES = %i[error info success warning].freeze
12
+
13
+ def initialize(messages:, message_type:, options: {})
14
+ messages = [messages] unless messages.is_a?(Array)
15
+ messages = messages.select(&:present?)
16
+
17
+ validate_arguments!(messages, message_type, options)
18
+
19
+ @messages = messages
20
+ @message_type = message_type
21
+ @options = options || {}
22
+ @message_color = color_theme.public_send(message_type)
23
+ @header = options[:header]
24
+ @ordered_list = options.fetch(:ordered_list, true)
25
+ end
26
+
27
+ def render
28
+ output_stream.puts to_s
29
+ end
30
+
31
+ def to_s
32
+ return if messages.empty?
33
+
34
+ strings = []
35
+
36
+ strings << apply_theme(header, theme_color: color_theme.header) if header.present?
37
+
38
+ strings << if messages.one?
39
+ apply_theme(messages[0], theme_color: message_color)
40
+ else
41
+ messages.each_with_index.map do |message, index|
42
+ message = "#{index + 1}. #{message}" if ordered_list?
43
+ apply_theme(message, theme_color: message_color)
44
+ end
45
+ end
46
+
47
+ strings.flatten.join("\n")
48
+ end
49
+
50
+ private
51
+
52
+ attr_reader :messages, :message_color, :message_type, :header, :options
53
+
54
+ def color_theme
55
+ @color_theme ||= begin
56
+ theme_name = options.fetch(:theme_name, Models::Configuration.new.theme_name)
57
+ Models::ColorTheme.find(theme_name: theme_name)
58
+ end
59
+ end
60
+
61
+ def ordered_list?
62
+ @ordered_list
63
+ end
64
+
65
+ def output_stream
66
+ @output_stream ||= options.fetch(:output_stream, $stdout)
67
+ end
68
+
69
+ def validate_arguments!(messages, message_type, options)
70
+ raise ArgumentError, 'messages is empty' if messages.empty?
71
+ unless Models::ColorTheme::DEFAULT_THEME_COLORS.key?(message_type)
72
+ raise ArgumentError, 'message_type is not a valid message type'
73
+ end
74
+ raise ArgumentError, 'options is nil' if options.nil?
75
+
76
+ %i[\[\] fetch].each do |method|
77
+ next if options.respond_to?(method)
78
+
79
+ raise ArgumentError, "options does not respond to :#{method}"
80
+ end
81
+ end
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'error'
4
+
5
+ module Dsu
6
+ module Views
7
+ module Shared
8
+ class ModelErrors < Error
9
+ def initialize(model:, options: {})
10
+ raise ArgumentError, 'model is nil' if model.nil?
11
+ raise ArgumentError, "model is the wrong object type: \"#{model}\"" unless model.is_a?(ActiveModel::Model)
12
+
13
+ header = options[:header] || 'The following ERRORS were encountered; changes could not be saved:'
14
+ super(messages: model.errors.full_messages, header: header, options: options)
15
+
16
+ @model = model
17
+ end
18
+
19
+ def render
20
+ return if model.valid?
21
+
22
+ super
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :model
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'message'
4
+
5
+ module Dsu
6
+ module Views
7
+ module Shared
8
+ class Success < Message
9
+ def initialize(messages:, header: nil, options: {})
10
+ options = { header: header, output_stream: $stdout }.merge(options)
11
+
12
+ super(messages: messages, message_type: :success, options: options)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'message'
4
+
5
+ module Dsu
6
+ module Views
7
+ module Shared
8
+ class Warning < Message
9
+ def initialize(messages:, header: nil, options: {})
10
+ options = { header: header, output_stream: $stdout }.merge(options)
11
+
12
+ super(messages: messages, message_type: :warning, options: options)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end