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
data/README.md
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
# `dsu`
|
2
2
|
|
3
3
|
[](https://github.com/gangelo/dsu/actions/workflows/ruby.yml)
|
4
|
-
[](https://badge.fury.io/gh/gangelo%2Fdsu)
|
5
|
+
[](https://badge.fury.io/rb/dsu)
|
6
6
|
[](http://www.rubydoc.info/gems/dsu/)
|
7
7
|
[](https://github.com/gangelo/dsu/issues)
|
8
8
|
[](#license)
|
9
9
|
|
10
|
-
`dsu` is a [ruby gem](https://rubygems.org/gems/dsu) that enables anyone practicing [Agile methodology](https://www.agilealliance.org/agile101/) to record, keep track of and manage their [daily standup (DSU)](https://www.agilealliance.org/glossary/daily-meeting/) activities.
|
10
|
+
`dsu` is a [ruby gem](https://rubygems.org/gems/dsu) that enables anyone practicing the [Agile methodology](https://www.agilealliance.org/agile101/) to record, keep track of and manage their [daily standup (DSU)](https://www.agilealliance.org/glossary/daily-meeting/) activities.
|
11
11
|
|
12
12
|
- `dsu` uses _no_ network connections whatsoever.
|
13
13
|
- `dsu` stores all of its data _locally_, in .json files.
|
@@ -22,12 +22,11 @@ gem install dsu
|
|
22
22
|
```
|
23
23
|
|
24
24
|
# Documentation
|
25
|
-
[
|
25
|
+
The [dsu wiki](https://github.com/gangelo/dsu/wiki) is currently the gold standard for `dsu` documentation.
|
26
26
|
|
27
27
|
# Examples
|
28
|
-
The [dsu wiki](https://github.com/gangelo/dsu/wiki) is repleat with practical examples on how to use `dsu`.
|
29
|
-
|
30
|
-
It you're interested in how _I personally_ use `dsu` every day, I blog about it [here](https://genemangelojr.blogspot.com/2024/01/the-dsu-ruby-gem-workflow-how-to-use-it.html).
|
28
|
+
* The [dsu wiki](https://github.com/gangelo/dsu/wiki) is repleat with practical examples on how to use `dsu`.
|
29
|
+
* Visit the [How I use dsu daily as an Agile developer](https://github.com/gangelo/dsu/wiki/How-I-use-dsu-daily-as-an-Agile-developer) wiki for examples of how _I_ use `dsu` on a daily basis.
|
31
30
|
|
32
31
|
# Supported ruby versions
|
33
32
|
`dsu` _should_ work with any ruby version `['>= 3.0.1', '< 4.0']`; however, `dsu` is currently tested against the following ruby versions:
|
@@ -36,4 +35,4 @@ It you're interested in how _I personally_ use `dsu` every day, I blog about it
|
|
36
35
|
- 3.1.4
|
37
36
|
- 3.2.2
|
38
37
|
|
39
|
-
Copyright (c) 2023-2024
|
38
|
+
Copyright (c) 2023-2024 Gene Angelo. See [LICENSE](https://github.com/gangelo/dsu/blob/main/LICENSE.txt) for details.
|
data/Rakefile
CHANGED
@@ -10,3 +10,9 @@ require "rubocop/rake_task"
|
|
10
10
|
RuboCop::RakeTask.new
|
11
11
|
|
12
12
|
task default: %i[spec rubocop]
|
13
|
+
|
14
|
+
desc 'Generate a migration timestamp'
|
15
|
+
task :timestamp do
|
16
|
+
puts 'The below migration timestamp should be placed in the "lib/dsu/migration/version.rb" file.'
|
17
|
+
puts Time.now.strftime('%Y%m%d%H%M%S')
|
18
|
+
end
|
data/current_project.bak
ADDED
data/lib/dsu/cli.rb
CHANGED
@@ -11,12 +11,18 @@ require_relative 'subcommands/edit'
|
|
11
11
|
require_relative 'subcommands/export'
|
12
12
|
require_relative 'subcommands/import'
|
13
13
|
require_relative 'subcommands/list'
|
14
|
+
require_relative 'subcommands/project'
|
14
15
|
require_relative 'subcommands/theme'
|
16
|
+
require_relative 'support/command_options/time_mnemonic'
|
17
|
+
require_relative 'support/time_formatable'
|
15
18
|
require_relative 'views/entry_group/list'
|
16
19
|
|
17
20
|
module Dsu
|
18
21
|
# The `dsu` command.
|
19
22
|
class CLI < BaseCLI
|
23
|
+
include Support::CommandOptions::TimeMnemonic
|
24
|
+
include Support::TimeFormatable
|
25
|
+
|
20
26
|
map I18n.t('commands.add.key_mappings') => :add
|
21
27
|
map I18n.t('commands.browse.key_mappings') => :browse
|
22
28
|
map I18n.t('commands.config.key_mappings') => :config
|
@@ -27,30 +33,39 @@ module Dsu
|
|
27
33
|
map I18n.t('commands.import.key_mappings') => :import
|
28
34
|
map I18n.t('commands.info.key_mappings') => :info
|
29
35
|
map I18n.t('commands.list.key_mappings') => :list
|
36
|
+
map I18n.t('commands.project.key_mappings') => :project
|
30
37
|
map I18n.t('commands.theme.key_mappings') => :theme
|
31
38
|
map I18n.t('commands.version.key_mappings') => :version
|
32
39
|
|
33
40
|
desc I18n.t('commands.add.desc'), I18n.t('commands.add.usage')
|
34
|
-
long_desc I18n.t('commands.add.long_desc',
|
41
|
+
long_desc I18n.t('commands.add.long_desc',
|
42
|
+
date_option_description: date_option_description, mnemonic_option_description: mnemonic_option_description)
|
35
43
|
option I18n.t('options.date.name'), aliases: I18n.t('options.date.aliases'), type: :string
|
36
44
|
option I18n.t('options.tomorrow.name'), aliases: I18n.t('options.tomorrow.aliases'), type: :boolean
|
37
45
|
option I18n.t('options.yesterday.name'), aliases: I18n.t('options.yesterday.aliases'), type: :boolean
|
38
46
|
option I18n.t('options.today.name'), aliases: I18n.t('options.today.aliases'), type: :boolean, default: true
|
39
47
|
def add(description)
|
40
|
-
|
41
|
-
|
48
|
+
date_or_mnemonic = if options[I18n.t('options.date.name')].present?
|
49
|
+
options[I18n.t('options.date.name')]
|
42
50
|
elsif options[I18n.t('options.tomorrow.name')].present?
|
43
|
-
|
51
|
+
I18n.t('options.tomorrow.name')
|
44
52
|
elsif options[I18n.t('options.yesterday.name')].present?
|
45
|
-
|
53
|
+
I18n.t('options.yesterday.name')
|
46
54
|
elsif options[I18n.t('options.today.name')].present?
|
47
|
-
|
55
|
+
I18n.t('options.today.name')
|
56
|
+
end
|
57
|
+
time = if time_mnemonic?(date_or_mnemonic)
|
58
|
+
time_from_mnemonic(command_option: date_or_mnemonic)
|
59
|
+
else
|
60
|
+
Time.parse(date_or_mnemonic)
|
48
61
|
end
|
49
62
|
entry = Models::Entry.new(description: description)
|
50
63
|
CommandServices::AddEntryService.new(entry: entry, time: time).call
|
51
64
|
presenter = Presenters::EntryGroup::List::DatePresenter.new(times: [time], options: options)
|
52
65
|
# TODO: Refactor View::EntryGroup::Show to accept a presenter and use it here
|
53
66
|
Views::EntryGroup::List.new(presenter: presenter).render
|
67
|
+
rescue ArgumentError => e
|
68
|
+
Views::Shared::Error.new(messages: e.message).render
|
54
69
|
end
|
55
70
|
|
56
71
|
desc I18n.t('commands.browse.desc'), I18n.t('commands.browse.usage')
|
@@ -59,6 +74,9 @@ module Dsu
|
|
59
74
|
desc I18n.t('commands.list.desc'), I18n.t('commands.list.usage')
|
60
75
|
subcommand :list, Subcommands::List
|
61
76
|
|
77
|
+
desc I18n.t('commands.project.desc'), I18n.t('commands.project.usage')
|
78
|
+
subcommand :project, Subcommands::Project
|
79
|
+
|
62
80
|
desc I18n.t('commands.config.desc'), I18n.t('commands.config.usage')
|
63
81
|
subcommand :config, Subcommands::Config
|
64
82
|
|
data/lib/dsu/crud/json_file.rb
CHANGED
@@ -114,6 +114,9 @@ module Dsu
|
|
114
114
|
end
|
115
115
|
|
116
116
|
def write!(file_data:, file_path:)
|
117
|
+
# TODO: Should we be raising an error if the file does not exist?
|
118
|
+
# raise file_does_not_exist_message(file_path: file_path) unless file_exist?(file_path: file_path)
|
119
|
+
|
117
120
|
write(file_data: file_data, file_path: file_path)
|
118
121
|
end
|
119
122
|
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'json'
|
4
3
|
require_relative '../crud/json_file'
|
5
4
|
require_relative '../migration/version'
|
6
5
|
require_relative '../support/color_themable'
|
@@ -76,7 +75,7 @@ module Dsu
|
|
76
75
|
@theme_name = theme_name
|
77
76
|
@options = options || {}
|
78
77
|
|
79
|
-
super(self.class.send(:
|
78
|
+
super(self.class.send(:themes_path, theme_name: @theme_name))
|
80
79
|
|
81
80
|
theme_hash ||= DEFAULT_THEME.merge(description: "#{@theme_name.capitalize} theme")
|
82
81
|
|
@@ -112,6 +111,8 @@ module Dsu
|
|
112
111
|
end
|
113
112
|
|
114
113
|
class << self
|
114
|
+
delegate :themes_folder, :themes_path, to: Support::Fileable
|
115
|
+
|
115
116
|
def all
|
116
117
|
Dir.glob("#{themes_folder}/*").map do |file_path|
|
117
118
|
theme_name = File.basename(file_path, '.*')
|
@@ -141,11 +142,11 @@ module Dsu
|
|
141
142
|
end
|
142
143
|
|
143
144
|
def delete(theme_name:)
|
144
|
-
superclass.delete(file_path:
|
145
|
+
superclass.delete(file_path: themes_path(theme_name: theme_name))
|
145
146
|
end
|
146
147
|
|
147
148
|
def delete!(theme_name:)
|
148
|
-
superclass.delete!(file_path:
|
149
|
+
superclass.delete!(file_path: themes_path(theme_name: theme_name))
|
149
150
|
end
|
150
151
|
|
151
152
|
def ensure_color_theme_color_defaults_for(theme_hash: DEFAULT_THEME)
|
@@ -160,11 +161,11 @@ module Dsu
|
|
160
161
|
end
|
161
162
|
|
162
163
|
def exist?(theme_name:)
|
163
|
-
superclass.file_exist?(file_path:
|
164
|
+
superclass.file_exist?(file_path: themes_path(theme_name: theme_name))
|
164
165
|
end
|
165
166
|
|
166
167
|
def find(theme_name:)
|
167
|
-
theme_hash = read!(file_path:
|
168
|
+
theme_hash = read!(file_path: themes_path(theme_name: theme_name))
|
168
169
|
Services::ColorTheme::HydratorService.new(theme_name: theme_name, theme_hash: theme_hash).call
|
169
170
|
end
|
170
171
|
|
@@ -180,55 +181,11 @@ module Dsu
|
|
180
181
|
new(theme_name: theme_name)
|
181
182
|
end
|
182
183
|
|
183
|
-
# TODO: Unused?
|
184
|
-
# def build_color_theme(theme_name:, base_color:, description:)
|
185
|
-
# theme_hash = Models::ColorTheme.send(:replace, color_theme: default,
|
186
|
-
# replace_color: :cyan, with_color: base_color).tap do |hash|
|
187
|
-
# hash[:description] = description
|
188
|
-
# end
|
189
|
-
# new(theme_name: theme_name, theme_hash: theme_hash)
|
190
|
-
# end
|
191
|
-
|
192
184
|
private
|
193
185
|
|
194
186
|
def default_theme_color_keys
|
195
187
|
DEFAULT_THEME_COLORS.keys
|
196
188
|
end
|
197
|
-
|
198
|
-
def replace(color_theme:, replace_color:, with_color:)
|
199
|
-
colors_theme_hash = color_theme.to_theme_colors_h.tap do |hash|
|
200
|
-
hash.each_key do |key|
|
201
|
-
hash[key] = replace_color(theme_color: hash[key],
|
202
|
-
replace_color: replace_color, with_color: with_color)
|
203
|
-
end
|
204
|
-
end
|
205
|
-
DEFAULT_THEME.merge(colors_theme_hash)
|
206
|
-
end
|
207
|
-
|
208
|
-
def replace_color(theme_color:, replace_color:, with_color:)
|
209
|
-
%i[color background].each do |color_type|
|
210
|
-
color = theme_color[color_type].to_s.sub(replace_color.to_s, with_color.to_s)
|
211
|
-
theme_color[color_type] = color.sub('light_light_', 'light_').to_sym
|
212
|
-
end
|
213
|
-
theme_color
|
214
|
-
end
|
215
|
-
|
216
|
-
# If the color theme is deleted (deleted_theme_name) and the current
|
217
|
-
# theme_name in the configuration is the same as the deleted theme,
|
218
|
-
# we need to reset the configuration theme to the default theme.
|
219
|
-
def reset_default_configuration_color_theme_if!(deleted_theme_name:)
|
220
|
-
config = configuration
|
221
|
-
return if config.theme_name == self::DEFAULT_THEME_NAME
|
222
|
-
return unless config.theme_name == deleted_theme_name
|
223
|
-
return unless config.exist?
|
224
|
-
|
225
|
-
config.theme_name = self::DEFAULT_THEME_NAME
|
226
|
-
config.write!
|
227
|
-
end
|
228
|
-
|
229
|
-
def themes_path_for(theme_name:)
|
230
|
-
Support::Fileable.themes_path(theme_name: theme_name)
|
231
|
-
end
|
232
189
|
end
|
233
190
|
|
234
191
|
def to_h
|
@@ -239,14 +196,6 @@ module Dsu
|
|
239
196
|
end
|
240
197
|
end
|
241
198
|
|
242
|
-
def to_theme_colors_h
|
243
|
-
{}.tap do |hash|
|
244
|
-
DEFAULT_THEME_COLORS.each_key do |key|
|
245
|
-
hash[key] = public_send(key)
|
246
|
-
end
|
247
|
-
end
|
248
|
-
end
|
249
|
-
|
250
199
|
def ==(other)
|
251
200
|
return false unless other.is_a?(self.class)
|
252
201
|
return false unless other.theme_name == theme_name
|
@@ -42,7 +42,9 @@ module Dsu
|
|
42
42
|
# The currently selected color theme. Should be equal to
|
43
43
|
# Models::ColorTheme::DEFAULT_THEME_NAME or the name of a custom
|
44
44
|
# theme (with the same file name) that resides in the themes_folder.
|
45
|
-
theme_name: 'default'
|
45
|
+
theme_name: 'default',
|
46
|
+
# The default project to use.
|
47
|
+
default_project: 'default'
|
46
48
|
}.freeze
|
47
49
|
|
48
50
|
validates_with Validators::VersionValidator
|
@@ -53,13 +55,16 @@ module Dsu
|
|
53
55
|
validates :include_all, inclusion: { in: [true, false], message: 'must be true or false' }
|
54
56
|
validates :theme_name, presence: true
|
55
57
|
validate :validate_theme_file
|
58
|
+
validates :default_project, presence: true
|
59
|
+
validate :validate_default_project
|
56
60
|
|
57
61
|
attr_accessor :version,
|
58
62
|
:editor,
|
59
63
|
:entries_display_order,
|
60
64
|
:carry_over_entries_to_today,
|
61
65
|
:include_all,
|
62
|
-
:theme_name
|
66
|
+
:theme_name,
|
67
|
+
:default_project
|
63
68
|
|
64
69
|
attr_reader :options
|
65
70
|
|
@@ -114,7 +119,8 @@ module Dsu
|
|
114
119
|
entries_display_order: entries_display_order,
|
115
120
|
carry_over_entries_to_today: carry_over_entries_to_today,
|
116
121
|
include_all: include_all,
|
117
|
-
theme_name: theme_name
|
122
|
+
theme_name: theme_name,
|
123
|
+
default_project: default_project
|
118
124
|
}
|
119
125
|
end
|
120
126
|
|
@@ -150,6 +156,7 @@ module Dsu
|
|
150
156
|
DEFAULT_CONFIGURATION[:carry_over_entries_to_today])
|
151
157
|
@include_all = config_hash.fetch(:include_all, DEFAULT_CONFIGURATION[:include_all])
|
152
158
|
@theme_name = config_hash.fetch(:theme_name, DEFAULT_CONFIGURATION[:theme_name])
|
159
|
+
@default_project = config_hash.fetch(:default_project, DEFAULT_CONFIGURATION[:default_project])
|
153
160
|
end
|
154
161
|
|
155
162
|
def validate_theme_file
|
@@ -159,6 +166,14 @@ module Dsu
|
|
159
166
|
i18n_key = 'configuration.errors.theme_file_missing'
|
160
167
|
errors.add(:base, I18n.t(i18n_key, theme_path: theme_path))
|
161
168
|
end
|
169
|
+
|
170
|
+
def validate_default_project
|
171
|
+
default_project_folder = File.join(projects_folder, default_project.presence || '{{blank}}')
|
172
|
+
return if Dir.exist?(default_project_folder)
|
173
|
+
|
174
|
+
i18n_key = 'configuration.errors.project_path_missing'
|
175
|
+
errors.add(:base, I18n.t(i18n_key, project_folder: default_project_folder))
|
176
|
+
end
|
162
177
|
end
|
163
178
|
end
|
164
179
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'active_model'
|
4
3
|
require_relative '../crud/json_file'
|
5
4
|
require_relative '../migration/version'
|
6
5
|
require_relative '../services/entry_group/editor_service'
|
@@ -177,12 +176,6 @@ module Dsu
|
|
177
176
|
Services::EntryGroup::HydratorService.new(entry_group_hash: entry_group_hash).call
|
178
177
|
end
|
179
178
|
|
180
|
-
def find_or_create(time:)
|
181
|
-
find_or_initialize(time: time).tap do |entry_group|
|
182
|
-
entry_group.write! unless entry_group.exist?
|
183
|
-
end
|
184
|
-
end
|
185
|
-
|
186
179
|
def find_or_initialize(time:)
|
187
180
|
file_path = entries_path_for(time: time)
|
188
181
|
read(file_path: file_path) do |entry_group_hash|
|
@@ -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 Dsu
|
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 = 24
|
29
|
+
MIN_DESCRIPTION_LENGTH = 2
|
30
|
+
MAX_DESCRIPTION_LENGTH = 32
|
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
|
@@ -1,26 +1,15 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'delegate'
|
4
|
-
require_relative '../models/color_theme'
|
5
|
-
require_relative '../support/color_themable'
|
6
|
-
|
7
3
|
module Dsu
|
8
4
|
module Presenters
|
9
5
|
class BasePresenterEx
|
10
|
-
include Support::ColorThemable
|
11
|
-
|
12
6
|
def initialize(options: {})
|
13
7
|
@options = options&.dup || {}
|
14
|
-
@color_theme = Models::ColorTheme.find(theme_name: theme_name)
|
15
8
|
end
|
16
9
|
|
17
10
|
private
|
18
11
|
|
19
|
-
|
20
|
-
|
21
|
-
def theme_name
|
22
|
-
@theme_name ||= options.fetch(:theme_name, Models::Configuration.new.theme_name)
|
23
|
-
end
|
12
|
+
attr_accessor :options
|
24
13
|
end
|
25
14
|
end
|
26
15
|
end
|