dsu 2.4.3 → 3.0.0.alpha.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 +12 -0
- data/CHANGELOG.md +181 -204
- data/Gemfile.lock +13 -13
- data/README.md +7 -8
- data/Rakefile +6 -0
- data/current_project.bak +4 -0
- data/lib/dsu/cli.rb +24 -6
- data/lib/dsu/crud/json_file.rb +3 -0
- data/lib/dsu/migration/version.rb +1 -1
- data/lib/dsu/models/color_theme.rb +7 -58
- data/lib/dsu/models/configuration.rb +18 -3
- data/lib/dsu/models/entry_group.rb +0 -7
- data/lib/dsu/models/migration_version.rb +0 -1
- data/lib/dsu/models/project.rb +295 -0
- data/lib/dsu/presenters/base_presenter_ex.rb +1 -12
- data/lib/dsu/presenters/export/all_presenter.rb +14 -19
- data/lib/dsu/presenters/export/dates_presenter.rb +17 -20
- data/lib/dsu/presenters/import/all_presenter.rb +20 -25
- data/lib/dsu/presenters/import/dates_presenter.rb +25 -27
- data/lib/dsu/presenters/import/import_entry.rb +22 -0
- data/lib/dsu/presenters/import/import_file.rb +9 -1
- data/lib/dsu/presenters/project/create_presenter.rb +44 -0
- data/lib/dsu/presenters/project/delete_by_number_presenter.rb +54 -0
- data/lib/dsu/presenters/project/delete_presenter.rb +53 -0
- data/lib/dsu/presenters/project/list_presenter.rb +24 -0
- data/lib/dsu/presenters/project/rename_by_number_presenter.rb +63 -0
- data/lib/dsu/presenters/project/rename_presenter.rb +57 -0
- data/lib/dsu/presenters/project/use_by_number_presenter.rb +53 -0
- data/lib/dsu/presenters/project/use_presenter.rb +52 -0
- data/lib/dsu/services/entry_group/exporter_service.rb +22 -5
- data/lib/dsu/services/entry_group/importer_service.rb +41 -8
- data/lib/dsu/services/project/hydrator_service.rb +40 -0
- data/lib/dsu/services/project/rename_service.rb +70 -0
- data/lib/dsu/subcommands/export.rb +4 -2
- data/lib/dsu/subcommands/import.rb +7 -3
- data/lib/dsu/subcommands/project.rb +149 -0
- data/lib/dsu/support/ask.rb +10 -3
- data/lib/dsu/support/color_themable.rb +1 -1
- data/lib/dsu/support/command_hookable.rb +7 -2
- data/lib/dsu/support/descriptable.rb +5 -21
- data/lib/dsu/support/fileable.rb +39 -1
- data/lib/dsu/support/project_file_system.rb +121 -0
- data/lib/dsu/support/short_string.rb +24 -0
- data/lib/dsu/support/time_comparable.rb +2 -0
- data/lib/dsu/support/transform_project_name.rb +24 -0
- data/lib/dsu/validators/project_name_validator.rb +58 -0
- data/lib/dsu/version.rb +1 -1
- data/lib/dsu/views/base_list_view.rb +41 -0
- data/lib/dsu/views/export.rb +60 -6
- data/lib/dsu/views/import.rb +83 -7
- data/lib/dsu/views/import_dates.rb +17 -0
- data/lib/dsu/views/project/create.rb +87 -0
- data/lib/dsu/views/project/delete.rb +96 -0
- data/lib/dsu/views/project/delete_by_number.rb +19 -0
- data/lib/dsu/views/project/list.rb +115 -0
- data/lib/dsu/views/project/rename.rb +98 -0
- data/lib/dsu/views/project/rename_by_number.rb +21 -0
- data/lib/dsu/views/project/use.rb +97 -0
- data/lib/dsu/views/project/use_by_number.rb +19 -0
- data/lib/dsu.rb +2 -10
- data/lib/locales/en/active_record.yml +9 -0
- data/lib/locales/en/commands.yml +9 -3
- data/lib/locales/en/miscellaneous.yml +4 -0
- data/lib/locales/en/services.yml +4 -0
- data/lib/locales/en/subcommands.yml +247 -15
- data/project.bak +0 -0
- metadata +34 -9
- data/lib/dsu/presenters/export/messages.rb +0 -32
- data/lib/dsu/presenters/export/nothing_to_export.rb +0 -13
- data/lib/dsu/presenters/export/service_callable.rb +0 -20
- data/lib/dsu/presenters/import/messages.rb +0 -42
- data/lib/dsu/presenters/import/service_callable.rb +0 -21
@@ -0,0 +1,53 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/project'
|
4
|
+
require_relative '../base_presenter_ex'
|
5
|
+
|
6
|
+
module Dsu
|
7
|
+
module Presenters
|
8
|
+
module Project
|
9
|
+
class UseByNumberPresenter < BasePresenterEx
|
10
|
+
attr_reader :project_number
|
11
|
+
|
12
|
+
delegate :project_name, to: :project, allow_nil: true
|
13
|
+
delegate :description, to: :project, prefix: true, allow_nil: true
|
14
|
+
|
15
|
+
def initialize(project_number:, options: {})
|
16
|
+
super(options: options)
|
17
|
+
|
18
|
+
raise ArgumentError, 'project_number is blank' if project_number.blank?
|
19
|
+
|
20
|
+
self.project_number = project_number
|
21
|
+
end
|
22
|
+
|
23
|
+
def respond(response:)
|
24
|
+
return false unless response
|
25
|
+
|
26
|
+
project.use! if project&.present?
|
27
|
+
end
|
28
|
+
|
29
|
+
def already_current_project?
|
30
|
+
project&.current_project?
|
31
|
+
end
|
32
|
+
|
33
|
+
def project_does_not_exist?
|
34
|
+
!project&.exist?
|
35
|
+
end
|
36
|
+
|
37
|
+
def project_errors
|
38
|
+
return false unless project&.persisted?
|
39
|
+
|
40
|
+
project.errors.full_messages
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
attr_writer :project_number
|
46
|
+
|
47
|
+
def project
|
48
|
+
@project ||= Models::Project.find_by_number(project_number: project_number)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/project'
|
4
|
+
require_relative '../base_presenter_ex'
|
5
|
+
|
6
|
+
module Dsu
|
7
|
+
module Presenters
|
8
|
+
module Project
|
9
|
+
class UsePresenter < BasePresenterEx
|
10
|
+
attr_reader :project_name
|
11
|
+
|
12
|
+
delegate :description, to: :project, prefix: true, allow_nil: true
|
13
|
+
|
14
|
+
def initialize(project_name:, options: {})
|
15
|
+
super(options: options)
|
16
|
+
|
17
|
+
raise ArgumentError, 'project_name is blank' if project_name.blank?
|
18
|
+
|
19
|
+
self.project_name = project_name
|
20
|
+
end
|
21
|
+
|
22
|
+
def respond(response:)
|
23
|
+
return false unless response
|
24
|
+
|
25
|
+
project.use! if project&.present?
|
26
|
+
end
|
27
|
+
|
28
|
+
def already_current_project?
|
29
|
+
project&.current_project?
|
30
|
+
end
|
31
|
+
|
32
|
+
def project_does_not_exist?
|
33
|
+
!project.exist?
|
34
|
+
end
|
35
|
+
|
36
|
+
def project_errors
|
37
|
+
return false unless project.persisted?
|
38
|
+
|
39
|
+
project.errors.full_messages
|
40
|
+
end
|
41
|
+
|
42
|
+
private
|
43
|
+
|
44
|
+
attr_writer :project_name
|
45
|
+
|
46
|
+
def project
|
47
|
+
@project ||= Models::Project.find_or_initialize(project_name: project_name)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
@@ -3,26 +3,34 @@
|
|
3
3
|
require 'csv'
|
4
4
|
require_relative '../../models/entry_group'
|
5
5
|
require_relative '../../support/fileable'
|
6
|
+
require_relative '../../support/transform_project_name'
|
6
7
|
|
7
8
|
module Dsu
|
8
9
|
module Services
|
9
10
|
module EntryGroup
|
11
|
+
# ExporterService exports entry groups to a CSV file.
|
12
|
+
# NOTE: This class exports all entries passed to it. It does not filter
|
13
|
+
# entries based on optional options[:time] that may be passed to it.
|
14
|
+
# Rather, times are used to determine the export file name only.
|
10
15
|
class ExporterService
|
11
16
|
include Support::Fileable
|
17
|
+
include Support::TransformProjectName
|
12
18
|
|
13
|
-
def initialize(entry_groups:, options: {})
|
19
|
+
def initialize(project_name:, entry_groups:, options: {})
|
20
|
+
raise ArgumentError, 'Argument project_name is blank' if project_name.blank?
|
14
21
|
raise ArgumentError, 'Argument entry_groups is blank' if entry_groups.blank?
|
15
22
|
raise ArgumentError, 'Argument entry_groups are not all valid' unless entry_groups.all?(&:valid?)
|
16
23
|
|
17
24
|
validate_entry_group_entries_present! entry_groups
|
18
25
|
|
26
|
+
@project_name = project_name
|
19
27
|
@entry_groups = entry_groups
|
20
28
|
@options = options
|
21
29
|
end
|
22
30
|
|
23
31
|
def call
|
24
32
|
CSV.open(export_file_path, 'w') do |csv|
|
25
|
-
csv << %i[version entry_group entry_no total_entries entry_group_entry]
|
33
|
+
csv << %i[project_name version entry_group entry_no total_entries entry_group_entry]
|
26
34
|
|
27
35
|
entry_groups.each do |entry_group|
|
28
36
|
export_entry_group(entry_group: entry_group, csv: csv)
|
@@ -38,10 +46,11 @@ module Dsu
|
|
38
46
|
|
39
47
|
private
|
40
48
|
|
41
|
-
attr_reader :entry_groups, :options
|
49
|
+
attr_reader :project_name, :entry_groups, :options
|
42
50
|
|
43
51
|
def export_entry_data(entry_group:, entry:, entry_index:)
|
44
52
|
[
|
53
|
+
project_name,
|
45
54
|
entry_group.version,
|
46
55
|
entry_group.time.to_date,
|
47
56
|
entry_index + 1,
|
@@ -57,11 +66,19 @@ module Dsu
|
|
57
66
|
end
|
58
67
|
|
59
68
|
def export_file_name
|
60
|
-
|
69
|
+
transformed_file_name = transform_project_name project_name, options: options
|
70
|
+
"dsu-export-#{transformed_file_name}-" \
|
71
|
+
"#{timestamp}-#{export_scope}-#{times.min.to_date}-thru-#{times.max.to_date}.csv"
|
72
|
+
end
|
73
|
+
|
74
|
+
def export_scope
|
75
|
+
return 'all-entry-groups' unless options.fetch(:times, nil)
|
76
|
+
|
77
|
+
'entry-groups'
|
61
78
|
end
|
62
79
|
|
63
80
|
def times
|
64
|
-
@times ||= entry_groups.map(&:time)
|
81
|
+
@times ||= options.fetch(:times, entry_groups.map(&:time))
|
65
82
|
end
|
66
83
|
|
67
84
|
def timestamp
|
@@ -2,23 +2,34 @@
|
|
2
2
|
|
3
3
|
require 'csv'
|
4
4
|
require_relative '../../models/entry_group'
|
5
|
+
require_relative '../../models/project'
|
5
6
|
|
6
7
|
module Dsu
|
7
8
|
module Services
|
8
9
|
module EntryGroup
|
9
10
|
# Expects a hash having the following format:
|
10
11
|
# {
|
11
|
-
#
|
12
|
-
#
|
13
|
-
#
|
12
|
+
# "Project 1 Name" => {
|
13
|
+
# "2023-12-29" => ["Entry 1 description", "Entry 2 description", ...],
|
14
|
+
# "2023-12-30" => ["Entry 1 description", ...],
|
15
|
+
# "2023-12-31" => ["Entry 1 description", ...]
|
16
|
+
# },
|
17
|
+
# "Project 2 Name" => {
|
18
|
+
# "2023-12-29" => ["Entry 1 description", "Entry 2 description", ...],
|
19
|
+
# "2023-12-30" => ["Entry 1 description", ...],
|
20
|
+
# "2023-12-31" => ["Entry 1 description", ...]
|
21
|
+
# }
|
14
22
|
# }
|
15
23
|
class ImporterService
|
16
24
|
include Support::Fileable
|
17
25
|
|
18
|
-
def initialize(
|
19
|
-
raise ArgumentError, 'Argument
|
26
|
+
def initialize(import_projects:, options: {})
|
27
|
+
raise ArgumentError, 'Argument import_projects is blank' if import_projects.blank?
|
28
|
+
raise ArgumentError, 'Argument import_projects is not a Hash' unless import_projects.is_a?(Hash)
|
20
29
|
|
21
|
-
|
30
|
+
raise_if_more_than_one_project(import_projects)
|
31
|
+
|
32
|
+
@import_projects = import_projects
|
22
33
|
@options = options
|
23
34
|
end
|
24
35
|
|
@@ -28,10 +39,10 @@ module Dsu
|
|
28
39
|
|
29
40
|
private
|
30
41
|
|
31
|
-
attr_reader :
|
42
|
+
attr_reader :import_projects, :options
|
32
43
|
|
33
44
|
def import!
|
34
|
-
|
45
|
+
project_entry_groups.each_pair do |entry_group_date, entry_descriptions|
|
35
46
|
entry_group_for(entry_group_date).tap do |entry_group|
|
36
47
|
entry_descriptions.each do |entry_description|
|
37
48
|
add_entry_group_entry_if(entry_group: entry_group, entry_description: entry_description)
|
@@ -67,6 +78,14 @@ module Dsu
|
|
67
78
|
entry_group.entries << entry
|
68
79
|
end
|
69
80
|
|
81
|
+
def project_entry_groups
|
82
|
+
@project_entry_groups ||= if override_project?
|
83
|
+
import_projects.values.first || {}
|
84
|
+
else
|
85
|
+
import_projects.fetch(current_project_name, {})
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
70
89
|
def merge?
|
71
90
|
options.fetch(:merge, true)
|
72
91
|
end
|
@@ -75,9 +94,23 @@ module Dsu
|
|
75
94
|
!merge?
|
76
95
|
end
|
77
96
|
|
97
|
+
def override_project?
|
98
|
+
options.fetch(:override, false)
|
99
|
+
end
|
100
|
+
|
78
101
|
def import_messages
|
79
102
|
@import_messages ||= {}
|
80
103
|
end
|
104
|
+
|
105
|
+
def current_project_name
|
106
|
+
@current_project_name ||= Models::Project.current_project.project_name
|
107
|
+
end
|
108
|
+
|
109
|
+
def raise_if_more_than_one_project(import_projects)
|
110
|
+
return if import_projects.keys.one?
|
111
|
+
|
112
|
+
raise ArgumentError, 'Only one project can be imported at a time'
|
113
|
+
end
|
81
114
|
end
|
82
115
|
end
|
83
116
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/color_theme'
|
4
|
+
|
5
|
+
module Dsu
|
6
|
+
module Services
|
7
|
+
module Project
|
8
|
+
class HydratorService
|
9
|
+
def initialize(project_hash:, options: {})
|
10
|
+
raise ArgumentError, 'project_hash is nil' if project_hash.nil?
|
11
|
+
|
12
|
+
unless project_hash.is_a?(Hash)
|
13
|
+
raise ArgumentError,
|
14
|
+
"project_hash is the wrong object type: \"#{project_hash}\""
|
15
|
+
end
|
16
|
+
raise ArgumentError, 'options is nil' if options.nil?
|
17
|
+
raise ArgumentError, "options is the wrong object type:\"#{options}\"" unless options.is_a?(Hash)
|
18
|
+
|
19
|
+
@project_hash = project_hash
|
20
|
+
@options = options || {}
|
21
|
+
end
|
22
|
+
|
23
|
+
def call
|
24
|
+
Models::Project.new(**hydrate)
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :project_hash, :options
|
30
|
+
|
31
|
+
# Not much going on here at all, but it's here for consistency.
|
32
|
+
# Perform any pre-processing of the project_hash here (e.g. symbolize keys,
|
33
|
+
# convert values to the correct type, etc.).
|
34
|
+
def hydrate
|
35
|
+
project_hash
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/project'
|
4
|
+
require_relative '../../support/fileable'
|
5
|
+
|
6
|
+
module Dsu
|
7
|
+
module Services
|
8
|
+
module Project
|
9
|
+
class RenameService
|
10
|
+
def initialize(from_project_name:, to_project_name:, to_project_description:, options: {})
|
11
|
+
@from_project_name = from_project_name
|
12
|
+
@to_project_name = to_project_name
|
13
|
+
@to_project_description = to_project_description
|
14
|
+
@options = options
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
validate!
|
19
|
+
|
20
|
+
# NOTE: The default and current states need to be captured before
|
21
|
+
# the project is renamed.
|
22
|
+
rename!(
|
23
|
+
make_default: Models::Project.default_project?(project_name: from_project_name),
|
24
|
+
make_current: Models::Project.current_project?(project_name: from_project_name)
|
25
|
+
)
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :from_project_name, :to_project_name, :to_project_description, :options
|
31
|
+
|
32
|
+
def rename!(make_default:, make_current:)
|
33
|
+
move_project
|
34
|
+
|
35
|
+
Models::Project.update(project_name: to_project_name,
|
36
|
+
description: to_project_description, options: options).tap do |project|
|
37
|
+
project.default! if make_default
|
38
|
+
project.use! if make_current
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
def move_project
|
43
|
+
FileUtils.mv(Support::Fileable.project_folder_for(project_name: from_project_name), temp_project_folder)
|
44
|
+
FileUtils.mv(temp_project_folder, Support::Fileable.project_folder_for(project_name: to_project_name))
|
45
|
+
end
|
46
|
+
|
47
|
+
def temp_project_folder
|
48
|
+
@temp_project_folder ||= Support::Fileable.project_folder_for(project_name: SecureRandom.uuid)
|
49
|
+
end
|
50
|
+
|
51
|
+
def validate!
|
52
|
+
validate_from_project_name!
|
53
|
+
validate_to_project_name!
|
54
|
+
end
|
55
|
+
|
56
|
+
def validate_from_project_name!
|
57
|
+
unless Models::Project.project_file_exist?(project_name: from_project_name)
|
58
|
+
raise I18n.t('models.project.errors.does_not_exist', project_name: from_project_name)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def validate_to_project_name!
|
63
|
+
if Models::Project.project_file_exist?(project_name: to_project_name)
|
64
|
+
raise I18n.t('models.project.errors.new_project_already_exists', project_name: to_project_name)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -23,7 +23,8 @@ module Dsu
|
|
23
23
|
long_desc I18n.t('subcommands.export.all.long_desc')
|
24
24
|
option :prompts, type: :hash, default: {}, hide: true, aliases: '-p'
|
25
25
|
def all
|
26
|
-
|
26
|
+
options = configuration.to_h.merge(self.options).with_indifferent_access
|
27
|
+
Views::Export.new(presenter: all_presenter(options: options), options: options).render
|
27
28
|
end
|
28
29
|
|
29
30
|
desc I18n.t('subcommands.export.dates.desc'), I18n.t('subcommands.export.dates.usage')
|
@@ -41,7 +42,8 @@ module Dsu
|
|
41
42
|
return
|
42
43
|
end
|
43
44
|
|
44
|
-
Views::Export.new(presenter:
|
45
|
+
Views::Export.new(presenter:
|
46
|
+
dates_presenter_for(from: times.min, to: times.max, options: options), options: options).render
|
45
47
|
rescue ArgumentError => e
|
46
48
|
Views::Shared::Error.new(messages: e.message).render
|
47
49
|
end
|
@@ -6,6 +6,7 @@ require_relative '../support/command_options/dsu_times'
|
|
6
6
|
require_relative '../support/command_options/time_mnemonic'
|
7
7
|
require_relative '../support/time_formatable'
|
8
8
|
require_relative '../views/import'
|
9
|
+
require_relative '../views/import_dates'
|
9
10
|
require_relative '../views/shared/error'
|
10
11
|
require_relative 'base_subcommand'
|
11
12
|
|
@@ -23,10 +24,12 @@ module Dsu
|
|
23
24
|
long_desc I18n.t('subcommands.import.all.long_desc')
|
24
25
|
option :import_file, type: :string, required: true, aliases: '-i', banner: 'IMPORT_CVS_FILE'
|
25
26
|
option :merge, type: :boolean, default: true, aliases: '-m'
|
27
|
+
option :override, type: :boolean, default: false, aliases: '-o'
|
26
28
|
option :prompts, type: :hash, default: {}, hide: true, aliases: '-p'
|
27
29
|
def all
|
30
|
+
options = configuration.to_h.merge(self.options).with_indifferent_access
|
28
31
|
Views::Import.new(presenter: all_presenter(import_file_path: options[:import_file],
|
29
|
-
options: options)).render
|
32
|
+
options: options), options: options).render
|
30
33
|
end
|
31
34
|
|
32
35
|
desc I18n.t('subcommands.import.dates.desc'), I18n.t('subcommands.import.dates.usage')
|
@@ -37,6 +40,7 @@ module Dsu
|
|
37
40
|
option :to, type: :string, required: true, aliases: '-t', banner: 'DATE|MNEMONIC'
|
38
41
|
option :import_file, type: :string, required: true, aliases: '-i', banner: 'IMPORT_CVS_FILE'
|
39
42
|
option :merge, type: :boolean, default: true, aliases: '-m'
|
43
|
+
option :override, type: :boolean, default: false, aliases: '-o'
|
40
44
|
option :prompts, type: :hash, default: {}, hide: true, aliases: '-p'
|
41
45
|
def dates
|
42
46
|
options = configuration.to_h.merge(self.options).with_indifferent_access
|
@@ -46,10 +50,10 @@ module Dsu
|
|
46
50
|
return
|
47
51
|
end
|
48
52
|
|
49
|
-
Views::
|
53
|
+
Views::ImportDates.new(presenter: dates_presenter_for(from: times.min,
|
50
54
|
to: times.max,
|
51
55
|
import_file_path: options[:import_file],
|
52
|
-
options: options)).render
|
56
|
+
options: options), options: options).render
|
53
57
|
rescue ArgumentError => e
|
54
58
|
Views::Shared::Error.new(messages: e.message).render
|
55
59
|
end
|
@@ -0,0 +1,149 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../presenters/project/create_presenter'
|
4
|
+
require_relative '../presenters/project/delete_presenter'
|
5
|
+
require_relative '../presenters/project/list_presenter'
|
6
|
+
require_relative '../presenters/project/use_by_number_presenter'
|
7
|
+
require_relative '../presenters/project/use_presenter'
|
8
|
+
require_relative '../views/project/create'
|
9
|
+
require_relative '../views/project/use'
|
10
|
+
require_relative '../views/project/use_by_number'
|
11
|
+
require_relative '../views/shared/error'
|
12
|
+
require_relative 'base_subcommand'
|
13
|
+
|
14
|
+
module Dsu
|
15
|
+
module Subcommands
|
16
|
+
class Project < BaseSubcommand
|
17
|
+
# TODO: I18n.
|
18
|
+
map %w[c] => :create
|
19
|
+
map %w[d] => :delete
|
20
|
+
map %w[l] => :list
|
21
|
+
map %w[r] => :rename
|
22
|
+
map %w[u] => :use
|
23
|
+
|
24
|
+
desc I18n.t('subcommands.project.create.desc'), I18n.t('subcommands.project.create.usage')
|
25
|
+
long_desc I18n.t('subcommands.project.create.long_desc')
|
26
|
+
option :prompts, type: :hash, default: {}, hide: true, aliases: '-p'
|
27
|
+
def create(project_name = nil, description = nil)
|
28
|
+
project_name = project_name.to_s.strip
|
29
|
+
description = description.to_s.strip
|
30
|
+
if project_name.blank?
|
31
|
+
return Views::Shared::Error.new(
|
32
|
+
messages: I18n.t('subcommands.project.messages.project_name_blank')
|
33
|
+
).render
|
34
|
+
end
|
35
|
+
|
36
|
+
options = configuration.to_h.merge(self.options).with_indifferent_access
|
37
|
+
presenter = Presenters::Project::CreatePresenter.new(project_name: project_name,
|
38
|
+
description: description, options: options)
|
39
|
+
Views::Project::Create.new(presenter: presenter, options: options).render
|
40
|
+
end
|
41
|
+
|
42
|
+
desc I18n.t('subcommands.project.delete.desc'), I18n.t('subcommands.project.delete.usage')
|
43
|
+
long_desc I18n.t('subcommands.project.delete.long_desc')
|
44
|
+
option :prompts, type: :hash, default: {}, hide: true, aliases: '-p'
|
45
|
+
def delete(project_name_or_number = nil)
|
46
|
+
options = configuration.to_h.merge(self.options).with_indifferent_access
|
47
|
+
presenter = delete_presenter_for(project_name_or_number, options: options)
|
48
|
+
delete_view_for(project_name_or_number, presenter: presenter, options: options).render
|
49
|
+
end
|
50
|
+
|
51
|
+
desc I18n.t('subcommands.project.list.desc'), I18n.t('subcommands.project.list.usage')
|
52
|
+
long_desc I18n.t('subcommands.project.list.long_desc')
|
53
|
+
option :prompts, type: :hash, default: {}, hide: true, aliases: '-p'
|
54
|
+
def list
|
55
|
+
options = configuration.to_h.merge(self.options).with_indifferent_access
|
56
|
+
presenter = Presenters::Project::ListPresenter.new(options: options)
|
57
|
+
Views::Project::List.new(presenter: presenter, options: options).render
|
58
|
+
end
|
59
|
+
|
60
|
+
desc I18n.t('subcommands.project.rename.desc'), I18n.t('subcommands.project.rename.usage')
|
61
|
+
long_desc I18n.t('subcommands.project.rename.long_desc')
|
62
|
+
option :prompts, type: :hash, default: {}, hide: true, aliases: '-p'
|
63
|
+
def rename(project_name_or_number = nil, new_project_name = nil, new_project_description = nil)
|
64
|
+
project_name_or_number = project_name_or_number.to_s.strip
|
65
|
+
new_project_name = new_project_name&.to_s&.strip
|
66
|
+
new_project_description = new_project_description&.to_s&.strip
|
67
|
+
|
68
|
+
if new_project_name.blank?
|
69
|
+
return Views::Shared::Error.new(
|
70
|
+
messages: I18n.t('subcommands.project.messages.new_project_name_blank')
|
71
|
+
).render
|
72
|
+
end
|
73
|
+
|
74
|
+
options = configuration.to_h.merge(self.options).with_indifferent_access
|
75
|
+
presenter = rename_presenter_for(project_name_or_number, new_project_name: new_project_name,
|
76
|
+
new_project_description: new_project_description, options: options)
|
77
|
+
rename_view_for(project_name_or_number, presenter: presenter, options: options).render
|
78
|
+
end
|
79
|
+
|
80
|
+
desc I18n.t('subcommands.project.use.desc'), I18n.t('subcommands.project.use.usage')
|
81
|
+
long_desc I18n.t('subcommands.project.use.long_desc')
|
82
|
+
option :prompts, type: :hash, default: {}, hide: true, aliases: '-p'
|
83
|
+
def use(project_name_or_number = nil)
|
84
|
+
options = configuration.to_h.merge(self.options).with_indifferent_access
|
85
|
+
presenter = use_presenter_for(project_name_or_number, options: options)
|
86
|
+
use_view_for(project_name_or_number, presenter: presenter, options: options).render
|
87
|
+
end
|
88
|
+
|
89
|
+
private
|
90
|
+
|
91
|
+
def delete_view_for(project_name, presenter:, options:)
|
92
|
+
if project_number?(project_name)
|
93
|
+
Views::Project::DeleteByNumber.new(presenter: presenter, options: options)
|
94
|
+
else
|
95
|
+
Views::Project::Delete.new(presenter: presenter, options: options)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def delete_presenter_for(project_name, options:)
|
100
|
+
if project_number?(project_name)
|
101
|
+
Presenters::Project::DeleteByNumberPresenter.new(project_number: project_name.to_i, options: options)
|
102
|
+
else
|
103
|
+
project_name = Models::Project.default_project_name if project_name.blank?
|
104
|
+
Presenters::Project::DeletePresenter.new(project_name: project_name, options: options)
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
def rename_view_for(project_name, presenter:, options:)
|
109
|
+
if project_number?(project_name)
|
110
|
+
Views::Project::RenameByNumber.new(presenter: presenter, options: options)
|
111
|
+
else
|
112
|
+
Views::Project::Rename.new(presenter: presenter, options: options)
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
def rename_presenter_for(project_name, new_project_name:, new_project_description:, options:)
|
117
|
+
if project_number?(project_name)
|
118
|
+
Presenters::Project::RenameByNumberPresenter.new(project_number: project_name.to_i,
|
119
|
+
new_project_name: new_project_name, new_project_description: new_project_description, options: options)
|
120
|
+
else
|
121
|
+
project_name = Models::Project.default_project_name if project_name.blank?
|
122
|
+
Presenters::Project::RenamePresenter.new(project_name: project_name,
|
123
|
+
new_project_name: new_project_name, new_project_description: new_project_description, options: options)
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def use_view_for(project_name, presenter:, options:)
|
128
|
+
if project_number?(project_name)
|
129
|
+
Views::Project::UseByNumber.new(presenter: presenter, options: options)
|
130
|
+
else
|
131
|
+
Views::Project::Use.new(presenter: presenter, options: options)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def use_presenter_for(project_name, options:)
|
136
|
+
if project_number?(project_name)
|
137
|
+
Presenters::Project::UseByNumberPresenter.new(project_number: project_name.to_i, options: options)
|
138
|
+
else
|
139
|
+
project_name = Models::Project.default_project_name if project_name.blank?
|
140
|
+
Presenters::Project::UsePresenter.new(project_name: project_name, options: options)
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
def project_number?(project_name)
|
145
|
+
/^[+-]?\d+(\.\d+)?$/.match?(project_name.to_s)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
end
|
data/lib/dsu/support/ask.rb
CHANGED
@@ -1,13 +1,20 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'io/console'
|
3
4
|
require 'thor'
|
4
5
|
|
5
6
|
module Dsu
|
6
7
|
module Support
|
7
8
|
module Ask
|
8
|
-
def
|
9
|
-
|
10
|
-
|
9
|
+
def ask_while(prompt, options: {}) # rubocop:disable Lint/UnusedMethodArgument
|
10
|
+
loop do
|
11
|
+
print prompt
|
12
|
+
char = $stdin.getch
|
13
|
+
puts char
|
14
|
+
return char if yield(char)
|
15
|
+
|
16
|
+
char
|
17
|
+
end
|
11
18
|
end
|
12
19
|
|
13
20
|
def yes?(prompt, options: {})
|
@@ -8,7 +8,7 @@ module Dsu
|
|
8
8
|
def prompt_with_options(prompt:, options:)
|
9
9
|
# HACK: This module needs to be refactored to be more generic.
|
10
10
|
target_color_theme = defined?(color_theme) ? color_theme : self
|
11
|
-
options = "[#{options.join('
|
11
|
+
options = "[#{options.join(',')}]"
|
12
12
|
"#{apply_theme(prompt, theme_color: target_color_theme.prompt)} " \
|
13
13
|
"#{apply_theme(options, theme_color: target_color_theme.prompt_options)}" \
|
14
14
|
"#{apply_theme('>', theme_color: target_color_theme.prompt)}"
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require_relative '../env'
|
4
4
|
require_relative '../models/color_theme'
|
5
|
+
require_relative '../models/project'
|
5
6
|
require_relative '../services/stderr_redirector_service'
|
6
7
|
require_relative '../views/shared/error'
|
7
8
|
require_relative 'color_themable'
|
@@ -34,14 +35,18 @@ module Dsu
|
|
34
35
|
end
|
35
36
|
|
36
37
|
def display_dsu_footer
|
37
|
-
puts apply_theme('_' *
|
38
|
+
puts apply_theme('_' * 50, theme_color: color_theme.dsu_footer)
|
38
39
|
# TODO: I18n.
|
39
|
-
puts apply_theme("dsu |
|
40
|
+
puts apply_theme("dsu v#{Dsu::VERSION} | Project: #{project} | Theme: #{color_theme.theme_name}",
|
40
41
|
theme_color: color_theme.dsu_footer)
|
41
42
|
end
|
42
43
|
|
43
44
|
private
|
44
45
|
|
46
|
+
def project
|
47
|
+
Models::Project.current_project_name
|
48
|
+
end
|
49
|
+
|
45
50
|
def suspend_header?(args, _options)
|
46
51
|
return false unless args.count > 1
|
47
52
|
|