dsu 0.1.0.alpha.1

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.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.reek.yml +20 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +192 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +2 -0
  7. data/CODE_OF_CONDUCT.md +84 -0
  8. data/Gemfile +19 -0
  9. data/Gemfile.lock +133 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +128 -0
  12. data/Rakefile +12 -0
  13. data/bin/console +15 -0
  14. data/bin/setup +8 -0
  15. data/exe/dsu +11 -0
  16. data/lib/dsu/cli.rb +178 -0
  17. data/lib/dsu/command_services/add_entry_service.rb +61 -0
  18. data/lib/dsu/models/entry.rb +49 -0
  19. data/lib/dsu/models/entry_group.rb +70 -0
  20. data/lib/dsu/services/configuration_loader_service.rb +34 -0
  21. data/lib/dsu/services/entry_group_deleter_service.rb +31 -0
  22. data/lib/dsu/services/entry_group_hydrator_service.rb +43 -0
  23. data/lib/dsu/services/entry_group_reader_service.rb +36 -0
  24. data/lib/dsu/services/entry_group_writer_service.rb +45 -0
  25. data/lib/dsu/services/entry_hydrator_service.rb +35 -0
  26. data/lib/dsu/subcommands/config.rb +49 -0
  27. data/lib/dsu/support/ask.rb +38 -0
  28. data/lib/dsu/support/colorable.rb +13 -0
  29. data/lib/dsu/support/commander/command.rb +130 -0
  30. data/lib/dsu/support/commander/command_help.rb +62 -0
  31. data/lib/dsu/support/commander/subcommand.rb +45 -0
  32. data/lib/dsu/support/configuration.rb +89 -0
  33. data/lib/dsu/support/entry_group_fileable.rb +41 -0
  34. data/lib/dsu/support/entry_group_loadable.rb +52 -0
  35. data/lib/dsu/support/field_errors.rb +11 -0
  36. data/lib/dsu/support/folder_locations.rb +21 -0
  37. data/lib/dsu/support/interactive/cli.rb +161 -0
  38. data/lib/dsu/support/say.rb +40 -0
  39. data/lib/dsu/support/time_formatable.rb +42 -0
  40. data/lib/dsu/validators/entries_validator.rb +64 -0
  41. data/lib/dsu/validators/time_validator.rb +34 -0
  42. data/lib/dsu/version.rb +5 -0
  43. data/lib/dsu/views/entry_group/show.rb +60 -0
  44. data/lib/dsu.rb +38 -0
  45. data/sig/dsu.rbs +4 -0
  46. metadata +199 -0
