dsu 0.1.0.alpha.4 → 1.0.0

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.
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../shared/messages'
4
+
5
+ module Dsu
6
+ module Views
7
+ module EditedEntries
8
+ module Shared
9
+ class Errors
10
+ def initialize(edited_entries:, options: {})
11
+ raise ArgumentError, 'edited_entries is nil' if edited_entries.nil?
12
+ raise ArgumentError, 'edited_entries is the wrong object type' unless edited_entries.is_a?(Array)
13
+ unless edited_entries.all?(Models::EditedEntry)
14
+ raise ArgumentError, 'edited_entries elements are the wrong object type'
15
+ end
16
+ raise ArgumentError, 'options is nil' if options.nil?
17
+ raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
18
+
19
+ @edited_entries = edited_entries
20
+ @options = options || {}
21
+ @header = options[:header] || 'The following ERRORS were encountered; these changes were not saved:'
22
+ end
23
+
24
+ def render
25
+ return if edited_entries.empty?
26
+ return if edited_entries.all?(&:valid?)
27
+
28
+ messages = edited_entries.map { |edited_entry| edited_entry.errors.full_messages }.flatten
29
+ Views::Shared::Messages.new(messages: messages, message_type: :error, options: { header: header }).render
30
+ end
31
+
32
+ private
33
+
34
+ attr_reader :edited_entries, :header, :options
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -3,8 +3,6 @@
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'
8
6
  require_relative '../../support/time_formatable'
9
7
 
10
8
  module Dsu
@@ -25,44 +23,43 @@ module Dsu
25
23
  @options = options || {}
26
24
  end
27
25
 
28
- def call
29
- # Just in case the entry group is invalid, we'll
30
- # validate it before displaying it.
31
- entry_group.validate!
32
- render_entry_group!
33
- rescue ActiveModel::ValidationError
34
- puts "Error(s) encountered: #{entry_group.errors.full_messages}"
35
- raise
26
+ def render
27
+ puts render_as_string
36
28
  end
37
- alias render call
38
29
 
39
- private
30
+ def render_as_string
31
+ # Just in case the entry group is invalid, we'll validate it before displaying it.
32
+ entry_group.validate!
40
33
 
41
- attr_reader :entry_group, :options
34
+ # TODO: Display entry group entries from the previous DSU date so they can be
35
+ # easily copied over; or, add them to the current entry group entries below as
36
+ # a "# [+|a|add] <entry group from previous DSU entry description>"
37
+ # (e.g. commented out) by default?
42
38
 
43
- def render_entry_group!
44
- say "# Editing DSU Entries for #{formatted_time(time: entry_group.time)}"
45
- say('(no entries available for this day)') and return if entry_group.entries.empty?
39
+ <<~EDIT_VIEW
40
+ # Editing DSU Entries for #{formatted_time(time: entry_group.time)}
41
+ # [ENTRY DESCRIPTION]
46
42
 
47
- say ''
48
- say '# [SHA/COMMAND] [DESCRIPTION]'
43
+ #{entry_group_entry_lines.each(&:strip).join("\n")}
49
44
 
50
- entry_group.entries.each do |entry|
51
- say "#{entry.uuid} #{entry.description}"
52
- end
45
+ # INSTRUCTIONS:
46
+ # ADD a DSU entry: type an ENTRY DESCRIPTION on a new line.
47
+ # EDIT a DSU entry: change the existing ENTRY DESCRIPTION.
48
+ # DELETE a DSU entry: delete the ENTRY DESCRIPTION.
49
+ # NOTE: deleting all of the ENTRY DESCRIPTIONs will delete the entry group file;
50
+ # this is preferable if this is what you want to do :)
51
+ # REORDER a DSU entry: reorder the ENTRY DESCRIPTIONs in order preference.
52
+ #
53
+ # *** When you are done, save and close your editor ***
54
+ EDIT_VIEW
55
+ end
56
+
57
+ private
58
+
59
+ attr_reader :entry_group, :options
53
60
 
