doto 0.0.1.pre.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.
- checksums.yaml +7 -0
- data/.env.test +1 -0
- data/.reek.yml +20 -0
- data/.rspec +3 -0
- data/.rubocop.yml +206 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +7 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +30 -0
- data/Gemfile.lock +179 -0
- data/LICENSE.txt +21 -0
- data/README.md +38 -0
- data/Rakefile +16 -0
- data/bin/console +36 -0
- data/bin/doto +3 -0
- data/bin/setup +18 -0
- data/exe/doto +33 -0
- data/lib/core/ruby/color_theme_colors.rb +16 -0
- data/lib/core/ruby/color_theme_mode.rb +42 -0
- data/lib/core/ruby/wrap_and_join.rb +31 -0
- data/lib/doto/base_cli.rb +56 -0
- data/lib/doto/cli.rb +131 -0
- data/lib/doto/command_services/add_entry_service.rb +50 -0
- data/lib/doto/crud/json_file.rb +161 -0
- data/lib/doto/env.rb +44 -0
- data/lib/doto/migration/base_service.rb +118 -0
- data/lib/doto/migration/migrator.rb +24 -0
- data/lib/doto/migration/raw_helpers/color_theme_hash.rb +13 -0
- data/lib/doto/migration/raw_helpers/configuration_hash.rb +15 -0
- data/lib/doto/migration/raw_helpers/entry_group_hash.rb +13 -0
- data/lib/doto/migration/raw_json_file.rb +15 -0
- data/lib/doto/migration/raw_json_files.rb +56 -0
- data/lib/doto/migration/v20230613121411/service.rb +94 -0
- data/lib/doto/migration/v20240210161248/service.rb +148 -0
- data/lib/doto/migration/version.rb +7 -0
- data/lib/doto/models/color_theme.rb +224 -0
- data/lib/doto/models/configuration.rb +185 -0
- data/lib/doto/models/entry.rb +63 -0
- data/lib/doto/models/entry_group.rb +223 -0
- data/lib/doto/models/migration_version.rb +49 -0
- data/lib/doto/models/project.rb +295 -0
- data/lib/doto/presenters/base_presenter.rb +32 -0
- data/lib/doto/presenters/base_presenter_ex.rb +15 -0
- data/lib/doto/presenters/color_theme_presenter.rb +52 -0
- data/lib/doto/presenters/color_theme_show_presenter.rb +55 -0
- data/lib/doto/presenters/configuration_presenter.rb +50 -0
- data/lib/doto/presenters/entry_group/list/date_presenter.rb +77 -0
- data/lib/doto/presenters/entry_group/list/dates_presenter.rb +60 -0
- data/lib/doto/presenters/entry_group/list/messages.rb +15 -0
- data/lib/doto/presenters/entry_group/list/nothing_to_list.rb +15 -0
- data/lib/doto/presenters/entry_group_presenter.rb +35 -0
- data/lib/doto/presenters/entry_presenter.rb +25 -0
- data/lib/doto/presenters/export/all_presenter.rb +44 -0
- data/lib/doto/presenters/export/dates_presenter.rb +55 -0
- data/lib/doto/presenters/import/all_presenter.rb +57 -0
- data/lib/doto/presenters/import/dates_presenter.rb +70 -0
- data/lib/doto/presenters/import/import_entry.rb +22 -0
- data/lib/doto/presenters/import/import_file.rb +33 -0
- data/lib/doto/presenters/project/create_presenter.rb +44 -0
- data/lib/doto/presenters/project/defaultable.rb +15 -0
- data/lib/doto/presenters/project/delete_by_number_presenter.rb +54 -0
- data/lib/doto/presenters/project/delete_presenter.rb +53 -0
- data/lib/doto/presenters/project/list_presenter.rb +24 -0
- data/lib/doto/presenters/project/rename_by_number_presenter.rb +63 -0
- data/lib/doto/presenters/project/rename_presenter.rb +57 -0
- data/lib/doto/presenters/project/use_by_number_presenter.rb +57 -0
- data/lib/doto/presenters/project/use_presenter.rb +56 -0
- data/lib/doto/services/color_theme/hydrator_service.rb +42 -0
- data/lib/doto/services/configuration/hydrator_service.rb +42 -0
- data/lib/doto/services/entry/hydrator_service.rb +33 -0
- data/lib/doto/services/entry_group/browse_service.rb +100 -0
- data/lib/doto/services/entry_group/counter_service.rb +32 -0
- data/lib/doto/services/entry_group/deleter_service.rb +35 -0
- data/lib/doto/services/entry_group/editor_service.rb +103 -0
- data/lib/doto/services/entry_group/exporter_service.rb +98 -0
- data/lib/doto/services/entry_group/hydrator_service.rb +37 -0
- data/lib/doto/services/entry_group/importer_service.rb +117 -0
- data/lib/doto/services/migration_version/hydrator_service.rb +36 -0
- data/lib/doto/services/project/hydrator_service.rb +40 -0
- data/lib/doto/services/project/rename_service.rb +70 -0
- data/lib/doto/services/stderr_redirector_service.rb +27 -0
- data/lib/doto/services/stdout_redirector_service.rb +27 -0
- data/lib/doto/services/temp_file/reader_service.rb +33 -0
- data/lib/doto/services/temp_file/writer_service.rb +35 -0
- data/lib/doto/subcommands/base_subcommand.rb +12 -0
- data/lib/doto/subcommands/browse.rb +49 -0
- data/lib/doto/subcommands/config.rb +81 -0
- data/lib/doto/subcommands/delete.rb +108 -0
- data/lib/doto/subcommands/edit.rb +48 -0
- data/lib/doto/subcommands/export.rb +62 -0
- data/lib/doto/subcommands/import.rb +72 -0
- data/lib/doto/subcommands/list.rb +95 -0
- data/lib/doto/subcommands/project.rb +146 -0
- data/lib/doto/subcommands/theme.rb +131 -0
- data/lib/doto/support/ask.rb +44 -0
- data/lib/doto/support/color_themable.rb +36 -0
- data/lib/doto/support/command_help_colorizeable.rb +34 -0
- data/lib/doto/support/command_hookable.rb +71 -0
- data/lib/doto/support/command_options/doto_times.rb +48 -0
- data/lib/doto/support/command_options/time.rb +84 -0
- data/lib/doto/support/command_options/time_mnemonic.rb +108 -0
- data/lib/doto/support/command_options/time_mnemonics.rb +16 -0
- data/lib/doto/support/descriptable.rb +29 -0
- data/lib/doto/support/entry_group_browsable.rb +104 -0
- data/lib/doto/support/field_errors.rb +11 -0
- data/lib/doto/support/fileable.rb +136 -0
- data/lib/doto/support/presentable.rb +11 -0
- data/lib/doto/support/project_file_system.rb +118 -0
- data/lib/doto/support/short_string.rb +24 -0
- data/lib/doto/support/time_comparable.rb +21 -0
- data/lib/doto/support/time_formatable.rb +65 -0
- data/lib/doto/support/times_sortable.rb +71 -0
- data/lib/doto/support/transform_project_name.rb +24 -0
- data/lib/doto/support/utils.rb +11 -0
- data/lib/doto/validators/color_theme_validator.rb +74 -0
- data/lib/doto/validators/description_validator.rb +51 -0
- data/lib/doto/validators/entries_validator.rb +77 -0
- data/lib/doto/validators/project_name_validator.rb +58 -0
- data/lib/doto/validators/time_validator.rb +25 -0
- data/lib/doto/validators/version_validator.rb +29 -0
- data/lib/doto/version.rb +6 -0
- data/lib/doto/views/base_list_view.rb +41 -0
- data/lib/doto/views/color_theme/index.rb +62 -0
- data/lib/doto/views/color_theme/show.rb +107 -0
- data/lib/doto/views/configuration/show.rb +41 -0
- data/lib/doto/views/entry_group/edit.rb +121 -0
- data/lib/doto/views/entry_group/list.rb +23 -0
- data/lib/doto/views/entry_group/shared/no_entries_to_display.rb +53 -0
- data/lib/doto/views/entry_group/shared/no_entries_to_display_for_month_of.rb +32 -0
- data/lib/doto/views/entry_group/shared/no_entries_to_display_for_week_of.rb +33 -0
- data/lib/doto/views/entry_group/shared/no_entries_to_display_for_year_of.rb +33 -0
- data/lib/doto/views/entry_group/show.rb +63 -0
- data/lib/doto/views/export.rb +82 -0
- data/lib/doto/views/import.rb +105 -0
- data/lib/doto/views/import_dates.rb +17 -0
- data/lib/doto/views/project/create.rb +87 -0
- data/lib/doto/views/project/delete.rb +96 -0
- data/lib/doto/views/project/delete_by_number.rb +19 -0
- data/lib/doto/views/project/list.rb +115 -0
- data/lib/doto/views/project/rename.rb +98 -0
- data/lib/doto/views/project/rename_by_number.rb +21 -0
- data/lib/doto/views/project/use.rb +97 -0
- data/lib/doto/views/project/use_by_number.rb +19 -0
- data/lib/doto/views/shared/error.rb +17 -0
- data/lib/doto/views/shared/info.rb +17 -0
- data/lib/doto/views/shared/message.rb +85 -0
- data/lib/doto/views/shared/model_errors.rb +32 -0
- data/lib/doto/views/shared/success.rb +17 -0
- data/lib/doto/views/shared/warning.rb +17 -0
- data/lib/doto.rb +33 -0
- data/lib/locales/en/active_record.yml +17 -0
- data/lib/locales/en/commands.yml +165 -0
- data/lib/locales/en/miscellaneous.yml +29 -0
- data/lib/locales/en/presenters.yml +19 -0
- data/lib/locales/en/services.yml +14 -0
- data/lib/locales/en/subcommands.yml +786 -0
- data/lib/seed_data/0/.todo +5 -0
- data/lib/seed_data/20230613121411/.doto +8 -0
- data/lib/seed_data/20230613121411/doto/migration_version.json +3 -0
- data/lib/seed_data/20230613121411/doto/themes/cherry.json +79 -0
- data/lib/seed_data/20230613121411/doto/themes/christmas.json +79 -0
- data/lib/seed_data/20230613121411/doto/themes/default.json +79 -0
- data/lib/seed_data/20230613121411/doto/themes/lemon.json +79 -0
- data/lib/seed_data/20230613121411/doto/themes/light.json +79 -0
- data/lib/seed_data/20230613121411/doto/themes/matrix.json +79 -0
- data/lib/seed_data/20230613121411/doto/themes/whiteout.json +79 -0
- data/lib/seed_data/20240210161248/.doto +9 -0
- data/lib/seed_data/20240210161248/doto/current_project.json +4 -0
- data/lib/seed_data/20240210161248/doto/migration_version.json +3 -0
- data/lib/seed_data/20240210161248/doto/projects/default/project.json +5 -0
- data/lib/seed_data/20240210161248/doto/themes/cherry.json +79 -0
- data/lib/seed_data/20240210161248/doto/themes/christmas.json +79 -0
- data/lib/seed_data/20240210161248/doto/themes/default.json +79 -0
- data/lib/seed_data/20240210161248/doto/themes/lemon.json +79 -0
- data/lib/seed_data/20240210161248/doto/themes/light.json +79 -0
- data/lib/seed_data/20240210161248/doto/themes/matrix.json +79 -0
- data/lib/seed_data/20240210161248/doto/themes/whiteout.json +79 -0
- data/sig/dsu.rbs +4 -0
- metadata +406 -0
|
@@ -0,0 +1,295 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
|
|
5
|
+
require_relative '../crud/json_file'
|
|
6
|
+
require_relative '../migration/version'
|
|
7
|
+
require_relative '../models/configuration'
|
|
8
|
+
require_relative '../services/project/hydrator_service'
|
|
9
|
+
require_relative '../services/project/rename_service'
|
|
10
|
+
require_relative '../support/descriptable'
|
|
11
|
+
require_relative '../support/fileable'
|
|
12
|
+
require_relative '../support/project_file_system'
|
|
13
|
+
require_relative '../validators/description_validator'
|
|
14
|
+
require_relative '../validators/project_name_validator'
|
|
15
|
+
require_relative '../validators/version_validator'
|
|
16
|
+
|
|
17
|
+
module Doto
|
|
18
|
+
module Models
|
|
19
|
+
# This class represents a project. A project is a collection of entry groups.
|
|
20
|
+
class Project
|
|
21
|
+
include ActiveModel::Model
|
|
22
|
+
include Support::Descriptable
|
|
23
|
+
include Support::Fileable
|
|
24
|
+
include Support::ProjectFileSystem
|
|
25
|
+
|
|
26
|
+
VERSION = Migration::VERSION
|
|
27
|
+
MIN_PROJECT_NAME_LENGTH = 2
|
|
28
|
+
MAX_PROJECT_NAME_LENGTH = 32
|
|
29
|
+
MIN_DESCRIPTION_LENGTH = 2
|
|
30
|
+
MAX_DESCRIPTION_LENGTH = 64
|
|
31
|
+
|
|
32
|
+
attr_reader :project_name, :current_project_file, :description, :version, :options
|
|
33
|
+
|
|
34
|
+
validates_with Validators::DescriptionValidator
|
|
35
|
+
validates_with Validators::ProjectNameValidator
|
|
36
|
+
validates_with Validators::VersionValidator
|
|
37
|
+
|
|
38
|
+
def initialize(project_name:, description: nil, version: nil, options: {})
|
|
39
|
+
raise ArgumentError, 'project_name is blank' if project_name.blank?
|
|
40
|
+
raise ArgumentError, 'version is the wrong object type' unless version.is_a?(Integer) || version.nil?
|
|
41
|
+
|
|
42
|
+
self.project_name = project_name
|
|
43
|
+
self.description = description
|
|
44
|
+
self.version = version || VERSION
|
|
45
|
+
self.options = options || {}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Override == and hash so that we can compare Entry Group objects.
|
|
49
|
+
def ==(other)
|
|
50
|
+
other.is_a?(Project) &&
|
|
51
|
+
project_name == other.project_name &&
|
|
52
|
+
description == other.description &&
|
|
53
|
+
version == other.version
|
|
54
|
+
end
|
|
55
|
+
alias eql? ==
|
|
56
|
+
|
|
57
|
+
def can_delete?
|
|
58
|
+
self.class.can_delete?(project_name: project_name)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def create
|
|
62
|
+
self.class.create(project_name: project_name, description: description)
|
|
63
|
+
end
|
|
64
|
+
alias save create
|
|
65
|
+
|
|
66
|
+
def create!
|
|
67
|
+
self.class.create!(project_name: project_name, description: description)
|
|
68
|
+
end
|
|
69
|
+
alias save! create!
|
|
70
|
+
|
|
71
|
+
def current_project?
|
|
72
|
+
self.class.current_project?(project_name: project_name)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def default!
|
|
76
|
+
return if default_project?
|
|
77
|
+
|
|
78
|
+
self.class.default!(project: self)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def default_project?
|
|
82
|
+
self.class.default_project?(project_name: project_name)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def delete
|
|
86
|
+
self.class.delete(project_name: project_name)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def delete!
|
|
90
|
+
self.class.delete!(project_name: project_name)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def hash
|
|
94
|
+
[project_name, description, version].map(&:hash).hash
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def project_file
|
|
98
|
+
self.class.project_file(project_name: project_name)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def project_folder
|
|
102
|
+
self.class.project_folder(project_name: project_name)
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def rename!(new_project_name:, new_project_description: nil)
|
|
106
|
+
self.class.rename!(project_name: project_name,
|
|
107
|
+
new_project_name: new_project_name, new_project_description: new_project_description, options: options)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def to_h
|
|
111
|
+
{
|
|
112
|
+
version: version,
|
|
113
|
+
project_name: project_name,
|
|
114
|
+
description: description
|
|
115
|
+
}
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
# def update
|
|
119
|
+
# self.class.update(project_name: project_name, description: description, version: version, options: options)
|
|
120
|
+
# end
|
|
121
|
+
|
|
122
|
+
# def update!
|
|
123
|
+
# self.class.update!(project_name: project_name, description: description, version: version, options: options)
|
|
124
|
+
# end
|
|
125
|
+
|
|
126
|
+
def use!
|
|
127
|
+
return if current_project?
|
|
128
|
+
|
|
129
|
+
self.class.use!(project: self)
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
class << self
|
|
133
|
+
delegate :project_file_for, :project_folder_for, to: Support::Fileable
|
|
134
|
+
|
|
135
|
+
def all
|
|
136
|
+
project_metadata.map do |metadata|
|
|
137
|
+
find(project_name: metadata[:project_name])
|
|
138
|
+
end
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
def can_delete?(project_name:)
|
|
142
|
+
exist?(project_name: project_name) &&
|
|
143
|
+
# Cannot delete the last project.
|
|
144
|
+
count > 1 &&
|
|
145
|
+
# Do not allow the project to be deleted if it
|
|
146
|
+
# is currently the default project.
|
|
147
|
+
# The user needs to change to another default
|
|
148
|
+
# project before they can delete this project.
|
|
149
|
+
!default_project?(project_name: project_name)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def count
|
|
153
|
+
project_metadata.count
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def create(project_name:, description: nil, options: {})
|
|
157
|
+
Models::Project.new(project_name: project_name, description: description, options: options).tap do |project|
|
|
158
|
+
project.validate!
|
|
159
|
+
initialize_project(project_name: project_name)
|
|
160
|
+
Crud::JsonFile.write!(file_data: project.to_h,
|
|
161
|
+
file_path: project_file_for(project_name: project_name))
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
alias update create
|
|
165
|
+
|
|
166
|
+
def create!(project_name:, description: nil, options: {})
|
|
167
|
+
if exist?(project_name: project_name)
|
|
168
|
+
raise I18n.t('models.project.errors.already_exists', project_name: project_name)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
create(project_name: project_name, description: description, options: options)
|
|
172
|
+
end
|
|
173
|
+
alias update! create!
|
|
174
|
+
|
|
175
|
+
def current_project
|
|
176
|
+
find(project_name: current_project_name)
|
|
177
|
+
end
|
|
178
|
+
|
|
179
|
+
def current_project?(project_name:)
|
|
180
|
+
current_project_name == project_name
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def default!(project:)
|
|
184
|
+
project.validate!
|
|
185
|
+
|
|
186
|
+
Models::Configuration.new.tap do |configuration|
|
|
187
|
+
configuration.default_project = project.project_name
|
|
188
|
+
configuration.save!
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
def default_project
|
|
193
|
+
find(project_name: default_project_name)
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
def default_project?(project_name:)
|
|
197
|
+
project_name == default_project_name
|
|
198
|
+
end
|
|
199
|
+
|
|
200
|
+
def delete(project_name:)
|
|
201
|
+
return false unless can_delete?(project_name: project_name)
|
|
202
|
+
|
|
203
|
+
project_folder = project_folder_for(project_name: project_name)
|
|
204
|
+
FileUtils.rm_rf(project_folder)
|
|
205
|
+
|
|
206
|
+
true
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def delete!(project_name:)
|
|
210
|
+
unless exist?(project_name: project_name)
|
|
211
|
+
raise I18n.t('models.project.errors.does_not_exist', project_name: project_name)
|
|
212
|
+
end
|
|
213
|
+
|
|
214
|
+
raise I18n.t('models.project.errors.delete_only_project', project_name: project_name) unless count > 1
|
|
215
|
+
|
|
216
|
+
if default_project?(project_name: project_name)
|
|
217
|
+
raise I18n.t('models.project.errors.delete_default_project', project_name: project_name)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
delete(project_name: project_name)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
def find(project_name:)
|
|
224
|
+
unless project_folder_exist?(project_name: project_name)
|
|
225
|
+
raise I18n.t('models.project.errors.does_not_exist', project_name: project_name)
|
|
226
|
+
end
|
|
227
|
+
|
|
228
|
+
project_file = project_file_for(project_name: project_name)
|
|
229
|
+
|
|
230
|
+
unless project_file_exist?(project_name: project_name)
|
|
231
|
+
raise I18n.t('models.project.errors.project_file_not_exist', project_file: project_file)
|
|
232
|
+
end
|
|
233
|
+
|
|
234
|
+
project_hash = Crud::JsonFile.read!(file_path: project_file)
|
|
235
|
+
Services::Project::HydratorService.new(project_hash: project_hash).call
|
|
236
|
+
end
|
|
237
|
+
|
|
238
|
+
# project_number is 1 based.
|
|
239
|
+
def find_by_number(project_number:)
|
|
240
|
+
project = project_metadata.find do |metadata|
|
|
241
|
+
metadata[:project_number] == project_number.to_i
|
|
242
|
+
end
|
|
243
|
+
return unless project
|
|
244
|
+
|
|
245
|
+
find(project_name: project[:project_name])
|
|
246
|
+
end
|
|
247
|
+
|
|
248
|
+
# def find_or_create(project_name:)
|
|
249
|
+
# find_or_initialize(project_name: project_name).tap do |project|
|
|
250
|
+
# project.save! unless project.persisted?
|
|
251
|
+
# end
|
|
252
|
+
# end
|
|
253
|
+
|
|
254
|
+
def find_or_initialize(project_name:)
|
|
255
|
+
return Models::Project.new(project_name: project_name) unless project_file_exist?(project_name: project_name)
|
|
256
|
+
|
|
257
|
+
project_file = project_file_for(project_name: project_name)
|
|
258
|
+
project_hash = Crud::JsonFile.read!(file_path: project_file)
|
|
259
|
+
Services::Project::HydratorService.new(project_hash: project_hash).call
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def rename!(project_name:, new_project_name:, new_project_description: nil, options: {})
|
|
263
|
+
Services::Project::RenameService.new(from_project_name: project_name,
|
|
264
|
+
to_project_name: new_project_name, to_project_description: new_project_description, options: options).call
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
def use!(project:)
|
|
268
|
+
project.validate!
|
|
269
|
+
|
|
270
|
+
current_project_hash = { version: project.version, project_name: project.project_name }
|
|
271
|
+
Crud::JsonFile.write!(file_data: current_project_hash, file_path: current_project_file)
|
|
272
|
+
end
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
private
|
|
276
|
+
|
|
277
|
+
attr_writer :current_project_file, :options, :version
|
|
278
|
+
|
|
279
|
+
def description=(value)
|
|
280
|
+
@description = if value.blank?
|
|
281
|
+
"#{project_name} project"
|
|
282
|
+
else
|
|
283
|
+
value
|
|
284
|
+
end
|
|
285
|
+
end
|
|
286
|
+
|
|
287
|
+
def project_name=(value)
|
|
288
|
+
@project_name = begin
|
|
289
|
+
@current_project_file = project_folder_for(project_name: value)
|
|
290
|
+
value
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
end
|
|
294
|
+
end
|
|
295
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'delegate'
|
|
4
|
+
require_relative '../models/color_theme'
|
|
5
|
+
require_relative '../support/color_themable'
|
|
6
|
+
|
|
7
|
+
module Doto
|
|
8
|
+
module Presenters
|
|
9
|
+
class BasePresenter < SimpleDelegator
|
|
10
|
+
include Support::ColorThemable
|
|
11
|
+
|
|
12
|
+
attr_reader :color_theme
|
|
13
|
+
|
|
14
|
+
def initialize(object, options: {})
|
|
15
|
+
super(object)
|
|
16
|
+
|
|
17
|
+
@options = options || {}
|
|
18
|
+
theme_name = options.fetch(:theme_name, Models::Configuration.new.theme_name)
|
|
19
|
+
@color_theme = Models::ColorTheme.find(theme_name: theme_name)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
attr_reader :options
|
|
25
|
+
|
|
26
|
+
def formatted_index(index:)
|
|
27
|
+
apply_theme("#{format('%02s', index + 1)}. ",
|
|
28
|
+
theme_color: color_theme.index)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_presenter'
|
|
4
|
+
|
|
5
|
+
module Doto
|
|
6
|
+
module Presenters
|
|
7
|
+
class ColorThemePresenter < BasePresenter
|
|
8
|
+
attr_reader :color_theme
|
|
9
|
+
|
|
10
|
+
def initialize(color_theme, options: {})
|
|
11
|
+
super
|
|
12
|
+
|
|
13
|
+
@color_theme = color_theme
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def header
|
|
17
|
+
header = I18n.t('presenters.color_theme_presenter.headers.color_themes')
|
|
18
|
+
apply_theme(header, theme_color: color_theme.subheader)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def footer
|
|
22
|
+
header = I18n.t('presenters.color_theme_presenter.headers.current_theme')
|
|
23
|
+
apply_theme(header, theme_color: color_theme.footer)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def detail
|
|
27
|
+
"#{apply_theme(theme_name_formatted, theme_color: color_theme.body)} - " \
|
|
28
|
+
"#{apply_theme(description, theme_color: color_theme.body)}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def detail_with_index(index:)
|
|
32
|
+
"#{formatted_index(index: index)} #{detail}"
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
private
|
|
36
|
+
|
|
37
|
+
def theme_name_formatted
|
|
38
|
+
return theme_name unless default_color_theme?
|
|
39
|
+
|
|
40
|
+
"*#{theme_name}"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def default_color_theme?
|
|
44
|
+
theme_name == default_color_theme.theme_name
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def default_color_theme
|
|
48
|
+
@default_color_theme ||= Models::ColorTheme.current_or_default
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_presenter'
|
|
4
|
+
|
|
5
|
+
module Doto
|
|
6
|
+
module Presenters
|
|
7
|
+
class ColorThemeShowPresenter < BasePresenter
|
|
8
|
+
def initialize(color_theme, options: {})
|
|
9
|
+
super(color_theme, options: options.merge(theme_name: color_theme.theme_name))
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def detail
|
|
13
|
+
headers = [I18n.t('presenters.color_theme_show_presenter.headers.number'),
|
|
14
|
+
I18n.t('presenters.color_theme_show_presenter.headers.color'),
|
|
15
|
+
I18n.t('presenters.color_theme_show_presenter.headers.values')]
|
|
16
|
+
puts_detail(*headers, header: true)
|
|
17
|
+
|
|
18
|
+
Models::ColorTheme::DEFAULT_THEME_COLORS.keys.each_with_index do |color_key, index|
|
|
19
|
+
index = formatted_index(index: index)
|
|
20
|
+
color_hash = color_theme.public_send(color_key)
|
|
21
|
+
puts_detail(index, color_key, color_hash)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def detail_with_index(index:)
|
|
26
|
+
"#{formatted_index(index: index)} #{detail}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def footer
|
|
30
|
+
header = I18n.t('presenters.color_theme_show_presenter.headers.footer_example')
|
|
31
|
+
apply_theme(header, theme_color: color_theme.footer)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def header
|
|
35
|
+
header = I18n.t('presenters.color_theme_show_presenter.headers.viewing_color_theme',
|
|
36
|
+
theme_name: color_theme.theme_name)
|
|
37
|
+
apply_theme(header, theme_color: color_theme.subheader)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
def puts_detail(index, color_key, color_hash, header: false)
|
|
43
|
+
if header
|
|
44
|
+
puts "#{apply_theme(index.to_s.ljust(4), theme_color: color_theme.index.bold!)} " \
|
|
45
|
+
"#{apply_theme(color_key.to_s.ljust(15), theme_color: color_theme.index.bold!)} " \
|
|
46
|
+
"#{apply_theme(color_hash.to_s.ljust(10), theme_color: color_theme.index.bold!)}"
|
|
47
|
+
else
|
|
48
|
+
puts "#{apply_theme(index.to_s.ljust(4), theme_color: color_theme.index)} " \
|
|
49
|
+
"#{apply_theme(color_key.to_s.ljust(15), theme_color: color_hash)} " \
|
|
50
|
+
"#{apply_theme(color_hash.to_s.ljust(10), theme_color: color_theme.body)}"
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../env'
|
|
4
|
+
require_relative '../support/color_themable'
|
|
5
|
+
require_relative 'base_presenter'
|
|
6
|
+
|
|
7
|
+
module Doto
|
|
8
|
+
module Presenters
|
|
9
|
+
class ConfigurationPresenter < BasePresenter
|
|
10
|
+
attr_reader :config
|
|
11
|
+
|
|
12
|
+
def initialize(config, options: {})
|
|
13
|
+
super
|
|
14
|
+
|
|
15
|
+
@config = config
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def configuration_header
|
|
19
|
+
header = I18n.t('presenters.configuration_presenter.headers.file_contents', config_path: config_path)
|
|
20
|
+
apply_theme(header, theme_color: color_theme.header)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def configuration_details
|
|
24
|
+
to_h.each_with_index.filter_map do |config_entry, index|
|
|
25
|
+
formatted_config_entry_with_index(config_entry, index: index, theme_color: color_theme.body)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
def config_path
|
|
32
|
+
@config_path ||= if Doto.env.screen_shot_mode?
|
|
33
|
+
"/Users/#{Doto.env.screen_shot_username}/.doto"
|
|
34
|
+
else
|
|
35
|
+
config.file_path
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def formatted_config_entry_with_index(config_entry, index:, theme_color:)
|
|
40
|
+
"#{formatted_index(index: index)} #{formatted_config_entry(config_entry: config_entry,
|
|
41
|
+
theme_color: theme_color)}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def formatted_config_entry(config_entry:, theme_color:)
|
|
45
|
+
config_entry = "#{config_entry[0]}: '#{config_entry[1]}'"
|
|
46
|
+
apply_theme(config_entry, theme_color: theme_color)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../../models/configuration'
|
|
4
|
+
require_relative '../../../models/entry_group'
|
|
5
|
+
require_relative '../../../views/entry_group/shared/no_entries_to_display'
|
|
6
|
+
require_relative '../../base_presenter_ex'
|
|
7
|
+
require_relative 'messages'
|
|
8
|
+
require_relative 'nothing_to_list'
|
|
9
|
+
|
|
10
|
+
module Doto
|
|
11
|
+
module Presenters
|
|
12
|
+
module EntryGroup
|
|
13
|
+
module List
|
|
14
|
+
class DatePresenter < BasePresenterEx
|
|
15
|
+
include Messages
|
|
16
|
+
include NothingToList
|
|
17
|
+
|
|
18
|
+
def initialize(times:, options: {})
|
|
19
|
+
raise ArgumentError, 'times must be an Array' unless times.is_a?(Array)
|
|
20
|
+
raise ArgumentError, 'options must be a Hash' unless options.is_a?(Hash)
|
|
21
|
+
|
|
22
|
+
super(options: options)
|
|
23
|
+
|
|
24
|
+
@times = times
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def render
|
|
28
|
+
return if nothing_to_list?
|
|
29
|
+
|
|
30
|
+
entry_groups.each do |entry_group|
|
|
31
|
+
Views::EntryGroup::Show.new(entry_group: entry_group).render
|
|
32
|
+
puts
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def display_nothing_to_list_message
|
|
37
|
+
# This presenter will ALWAYS have something to list (display) since the first
|
|
38
|
+
# and last (if different) entry groups will always be displayed.
|
|
39
|
+
raise 'display_nothing_to_list_message called when there are entries to display'
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
attr_reader :times
|
|
45
|
+
|
|
46
|
+
def entry_groups
|
|
47
|
+
@entry_groups ||= begin
|
|
48
|
+
options = configuration.to_h.merge(self.options).with_indifferent_access
|
|
49
|
+
|
|
50
|
+
times.filter_map do |time|
|
|
51
|
+
view_options = options.dup
|
|
52
|
+
# Always show the first and last entry groups.
|
|
53
|
+
view_options[:include_all] = true if times_min_max.include?(time)
|
|
54
|
+
|
|
55
|
+
next unless show_entry_group?(time: time, options: view_options)
|
|
56
|
+
|
|
57
|
+
Models::EntryGroup.find_or_initialize(time: time)
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def times_min_max
|
|
63
|
+
@times_min_max ||= times.minmax
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def configuration
|
|
67
|
+
@configuration ||= Models::Configuration.new
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def show_entry_group?(time:, options:)
|
|
71
|
+
Models::EntryGroup.exist?(time: time) || options[:include_all]
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../../models/entry_group'
|
|
4
|
+
require_relative '../../../views/entry_group/shared/no_entries_to_display'
|
|
5
|
+
require_relative '../../base_presenter_ex'
|
|
6
|
+
require_relative 'messages'
|
|
7
|
+
require_relative 'nothing_to_list'
|
|
8
|
+
|
|
9
|
+
module Doto
|
|
10
|
+
module Presenters
|
|
11
|
+
module EntryGroup
|
|
12
|
+
module List
|
|
13
|
+
class DatesPresenter < BasePresenterEx
|
|
14
|
+
include Messages
|
|
15
|
+
include NothingToList
|
|
16
|
+
|
|
17
|
+
def initialize(times:, options: {})
|
|
18
|
+
raise ArgumentError, 'times must be an Array' unless times.is_a?(Array)
|
|
19
|
+
raise ArgumentError, 'options must be a Hash' unless options.is_a?(Hash)
|
|
20
|
+
|
|
21
|
+
super(options: options)
|
|
22
|
+
|
|
23
|
+
@times = times
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def render
|
|
27
|
+
return if nothing_to_list?
|
|
28
|
+
|
|
29
|
+
entry_groups.each do |entry_group|
|
|
30
|
+
Views::EntryGroup::Show.new(entry_group: entry_group).render
|
|
31
|
+
puts
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def display_nothing_to_list_message
|
|
36
|
+
raise 'display_nothing_to_list_message called when there are entries to display' unless nothing_to_list?
|
|
37
|
+
|
|
38
|
+
Views::EntryGroup::Shared::NoEntriesToDisplay.new(times: times, options: options).render
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
attr_reader :times
|
|
44
|
+
|
|
45
|
+
def entry_groups
|
|
46
|
+
@entry_groups ||= times.filter_map do |time|
|
|
47
|
+
next unless show_entry_group?(time: time, options: options)
|
|
48
|
+
|
|
49
|
+
Models::EntryGroup.find_or_initialize(time: time)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def show_entry_group?(time:, options:)
|
|
54
|
+
Models::EntryGroup.exist?(time: time) || options[:include_all]
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|