dsu 1.2.1 → 2.0.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 +4 -4
- data/CHANGELOG.md +65 -21
- data/Gemfile.lock +7 -7
- data/README.md +28 -35
- data/bin/console +23 -1
- data/bin/dsu +3 -0
- data/bin/setup +14 -3
- data/exe/dsu +23 -1
- data/exe/dsu_migrate.rb +43 -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/not_today.rb +7 -0
- data/lib/core/ruby/wrap_and_join.rb +31 -0
- data/lib/dsu/base_cli.rb +19 -23
- data/lib/dsu/cli.rb +47 -37
- data/lib/dsu/command_services/add_entry_service.rb +10 -21
- data/lib/dsu/crud/json_file.rb +139 -0
- data/lib/dsu/crud/raw_json_file.rb +51 -0
- data/lib/dsu/env.rb +21 -0
- data/lib/dsu/migration/service.rb +196 -0
- data/lib/dsu/migration/version.rb +7 -0
- data/lib/dsu/models/color_theme.rb +270 -0
- data/lib/dsu/models/configuration.rb +160 -0
- data/lib/dsu/models/entry.rb +6 -2
- data/lib/dsu/models/entry_group.rb +143 -42
- data/lib/dsu/models/migration_version.rb +48 -0
- data/lib/dsu/presenters/base_presenter.rb +32 -0
- data/lib/dsu/presenters/color_theme_presenter.rb +50 -0
- data/lib/dsu/presenters/color_theme_show_presenter.rb +49 -0
- data/lib/dsu/presenters/configuration_presenter.rb +45 -0
- data/lib/dsu/presenters/entry_group_presenter.rb +35 -0
- data/lib/dsu/presenters/entry_presenter.rb +25 -0
- data/lib/dsu/services/color_theme/hydrator_service.rb +42 -0
- data/lib/dsu/services/configuration/hydrator_service.rb +42 -0
- data/lib/dsu/services/entry/hydrator_service.rb +33 -0
- data/lib/dsu/services/entry_group/editor_service.rb +107 -0
- data/lib/dsu/services/entry_group/hydrator_service.rb +37 -0
- data/lib/dsu/services/migration_version/hydrator_service.rb +36 -0
- data/lib/dsu/services/stderr_redirector_service.rb +27 -0
- data/lib/dsu/services/temp_file/reader_service.rb +33 -0
- data/lib/dsu/services/temp_file/writer_service.rb +35 -0
- data/lib/dsu/subcommands/base_subcommand.rb +14 -0
- data/lib/dsu/subcommands/config.rb +92 -32
- data/lib/dsu/subcommands/edit.rb +3 -3
- data/lib/dsu/subcommands/list.rb +70 -93
- data/lib/dsu/subcommands/theme.rb +159 -0
- data/lib/dsu/support/ask.rb +14 -19
- data/lib/dsu/support/color_themable.rb +34 -0
- data/lib/dsu/support/command_help_colorizeable.rb +27 -0
- data/lib/dsu/support/command_hookable.rb +60 -0
- data/lib/dsu/support/command_options/dsu_times.rb +32 -21
- data/lib/dsu/support/command_options/time.rb +7 -1
- data/lib/dsu/support/command_options/time_mneumonic.rb +7 -1
- data/lib/dsu/support/descriptable.rb +6 -4
- data/lib/dsu/support/entry_group_viewable.rb +28 -4
- data/lib/dsu/support/fileable.rb +94 -0
- data/lib/dsu/support/presentable.rb +11 -0
- data/lib/dsu/support/subcommand_help_colorizeable.rb +27 -0
- data/lib/dsu/support/time_comparable.rb +19 -0
- data/lib/dsu/support/time_formatable.rb +12 -0
- data/lib/dsu/support/times_sortable.rb +48 -14
- data/lib/dsu/support/utils.rb +11 -0
- data/lib/dsu/validators/color_theme_validator.rb +74 -0
- data/lib/dsu/validators/entries_validator.rb +4 -8
- data/lib/dsu/validators/version_validator.rb +29 -0
- data/lib/dsu/version.rb +2 -1
- data/lib/dsu/views/color_theme/index.rb +62 -0
- data/lib/dsu/views/color_theme/show.rb +106 -0
- data/lib/dsu/views/configuration/show.rb +41 -0
- data/lib/dsu/views/entry_group/edit.rb +3 -5
- data/lib/dsu/views/entry_group/shared/no_entries_to_display.rb +41 -0
- data/lib/dsu/views/entry_group/show.rb +16 -15
- data/lib/dsu/views/shared/error.rb +17 -0
- data/lib/dsu/views/shared/info.rb +17 -0
- data/lib/dsu/views/shared/message.rb +85 -0
- data/lib/dsu/views/shared/model_errors.rb +31 -0
- data/lib/dsu/views/shared/success.rb +17 -0
- data/lib/dsu/views/shared/warning.rb +17 -0
- data/lib/dsu.rb +22 -1
- data/lib/seed_data/themes/cherry.json +79 -0
- data/lib/seed_data/themes/default.json +79 -0
- data/lib/seed_data/themes/lemon.json +79 -0
- data/lib/seed_data/themes/matrix.json +79 -0
- data/lib/seed_data/themes/whiteout.json +79 -0
- metadata +68 -23
- data/lib/dsu/core/ruby/not_today.rb +0 -11
- data/lib/dsu/services/ai/tense_translator_service.rb +0 -63
- data/lib/dsu/services/configuration_loader_service.rb +0 -55
- data/lib/dsu/services/entry_group_deleter_service.rb +0 -31
- data/lib/dsu/services/entry_group_editor_service.rb +0 -96
- data/lib/dsu/services/entry_group_hydrator_service.rb +0 -43
- data/lib/dsu/services/entry_group_reader_service.rb +0 -36
- data/lib/dsu/services/entry_group_writer_service.rb +0 -46
- data/lib/dsu/services/entry_hydrator_service.rb +0 -35
- data/lib/dsu/services/temp_file_reader_service.rb +0 -31
- data/lib/dsu/services/temp_file_writer_service.rb +0 -33
- data/lib/dsu/support/colorable.rb +0 -14
- data/lib/dsu/support/configurable.rb +0 -15
- data/lib/dsu/support/configuration.rb +0 -112
- data/lib/dsu/support/entry_group_fileable.rb +0 -49
- data/lib/dsu/support/entry_group_loadable.rb +0 -49
- data/lib/dsu/support/folder_locations.rb +0 -21
- data/lib/dsu/support/say.rb +0 -40
- data/lib/dsu/views/edited_entries/shared/errors.rb +0 -39
- data/lib/dsu/views/shared/messages.rb +0 -56
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_model'
|
|
4
|
+
require_relative '../crud/json_file'
|
|
5
|
+
require_relative '../services/migration_version/hydrator_service'
|
|
6
|
+
require_relative '../validators/version_validator'
|
|
7
|
+
|
|
8
|
+
module Dsu
|
|
9
|
+
module Models
|
|
10
|
+
# This class represents a dsu migration_version.
|
|
11
|
+
class MigrationVersion < Crud::JsonFile
|
|
12
|
+
include Support::Fileable
|
|
13
|
+
|
|
14
|
+
attr_reader :options
|
|
15
|
+
|
|
16
|
+
def initialize(version: nil, options: {})
|
|
17
|
+
super(migration_version_path)
|
|
18
|
+
|
|
19
|
+
FileUtils.mkdir_p migration_version_folder
|
|
20
|
+
|
|
21
|
+
@options = options || {}
|
|
22
|
+
@version = version and return if version
|
|
23
|
+
|
|
24
|
+
file_hash = if exist?
|
|
25
|
+
read do |migration_version_hash|
|
|
26
|
+
hydrated_hash =
|
|
27
|
+
Services::MigrationVersion::HydratorService.new(migration_version_hash: migration_version_hash).call
|
|
28
|
+
migration_version_hash.merge!(hydrated_hash)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
self.version = file_hash.try(:[], :version) || 0
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns true if the current dsu install is the
|
|
36
|
+
# current migration version.
|
|
37
|
+
def current_migration?
|
|
38
|
+
version == Dsu::Migration::VERSION
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def to_h
|
|
42
|
+
{
|
|
43
|
+
version: version
|
|
44
|
+
}
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
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 Dsu
|
|
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,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_presenter'
|
|
4
|
+
|
|
5
|
+
module Dsu
|
|
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
|
+
apply_theme('Color Themes', theme_color: color_theme.subheader)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def footer
|
|
21
|
+
apply_theme('* current theme', theme_color: color_theme.footer)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def detail
|
|
25
|
+
"#{apply_theme(theme_name_formatted, theme_color: color_theme.body)} - " \
|
|
26
|
+
"#{apply_theme(description, theme_color: color_theme.body)}"
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def detail_with_index(index:)
|
|
30
|
+
"#{formatted_index(index: index)} #{detail}"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def theme_name_formatted
|
|
36
|
+
return theme_name unless default_color_theme?
|
|
37
|
+
|
|
38
|
+
"*#{theme_name}"
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def default_color_theme?
|
|
42
|
+
theme_name == default_color_theme.theme_name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def default_color_theme
|
|
46
|
+
@default_color_theme ||= Models::ColorTheme.current_or_default
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_presenter'
|
|
4
|
+
|
|
5
|
+
module Dsu
|
|
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
|
+
puts_detail('No.', 'Color', 'Values', header: true)
|
|
14
|
+
|
|
15
|
+
Models::ColorTheme::DEFAULT_THEME_COLORS.keys.each_with_index do |color_key, index|
|
|
16
|
+
index = formatted_index(index: index)
|
|
17
|
+
color_hash = color_theme.public_send(color_key)
|
|
18
|
+
puts_detail(index, color_key, color_hash)
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def detail_with_index(index:)
|
|
23
|
+
"#{formatted_index(index: index)} #{detail}"
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def footer
|
|
27
|
+
apply_theme('Footer example', theme_color: color_theme.footer)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def header
|
|
31
|
+
apply_theme("Viewing color theme: #{color_theme.theme_name}", theme_color: color_theme.subheader)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
private
|
|
35
|
+
|
|
36
|
+
def puts_detail(index, color_key, color_hash, header: false)
|
|
37
|
+
if header
|
|
38
|
+
puts "#{apply_theme(index.to_s.ljust(4), theme_color: color_theme.index.bold!)} " \
|
|
39
|
+
"#{apply_theme(color_key.to_s.ljust(15), theme_color: color_theme.index.bold!)} " \
|
|
40
|
+
"#{apply_theme(color_hash.to_s.ljust(10), theme_color: color_theme.index.bold!)}"
|
|
41
|
+
else
|
|
42
|
+
puts "#{apply_theme(index.to_s.ljust(4), theme_color: color_theme.index)} " \
|
|
43
|
+
"#{apply_theme(color_key.to_s.ljust(15), theme_color: color_hash)} " \
|
|
44
|
+
"#{apply_theme(color_hash.to_s.ljust(10), theme_color: color_theme.body)}"
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../support/color_themable'
|
|
4
|
+
require_relative 'base_presenter'
|
|
5
|
+
|
|
6
|
+
module Dsu
|
|
7
|
+
module Presenters
|
|
8
|
+
class ConfigurationPresenter < BasePresenter
|
|
9
|
+
attr_reader :config
|
|
10
|
+
|
|
11
|
+
def initialize(config, options: {})
|
|
12
|
+
super
|
|
13
|
+
|
|
14
|
+
@config = config
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def configuration_header
|
|
18
|
+
apply_theme("Configuration file contents (#{config_path})",
|
|
19
|
+
theme_color: color_theme.header)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def configuration_details
|
|
23
|
+
to_h.each_with_index.filter_map do |config_entry, index|
|
|
24
|
+
formatted_config_entry_with_index(config_entry, index: index, theme_color: color_theme.body)
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def config_path
|
|
31
|
+
@config_path ||= config.file_path
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def formatted_config_entry_with_index(config_entry, index:, theme_color:)
|
|
35
|
+
"#{formatted_index(index: index)} #{formatted_config_entry(config_entry: config_entry,
|
|
36
|
+
theme_color: theme_color)}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def formatted_config_entry(config_entry:, theme_color:)
|
|
40
|
+
config_entry = "#{config_entry[0]}: '#{config_entry[1]}'"
|
|
41
|
+
apply_theme(config_entry, theme_color: theme_color)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../support/time_formatable'
|
|
4
|
+
require_relative 'base_presenter'
|
|
5
|
+
|
|
6
|
+
module Dsu
|
|
7
|
+
module Presenters
|
|
8
|
+
class EntryGroupPresenter < BasePresenter
|
|
9
|
+
attr_reader :entry_group
|
|
10
|
+
|
|
11
|
+
def initialize(entry_group, options: {})
|
|
12
|
+
super
|
|
13
|
+
|
|
14
|
+
@entry_group = entry_group
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def formatted_time
|
|
18
|
+
colors = color_theme.date
|
|
19
|
+
apply_theme(Support::TimeFormatable.formatted_time(time: time), theme_color: colors)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def formatted_errors
|
|
23
|
+
return if valid?
|
|
24
|
+
|
|
25
|
+
colors = color_theme.error
|
|
26
|
+
apply_theme(errors.full_messages.join(', '), theme_color: colors)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def no_entries_available
|
|
30
|
+
colors = color_theme.info
|
|
31
|
+
apply_theme('(no entries available for this day)', theme_color: colors)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'base_presenter'
|
|
4
|
+
|
|
5
|
+
module Dsu
|
|
6
|
+
module Presenters
|
|
7
|
+
class EntryPresenter < BasePresenter
|
|
8
|
+
attr_reader :entry
|
|
9
|
+
|
|
10
|
+
def initialize(entry, options: {})
|
|
11
|
+
super
|
|
12
|
+
|
|
13
|
+
@entry = entry
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def formatted_description
|
|
17
|
+
apply_theme(description, theme_color: color_theme.body)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def formatted_description_with_index(index:)
|
|
21
|
+
"#{formatted_index(index: index)} #{formatted_description}"
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../../models/color_theme'
|
|
4
|
+
|
|
5
|
+
module Dsu
|
|
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 Dsu
|
|
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 Dsu
|
|
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
|
+
Dsu::Models::Entry.new(**entry_hash)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,107 @@
|
|
|
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 Dsu
|
|
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
|
+
puts apply_theme("Editing entry group #{formatted_time(time: entry_group.time)}...",
|
|
44
|
+
theme_color: color_theme.header)
|
|
45
|
+
# TODO: Call #render_as_string directly below?
|
|
46
|
+
StdoutRedirectorService.call { Views::EntryGroup::Edit.new(entry_group: entry_group).render }
|
|
47
|
+
# Views::EntryGroup::Edit.new(entry_group: entry_group).render_as_string
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Writes the temporary file contents to disk and opens it in the editor
|
|
51
|
+
# for editing. It then copies the changes to the entry group and writes
|
|
52
|
+
# the changes to the entry group file.
|
|
53
|
+
def edit(edit_view)
|
|
54
|
+
entry_group_with_edits = Models::EntryGroup.new(time: entry_group.time)
|
|
55
|
+
|
|
56
|
+
TempFile::WriterService.new(tmp_file_content: edit_view).call do |tmp_file_path|
|
|
57
|
+
if Kernel.system("${EDITOR:-#{configuration.editor}} #{tmp_file_path}")
|
|
58
|
+
TempFile::ReaderService.new(tmp_file_path: tmp_file_path).call do |editor_line|
|
|
59
|
+
next unless process_description?(editor_line)
|
|
60
|
+
|
|
61
|
+
entry_group_with_edits.entries << Models::Entry.new(description: editor_line)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
process_entry_group!(entry_group_with_edits)
|
|
65
|
+
else
|
|
66
|
+
puts apply_theme(
|
|
67
|
+
[
|
|
68
|
+
"Failed to open temporary file in editor '#{configuration.editor}'; " \
|
|
69
|
+
"the system error returned was: '#{$CHILD_STATUS}'.",
|
|
70
|
+
'Either set the EDITOR environment variable ' \
|
|
71
|
+
'or set the dsu editor configuration option (`$ dsu config info`).',
|
|
72
|
+
'Run `$ dsu help config` for more information.'
|
|
73
|
+
], theme_color: color_theme.error
|
|
74
|
+
)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def process_entry_group!(entry_group_with_edits)
|
|
80
|
+
if entry_group_with_edits.entries.empty?
|
|
81
|
+
entry_group.delete
|
|
82
|
+
return
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
Views::Shared::ModelErrors.new(model: entry_group_with_edits).render if entry_group_with_edits.invalid?
|
|
86
|
+
|
|
87
|
+
# Make sure we're saving only valid, unique entries.
|
|
88
|
+
entry_group.entries = entry_group_with_edits.valid_unique_entries
|
|
89
|
+
entry_group.save!
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def process_description?(description)
|
|
93
|
+
description = Models::Entry.clean_description(description)
|
|
94
|
+
!(description.blank? || description[0] == '#')
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def color_theme
|
|
98
|
+
@color_theme ||= Models::ColorTheme.current_or_default
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def configuration
|
|
102
|
+
Models::Configuration.new
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
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 Dsu
|
|
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
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dsu
|
|
4
|
+
module Services
|
|
5
|
+
module MigrationVersion
|
|
6
|
+
class HydratorService
|
|
7
|
+
def initialize(migration_version_hash:, options: {})
|
|
8
|
+
raise ArgumentError, 'migration_version_hash is nil' if migration_version_hash.nil?
|
|
9
|
+
|
|
10
|
+
unless migration_version_hash.is_a?(Hash)
|
|
11
|
+
raise ArgumentError,
|
|
12
|
+
"migration_version_hash is the wrong object type: \"#{migration_version_hash}\""
|
|
13
|
+
end
|
|
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
|
+
@migration_version_hash = migration_version_hash.dup
|
|
18
|
+
@options = options || {}
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def call
|
|
22
|
+
hydrate
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_reader :migration_version_hash, :options
|
|
28
|
+
|
|
29
|
+
def hydrate
|
|
30
|
+
migration_version_hash[:version] = migration_version_hash[:version].to_i
|
|
31
|
+
migration_version_hash
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dsu
|
|
4
|
+
module Services
|
|
5
|
+
# This service captures $stderr, resirects it to a StringIO object,
|
|
6
|
+
# and returns the string value.
|
|
7
|
+
# https://stackoverflow.com/questions/4459330/how-do-i-temporarily-redirect-stderr-in-ruby/4459463#4459463
|
|
8
|
+
module StderrRedirectorService
|
|
9
|
+
class << self
|
|
10
|
+
def call
|
|
11
|
+
raise ArgumentError, 'no block was provided' unless block_given?
|
|
12
|
+
|
|
13
|
+
# The output stream must be an IO-like object. In this case we capture it in
|
|
14
|
+
# an in-memory IO object so we can return the string value. Any IO object can
|
|
15
|
+
# be used here.
|
|
16
|
+
string_io = StringIO.new
|
|
17
|
+
original_stderr, $stderr = $stderr, string_io # rubocop:disable Style/ParallelAssignment
|
|
18
|
+
yield
|
|
19
|
+
string_io.string
|
|
20
|
+
ensure
|
|
21
|
+
# Restore the original $stderr.
|
|
22
|
+
$stderr = original_stderr
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dsu
|
|
4
|
+
module Services
|
|
5
|
+
module TempFile
|
|
6
|
+
class ReaderService
|
|
7
|
+
def initialize(tmp_file_path:, options: {})
|
|
8
|
+
raise ArgumentError, 'tmp_file_path is nil' if tmp_file_path.nil?
|
|
9
|
+
raise ArgumentError, 'tmp_file_path is the wrong object type' unless tmp_file_path.is_a?(String)
|
|
10
|
+
raise ArgumentError, 'tmp_file_path is empty' if tmp_file_path.empty?
|
|
11
|
+
raise ArgumentError, 'tmp_file_path does not exist' unless File.exist?(tmp_file_path)
|
|
12
|
+
raise ArgumentError, 'options is nil' if options.nil?
|
|
13
|
+
raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
|
|
14
|
+
|
|
15
|
+
@tmp_file_path = tmp_file_path
|
|
16
|
+
@options = options || {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call
|
|
20
|
+
raise ArgumentError, 'no block given' unless block_given?
|
|
21
|
+
|
|
22
|
+
File.foreach(tmp_file_path) do |line|
|
|
23
|
+
yield line.strip
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
attr_reader :tmp_file_path, :options
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'tempfile'
|
|
4
|
+
|
|
5
|
+
module Dsu
|
|
6
|
+
module Services
|
|
7
|
+
module TempFile
|
|
8
|
+
class WriterService
|
|
9
|
+
def initialize(tmp_file_content:, options: {})
|
|
10
|
+
raise ArgumentError, 'tmp_file_content is nil' if tmp_file_content.nil?
|
|
11
|
+
raise ArgumentError, 'tmp_file_content is the wrong object type' unless tmp_file_content.is_a?(String)
|
|
12
|
+
raise ArgumentError, 'options is nil' if options.nil?
|
|
13
|
+
raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
|
|
14
|
+
|
|
15
|
+
@tmp_file_content = tmp_file_content
|
|
16
|
+
@options = options || {}
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def call
|
|
20
|
+
raise ArgumentError, 'no block given' unless block_given?
|
|
21
|
+
|
|
22
|
+
Tempfile.new('dsu').tap do |file|
|
|
23
|
+
file.write("#{tmp_file_content}\n")
|
|
24
|
+
file.close
|
|
25
|
+
yield file.path
|
|
26
|
+
end.unlink
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
private
|
|
30
|
+
|
|
31
|
+
attr_reader :tmp_file_content, :options
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|