dsu 0.1.0.alpha.5 → 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.
- checksums.yaml +4 -4
- data/.rubocop.yml +1 -1
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +1 -1
- data/README.md +27 -40
- data/lib/dsu/base_cli.rb +0 -3
- data/lib/dsu/cli.rb +42 -49
- data/lib/dsu/command_services/add_entry_service.rb +21 -21
- data/lib/dsu/models/entry.rb +32 -20
- data/lib/dsu/models/entry_group.rb +43 -98
- data/lib/dsu/services/configuration_loader_service.rb +1 -0
- data/lib/dsu/services/entry_group_editor_service.rb +35 -84
- data/lib/dsu/services/stdout_redirector_service.rb +27 -0
- data/lib/dsu/support/colorable.rb +1 -0
- data/lib/dsu/support/entry_group_loadable.rb +10 -12
- data/lib/dsu/validators/description_validator.rb +38 -0
- data/lib/dsu/validators/entries_validator.rb +43 -32
- data/lib/dsu/validators/time_validator.rb +11 -20
- data/lib/dsu/version.rb +1 -1
- data/lib/dsu/views/edited_entries/shared/errors.rb +39 -0
- data/lib/dsu/views/entry_group/edit.rb +30 -35
- data/lib/dsu/views/entry_group/show.rb +10 -4
- data/lib/dsu/views/shared/messages.rb +56 -0
- data/lib/dsu.rb +4 -0
- metadata +8 -8
- data/lib/dsu/support/commander/command.rb +0 -130
- data/lib/dsu/support/commander/command_help.rb +0 -62
- data/lib/dsu/support/commander/subcommand.rb +0 -45
- data/lib/dsu/support/interactive/cli.rb +0 -161
@@ -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,46 +23,43 @@ module Dsu
|
|
25
23
|
@options = options || {}
|
26
24
|
end
|
27
25
|
|
28
|
-
def
|
29
|
-
|
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
|
-
|
39
|
-
private
|
40
29
|
|
41
|
-
|
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!
|
42
33
|
|
43
|
-
def render_entry_group!
|
44
|
-
say "# Editing DSU Entries for #{formatted_time(time: entry_group.time)}"
|
45
34
|
# TODO: Display entry group entries from the previous DSU date so they can be
|
46
35
|
# easily copied over; or, add them to the current entry group entries below as
|
47
|
-
# a "# [+|a|add] <entry group from previous DSU entry description>"
|
48
|
-
# out) by default?
|
49
|
-
|
50
|
-
|
36
|
+
# a "# [+|a|add] <entry group from previous DSU entry description>"
|
37
|
+
# (e.g. commented out) by default?
|
38
|
+
|
39
|
+
<<~EDIT_VIEW
|
40
|
+
# Editing DSU Entries for #{formatted_time(time: entry_group.time)}
|
41
|
+
# [ENTRY DESCRIPTION]
|
51
42
|
|
52
|
-
|
53
|
-
|
54
|
-
|
43
|
+
#{entry_group_entry_lines.each(&:strip).join("\n")}
|
44
|
+
|
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
|
55
60
|
|
56
|
-
|
57
|
-
|
58
|
-
say '# ADD a DSU entry: use one of the following commands: [+|a|add] ' \
|
59
|
-
'followed by the description.'
|
60
|
-
say '# EDIT a DSU entry: change the description.'
|
61
|
-
say '# DELETE a DSU entry: delete the entry or replace the sha with one ' \
|
62
|
-
'of the following commands: [-|d|delete].'
|
63
|
-
say '# NOTE: deleting all the entries will delete the entry group file; '
|
64
|
-
say '# this is preferable if this is what you want to do :)'
|
65
|
-
say '# REORDER a DSU entry: reorder the DSU entries in order preference.'
|
66
|
-
say '#'
|
67
|
-
say '# *** When you are done, save and close your editor ***'
|
61
|
+
def entry_group_entry_lines
|
62
|
+
entry_group.entries.map(&:description)
|
68
63
|
end
|
69
64
|
end
|
70
65
|
end
|
@@ -26,7 +26,7 @@ module Dsu
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def call
|
29
|
-
|
29
|
+
render!
|
30
30
|
end
|
31
31
|
alias render call
|
32
32
|
|
@@ -34,18 +34,24 @@ module Dsu
|
|
34
34
|
|
35
35
|
attr_reader :entry_group, :options
|
36
36
|
|
37
|
-
def
|
37
|
+
def render!
|
38
38
|
say formatted_time(time: entry_group.time), HIGHLIGHT
|
39
39
|
say('(no entries available for this day)') 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)}.
|
42
|
+
prefix = "#{format('%03s', index + 1)}. "
|
43
43
|
description = colorize_string(string: entry.description, mode: :bold)
|
44
44
|
entry_info = "#{prefix} #{description}"
|
45
|
-
|
45
|
+
unless entry.valid?
|
46
|
+
entry_info = "#{entry_info} (validation failed: #{entry_errors(entry_group_deleter_service)})"
|
47
|
+
end
|
46
48
|
say entry_info
|
47
49
|
end
|
48
50
|
end
|
51
|
+
|
52
|
+
def entry_errors(entry)
|
53
|
+
entry.errors.full_messages.join(', ')
|
54
|
+
end
|
49
55
|
end
|
50
56
|
end
|
51
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:
|
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-
|
11
|
+
date: 2023-05-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -158,6 +158,7 @@ files:
|
|
158
158
|
- lib/dsu/services/entry_group_reader_service.rb
|
159
159
|
- lib/dsu/services/entry_group_writer_service.rb
|
160
160
|
- lib/dsu/services/entry_hydrator_service.rb
|
161
|
+
- lib/dsu/services/stdout_redirector_service.rb
|
161
162
|
- lib/dsu/services/temp_file_reader_service.rb
|
162
163
|
- lib/dsu/services/temp_file_writer_service.rb
|
163
164
|
- lib/dsu/subcommands/config.rb
|
@@ -165,9 +166,6 @@ files:
|
|
165
166
|
- lib/dsu/subcommands/list.rb
|
166
167
|
- lib/dsu/support/ask.rb
|
167
168
|
- lib/dsu/support/colorable.rb
|
168
|
-
- lib/dsu/support/commander/command.rb
|
169
|
-
- lib/dsu/support/commander/command_help.rb
|
170
|
-
- lib/dsu/support/commander/subcommand.rb
|
171
169
|
- lib/dsu/support/configuration.rb
|
172
170
|
- lib/dsu/support/descriptable.rb
|
173
171
|
- lib/dsu/support/entry_group_fileable.rb
|
@@ -175,15 +173,17 @@ files:
|
|
175
173
|
- lib/dsu/support/entry_group_viewable.rb
|
176
174
|
- lib/dsu/support/field_errors.rb
|
177
175
|
- lib/dsu/support/folder_locations.rb
|
178
|
-
- lib/dsu/support/interactive/cli.rb
|
179
176
|
- lib/dsu/support/say.rb
|
180
177
|
- lib/dsu/support/time_formatable.rb
|
181
178
|
- lib/dsu/support/times_sortable.rb
|
179
|
+
- lib/dsu/validators/description_validator.rb
|
182
180
|
- lib/dsu/validators/entries_validator.rb
|
183
181
|
- lib/dsu/validators/time_validator.rb
|
184
182
|
- lib/dsu/version.rb
|
183
|
+
- lib/dsu/views/edited_entries/shared/errors.rb
|
185
184
|
- lib/dsu/views/entry_group/edit.rb
|
186
185
|
- lib/dsu/views/entry_group/show.rb
|
186
|
+
- lib/dsu/views/shared/messages.rb
|
187
187
|
- sig/dsu.rbs
|
188
188
|
homepage: https://github.com/gangelo/dsu
|
189
189
|
licenses:
|
@@ -204,9 +204,9 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
204
204
|
version: 3.0.1
|
205
205
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
206
206
|
requirements:
|
207
|
-
- - "
|
207
|
+
- - ">="
|
208
208
|
- !ruby/object:Gem::Version
|
209
|
-
version:
|
209
|
+
version: '0'
|
210
210
|
requirements: []
|
211
211
|
rubygems_version: 3.3.22
|
212
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
|
@@ -1,161 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../ask'
|
4
|
-
require_relative '../colorable'
|
5
|
-
require_relative '../say'
|
6
|
-
|
7
|
-
module Dsu
|
8
|
-
module Interactive
|
9
|
-
class Cli
|
10
|
-
include Support::Colorable
|
11
|
-
include Support::Ask
|
12
|
-
include Support::Say
|
13
|
-
|
14
|
-
BACK_COMMANDS = %w[b].freeze
|
15
|
-
EXIT_COMMANDS = %w[x].freeze
|
16
|
-
HELP_COMMANDS = %w[?].freeze
|
17
|
-
PROMPT_TOKEN = '>'
|
18
|
-
|
19
|
-
attr_reader :name, :parent, :prompt
|
20
|
-
|
21
|
-
def initialize(name:, parent: nil, **options)
|
22
|
-
@name = name
|
23
|
-
@parent = parent
|
24
|
-
@prompt = options[:prompt]
|
25
|
-
end
|
26
|
-
|
27
|
-
# Starts our interactive loop.
|
28
|
-
def start
|
29
|
-
help
|
30
|
-
process_commands
|
31
|
-
end
|
32
|
-
|
33
|
-
def process(command:)
|
34
|
-
if command.cancelled?
|
35
|
-
nil
|
36
|
-
elsif help?(command.command)
|
37
|
-
help
|
38
|
-
elsif back_or_exit?(command.command)
|
39
|
-
parent&.help
|
40
|
-
else
|
41
|
-
unrecognized_command command.command
|
42
|
-
end
|
43
|
-
end
|
44
|
-
|
45
|
-
# Dispays the full help; header and body.
|
46
|
-
def help
|
47
|
-
help_header
|
48
|
-
help_body
|
49
|
-
end
|
50
|
-
|
51
|
-
private
|
52
|
-
|
53
|
-
# This is our interaction loop. Commands that are NOT help or
|
54
|
-
# back or exit commands are yielded to the subclass to execute. Help
|
55
|
-
# commands simply display help; back (or exit) commands transfer control
|
56
|
-
# back to the parent cli (if parent? is true) or exits the current
|
57
|
-
# cli (if parent? is false) respectfully.
|
58
|
-
def process_commands
|
59
|
-
loop do
|
60
|
-
command = wrap_command(ask)
|
61
|
-
process(command: command)
|
62
|
-
next if command.cancelled?
|
63
|
-
break if back_or_exit?(command.command)
|
64
|
-
end
|
65
|
-
say 'Done.', ABORTED unless parent?
|
66
|
-
end
|
67
|
-
|
68
|
-
def wrap_command(command)
|
69
|
-
Struct.new(:command, :args, :cancelled, keyword_init: true) do
|
70
|
-
def cancelled?
|
71
|
-
cancelled
|
72
|
-
end
|
73
|
-
|
74
|
-
def cancelled!
|
75
|
-
self[:cancelled] = true
|
76
|
-
end
|
77
|
-
end.new(
|
78
|
-
command: command.split[0],
|
79
|
-
args: command.split[1..],
|
80
|
-
cancelled: false
|
81
|
-
)
|
82
|
-
end
|
83
|
-
|
84
|
-
# This is the full prompt that needs to be displayed that includes
|
85
|
-
# all parent prompts, right down to the current prompt.
|
86
|
-
def full_prompt
|
87
|
-
prompts = full_prompt_build prompts: []
|
88
|
-
prompt_token = "#{PROMPT_TOKEN} "
|
89
|
-
"#{prompts.join prompt_token}#{prompt_token}"
|
90
|
-
end
|
91
|
-
|
92
|
-
def parent?
|
93
|
-
!parent.nil?
|
94
|
-
end
|
95
|
-
|
96
|
-
def back?(command)
|
97
|
-
back_commands.include? command
|
98
|
-
end
|
99
|
-
|
100
|
-
def back_commands
|
101
|
-
@back_commands ||= BACK_COMMANDS
|
102
|
-
end
|
103
|
-
|
104
|
-
def exit?(command)
|
105
|
-
exit_commands.include? command
|
106
|
-
end
|
107
|
-
|
108
|
-
def exit_commands
|
109
|
-
@exit_commands ||= EXIT_COMMANDS
|
110
|
-
end
|
111
|
-
|
112
|
-
def back_or_exit?(command)
|
113
|
-
(back_commands + exit_commands).include? command
|
114
|
-
end
|
115
|
-
|
116
|
-
def help?(command)
|
117
|
-
help_commands.include? command
|
118
|
-
end
|
119
|
-
|
120
|
-
# Returns what are considered to be commands associated with
|
121
|
-
# displaying help.
|
122
|
-
def help_commands
|
123
|
-
@help_commands ||= HELP_COMMANDS
|
124
|
-
end
|
125
|
-
|
126
|
-
# Displays the help header; override this if you want to customize
|
127
|
-
# your own help header in your subclass.
|
128
|
-
def help_header
|
129
|
-
say "#{name} Help", HIGHLIGHT
|
130
|
-
say '---', HIGHLIGHT
|
131
|
-
end
|
132
|
-
|
133
|
-
# Override this in your subclass and call super AFTER you've
|
134
|
-
# displayed your subclass' help body.
|
135
|
-
def help_body
|
136
|
-
say "[#{HELP_COMMANDS.join(' | ')}] Display help", HIGHLIGHT
|
137
|
-
say "[#{BACK_COMMANDS.join(' | ')}] Go back", HIGHLIGHT if parent?
|
138
|
-
say "[#{EXIT_COMMANDS.join(' | ')}] Exit", HIGHLIGHT unless parent?
|
139
|
-
end
|
140
|
-
|
141
|
-
# This simply outputs our prompt and accepts user input.
|
142
|
-
def ask
|
143
|
-
super full_prompt
|
144
|
-
end
|
145
|
-
|
146
|
-
def unrecognized_command(command)
|
147
|
-
say "Unrecognized command (\"#{command}\"). Try again.", ERROR
|
148
|
-
end
|
149
|
-
|
150
|
-
# Builds the full prompt to be used which amounts to:
|
151
|
-
# <parent cli prompt> PROMPT_TOKEN <child cli 1 prompt>
|
152
|
-
# PROMPT_TOKEN <child 2 cli prompt> ...
|
153
|
-
def full_prompt_build(prompts:)
|
154
|
-
parent.send(:full_prompt_build, prompts: prompts) if parent?
|
155
|
-
|
156
|
-
prompts << prompt
|
157
|
-
prompts.flatten
|
158
|
-
end
|
159
|
-
end
|
160
|
-
end
|
161
|
-
end
|