dsu 0.1.0.alpha.1

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