dsu 1.2.1 → 2.0.0.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (105) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +55 -21
  3. data/Gemfile.lock +7 -7
  4. data/README.md +28 -35
  5. data/bin/console +23 -1
  6. data/bin/dsu +3 -0
  7. data/bin/setup +14 -3
  8. data/exe/dsu +23 -1
  9. data/exe/dsu_migrate.rb +43 -0
  10. data/lib/core/ruby/color_theme_colors.rb +16 -0
  11. data/lib/core/ruby/color_theme_mode.rb +42 -0
  12. data/lib/core/ruby/not_today.rb +7 -0
  13. data/lib/core/ruby/wrap_and_join.rb +31 -0
  14. data/lib/dsu/base_cli.rb +19 -23
  15. data/lib/dsu/cli.rb +47 -37
  16. data/lib/dsu/command_services/add_entry_service.rb +10 -21
  17. data/lib/dsu/crud/json_file.rb +139 -0
  18. data/lib/dsu/crud/raw_json_file.rb +51 -0
  19. data/lib/dsu/env.rb +21 -0
  20. data/lib/dsu/migration/service.rb +196 -0
  21. data/lib/dsu/migration/version.rb +7 -0
  22. data/lib/dsu/models/color_theme.rb +270 -0
  23. data/lib/dsu/models/configuration.rb +160 -0
  24. data/lib/dsu/models/entry.rb +6 -2
  25. data/lib/dsu/models/entry_group.rb +143 -42
  26. data/lib/dsu/models/migration_version.rb +48 -0
  27. data/lib/dsu/presenters/base_presenter.rb +32 -0
  28. data/lib/dsu/presenters/color_theme_presenter.rb +50 -0
  29. data/lib/dsu/presenters/color_theme_show_presenter.rb +49 -0
  30. data/lib/dsu/presenters/configuration_presenter.rb +45 -0
  31. data/lib/dsu/presenters/entry_group_presenter.rb +35 -0
  32. data/lib/dsu/presenters/entry_presenter.rb +25 -0
  33. data/lib/dsu/services/color_theme/hydrator_service.rb +42 -0
  34. data/lib/dsu/services/configuration/hydrator_service.rb +42 -0
  35. data/lib/dsu/services/entry/hydrator_service.rb +33 -0
  36. data/lib/dsu/services/entry_group/editor_service.rb +107 -0
  37. data/lib/dsu/services/entry_group/hydrator_service.rb +37 -0
  38. data/lib/dsu/services/migration_version/hydrator_service.rb +36 -0
  39. data/lib/dsu/services/stderr_redirector_service.rb +27 -0
  40. data/lib/dsu/services/temp_file/reader_service.rb +33 -0
  41. data/lib/dsu/services/temp_file/writer_service.rb +35 -0
  42. data/lib/dsu/subcommands/base_subcommand.rb +14 -0
  43. data/lib/dsu/subcommands/config.rb +92 -32
  44. data/lib/dsu/subcommands/edit.rb +3 -3
  45. data/lib/dsu/subcommands/list.rb +70 -93
  46. data/lib/dsu/subcommands/theme.rb +159 -0
  47. data/lib/dsu/support/ask.rb +14 -19
  48. data/lib/dsu/support/color_themable.rb +34 -0
  49. data/lib/dsu/support/command_help_colorizeable.rb +27 -0
  50. data/lib/dsu/support/command_hookable.rb +60 -0
  51. data/lib/dsu/support/command_options/dsu_times.rb +32 -21
  52. data/lib/dsu/support/command_options/time.rb +7 -1
  53. data/lib/dsu/support/command_options/time_mneumonic.rb +7 -1
  54. data/lib/dsu/support/descriptable.rb +6 -4
  55. data/lib/dsu/support/entry_group_viewable.rb +28 -4
  56. data/lib/dsu/support/fileable.rb +94 -0
  57. data/lib/dsu/support/presentable.rb +11 -0
  58. data/lib/dsu/support/subcommand_help_colorizeable.rb +27 -0
  59. data/lib/dsu/support/time_comparable.rb +19 -0
  60. data/lib/dsu/support/time_formatable.rb +12 -0
  61. data/lib/dsu/support/times_sortable.rb +48 -14
  62. data/lib/dsu/support/utils.rb +11 -0
  63. data/lib/dsu/validators/color_theme_validator.rb +74 -0
  64. data/lib/dsu/validators/entries_validator.rb +4 -8
  65. data/lib/dsu/validators/version_validator.rb +29 -0
  66. data/lib/dsu/version.rb +2 -1
  67. data/lib/dsu/views/color_theme/index.rb +62 -0
  68. data/lib/dsu/views/color_theme/show.rb +106 -0
  69. data/lib/dsu/views/configuration/show.rb +41 -0
  70. data/lib/dsu/views/entry_group/edit.rb +3 -5
  71. data/lib/dsu/views/entry_group/shared/no_entries_to_display.rb +41 -0
  72. data/lib/dsu/views/entry_group/show.rb +16 -15
  73. data/lib/dsu/views/shared/error.rb +17 -0
  74. data/lib/dsu/views/shared/info.rb +17 -0
  75. data/lib/dsu/views/shared/message.rb +85 -0
  76. data/lib/dsu/views/shared/model_errors.rb +31 -0
  77. data/lib/dsu/views/shared/success.rb +17 -0
  78. data/lib/dsu/views/shared/warning.rb +17 -0
  79. data/lib/dsu.rb +22 -1
  80. data/lib/seed_data/themes/cherry.json +79 -0
  81. data/lib/seed_data/themes/default.json +79 -0
  82. data/lib/seed_data/themes/lemon.json +79 -0
  83. data/lib/seed_data/themes/matrix.json +79 -0
  84. data/lib/seed_data/themes/whiteout.json +79 -0
  85. metadata +70 -25
  86. data/lib/dsu/core/ruby/not_today.rb +0 -11
  87. data/lib/dsu/services/ai/tense_translator_service.rb +0 -63
  88. data/lib/dsu/services/configuration_loader_service.rb +0 -55
  89. data/lib/dsu/services/entry_group_deleter_service.rb +0 -31
  90. data/lib/dsu/services/entry_group_editor_service.rb +0 -96
  91. data/lib/dsu/services/entry_group_hydrator_service.rb +0 -43
  92. data/lib/dsu/services/entry_group_reader_service.rb +0 -36
  93. data/lib/dsu/services/entry_group_writer_service.rb +0 -46
  94. data/lib/dsu/services/entry_hydrator_service.rb +0 -35
  95. data/lib/dsu/services/temp_file_reader_service.rb +0 -31
  96. data/lib/dsu/services/temp_file_writer_service.rb +0 -33
  97. data/lib/dsu/support/colorable.rb +0 -14
  98. data/lib/dsu/support/configurable.rb +0 -15
  99. data/lib/dsu/support/configuration.rb +0 -112
  100. data/lib/dsu/support/entry_group_fileable.rb +0 -49
  101. data/lib/dsu/support/entry_group_loadable.rb +0 -49
  102. data/lib/dsu/support/folder_locations.rb +0 -21
  103. data/lib/dsu/support/say.rb +0 -40
  104. data/lib/dsu/views/edited_entries/shared/errors.rb +0 -39
  105. 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