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
@@ -1,10 +1,10 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'short_string'
|
4
|
+
|
3
5
|
module Dsu
|
4
6
|
module Support
|
5
7
|
module Descriptable
|
6
|
-
DESCRIPTION_MAX_COUNT = 25
|
7
|
-
|
8
8
|
class << self
|
9
9
|
def included(base)
|
10
10
|
base.extend(ClassMethods)
|
@@ -18,26 +18,10 @@ module Dsu
|
|
18
18
|
end
|
19
19
|
|
20
20
|
module ClassMethods
|
21
|
-
|
22
|
-
return elipsis unless string.is_a?(String)
|
23
|
-
|
24
|
-
elipsis_length = elipsis.length
|
25
|
-
count = elipsis_length if count.nil? || count < elipsis_length
|
26
|
-
|
27
|
-
return string if string.length <= count
|
28
|
-
|
29
|
-
tokens = string.split
|
30
|
-
string = ''
|
31
|
-
|
32
|
-
return "#{tokens.first[0...(count - elipsis_length)]}#{elipsis}" if tokens.count == 1
|
33
|
-
|
34
|
-
tokens.each do |token|
|
35
|
-
break if string.length + token.length + elipsis_length > count
|
36
|
-
|
37
|
-
string = "#{string} #{token}"
|
38
|
-
end
|
21
|
+
include ShortString
|
39
22
|
|
40
|
-
|
23
|
+
def short_description(string:, count: ShortString::SHORT_STRING_MAX_COUNT, elipsis: '...')
|
24
|
+
short_string(string: string, count: count, elipsis: elipsis)
|
41
25
|
end
|
42
26
|
end
|
43
27
|
end
|
data/lib/dsu/support/fileable.rb
CHANGED
@@ -26,7 +26,8 @@ module Dsu
|
|
26
26
|
# Entries
|
27
27
|
|
28
28
|
def entries_folder
|
29
|
-
|
29
|
+
project_folder = project_folder_for(project_name: Models::Project.current_project_name)
|
30
|
+
File.join(project_folder, 'entries')
|
30
31
|
end
|
31
32
|
|
32
33
|
def entries_file_name(time:, file_name_format: nil)
|
@@ -88,6 +89,43 @@ module Dsu
|
|
88
89
|
File.join(gem_dir, 'lib/seed_data')
|
89
90
|
end
|
90
91
|
|
92
|
+
# Projects
|
93
|
+
|
94
|
+
# Returns the folder where all the projects are stored.
|
95
|
+
def projects_folder
|
96
|
+
File.join(dsu_folder, 'projects')
|
97
|
+
end
|
98
|
+
|
99
|
+
# Current project
|
100
|
+
|
101
|
+
# Contains the name of the file that contains the current
|
102
|
+
# dsu project currently being used.
|
103
|
+
def current_project_file_name
|
104
|
+
'current_project.json'
|
105
|
+
end
|
106
|
+
|
107
|
+
# The complete path to the current project file.
|
108
|
+
def current_project_file
|
109
|
+
File.join(dsu_folder, current_project_file_name)
|
110
|
+
end
|
111
|
+
|
112
|
+
# Project helpers
|
113
|
+
|
114
|
+
# Returns the path of the project with the given name.
|
115
|
+
def project_folder_for(project_name:)
|
116
|
+
raise I18n.t('errors.project_name_invalid', project_name: '{{blank}}') if project_name.blank?
|
117
|
+
|
118
|
+
File.join(projects_folder, project_name)
|
119
|
+
end
|
120
|
+
alias project_folder project_folder_for
|
121
|
+
|
122
|
+
def project_file_for(project_name:)
|
123
|
+
project_folder = project_folder_for(project_name: project_name)
|
124
|
+
|
125
|
+
File.join(project_folder, 'project.json')
|
126
|
+
end
|
127
|
+
alias project_file project_file_for
|
128
|
+
|
91
129
|
extend self # rubocop:disable Style/ModuleFunction
|
92
130
|
end
|
93
131
|
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'pathname'
|
5
|
+
require_relative '../crud/json_file'
|
6
|
+
require_relative '../migration/version'
|
7
|
+
require_relative '../models/configuration'
|
8
|
+
require_relative 'fileable'
|
9
|
+
|
10
|
+
module Dsu
|
11
|
+
module Support
|
12
|
+
module ProjectFileSystem
|
13
|
+
class << self
|
14
|
+
def included(base)
|
15
|
+
base.extend(ClassMethods)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def exist?
|
20
|
+
self.class.project_file_exist?(project_name: project_name)
|
21
|
+
end
|
22
|
+
alias persisted? exist?
|
23
|
+
|
24
|
+
def project_initialized?
|
25
|
+
self.class.project_initialized?(project_name: project_name)
|
26
|
+
end
|
27
|
+
|
28
|
+
def project_number
|
29
|
+
self.class.project_number_for(project_name: project_name)
|
30
|
+
end
|
31
|
+
|
32
|
+
module ClassMethods
|
33
|
+
include Fileable
|
34
|
+
|
35
|
+
# Returns the currently selected (used) project name
|
36
|
+
# from dsu/current_project.json
|
37
|
+
def current_project_name
|
38
|
+
Crud::JsonFile.read!(file_path: current_project_file).fetch(:project_name)
|
39
|
+
end
|
40
|
+
|
41
|
+
def default_project_name
|
42
|
+
Models::Configuration.new.default_project
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize_project(project_name:)
|
46
|
+
return if project_initialized?(project_name: project_name)
|
47
|
+
|
48
|
+
# TODO: Don't know if I like this here.
|
49
|
+
unless current_project_file_exist?
|
50
|
+
file_data = {
|
51
|
+
version: Dsu::Migration::VERSION,
|
52
|
+
project_name: default_project_name
|
53
|
+
}
|
54
|
+
Crud::JsonFile.write!(file_data: file_data, file_path: current_project_file)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Creates dsu/projects/<project_name>
|
58
|
+
FileUtils.mkdir_p(project_folder_for(project_name: project_name))
|
59
|
+
end
|
60
|
+
|
61
|
+
def project_initialized?(project_name:)
|
62
|
+
# Checking these files, checks all the containing folders also
|
63
|
+
current_project_file_exist? &&
|
64
|
+
project_folder_exist?(project_name: project_name)
|
65
|
+
end
|
66
|
+
|
67
|
+
# Does dsu/projects/<project_name>/project.json file exist?
|
68
|
+
def project_file_exist?(project_name:)
|
69
|
+
project_file_path = project_file_for(project_name: project_name)
|
70
|
+
File.exist?(project_file_path)
|
71
|
+
end
|
72
|
+
alias exist? project_file_exist?
|
73
|
+
alias persisted? project_file_exist?
|
74
|
+
|
75
|
+
# Does dsu/current_project.json file exist?
|
76
|
+
def current_project_file_exist?
|
77
|
+
File.exist?(current_project_file)
|
78
|
+
end
|
79
|
+
alias current_project_file_persisted? current_project_file_exist?
|
80
|
+
|
81
|
+
# Does dsu/projects folder exist?
|
82
|
+
def projects_folder_exist?
|
83
|
+
Dir.exist?(projects_folder)
|
84
|
+
end
|
85
|
+
|
86
|
+
# Does dsu/projects/<project_name> folder exist?
|
87
|
+
def project_folder_exist?(project_name:)
|
88
|
+
Dir.exist?(project_folder_for(project_name: project_name))
|
89
|
+
end
|
90
|
+
|
91
|
+
def project_metadata
|
92
|
+
project_folder_names.each_with_index.with_object([]) do |(project_name, index), array|
|
93
|
+
array << {
|
94
|
+
project_number: index + 1,
|
95
|
+
project_name: project_name,
|
96
|
+
current_project: project_name == current_project_name,
|
97
|
+
default_projet: project_name == default_project_name
|
98
|
+
}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def project_number_for(project_name:)
|
103
|
+
project_metadata.find do |metadata|
|
104
|
+
metadata[:project_name] == project_name
|
105
|
+
end&.[](:project_number) || -1
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def project_folder_names
|
111
|
+
Pathname.new(projects_folder)
|
112
|
+
.children
|
113
|
+
.select(&:directory?)
|
114
|
+
.map(&:basename)
|
115
|
+
.map(&:to_s)
|
116
|
+
.sort { |a, b| a.casecmp(b) }
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dsu
|
4
|
+
module Support
|
5
|
+
module ShortString
|
6
|
+
SHORT_STRING_MAX_COUNT = 25
|
7
|
+
|
8
|
+
module_function
|
9
|
+
|
10
|
+
def short_string(string:, count: SHORT_STRING_MAX_COUNT, elipsis: '...')
|
11
|
+
return '' if string.blank?
|
12
|
+
return string if string.length <= count
|
13
|
+
|
14
|
+
# Trim to max count and cut at the last space within the limit
|
15
|
+
trimmed_string = string[0...count].rpartition(' ')[0]
|
16
|
+
|
17
|
+
# If no space found, trim by characters
|
18
|
+
trimmed_string = string[0...(count - elipsis.length)] if trimmed_string.empty? && !string.empty?
|
19
|
+
|
20
|
+
"#{trimmed_string}#{elipsis}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dsu
|
4
|
+
module Support
|
5
|
+
module TransformProjectName
|
6
|
+
TRANSFORM_PROJECT_NAME_REGEX = %r{[^/\w\s]|_}
|
7
|
+
TRANSFORM_PROJECT_NAME_SEPARATOR = '-'
|
8
|
+
|
9
|
+
module_function
|
10
|
+
|
11
|
+
def transform_project_name(project_name, options: {})
|
12
|
+
normalized_name = project_name
|
13
|
+
.gsub(TRANSFORM_PROJECT_NAME_REGEX, ' ') # Replace non-word characters and underscores with space
|
14
|
+
.strip # Remove leading and trailing spaces
|
15
|
+
.squeeze(' ') # Convert consecutive spaces to a single space
|
16
|
+
.tr(' ', TRANSFORM_PROJECT_NAME_SEPARATOR) # Replace spaces with hyphens
|
17
|
+
.squeeze(TRANSFORM_PROJECT_NAME_SEPARATOR) # Ensure no consecutive hyphens
|
18
|
+
|
19
|
+
normalized_name.downcase! if options[:downcase]
|
20
|
+
normalized_name
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../support/field_errors'
|
4
|
+
require_relative '../support/short_string'
|
5
|
+
|
6
|
+
# https://guides.rubyonrails.org/active_record_validations.html#validates-with
|
7
|
+
module Dsu
|
8
|
+
module Validators
|
9
|
+
# TODO: I18n.
|
10
|
+
class ProjectNameValidator < ActiveModel::Validator
|
11
|
+
include Support::FieldErrors
|
12
|
+
include Support::ShortString
|
13
|
+
|
14
|
+
def validate(record)
|
15
|
+
unless record.project_name.is_a?(String)
|
16
|
+
record.errors.add(:project_name, 'is the wrong object type. ' \
|
17
|
+
"\"String\" was expected, but \"#{record.project.class}\" was received.")
|
18
|
+
return
|
19
|
+
end
|
20
|
+
|
21
|
+
unless record.project_name.present?
|
22
|
+
record.errors.add(:project_name, :blank, '')
|
23
|
+
|
24
|
+
return
|
25
|
+
end
|
26
|
+
|
27
|
+
validate_project_name record
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def validate_project_name(record)
|
33
|
+
project_name = record.project_name
|
34
|
+
|
35
|
+
return if project_name.length.between?(min_project_name_length(record), max_project_name_length(record))
|
36
|
+
|
37
|
+
if project_name.length < min_project_name_length(record)
|
38
|
+
# TODO: I18n.
|
39
|
+
record.errors.add(:project_name, "is too short: \"#{record.project_name}\" " \
|
40
|
+
"(minimum is #{min_project_name_length(record)} characters).")
|
41
|
+
elsif project_name.length > max_project_name_length(record)
|
42
|
+
# TODO: I18n.
|
43
|
+
short_project_name = short_string(string: project_name, count: max_project_name_length(record))
|
44
|
+
record.errors.add(:project_name, "is too long: \"#{short_project_name}\" " \
|
45
|
+
"(maximum is #{max_project_name_length(record)} characters).")
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def min_project_name_length(record)
|
50
|
+
record.class::MIN_PROJECT_NAME_LENGTH
|
51
|
+
end
|
52
|
+
|
53
|
+
def max_project_name_length(record)
|
54
|
+
record.class::MAX_PROJECT_NAME_LENGTH
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/dsu/version.rb
CHANGED
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../env'
|
4
|
+
require_relative '../models/color_theme'
|
5
|
+
require_relative '../support/color_themable'
|
6
|
+
|
7
|
+
module Dsu
|
8
|
+
module Views
|
9
|
+
class BaseListView
|
10
|
+
include Support::ColorThemable
|
11
|
+
|
12
|
+
attr_reader :presenter
|
13
|
+
|
14
|
+
def initialize(presenter:, options: {})
|
15
|
+
@presenter = presenter
|
16
|
+
@options = options&.dup || {}
|
17
|
+
@color_theme = Models::ColorTheme.find(theme_name: theme_name)
|
18
|
+
end
|
19
|
+
|
20
|
+
def render
|
21
|
+
yield
|
22
|
+
rescue StandardError => e
|
23
|
+
puts apply_theme(e.message, theme_color: color_theme.error)
|
24
|
+
puts apply_theme(e.backtrace_locations.join("\n"), theme_color: color_theme.error) if Dsu.env.local?
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
attr_reader :color_theme, :options
|
30
|
+
|
31
|
+
def theme_name
|
32
|
+
@theme_name ||= options.fetch(:theme_name, Models::Configuration.new.theme_name)
|
33
|
+
end
|
34
|
+
|
35
|
+
def formatted_index(index:)
|
36
|
+
apply_theme("#{format('%02s', index + 1)}. ",
|
37
|
+
theme_color: color_theme.index)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/dsu/views/export.rb
CHANGED
@@ -1,28 +1,82 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../models/color_theme'
|
4
|
-
require_relative '../
|
4
|
+
require_relative '../support/ask'
|
5
5
|
require_relative '../support/color_themable'
|
6
6
|
|
7
7
|
module Dsu
|
8
8
|
module Views
|
9
9
|
class Export
|
10
|
+
include Support::Ask
|
10
11
|
include Support::ColorThemable
|
11
12
|
|
12
|
-
def initialize(presenter:)
|
13
|
+
def initialize(presenter:, options:)
|
13
14
|
@presenter = presenter
|
15
|
+
@options = options&.dup || {}
|
16
|
+
@color_theme = Models::ColorTheme.find(theme_name: theme_name)
|
14
17
|
end
|
15
18
|
|
16
19
|
def render
|
17
|
-
return
|
20
|
+
return display_nothing_to_export_message if presenter.nothing_to_export?
|
18
21
|
|
19
|
-
response =
|
20
|
-
presenter.
|
22
|
+
response = display_export_prompt
|
23
|
+
if presenter.respond response: response
|
24
|
+
display_exported_message
|
25
|
+
display_exported_to_message(file_path: presenter.export_file_path)
|
26
|
+
else
|
27
|
+
display_cancelled_message
|
28
|
+
end
|
29
|
+
rescue StandardError => e
|
30
|
+
puts apply_theme(e.message, theme_color: color_theme.error)
|
31
|
+
puts apply_theme(e.backtrace_locations.join("\n"), theme_color: color_theme.error) if Dsu.env.local?
|
21
32
|
end
|
22
33
|
|
23
34
|
private
|
24
35
|
|
25
|
-
attr_reader :presenter
|
36
|
+
attr_reader :presenter, :color_theme, :options
|
37
|
+
|
38
|
+
def project_name
|
39
|
+
presenter.project_name
|
40
|
+
end
|
41
|
+
|
42
|
+
def display_export_prompt
|
43
|
+
response = ask_while(prompt_with_options(prompt: export_prompt,
|
44
|
+
options: export_prompt_options), options: options) do |input|
|
45
|
+
message = I18n.t('information.input.try_again', options: export_prompt_options.join(','))
|
46
|
+
puts apply_theme(message, theme_color: color_theme.info) unless export_prompt_options.include?(input)
|
47
|
+
export_prompt_options.include?(input)
|
48
|
+
end
|
49
|
+
response == export_prompt_options.first
|
50
|
+
end
|
51
|
+
|
52
|
+
def display_cancelled_message
|
53
|
+
puts apply_theme(I18n.t('subcommands.export.messages.cancelled'), theme_color: color_theme.info)
|
54
|
+
end
|
55
|
+
|
56
|
+
def display_exported_message
|
57
|
+
puts apply_theme(I18n.t('subcommands.export.messages.exported'), theme_color: color_theme.success)
|
58
|
+
end
|
59
|
+
|
60
|
+
def display_exported_to_message(file_path:)
|
61
|
+
puts apply_theme(I18n.t('subcommands.export.messages.exported_to', file_path: file_path),
|
62
|
+
theme_color: color_theme.success)
|
63
|
+
end
|
64
|
+
|
65
|
+
def display_nothing_to_export_message
|
66
|
+
puts apply_theme(I18n.t('subcommands.export.messages.nothing_to_export'), theme_color: color_theme.info)
|
67
|
+
end
|
68
|
+
|
69
|
+
def export_prompt
|
70
|
+
I18n.t('subcommands.export.prompts.export_all_confirm', count: presenter.entry_group_count)
|
71
|
+
end
|
72
|
+
|
73
|
+
def export_prompt_options
|
74
|
+
I18n.t('subcommands.export.prompts.options')
|
75
|
+
end
|
76
|
+
|
77
|
+
def theme_name
|
78
|
+
@theme_name ||= options.fetch(:theme_name, Models::Configuration.new.theme_name)
|
79
|
+
end
|
26
80
|
end
|
27
81
|
end
|
28
82
|
end
|
data/lib/dsu/views/import.rb
CHANGED
@@ -1,29 +1,105 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require_relative '../models/color_theme'
|
4
|
-
require_relative '../
|
4
|
+
require_relative '../support/ask'
|
5
5
|
require_relative '../support/color_themable'
|
6
6
|
|
7
7
|
module Dsu
|
8
8
|
module Views
|
9
9
|
class Import
|
10
|
+
include Support::Ask
|
10
11
|
include Support::ColorThemable
|
11
12
|
|
12
|
-
def initialize(presenter:)
|
13
|
+
def initialize(presenter:, options:)
|
13
14
|
@presenter = presenter
|
15
|
+
@options = options&.dup || {}
|
16
|
+
@color_theme = Models::ColorTheme.find(theme_name: theme_name)
|
14
17
|
end
|
15
18
|
|
16
19
|
def render
|
17
|
-
return
|
18
|
-
return
|
20
|
+
return display_import_file_not_exist_message unless presenter.import_file_path_exist?
|
21
|
+
return display_nothing_to_import_message if presenter.nothing_to_import?
|
19
22
|
|
20
|
-
response =
|
21
|
-
|
23
|
+
response = display_import_prompt
|
24
|
+
return display_cancelled_message unless response
|
25
|
+
|
26
|
+
response = display_project_override_prompt if presenter.overriding_project?
|
27
|
+
return display_cancelled_message unless response
|
28
|
+
|
29
|
+
display_import_messages presenter.respond
|
30
|
+
rescue StandardError => e
|
31
|
+
puts apply_theme(e.backtrace_locations.join("\n"), theme_color: color_theme.error) if Dsu.env.local?
|
32
|
+
message = I18n.t('subcommands.import.messages.import_error_raised', error: e.message)
|
33
|
+
puts apply_theme(message, theme_color: color_theme.error)
|
22
34
|
end
|
23
35
|
|
24
36
|
private
|
25
37
|
|
26
|
-
attr_reader :presenter
|
38
|
+
attr_reader :presenter, :color_theme, :options
|
39
|
+
|
40
|
+
def display_import_prompt
|
41
|
+
response = ask_while(prompt_with_options(prompt: import_prompt,
|
42
|
+
options: import_prompt_options), options: options) do |input|
|
43
|
+
message = I18n.t('information.input.try_again', options: import_prompt_options.join(','))
|
44
|
+
puts apply_theme(message, theme_color: color_theme.info) unless import_prompt_options.include?(input)
|
45
|
+
import_prompt_options.include?(input)
|
46
|
+
end
|
47
|
+
response == import_prompt_options.first
|
48
|
+
end
|
49
|
+
|
50
|
+
def display_project_override_prompt
|
51
|
+
response = ask_while(prompt_with_options(prompt: project_override_prompt,
|
52
|
+
options: import_prompt_options), options: options) do |input|
|
53
|
+
message = I18n.t('information.input.try_again', options: import_prompt_options.join(','))
|
54
|
+
puts apply_theme(message, theme_color: color_theme.info) unless import_prompt_options.include?(input)
|
55
|
+
import_prompt_options.include?(input)
|
56
|
+
end
|
57
|
+
response == import_prompt_options.first
|
58
|
+
end
|
59
|
+
|
60
|
+
def display_cancelled_message
|
61
|
+
puts apply_theme(I18n.t('subcommands.import.messages.cancelled'), theme_color: color_theme.info)
|
62
|
+
end
|
63
|
+
|
64
|
+
def display_nothing_to_import_message
|
65
|
+
puts apply_theme(I18n.t('subcommands.import.messages.nothing_to_import'), theme_color: color_theme.info)
|
66
|
+
end
|
67
|
+
|
68
|
+
def import_prompt
|
69
|
+
I18n.t('subcommands.import.prompts.import_all_confirm',
|
70
|
+
count: presenter.import_entry_groups_count, project: presenter.project_name)
|
71
|
+
end
|
72
|
+
|
73
|
+
def project_override_prompt
|
74
|
+
I18n.t('subcommands.import.prompts.project_override_confirm')
|
75
|
+
end
|
76
|
+
|
77
|
+
def import_prompt_options
|
78
|
+
I18n.t('subcommands.import.prompts.options')
|
79
|
+
end
|
80
|
+
|
81
|
+
def display_import_file_not_exist_message
|
82
|
+
puts apply_theme(I18n.t('subcommands.import.messages.file_not_exist',
|
83
|
+
file_path: presenter.import_file_path), theme_color: color_theme.info)
|
84
|
+
end
|
85
|
+
|
86
|
+
def display_import_messages(import_results)
|
87
|
+
import_results.each_pair do |entry_group_date, errors|
|
88
|
+
if errors.empty?
|
89
|
+
puts apply_theme(I18n.t('subcommands.import.messages.import_success',
|
90
|
+
date: entry_group_date), theme_color: color_theme.success)
|
91
|
+
else
|
92
|
+
errors.each do |error|
|
93
|
+
puts apply_theme(I18n.t('subcommands.import.messages.import_error',
|
94
|
+
date: entry_group_date, error: error), theme_color: color_theme.error)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def theme_name
|
101
|
+
@theme_name ||= options.fetch(:theme_name, Models::Configuration.new.theme_name)
|
102
|
+
end
|
27
103
|
end
|
28
104
|
end
|
29
105
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'import'
|
4
|
+
|
5
|
+
module Dsu
|
6
|
+
module Views
|
7
|
+
class ImportDates < Import
|
8
|
+
private
|
9
|
+
|
10
|
+
def import_prompt
|
11
|
+
I18n.t('subcommands.import.prompts.import_dates_confirm',
|
12
|
+
from: presenter.from.to_date, to: presenter.to.to_date,
|
13
|
+
count: presenter.import_entry_groups_count, project: presenter.project_name)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|