54
- say ''
55
- say '# INSTRUCTIONS:'
56
- say '# ADD a DSU entry: use one of the following commands: [+|a|add] ' \
57
- 'followed by the description.'
58
- say '# EDIT a DSU entry: change the description.'
59
- say '# DELETE a DSU entry: delete the entry or replace the sha with one ' \
60
- 'of the following commands: [-|d|delete].'
61
- say '# NOTE: deleting all the entries will delete the entry group file; '
62
- say '# this is preferable if this is what you want to do :)'
63
- say '# REORDER a DSU entry: reorder the DSU entries in order preference.'
64
- say '#'
65
- say '# *** When you are done, save and close your editor ***'
61
+ def entry_group_entry_lines
62
+ entry_group.entries.map(&:description)
66
63
  end
67
64
  end
68
65
  end
@@ -26,13 +26,7 @@ module Dsu
26
26
  end
27
27
 
28
28
  def call
29
- # Just in case the entry group is invalid, we'll
30
- # validate it before displaying it.
31
- entry_group.validate!
32
- render_entry_group!
33
- rescue ActiveModel::ValidationError
34
- puts "Error(s) encountered: #{entry_group.errors.full_messages}"
35
- raise
29
+ render!
36
30
  end
37
31
  alias render call
38
32
 
@@ -40,16 +34,24 @@ module Dsu
40
34
 
41
35
  attr_reader :entry_group, :options
42
36
 
43
- def render_entry_group!
37
+ def render!
44
38
  say formatted_time(time: entry_group.time), HIGHLIGHT
45
39
  say('(no entries available for this day)') and return if entry_group.entries.empty?
46
40
 
47
41
  entry_group.entries.each_with_index do |entry, index|
48
- prefix = "#{format('%03s', index + 1)}. #{entry.uuid}"
42
+ prefix = "#{format('%03s', index + 1)}. "
49
43
  description = colorize_string(string: entry.description, mode: :bold)
50
- say "#{prefix} #{description}"
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
51
49
  end
52
50
  end
51
+
52
+ def entry_errors(entry)
53
+ entry.errors.full_messages.join(', ')
54
+ end
53
55
  end
54
56
  end
55
57
  end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../support/colorable'
4
+ require_relative '../../support/say'
5
+
6
+ module Dsu
7
+ module Views
8
+ module Shared
9
+ class Messages
10
+ include Support::Colorable
11
+ include Support::Say
12
+
13
+ MESSAGE_TYPES = %i[error info success warning].freeze
14
+
15
+ def initialize(messages:, message_type:, options: {})
16
+ messages = [messages] unless messages.is_a?(Array)
17
+
18
+ validate_arguments!(messages, message_type, options)
19
+
20
+ @messages = messages.select(&:present?)
21
+ @message_type = message_type
22
+ # We've inluded Support::Colorable, so simply upcase the message_type
23
+ # and convert it to a symbol; this will equate to the color we want.
24
+ @message_color = self.class.const_get(message_type.to_s.upcase)
25
+ @options = options || {}
26
+ @header = options[:header]
27
+ end
28
+
29
+ def render
30
+ return if messages.empty?
31
+
32
+ say header, message_color if header.present?
33
+
34
+ messages.each_with_index do |message, index|
35
+ say "#{index + 1}. #{message}", message_color
36
+ end
37
+ end
38
+
39
+ private
40
+
41
+ attr_reader :messages, :message_color, :message_type, :header, :options
42
+
43
+ def validate_arguments!(messages, message_type, options)
44
+ raise ArgumentError, 'messages is nil' if messages.nil?
45
+ raise ArgumentError, 'messages is the wrong object type' unless messages.is_a?(Array)
46
+ raise ArgumentError, 'messages elements are the wrong object type' unless messages.all?(String)
47
+ raise ArgumentError, 'message_type is nil' if message_type.nil?
48
+ raise ArgumentError, 'message_type is the wrong object type' unless message_type.is_a?(Symbol)
49
+ raise ArgumentError, 'message_type is not a valid message type' unless MESSAGE_TYPES.include?(message_type)
50
+ raise ArgumentError, 'options is nil' if options.nil?
51
+ raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
data/lib/dsu.rb CHANGED
@@ -5,6 +5,10 @@ require 'active_support/core_ext/object/blank'
5
5
  require 'active_support/core_ext/hash/indifferent_access'