data/lib/dsu/cli.rb ADDED
@@ -0,0 +1,178 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler'
4
+ require 'thor'
5
+ require_relative 'command_services/add_entry_service'
6
+ require_relative 'models/entry_group'
7
+ require_relative 'services/configuration_loader_service'
8
+ require_relative 'services/entry_group_hydrator_service'
9
+ require_relative 'services/entry_group_reader_service'
10
+ require_relative 'subcommands/config'
11
+ require_relative 'version'
12
+ require_relative 'views/entry_group/show'
13
+
14
+ module Dsu
15
+ #
16
+ # The `dsu` command.
17
+ #
18
+ class CLI < ::Thor
19
+ class_option :debug, type: :boolean, default: false
20
+
21
+ map %w[--version -v] => :version
22
+ # map %w[--interactive -i] => :interactive
23
+
24
+ default_command :help
25
+
26
+ class << self
27
+ def exit_on_failure?
28
+ false
29
+ end
30
+ end
31
+
32
+ def initialize(*args)
33
+ super
34
+
35
+ @configuration = Services::ConfigurationLoaderService.new.call
36
+ end
37
+
38
+ desc 'add [OPTIONS] DESCRIPTION [LONG-DESCRIPTION]',
39
+ 'Adds a dsu entry for the date associated with the given option.'
40
+ long_desc <<-LONG_DESC
41
+ TBD
42
+ LONG_DESC
43
+ option :date, type: :string, aliases: '-d'
44
+ option :next_day, type: :boolean, aliases: '-n'
45
+ option :previous_day, type: :boolean, aliases: '-p'
46
+ option :today, type: :boolean, aliases: '-t', default: true
47
+
48
+ def add(description, long_description = nil)
49
+ entry = Models::Entry.new(description: description, long_description: long_description)
50
+ time = if options[:date].present?
51
+ Time.parse(options[:date])
52
+ elsif options[:next_day].present?
53
+ 1.day.from_now
54
+ elsif options[:previous_day].present?
55
+ 1.day.ago
56
+ elsif options[:today].present?
57
+ Time.now
58
+ else
59
+ raise 'No date option specified.'
60
+ end
61
+ display_entry_group(time: 1.day.ago(time))
62
+ puts
63
+ CommandServices::AddEntryService.new(entry: entry, time: time).call
64
+ display_entry_group(time: time)
65
+ end
66
+
67
+ desc 'today',
68
+ 'Displays the dsu entries for today.'
69
+ long_desc <<-LONG_DESC
70
+ Displays the dsu entries for today. This command has no options.
71
+ LONG_DESC
72
+ def today
73
+ time = Time.now
74
+ sort_times(times: [1.day.ago(time), time]).each do |time| # rubocop:disable Lint/ShadowingOuterLocalVariable
75
+ display_entry_group(time: time)
76
+ puts
77
+ end
78
+ end
79
+
80
+ desc 'tomorrow',
81
+ 'Displays the dsu entries for tomorrow.'
82
+ long_desc <<-LONG_DESC
83
+ Displays the dsu entries for tomorrow. This command has no options.
84
+ LONG_DESC
85
+ def tomorrow
86
+ time = Time.now
87
+ sort_times(times: [1.day.from_now(time), time]).each do |time| # rubocop:disable Lint/ShadowingOuterLocalVariable
88
+ display_entry_group(time: time)
89
+ puts
90
+ end
91
+ end
92
+
93
+ desc 'yesterday',
94
+ 'Displays the dsu entries for yesterday.'
95
+ long_desc <<-LONG_DESC
96
+ Displays the dsu entries for yesterday. This command has no options.
97
+ LONG_DESC
98
+ def yesterday
99
+ time = Time.now
100
+ sort_times(times: [1.day.ago(time), 2.days.ago(time)]).each do |time| # rubocop:disable Lint/ShadowingOuterLocalVariable
101
+ display_entry_group(time: time)
102
+ puts
103
+ end
104
+ end
105
+
106
+ desc 'date',
107
+ 'Displays the dsu entries for DATE.'
108
+ long_desc <<-LONG_DESC
109
+ Displays the dsu entries for DATE, where DATE is any date string that can be parsed using `Time.parse`. For example: `require 'time'; Time.parse("2023-01-01")`.
110
+ LONG_DESC
111
+ def date(date)
112
+ time = Time.parse(date)
113
+ sort_times(times: [1.day.ago(time), time]).each do |time| # rubocop:disable Lint/ShadowingOuterLocalVariable
114
+ display_entry_group(time: time)
115
+ puts
116
+ end
117
+ end
118
+
119
+ # TODO: Implement this.
120
+ # desc 'interactive', 'Opens a dsu interactive session'
121
+ # long_desc ''
122
+ # option :next_day, type: :boolean, aliases: '-n'
123
+ # option :previous_day, type: :boolean, aliases: '-p'
124
+ # option :today, type: :boolean, aliases: '-t'
125
+
126
+ # # https://stackoverflow.com/questions/4604905/interactive-prompt-with-thor
127
+ # def interactive
128
+ # exit_commands = %w[x q exit quit]
129
+ # display_interactive_help
130
+ # loop do
131
+ # command = ask('dsu > ')
132
+ # display_interactive_help if command == 'h'
133
+ # break if exit_commands.include? command
134
+ # end
135
+ # say 'Done.'
136
+ # end
137
+
138
+ desc 'config SUBCOMMAND', 'Manage configuration file for this gem'
139
+ subcommand :config, Subcommands::Config
140
+
141
+ desc '--version, -v', 'Displays this gem version'
142
+ def version
143
+ say VERSION
144
+ end
145
+
146
+ private
147
+
148
+ attr_reader :configuration
149
+
150
+ def display_interactive_help
151
+ say 'Interactive Mode Commands:'
152
+ say '---'
153
+ say '[h]: show this help screen'
154
+ say '[n]: next day'
155
+ say '[p]: previous day'
156
+ say '[t]: today'
157
+ say '[x|q|exit|quit]: Exit interactive mode'
158
+ end
159
+
160
+ def display_entry_group(time:)
161
+ entry_group = if Models::EntryGroup.exists?(time: time)
162
+ entry_group_json = Services::EntryGroupReaderService.new(time: time).call
163
+ Services::EntryGroupHydratorService.new(entry_group_json: entry_group_json).call
164
+ else
165
+ Models::EntryGroup.new(time: time)
166
+ end
167
+ Views::EntryGroup::Show.new(entry_group: entry_group).display
168
+ end
169
+
170
+ def sort_times(times:)
171
+ if configuration[:entries_display_order] == 'asc'
172
+ times.sort # sort ascending
173
+ elsif configuration[:entries_display_order] == 'desc'
174
+ times.sort.reverse # sort descending
175
+ end
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../services/entry_group_writer_service'
4
+ require_relative '../models/entry'
5
+ require_relative '../support/entry_group_loadable'
6
+ require_relative '../support/folder_locations'
7
+
8
+ module Dsu
9
+ module CommandServices
10
+ # This class adds (does NOT update) an entry to an entry group.
11
+ class AddEntryService
12
+ include Dsu::Support::EntryGroupLoadable
13
+ include Dsu::Support::FolderLocations
14
+
15
+ attr_reader :entry, :entry_group, :time
16
+
17
+ # :entry is an Entry object
18
+ # :time is a Time object; the time of the entry group.
19
+ def initialize(entry:, time:)
20
+ raise ArgumentError, 'entry is nil' if entry.nil?
21
+ raise ArgumentError, 'entry is the wrong object type' unless entry.is_a?(Dsu::Models::Entry)
22
+ raise ArgumentError, 'time is nil' if time.nil?
23
+ raise ArgumentError, 'time is the wrong object type' unless time.is_a?(Time)
24
+
25
+ @entry = entry
26
+ @time = time
27
+ @entry_group = Dsu::Models::EntryGroup.load(time: time)
28
+ end
29
+
30
+ def call
31
+ entry.validate!
32
+ save_entry_group!
33
+ entry.uuid
34
+ rescue ActiveModel::ValidationError
35
+ puts "Error(s) encountered: #{entry.errors.full_messages}"
36
+ raise
37
+ end
38
+
39
+ private
40
+
41
+ attr_writer :entry, :entry_group, :time
42
+
43
+ def entry_exists?
44
+ @entry_exists ||= entry_group.entries.include? entry.uuid
45
+ end
46
+
47
+ def entry_group_hash
48
+ @entry_group_hash ||= entry_group_hash_for time: time
49
+ end
50
+
51
+ def save_entry_group!
52
+ raise "Entry #{entry.uuid} already exists in entry group #{time}" if entry_exists?
53
+
54
+ entry_group.entries << entry
55
+ entry_group.validate!
56
+
57
+ Dsu::Services::EntryGroupWriterService.new(entry_group: entry_group).call
58
+ end
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'deco_lite'
4
+ require 'securerandom'
5
+
6
+ module Dsu
7
+ module Models
8
+ class Entry < DecoLite::Model
9
+ validates :uuid, presence: true, format: {
10
+ with: /\A[0-9a-f]{8}\z/i,
11
+ message: 'is the wrong format. ' \
12
+ '0-9, a-f, and 8 characters were expected.' \
13
+ }
14
+ validates :description, presence: true, length: { minimum: 2, maximum: 80 }
15
+ validates :long_description, length: { minimum: 2, maximum: 256 }, allow_nil: true
16
+
17
+ def initialize(description:, uuid: nil, long_description: nil)
18
+ raise ArgumentError, 'description is nil' if description.nil?
19
+ raise ArgumentError, 'description is the wrong object type' unless description.is_a?(String)
20
+ raise ArgumentError, 'uuid is the wrong object type' unless uuid.is_a?(String) || uuid.nil?
21
+ raise ArgumentError, 'long_description is the wrong object type' unless long_description.is_a?(String) || long_description.nil?
22
+
23
+ uuid ||= SecureRandom.uuid[0..7]
24
+
25
+ super(hash: {
26
+ uuid: uuid,
27
+ description: description,
28
+ long_description: long_description
29
+ })
30
+ end
31
+
32
+ def required_fields
33
+ %i[uuid description]
34
+ end
35
+
36
+ def long_description?
37
+ long_description.present?
38
+ end
39
+
40
+ def ==(other)
41
+ return false unless other.is_a?(Entry)
42
+
43
+ uuid == other.uuid &&
44
+ description == other.description &&
45
+ long_description == other.long_description
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'deco_lite'
4
+ require_relative '../support/entry_group_loadable'
5
+ require_relative '../services/entry_group_reader_service'
6
+ require_relative '../validators/entries_validator'
7
+ require_relative '../validators/time_validator'
8
+
9
+ module Dsu
10
+ module Models
11
+ class EntryGroup < DecoLite::Model
12
+ extend Support::EntryGroupLoadable
13
+
14
+ validates_with Validators::EntriesValidator, fields: [:entries]
15
+ validates_with Validators::TimeValidator, fields: [:time]
16
+
17
+ def initialize(time: nil, entries: [])
18
+ raise ArgumentError, 'time is the wrong object type' unless time.is_a?(Time) || time.nil?
19
+ raise ArgumentError, 'entries is the wrong object type' unless entries.is_a?(Array) || entries.nil?
20
+
21
+ time ||= Time.now
22
+ time = time.localtime if time.utc?
23
+
24
+ entries ||= []
25
+
26
+ super(hash: {
27
+ time: time,
28
+ entries: entries
29
+ })
30
+ end
31
+
32
+ class << self
33
+ def exists?(time:)
34
+ Dsu::Services::EntryGroupReaderService.entry_group_file_exists?(time: time)
35
+ end
36
+
37
+ def load(time: nil)
38
+ new(**hydrated_entry_group_hash_for(time: time))
39
+ end
40
+
41
+ # This function returns a hash whose :time and :entries
42
+ # key values are hydrated with instantiated Time and Entry
43
+ # objects.
44
+ def hydrated_entry_group_hash_for(time:)
45
+ entry_group_hash = entry_group_hash_for(time: time)
46
+ hydrate_entry_group_hash(entry_group_hash: entry_group_hash, time: time)
47
+ end
48
+ end
49
+
50
+ def required_fields
51
+ %i[time entries]
52
+ end
53
+
54
+ def to_h
55
+ super.tap do |hash|
56
+ hash[:entries] = hash[:entries].dup
57
+ hash[:entries].each_with_index do |entry, index|
58
+ hash[:entries][index] = entry.to_h
59
+ end
60
+ end
61
+ end
62
+
63
+ def to_h_localized
64
+ to_h.tap do |hash|
65
+ hash[:time] = hash[:time].localtime
66
+ end
67
+ end
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'erb'
4
+ require 'yaml'
5
+ require_relative '../support/configuration'
6
+
7
+ module Dsu
8
+ module Services
9
+ class ConfigurationLoaderService
10
+ include Dsu::Support::Configuration
11
+
12
+ attr_reader :default_options
13
+
14
+ def initialize(default_options: Dsu::Support::Configuration::DEFAULT_DSU_OPTIONS)
15
+ @default_options = default_options
16
+ end
17
+
18
+ def call
19
+ load_config.merge(default_options || {}).presence&.with_indifferent_access || raise('No configuration options found')
20
+ end
21
+
22
+ private
23
+
24
+ attr_writer :default_options
25
+
26
+ def load_config
27
+ return {} unless config_file?
28
+
29
+ yaml_options = File.read(config_file)
30
+ YAML.safe_load ERB.new(yaml_options).result
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../support/entry_group_fileable'
4
+
5
+ # This class is responsible for deleting an entry group file.
6
+ module Dsu
7
+ module Services
8
+ class EntryGroupDeleterService
9
+ include Dsu::Support::EntryGroupFileable
10
+
11
+ def initialize(time:, options: {})
12
+ @time = time
13
+ @options = options || {}
14
+ end
15
+
16
+ def call
17
+ delete_entry_group_file!
18
+ end
19
+
20
+ private
21
+
22
+ attr_reader :time, :options
23
+
24
+ def delete_entry_group_file!
25
+ return unless entry_group_file_exists?
26
+
27
+ File.delete(entry_group_file_path)
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'entry_hydrator_service'
4
+
5
+ module Dsu
6
+ module Services
7
+ class EntryGroupHydratorService
8
+ def initialize(entry_group_json:, options: {})
9
+ raise ArgumentError, 'entry_group_json is nil' if entry_group_json.nil?
10
+ raise ArgumentError, 'entry_group_json is the wrong object type' unless entry_group_json.is_a?(String)
11
+ raise ArgumentError, 'options is nil' if options.nil?
12
+ raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
13
+
14
+ @entry_group_json = entry_group_json
15
+ @options = options || {}
16
+ end
17
+
18
+ def call
19
+ entry_group_hash = to_h
20
+ Dsu::Models::EntryGroup.new(**entry_group_hash)
21
+ end
22
+
23
+ class << self
24
+ # Returns a Hash with :time and :entries values hydrated
25
+ # (i.e. Time and Entry objects respectively).
26
+ def to_h(entry_group_json:, options: {})
27
+ JSON.parse(entry_group_json, symbolize_names: true).tap do |hash|
28
+ hash[:time] = Time.parse(hash[:time])
29
+ hash[:entries] = EntryHydratorService.hydrate(entries_array: hash[:entries], options: options)
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :entry_group_json, :options
37
+
38
+ def to_h
39
+ self.class.to_h(entry_group_json: entry_group_json, options: options)
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../support/entry_group_fileable'
4
+
5
+ module Dsu
6
+ module Services
7
+ class EntryGroupReaderService
8
+ include Dsu::Support::EntryGroupFileable
9
+
10
+ def initialize(time:, options: {})
11
+ @time = time
12
+ @options = options || {}
13
+ end
14
+
15
+ def call
16
+ read_entry_group_file
17
+ end
18
+
19
+ class << self
20
+ def entry_group_file_exists?(time:, options: {})
21
+ new(time: time, options: options).send(:entry_group_file_exists?)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ attr_reader :time, :options
28
+
29
+ def read_entry_group_file
30
+ return {} unless entry_group_file_exists?
31
+
32
+ File.read(entry_group_file_path)
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+ require 'active_support/core_ext/module/delegation'
5
+ require_relative '../models/entry_group'
6
+ require_relative '../support/entry_group_fileable'
7
+
8
+ module Dsu
9
+ module Services
10
+ class EntryGroupWriterService
11
+ include Dsu::Support::EntryGroupFileable
12
+
13
+ delegate :time, to: :entry_group
14
+
15
+ def initialize(entry_group:, options: {})
16
+ raise ArgumentError, 'entry_group is nil' if entry_group.nil?
17
+ raise ArgumentError, 'entry_group is the wrong object type' unless entry_group.is_a?(Dsu::Models::EntryGroup)
18
+ raise ArgumentError, 'options is nil' if options.nil?
19
+ raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
20
+
21
+ @entry_group = entry_group
22
+ @options = options || {}
23
+ end
24
+
25
+ def call
26
+ entry_group.validate!
27
+ create_entry_group_path_if!
28
+ write_entry_group_to_file!
29
+ rescue ActiveModel::ValidationError
30
+ puts "Error(s) encountered: #{entry_group.errors.full_messages}"
31
+ raise
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :entry_group, :options
37
+
38
+ def write_entry_group_to_file!
39
+ create_entry_group_path_if!
40
+ File.write(entry_group_file_path, JSON.pretty_generate(entry_group.to_h))
41
+ puts "Wrote group entry file: #{entry_group_file_path}" if ENV['ENV_DEV']
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../models/entry'
4
+
5
+ module Dsu
6
+ module Services
7
+ class EntryHydratorService
8
+ def initialize(entry_hash:, options: {})
9
+ raise ArgumentError, 'entry_hash is nil' if entry_hash.nil?
10
+ raise ArgumentError, 'entry_hash is the wrong object type' unless entry_hash.is_a?(Hash)
11
+ raise ArgumentError, 'options is nil' if options.nil?
12
+ raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
13
+
14
+ @entry_hash = entry_hash
15
+ @options = options || {}
16
+ end
17
+
18
+ def call
19
+ Dsu::Models::Entry.new(**entry_hash)
20
+ end
21
+
22
+ class << self
23
+ def hydrate(entries_array:, options: {})
24
+ entries_array.map do |entry_hash|
25
+ new(entry_hash: entry_hash, options: options).call
26
+ end
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :entry_hash, :options
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+ require_relative '../support/configuration'
5
+
6
+ module Dsu
7
+ module Subcommands
8
+ class Config < ::Thor
9
+ include Dsu::Support::Configuration
10
+
11
+ default_command :help
12
+
13
+ class << self
14
+ def exit_on_failure?
15
+ false
16
+ end
17
+ end
18
+
19
+ desc 'info', 'Displays information about this gem configuration'
20
+ long_desc <<-LONG_DESC
21
+ NAME
22
+ \x5
23
+ `dsu config info` -- Displays information about this gem configuration.
24
+
25
+ SYNOPSIS
26
+ \x5
27
+ dsu config info
28
+ LONG_DESC
29
+ def info
30
+ print_config_file
31
+ end
32
+
33
+ desc 'init', 'Creates and initializes a .dsu file in your home folder'
34
+ long_desc <<-LONG_DESC
35
+ NAME
36
+ \x5
37
+ `dsu config init` -- will create and initialize a .dsu file
38
+ in the "#{Dsu::Support::FolderLocations.root_folder}" folder.
39
+
40
+ SYNOPSIS
41
+ \x5
42
+ dsu config init
43
+ LONG_DESC
44
+ def init
45
+ create_config_file!
46
+ end
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'thor'
4
+
5
+ module Dsu
6
+ module Support
7
+ module Ask
8
+ ASK_YES = %w[y yes].freeze
9
+ # ASK_NO = %w[n no].freeze
10
+ # ASK_CANCEL = %w[c cancel].freeze
11
+ # ASK_YES_NO_CANCEL = ASK_YES.concat(ASK_NO).concat(ASK_CANCEL).freeze
12
+
13
+ def ask(prompt)
14
+ options = {}
15
+ Thor::LineEditor.readline(prompt, options)
16
+ end
17
+
18
+ def yes?(prompt, color = nil)
19
+ Thor::Base.shell.new.yes?(prompt, color)
20
+ end
21
+
22
+ # def no?(prompt)
23
+ # ask_with(prompt: prompt, values: ASK_NO)
24
+ # end
25
+
26
+ # def yes_no_cancel(prompt)
27
+ # ask_with(prompt: prompt, values: ASK_YES_NO_CANCEL)
28
+ # end
29
+
30
+ # private
31
+
32
+ # def ask_with(prompt:, values:)
33
+ # p "#{prompt}"
34
+ # values.include? STDIN.gets.chomp
35
+ # end
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsu
4
+ module Support
5
+ module Colorable
6
+ ABORTED = :red
7
+ ERROR = :red
8
+ HIGHLIGHT = :cyan
9
+ SUCCESS = :green
10
+ WARNING = :yellow
11
+ end
12
+ end
13
+ end