doto 0.0.1.pre.alpha.1
Sign up to get free protection for your applications and to get access to all the features.
- 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,57 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/project'
|
4
|
+
require_relative '../base_presenter_ex'
|
5
|
+
require_relative 'defaultable'
|
6
|
+
|
7
|
+
module Doto
|
8
|
+
module Presenters
|
9
|
+
module Project
|
10
|
+
class UseByNumberPresenter < BasePresenterEx
|
11
|
+
include Defaultable
|
12
|
+
|
13
|
+
attr_reader :project_number
|
14
|
+
|
15
|
+
delegate :project_name, to: :project, allow_nil: true
|
16
|
+
delegate :description, to: :project, prefix: true, allow_nil: true
|
17
|
+
|
18
|
+
def initialize(project_number:, options: {})
|
19
|
+
super(options: options)
|
20
|
+
|
21
|
+
raise ArgumentError, 'project_number is blank' if project_number.blank?
|
22
|
+
|
23
|
+
self.project_number = project_number
|
24
|
+
end
|
25
|
+
|
26
|
+
def respond(response:)
|
27
|
+
return false unless response
|
28
|
+
|
29
|
+
project.default! if make_default? && project&.present?
|
30
|
+
project.use! if project&.present?
|
31
|
+
end
|
32
|
+
|
33
|
+
def already_current_project?
|
34
|
+
project&.current_project?
|
35
|
+
end
|
36
|
+
|
37
|
+
def project_does_not_exist?
|
38
|
+
!project&.exist?
|
39
|
+
end
|
40
|
+
|
41
|
+
def project_errors
|
42
|
+
return false unless project&.persisted?
|
43
|
+
|
44
|
+
project.errors.full_messages
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_writer :project_number
|
50
|
+
|
51
|
+
def project
|
52
|
+
@project ||= Models::Project.find_by_number(project_number: project_number)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/project'
|
4
|
+
require_relative '../base_presenter_ex'
|
5
|
+
require_relative 'defaultable'
|
6
|
+
|
7
|
+
module Doto
|
8
|
+
module Presenters
|
9
|
+
module Project
|
10
|
+
class UsePresenter < BasePresenterEx
|
11
|
+
include Defaultable
|
12
|
+
|
13
|
+
attr_reader :project_name
|
14
|
+
|
15
|
+
delegate :description, to: :project, prefix: true, allow_nil: true
|
16
|
+
|
17
|
+
def initialize(project_name:, options: {})
|
18
|
+
super(options: options)
|
19
|
+
|
20
|
+
raise ArgumentError, 'project_name is blank' if project_name.blank?
|
21
|
+
|
22
|
+
self.project_name = project_name
|
23
|
+
end
|
24
|
+
|
25
|
+
def respond(response:)
|
26
|
+
return false unless response
|
27
|
+
|
28
|
+
project.default! if make_default? && project&.present?
|
29
|
+
project.use! if project&.present?
|
30
|
+
end
|
31
|
+
|
32
|
+
def already_current_project?
|
33
|
+
project&.current_project?
|
34
|
+
end
|
35
|
+
|
36
|
+
def project_does_not_exist?
|
37
|
+
!project.exist?
|
38
|
+
end
|
39
|
+
|
40
|
+
def project_errors
|
41
|
+
return false unless project.persisted?
|
42
|
+
|
43
|
+
project.errors.full_messages
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
attr_writer :project_name
|
49
|
+
|
50
|
+
def project
|
51
|
+
@project ||= Models::Project.find_or_initialize(project_name: project_name)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/color_theme'
|
4
|
+
|
5
|
+
module Doto
|
6
|
+
module Services
|
7
|
+
module ColorTheme
|
8
|
+
class HydratorService
|
9
|
+
def initialize(theme_name:, theme_hash:, options: {})
|
10
|
+
raise ArgumentError, 'theme_name is nil.' if theme_name.nil?
|
11
|
+
raise ArgumentError, "theme_name is the wrong object type: \"#{theme_name}\"." unless theme_hash.is_a?(Hash)
|
12
|
+
raise ArgumentError, 'theme_hash is nil' if theme_hash.nil?
|
13
|
+
raise ArgumentError, "theme_hash is the wrong object type: \"#{theme_hash}\"" unless theme_hash.is_a?(Hash)
|
14
|
+
raise ArgumentError, 'options is nil' if options.nil?
|
15
|
+
raise ArgumentError, "options is the wrong object type:\"#{options}\"" unless options.is_a?(Hash)
|
16
|
+
|
17
|
+
@theme_name = theme_name
|
18
|
+
@theme_hash = theme_hash
|
19
|
+
@options = options || {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def call
|
23
|
+
Models::ColorTheme.new(theme_name: theme_name, theme_hash: hydrate)
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :theme_hash, :theme_name, :options
|
29
|
+
|
30
|
+
def hydrate
|
31
|
+
theme_hash.each_pair do |key, value|
|
32
|
+
next if %i[version description].include?(key)
|
33
|
+
|
34
|
+
value.each_pair do |k, _v|
|
35
|
+
value[k] = value[k].to_sym
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/configuration'
|
4
|
+
require_relative '../../views/shared/message'
|
5
|
+
|
6
|
+
module Doto
|
7
|
+
module Services
|
8
|
+
module Configuration
|
9
|
+
class HydratorService
|
10
|
+
def initialize(config_hash:, options: {})
|
11
|
+
raise ArgumentError, 'config_hash is nil' if config_hash.nil?
|
12
|
+
|
13
|
+
unless config_hash.is_a?(Hash)
|
14
|
+
raise ArgumentError,
|
15
|
+
"config_hash is the wrong object type: \"#{config_hash}\""
|
16
|
+
end
|
17
|
+
raise ArgumentError, 'options is nil' if options.nil?
|
18
|
+
raise ArgumentError, "options is the wrong object type:\"#{options}\"" unless options.is_a?(Hash)
|
19
|
+
|
20
|
+
@config_hash = config_hash.dup
|
21
|
+
@options = options || {}
|
22
|
+
end
|
23
|
+
|
24
|
+
def call
|
25
|
+
hydrate
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :config_hash, :options
|
31
|
+
|
32
|
+
def hydrate
|
33
|
+
config_hash[:version] = config_hash[:version].to_i
|
34
|
+
config_hash[:entries_display_order] = config_hash[:entries_display_order].to_sym
|
35
|
+
config_hash
|
36
|
+
rescue JSON::ParserError => _e
|
37
|
+
Models::Configuration::DEFAULT_CONFIGURATION
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/entry'
|
4
|
+
|
5
|
+
module Doto
|
6
|
+
module Services
|
7
|
+
module Entry
|
8
|
+
class HydratorService
|
9
|
+
def initialize(entries_array:, options: {})
|
10
|
+
raise ArgumentError, 'entries_array is nil' if entries_array.nil?
|
11
|
+
raise ArgumentError, 'options is nil' if options.nil?
|
12
|
+
|
13
|
+
@entries_array = entries_array
|
14
|
+
@options = options || {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def call
|
18
|
+
hydrate
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :entries_array, :options
|
24
|
+
|
25
|
+
def hydrate
|
26
|
+
entries_array.map do |entry_hash|
|
27
|
+
Doto::Models::Entry.new(**entry_hash)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/entry_group'
|
4
|
+
require_relative '../../support/time_formatable'
|
5
|
+
require_relative '../../support/times_sortable'
|
6
|
+
|
7
|
+
module Doto
|
8
|
+
module Services
|
9
|
+
module EntryGroup
|
10
|
+
# This service is responsible for returning an array of
|
11
|
+
# sorted entry group dates as Time objects. The Time objects
|
12
|
+
# returned, and the sort order are determined by the options
|
13
|
+
# passed in.
|
14
|
+
class BrowseService
|
15
|
+
include Support::TimeFormatable
|
16
|
+
include Support::TimesSortable
|
17
|
+
|
18
|
+
def initialize(time:, options: {})
|
19
|
+
raise ArgumentError, 'Argument time is nil' if time.nil?
|
20
|
+
raise ArgumentError, 'Argument options is nil' if options.nil?
|
21
|
+
|
22
|
+
@time = time
|
23
|
+
@options = options
|
24
|
+
end
|
25
|
+
|
26
|
+
def call
|
27
|
+
return [] if entry_group_times.empty?
|
28
|
+
|
29
|
+
times_sort(times: entry_group_times, entries_display_order: entries_display_order)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :time, :options
|
35
|
+
|
36
|
+
def entry_group_times
|
37
|
+
@entry_group_times ||= (min_time.to_i..max_time.to_i).step(1.day).each_with_object([]) do |time_step, times|
|
38
|
+
time = Time.at(time_step)
|
39
|
+
next unless include_all? || entry_group_count(time).positive?
|
40
|
+
|
41
|
+
times << time
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def entry_group_count(time)
|
46
|
+
entry_group = Models::EntryGroup.find_or_initialize(time: time)
|
47
|
+
entry_group.persisted? ? entry_group.entries.count : 0
|
48
|
+
end
|
49
|
+
|
50
|
+
def min_time
|
51
|
+
@min_time ||= if week?
|
52
|
+
time.beginning_of_week
|
53
|
+
elsif month?
|
54
|
+
time.beginning_of_month
|
55
|
+
elsif year?
|
56
|
+
time.beginning_of_year
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def max_time
|
61
|
+
@max_time ||= if week?
|
62
|
+
time.end_of_week
|
63
|
+
elsif month?
|
64
|
+
time.end_of_month
|
65
|
+
elsif year?
|
66
|
+
time.end_of_year
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def entries_display_order
|
71
|
+
options[:entries_display_order] || default_entries_display_order
|
72
|
+
end
|
73
|
+
|
74
|
+
def default_entries_display_order
|
75
|
+
:asc
|
76
|
+
end
|
77
|
+
|
78
|
+
def week?
|
79
|
+
options.fetch(:browse, default_browse) == :week
|
80
|
+
end
|
81
|
+
|
82
|
+
def month?
|
83
|
+
options[:browse] == :month
|
84
|
+
end
|
85
|
+
|
86
|
+
def year?
|
87
|
+
options[:browse] == :year
|
88
|
+
end
|
89
|
+
|
90
|
+
def default_browse
|
91
|
+
:week
|
92
|
+
end
|
93
|
+
|
94
|
+
def include_all?
|
95
|
+
options.fetch(:include_all, false)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/entry_group'
|
4
|
+
|
5
|
+
module Doto
|
6
|
+
module Services
|
7
|
+
module EntryGroup
|
8
|
+
class CounterService
|
9
|
+
def initialize(times:, options: {})
|
10
|
+
raise ArgumentError, 'Argument times is nil' if times.nil?
|
11
|
+
|
12
|
+
@times = times
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
total_entry_groups = 0
|
18
|
+
|
19
|
+
times.each do |time|
|
20
|
+
total_entry_groups += 1 if Models::EntryGroup.exist?(time: time)
|
21
|
+
end
|
22
|
+
|
23
|
+
total_entry_groups
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :times, :options
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/entry_group'
|
4
|
+
|
5
|
+
module Doto
|
6
|
+
module Services
|
7
|
+
module EntryGroup
|
8
|
+
class DeleterService
|
9
|
+
def initialize(times:, options: {})
|
10
|
+
raise ArgumentError, 'Argument times is nil' if times.nil?
|
11
|
+
|
12
|
+
@times = times
|
13
|
+
@options = options
|
14
|
+
end
|
15
|
+
|
16
|
+
def call
|
17
|
+
deleted_entry_groups = 0
|
18
|
+
|
19
|
+
times.each do |time|
|
20
|
+
next unless Models::EntryGroup.exist?(time: time)
|
21
|
+
|
22
|
+
Models::EntryGroup.delete(time: time)
|
23
|
+
deleted_entry_groups += 1
|
24
|
+
end
|
25
|
+
|
26
|
+
deleted_entry_groups
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :times, :options
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/color_theme'
|
4
|
+
require_relative '../../models/entry'
|
5
|
+
require_relative '../../support/color_themable'
|
6
|
+
require_relative '../../support/time_formatable'
|
7
|
+
require_relative '../../views/shared/model_errors'
|
8
|
+
require_relative '../stdout_redirector_service'
|
9
|
+
require_relative '../temp_file/reader_service'
|
10
|
+
require_relative '../temp_file/writer_service'
|
11
|
+
|
12
|
+
module Doto
|
13
|
+
module Services
|
14
|
+
module EntryGroup
|
15
|
+
class EditorService
|
16
|
+
include Support::ColorThemable
|
17
|
+
include Support::TimeFormatable
|
18
|
+
|
19
|
+
def initialize(entry_group:, options: {})
|
20
|
+
raise ArgumentError, 'entry_group is nil' if entry_group.nil?
|
21
|
+
raise ArgumentError, 'entry_group is the wrong object type' unless entry_group.is_a?(Models::EntryGroup)
|
22
|
+
raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash) || options.nil?
|
23
|
+
|
24
|
+
@entry_group = entry_group
|
25
|
+
@options = options || {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def call
|
29
|
+
edit_view = render_edit_view
|
30
|
+
edit edit_view
|
31
|
+
# NOTE: Return the original entry group object as any permanent changes
|
32
|
+
# will have been applied to it.
|
33
|
+
entry_group
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :entry_group, :options
|
39
|
+
|
40
|
+
# Renders the edit view to a string so we can write it to a temporary file
|
41
|
+
# and edit it. The edits will be used to update the entry group.
|
42
|
+
def render_edit_view
|
43
|
+
message = I18n.t('services.editor_service.messages.editing',
|
44
|
+
formatted_time: formatted_time(time: entry_group.time))
|
45
|
+
puts apply_theme(message, theme_color: color_theme.header)
|
46
|
+
# TODO: Call #render_as_string directly below?
|
47
|
+
StdoutRedirectorService.call { Views::EntryGroup::Edit.new(entry_group: entry_group).render }
|
48
|
+
# Views::EntryGroup::Edit.new(entry_group: entry_group).render_as_string
|
49
|
+
end
|
50
|
+
|
51
|
+
# Writes the temporary file contents to disk and opens it in the editor
|
52
|
+
# for editing. It then copies the changes to the entry group and writes
|
53
|
+
# the changes to the entry group file.
|
54
|
+
def edit(edit_view)
|
55
|
+
entry_group_with_edits = Models::EntryGroup.new(time: entry_group.time)
|
56
|
+
|
57
|
+
TempFile::WriterService.new(tmp_file_content: edit_view).call do |tmp_file_path|
|
58
|
+
if Kernel.system("${EDITOR:-#{configuration.editor}} #{tmp_file_path}")
|
59
|
+
TempFile::ReaderService.new(tmp_file_path: tmp_file_path).call do |editor_line|
|
60
|
+
next unless process_description?(editor_line)
|
61
|
+
|
62
|
+
entry_group_with_edits.entries << Models::Entry.new(description: editor_line)
|
63
|
+
end
|
64
|
+
|
65
|
+
process_entry_group!(entry_group_with_edits)
|
66
|
+
else
|
67
|
+
message_array = I18n.t('services.editor_service.errors.temp_file_error',
|
68
|
+
editor: ENV.fetch('EDITOR', configuration.editor),
|
69
|
+
status: $CHILD_STATUS).split("\n")
|
70
|
+
puts apply_theme(message_array, theme_color: color_theme.error)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
def process_entry_group!(entry_group_with_edits)
|
76
|
+
if entry_group_with_edits.entries.empty?
|
77
|
+
entry_group.delete
|
78
|
+
return
|
79
|
+
end
|
80
|
+
|
81
|
+
Views::Shared::ModelErrors.new(model: entry_group_with_edits).render if entry_group_with_edits.invalid?
|
82
|
+
|
83
|
+
# Make sure we're saving only valid, unique entries.
|
84
|
+
entry_group.entries = entry_group_with_edits.valid_unique_entries
|
85
|
+
entry_group.save!
|
86
|
+
end
|
87
|
+
|
88
|
+
def process_description?(description)
|
89
|
+
description = Models::Entry.clean_description(description)
|
90
|
+
!(description.blank? || description[0] == '#')
|
91
|
+
end
|
92
|
+
|
93
|
+
def color_theme
|
94
|
+
@color_theme ||= Models::ColorTheme.current_or_default
|
95
|
+
end
|
96
|
+
|
97
|
+
def configuration
|
98
|
+
Models::Configuration.new
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
require_relative '../../models/entry_group'
|
5
|
+
require_relative '../../support/fileable'
|
6
|
+
require_relative '../../support/transform_project_name'
|
7
|
+
|
8
|
+
module Doto
|
9
|
+
module Services
|
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.
|
15
|
+
class ExporterService
|
16
|
+
include Support::Fileable
|
17
|
+
include Support::TransformProjectName
|
18
|
+
|
19
|
+
def initialize(project_name:, entry_groups:, options: {})
|
20
|
+
raise ArgumentError, 'Argument project_name is blank' if project_name.blank?
|
21
|
+
raise ArgumentError, 'Argument entry_groups is blank' if entry_groups.blank?
|
22
|
+
raise ArgumentError, 'Argument entry_groups are not all valid' unless entry_groups.all?(&:valid?)
|
23
|
+
|
24
|
+
validate_entry_group_entries_present! entry_groups
|
25
|
+
|
26
|
+
@project_name = project_name
|
27
|
+
@entry_groups = entry_groups
|
28
|
+
@options = options
|
29
|
+
end
|
30
|
+
|
31
|
+
def call
|
32
|
+
CSV.open(export_file_path, 'w') do |csv|
|
33
|
+
csv << %i[project_name version entry_group entry_no total_entries entry_group_entry]
|
34
|
+
|
35
|
+
entry_groups.each do |entry_group|
|
36
|
+
export_entry_group(entry_group: entry_group, csv: csv)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
export_file_path
|
41
|
+
end
|
42
|
+
|
43
|
+
def export_file_path
|
44
|
+
@export_file_path ||= File.join(temp_folder, export_file_name)
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
attr_reader :project_name, :entry_groups, :options
|
50
|
+
|
51
|
+
def export_entry_data(entry_group:, entry:, entry_index:)
|
52
|
+
[
|
53
|
+
project_name,
|
54
|
+
entry_group.version,
|
55
|
+
entry_group.time.to_date,
|
56
|
+
entry_index + 1,
|
57
|
+
entry_group.entries.count,
|
58
|
+
entry.description
|
59
|
+
]
|
60
|
+
end
|
61
|
+
|
62
|
+
def export_entry_group(entry_group:, csv:)
|
63
|
+
entry_group.entries.each_with_index do |entry, index|
|
64
|
+
csv << export_entry_data(entry_group: entry_group, entry: entry, entry_index: index)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def export_file_name
|
69
|
+
transformed_file_name = transform_project_name project_name, options: options
|
70
|
+
"doto-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'
|
78
|
+
end
|
79
|
+
|
80
|
+
def times
|
81
|
+
@times ||= options.fetch(:times, entry_groups.map(&:time))
|
82
|
+
end
|
83
|
+
|
84
|
+
def timestamp
|
85
|
+
@timestamp ||= Time.now.in_time_zone.strftime('%Y%m%d%H%M%S')
|
86
|
+
end
|
87
|
+
|
88
|
+
def validate_entry_group_entries_present!(entry_groups)
|
89
|
+
entry_groups.each do |entry_group|
|
90
|
+
next if entry_group.entries.present?
|
91
|
+
|
92
|
+
raise ArgumentError, "Argument entry_groups entry group for #{entry_group.time_yyyy_mm_dd} has no entries"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/entry_group'
|
4
|
+
require_relative '../entry/hydrator_service'
|
5
|
+
|
6
|
+
module Doto
|
7
|
+
module Services
|
8
|
+
module EntryGroup
|
9
|
+
class HydratorService
|
10
|
+
def initialize(entry_group_hash:, options: {})
|
11
|
+
raise ArgumentError, 'entry_group_hash is nil' if entry_group_hash.nil?
|
12
|
+
raise ArgumentError, 'options is nil' if options.nil?
|
13
|
+
|
14
|
+
@entry_group_hash = entry_group_hash
|
15
|
+
@options = options || {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
Models::EntryGroup.new(**hydrate)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
attr_reader :entry_group_hash, :options
|
25
|
+
|
26
|
+
# Returns a Hash with :time and :entries values hydrated (i.e. Time and Entry objects respectively).
|
27
|
+
def hydrate
|
28
|
+
entry_group_hash.tap do |hash|
|
29
|
+
hash[:time] = Time.parse(hash[:time])
|
30
|
+
hash[:entries] =
|
31
|
+
Entry::HydratorService.new(entries_array: hash[:entries], options: options).call
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|