6
6
  require 'active_support/core_ext/numeric/time'
7
7
 
8
+ Dir.glob("#{__dir__}/lib/core/**/*.rb").each do |file|
9
+ require file
10
+ end
11
+
8
12
  Dir.glob("#{__dir__}/dsu/**/*.rb").each do |file|
9
13
  require file
10
14
  end
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: 0.1.0.alpha.4
4
+ version: 1.0.0
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-05-09 00:00:00.000000000 Z
11
+ date: 2023-05-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -153,10 +153,12 @@ files:
153
153
  - lib/dsu/models/entry_group.rb
154
154
  - lib/dsu/services/configuration_loader_service.rb
155
155
  - lib/dsu/services/entry_group_deleter_service.rb
156
+ - lib/dsu/services/entry_group_editor_service.rb
156
157
  - lib/dsu/services/entry_group_hydrator_service.rb
157
158
  - lib/dsu/services/entry_group_reader_service.rb
158
159
  - lib/dsu/services/entry_group_writer_service.rb
159
160
  - lib/dsu/services/entry_hydrator_service.rb
161
+ - lib/dsu/services/stdout_redirector_service.rb
160
162
  - lib/dsu/services/temp_file_reader_service.rb
161
163
  - lib/dsu/services/temp_file_writer_service.rb
162
164
  - lib/dsu/subcommands/config.rb
@@ -164,24 +166,24 @@ files:
164
166
  - lib/dsu/subcommands/list.rb
165
167
  - lib/dsu/support/ask.rb
166
168
  - lib/dsu/support/colorable.rb
167
- - lib/dsu/support/commander/command.rb
168
- - lib/dsu/support/commander/command_help.rb
169
- - lib/dsu/support/commander/subcommand.rb
170
169
  - lib/dsu/support/configuration.rb
170
+ - lib/dsu/support/descriptable.rb
171
171
  - lib/dsu/support/entry_group_fileable.rb
172
172
  - lib/dsu/support/entry_group_loadable.rb
173
173
  - lib/dsu/support/entry_group_viewable.rb
174
174
  - lib/dsu/support/field_errors.rb
175
175
  - lib/dsu/support/folder_locations.rb
176
- - lib/dsu/support/interactive/cli.rb
177
176
  - lib/dsu/support/say.rb
178
177
  - lib/dsu/support/time_formatable.rb
179
178
  - lib/dsu/support/times_sortable.rb
179
+ - lib/dsu/validators/description_validator.rb
180
180
  - lib/dsu/validators/entries_validator.rb
181
181
  - lib/dsu/validators/time_validator.rb
182
182
  - lib/dsu/version.rb
183
+ - lib/dsu/views/edited_entries/shared/errors.rb
183
184
  - lib/dsu/views/entry_group/edit.rb
184
185
  - lib/dsu/views/entry_group/show.rb
186
+ - lib/dsu/views/shared/messages.rb
185
187
  - sig/dsu.rbs
186
188
  homepage: https://github.com/gangelo/dsu
187
189
  licenses:
@@ -202,9 +204,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
202
204
  version: 3.0.1
203
205
  required_rubygems_version: !ruby/object:Gem::Requirement
204
206
  requirements:
205
- - - ">"
207
+ - - ">="
206
208
  - !ruby/object:Gem::Version
207
- version: 1.3.1
209
+ version: '0'
208
210
  requirements: []
209
211
  rubygems_version: 3.3.22
210
212
  signing_key:
@@ -1,130 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'command_help'
4
- require_relative 'subcommand'
5
-
6
- module Dsu
7
- module Support
8
- module Commander
9
- # https://www.toptal.com/ruby/ruby-dsl-metaprogramming-guide
10
- module Command
11
- class << self
12
- def included(base)
13
- base.extend ClassMethods
14
- base.engine.command_namespace to_command_namespace_symbol base.name
15
- base.engine.command_prompt base.engine.command_namespace
16
- binding.pry
17
- base.singleton_class.delegate :command_namespace, :command_namespaces, :commands,
18
- :command_add, :command_subcommand_add, :command_prompt, :command_parent,
19
- :help, to: :engine
20
- end
21
-
22
- private
23
-
24
- def to_command_namespace_symbol(namespace, join_token: '_')
25
- namespace.delete(':').split(/(?=[A-Z])/).join(join_token).downcase
26
- end
27
- end
28
-
29
- module ClassMethods
30
- def command_subcommand_create(command_parent:)
31
- new.tap do |subcommand|
32
- subcommand.extend Subcommand
33
- subcommand.command_parent command_parent
34
- end
35
- end
36
-
37
- def engine
38
- @engine ||= Engine.new(owning_command: self)
39
- end
40
-
41
- class Engine
42
- include CommandHelp
43
-
44
- attr_reader :owning_command
45
-
46
- def initialize(owning_command:)
47
- @owning_command = owning_command
48
- end
49
-
50
- def command_add(command:, desc:, long_desc: nil, options: {}, commands: [])
51
- self.commands[command_namespace] ||= {}
52
- self.commands[command_namespace][command] = {
53
- desc: desc,
54
- long_desc: long_desc,
55
- options: options,
56
- commands: commands,
57
- help: command_help_for(command: command, desc: desc,
58
- long_desc: long_desc, options: options, commands: commands)
59
- }
60
- end
61
-
62
- def command_subcommand_add(subcommand, command_parent: nil)
63
- command_parent ||= @owning_command
64
- subcommand = subcommand.command_subcommand_create command_parent: command_parent
65
- commands[command_namespace] ||= {}
66
- binding.pry
67
- subcommand.command_namespaces.each_with_index do |namespace, index|
68
- next if index.zero?
69
-
70
- target = commands.dig(*subcommand.command_namespaces[1..index])
71
- target ||= commands.dig(*subcommand.command_namespaces[0..index - 1])
72
- target[namespace] ||= {}
73
- end
74
- commands.dig(*subcommand.command_namespaces[0..])[subcommand.command_namespaces.last] = subcommand
75
-
76
- # subcommand.commands.each do |command_namespace, command|
77
- # command.each do |subcommand_command, data|
78
- # commands[self.command_namespace][command_namespace] ||= {}
79
- # commands[self.command_namespace][command_namespace][subcommand_command] = {
80
- # desc: data[:desc],
81
- # long_desc: data[:long_desc],
82
- # options: data[:options],
83
- # commands: data[:commands],
84
- # help: command_help_for(command: subcommand_command, namespaces: subcommand.command_namespaces, desc: data[:desc],
85
- # long_desc: data[:long_desc], options: data[:options], commands: data[:commands])
86
- # }
87
- # end
88
- # end
89
- end
90
-
91
- def command_namespaces(namespaces = [])
92
- command_parent&.command_namespaces(namespaces)
93
-
94
- namespaces << command_namespace
95
- namespaces
96
- end
97
-
98
- def command_namespace(namespace = nil)
99
- return @command_namespace || name if namespace.nil?
100
-
101
- @command_namespace = namespace
102
- end
103
-
104
- def command_prompt(value = nil)
105
- return @command_prompt || name if value.nil?
106
-
107
- @command_prompt = value
108
- end
109
-
110
- def command_parent(parent = nil)
111
- return @command_parent if parent.nil?
112
-
113
- @command_parent = parent
114
- end
115
-
116
- def commands
117
- @commands ||= {}
118
- end
119
-
120
- def help
121
- commands.each do |_command, command_data|
122
- puts "#{command_namespaces.join(' ')} #{command_data[:help]}"
123
- end
124
- end
125
- end
126
- end
127
- end
128
- end
129
- end
130
- end
@@ -1,62 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dsu
4
- module Support
5
- module Commander
6
- module CommandHelp
7
- private
8
-
9
- # rubocop:disable Lint/UnusedMethodArgument
10
- def command_help_for(command:, desc:, namespaces: nil, long_desc: nil, options: {}, commands: [])
11
- namespaces ||= command_namespaces
12
- help =
13
- <<~HELP
14
- #{namespaces&.join(' ')} #{command}#{' [OPTIONS]' if options&.any?} - #{desc}
15
- #{'OPTIONS:' if options&.any?}
16
- #{options_help_for options}
17
- #{'OPTION ALIASES:' if any_option_aliases_for?(options)}
18
- #{options_aliases_help_for options}
19
- #{'---' unless long_desc.blank?}
20
- #{long_desc}
21
- HELP
22
- help.gsub(/\n{2,}/, "\n")
23
- end
24
- # rubocop:enable Lint/UnusedMethodArgument
25
-
26
- def options_help_for(options)
27
- return [] if options.blank?
28
-
29
- options.map do |option, data|
30
- type = option_to_a(data[:type])&.join(' | ')
31
- type = :boolean if type.blank?
32
- "#{option} <#{type}>, default: #{data[:default]}"
33
- end.join("\n")
34
- end
35
-
36
- def options_aliases_help_for(options)
37
- return unless any_option_aliases_for?(options)
38
-
39
- options.filter_map do |option, data|
40
- aliases = option_to_a(data[:aliases])&.join(' | ')
41
- <<~HELP
42
- #{option} aliases: [#{aliases}]
43
- HELP
44
- end.join("\n")
45
- end
46
-
47
- def any_option_aliases_for?(options)
48
- return false if options.blank?
49
-
50
- options.keys.any? { |key| options.dig(key, :aliases).any? }
51
- end
52
-
53
- def option_to_a(option)
54
- return [] if option.blank?
55
- return option if option.is_a? Array
56
-
57
- [option]
58
- end
59
- end
60
- end
61
- end
62
- end
@@ -1,45 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Dsu
4
- module Support
5
- module Commander
6
- # Subcommands should extend this module once they are instantiated
7
- # so that the Subcommand instance has access to all necessary
8
- # class methods for this subcommand to work.
9
- module Subcommand
10
- class << self
11
- def extended(mod)
12
- mod.singleton_class.delegate :command_namespace, :commands,
13
- :command_add, :command_subcommand_add, :command_prompt, :help, to: mod.class
14
- end
15
- end
16
-
17
- # Subcommand-specific instance methods.
18
- #
19
- # Define Subcommand-specific method equivalents of the Command class
20
- # methods needed to make this Subcommand instance unique.
21
-
22
- # def command_namespace(namespace = nil)
23
- # return @command_namespace || name if namespace.nil?
24
-
25
- # @command_namespace = namespace
26
- # end
27
-
28
- # Subcommands can be used by any Command, so the :command_parent needs
29
- # to be unique to this Subcommand instance.
30
- def command_parent(parent = nil)
31
- return @command_prompt if parent.nil?
32
-
33
- @command_prompt = parent
34
- end
35
-
36
- def command_namespaces(namespaces = [])
37
- command_parent&.command_namespaces(namespaces)
38
-
39
- namespaces << command_namespace
40
- namespaces
41
- end
42
- end
43
- end
44
- end
45
- end