doto 0.0.1.pre.alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (179) hide show
  1. checksums.yaml +7 -0
  2. data/.env.test +1 -0
  3. data/.reek.yml +20 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +206 -0
  6. data/.ruby-version +1 -0
  7. data/CHANGELOG.md +7 -0
  8. data/CODE_OF_CONDUCT.md +84 -0
  9. data/Gemfile +30 -0
  10. data/Gemfile.lock +179 -0
  11. data/LICENSE.txt +21 -0
  12. data/README.md +38 -0
  13. data/Rakefile +16 -0
  14. data/bin/console +36 -0
  15. data/bin/doto +3 -0
  16. data/bin/setup +18 -0
  17. data/exe/doto +33 -0
  18. data/lib/core/ruby/color_theme_colors.rb +16 -0
  19. data/lib/core/ruby/color_theme_mode.rb +42 -0
  20. data/lib/core/ruby/wrap_and_join.rb +31 -0
  21. data/lib/doto/base_cli.rb +56 -0
  22. data/lib/doto/cli.rb +131 -0
  23. data/lib/doto/command_services/add_entry_service.rb +50 -0
  24. data/lib/doto/crud/json_file.rb +161 -0
  25. data/lib/doto/env.rb +44 -0
  26. data/lib/doto/migration/base_service.rb +118 -0
  27. data/lib/doto/migration/migrator.rb +24 -0
  28. data/lib/doto/migration/raw_helpers/color_theme_hash.rb +13 -0
  29. data/lib/doto/migration/raw_helpers/configuration_hash.rb +15 -0
  30. data/lib/doto/migration/raw_helpers/entry_group_hash.rb +13 -0
  31. data/lib/doto/migration/raw_json_file.rb +15 -0
  32. data/lib/doto/migration/raw_json_files.rb +56 -0
  33. data/lib/doto/migration/v20230613121411/service.rb +94 -0
  34. data/lib/doto/migration/v20240210161248/service.rb +148 -0
  35. data/lib/doto/migration/version.rb +7 -0
  36. data/lib/doto/models/color_theme.rb +224 -0
  37. data/lib/doto/models/configuration.rb +185 -0
  38. data/lib/doto/models/entry.rb +63 -0
  39. data/lib/doto/models/entry_group.rb +223 -0
  40. data/lib/doto/models/migration_version.rb +49 -0
  41. data/lib/doto/models/project.rb +295 -0
  42. data/lib/doto/presenters/base_presenter.rb +32 -0
  43. data/lib/doto/presenters/base_presenter_ex.rb +15 -0
  44. data/lib/doto/presenters/color_theme_presenter.rb +52 -0
  45. data/lib/doto/presenters/color_theme_show_presenter.rb +55 -0
  46. data/lib/doto/presenters/configuration_presenter.rb +50 -0
  47. data/lib/doto/presenters/entry_group/list/date_presenter.rb +77 -0
  48. data/lib/doto/presenters/entry_group/list/dates_presenter.rb +60 -0
  49. data/lib/doto/presenters/entry_group/list/messages.rb +15 -0
  50. data/lib/doto/presenters/entry_group/list/nothing_to_list.rb +15 -0
  51. data/lib/doto/presenters/entry_group_presenter.rb +35 -0
  52. data/lib/doto/presenters/entry_presenter.rb +25 -0
  53. data/lib/doto/presenters/export/all_presenter.rb +44 -0
  54. data/lib/doto/presenters/export/dates_presenter.rb +55 -0
  55. data/lib/doto/presenters/import/all_presenter.rb +57 -0
  56. data/lib/doto/presenters/import/dates_presenter.rb +70 -0
  57. data/lib/doto/presenters/import/import_entry.rb +22 -0
  58. data/lib/doto/presenters/import/import_file.rb +33 -0
  59. data/lib/doto/presenters/project/create_presenter.rb +44 -0
  60. data/lib/doto/presenters/project/defaultable.rb +15 -0
  61. data/lib/doto/presenters/project/delete_by_number_presenter.rb +54 -0
  62. data/lib/doto/presenters/project/delete_presenter.rb +53 -0
  63. data/lib/doto/presenters/project/list_presenter.rb +24 -0
  64. data/lib/doto/presenters/project/rename_by_number_presenter.rb +63 -0
  65. data/lib/doto/presenters/project/rename_presenter.rb +57 -0
  66. data/lib/doto/presenters/project/use_by_number_presenter.rb +57 -0
  67. data/lib/doto/presenters/project/use_presenter.rb +56 -0
  68. data/lib/doto/services/color_theme/hydrator_service.rb +42 -0
  69. data/lib/doto/services/configuration/hydrator_service.rb +42 -0
  70. data/lib/doto/services/entry/hydrator_service.rb +33 -0
  71. data/lib/doto/services/entry_group/browse_service.rb +100 -0
  72. data/lib/doto/services/entry_group/counter_service.rb +32 -0
  73. data/lib/doto/services/entry_group/deleter_service.rb +35 -0
  74. data/lib/doto/services/entry_group/editor_service.rb +103 -0
  75. data/lib/doto/services/entry_group/exporter_service.rb +98 -0
  76. data/lib/doto/services/entry_group/hydrator_service.rb +37 -0
  77. data/lib/doto/services/entry_group/importer_service.rb +117 -0
  78. data/lib/doto/services/migration_version/hydrator_service.rb +36 -0
  79. data/lib/doto/services/project/hydrator_service.rb +40 -0
  80. data/lib/doto/services/project/rename_service.rb +70 -0
  81. data/lib/doto/services/stderr_redirector_service.rb +27 -0
  82. data/lib/doto/services/stdout_redirector_service.rb +27 -0
  83. data/lib/doto/services/temp_file/reader_service.rb +33 -0
  84. data/lib/doto/services/temp_file/writer_service.rb +35 -0
  85. data/lib/doto/subcommands/base_subcommand.rb +12 -0
  86. data/lib/doto/subcommands/browse.rb +49 -0
  87. data/lib/doto/subcommands/config.rb +81 -0
  88. data/lib/doto/subcommands/delete.rb +108 -0
  89. data/lib/doto/subcommands/edit.rb +48 -0
  90. data/lib/doto/subcommands/export.rb +62 -0
  91. data/lib/doto/subcommands/import.rb +72 -0
  92. data/lib/doto/subcommands/list.rb +95 -0
  93. data/lib/doto/subcommands/project.rb +146 -0
  94. data/lib/doto/subcommands/theme.rb +131 -0
  95. data/lib/doto/support/ask.rb +44 -0
  96. data/lib/doto/support/color_themable.rb +36 -0
  97. data/lib/doto/support/command_help_colorizeable.rb +34 -0
  98. data/lib/doto/support/command_hookable.rb +71 -0
  99. data/lib/doto/support/command_options/doto_times.rb +48 -0
  100. data/lib/doto/support/command_options/time.rb +84 -0
  101. data/lib/doto/support/command_options/time_mnemonic.rb +108 -0
  102. data/lib/doto/support/command_options/time_mnemonics.rb +16 -0
  103. data/lib/doto/support/descriptable.rb +29 -0
  104. data/lib/doto/support/entry_group_browsable.rb +104 -0
  105. data/lib/doto/support/field_errors.rb +11 -0
  106. data/lib/doto/support/fileable.rb +136 -0
  107. data/lib/doto/support/presentable.rb +11 -0
  108. data/lib/doto/support/project_file_system.rb +118 -0
  109. data/lib/doto/support/short_string.rb +24 -0
  110. data/lib/doto/support/time_comparable.rb +21 -0
  111. data/lib/doto/support/time_formatable.rb +65 -0
  112. data/lib/doto/support/times_sortable.rb +71 -0
  113. data/lib/doto/support/transform_project_name.rb +24 -0
  114. data/lib/doto/support/utils.rb +11 -0
  115. data/lib/doto/validators/color_theme_validator.rb +74 -0
  116. data/lib/doto/validators/description_validator.rb +51 -0
  117. data/lib/doto/validators/entries_validator.rb +77 -0
  118. data/lib/doto/validators/project_name_validator.rb +58 -0
  119. data/lib/doto/validators/time_validator.rb +25 -0
  120. data/lib/doto/validators/version_validator.rb +29 -0
  121. data/lib/doto/version.rb +6 -0
  122. data/lib/doto/views/base_list_view.rb +41 -0
  123. data/lib/doto/views/color_theme/index.rb +62 -0
  124. data/lib/doto/views/color_theme/show.rb +107 -0
  125. data/lib/doto/views/configuration/show.rb +41 -0
  126. data/lib/doto/views/entry_group/edit.rb +121 -0
  127. data/lib/doto/views/entry_group/list.rb +23 -0
  128. data/lib/doto/views/entry_group/shared/no_entries_to_display.rb +53 -0
  129. data/lib/doto/views/entry_group/shared/no_entries_to_display_for_month_of.rb +32 -0
  130. data/lib/doto/views/entry_group/shared/no_entries_to_display_for_week_of.rb +33 -0
  131. data/lib/doto/views/entry_group/shared/no_entries_to_display_for_year_of.rb +33 -0
  132. data/lib/doto/views/entry_group/show.rb +63 -0
  133. data/lib/doto/views/export.rb +82 -0
  134. data/lib/doto/views/import.rb +105 -0
  135. data/lib/doto/views/import_dates.rb +17 -0
  136. data/lib/doto/views/project/create.rb +87 -0
  137. data/lib/doto/views/project/delete.rb +96 -0
  138. data/lib/doto/views/project/delete_by_number.rb +19 -0
  139. data/lib/doto/views/project/list.rb +115 -0
  140. data/lib/doto/views/project/rename.rb +98 -0
  141. data/lib/doto/views/project/rename_by_number.rb +21 -0
  142. data/lib/doto/views/project/use.rb +97 -0
  143. data/lib/doto/views/project/use_by_number.rb +19 -0
  144. data/lib/doto/views/shared/error.rb +17 -0
  145. data/lib/doto/views/shared/info.rb +17 -0
  146. data/lib/doto/views/shared/message.rb +85 -0
  147. data/lib/doto/views/shared/model_errors.rb +32 -0
  148. data/lib/doto/views/shared/success.rb +17 -0
  149. data/lib/doto/views/shared/warning.rb +17 -0
  150. data/lib/doto.rb +33 -0
  151. data/lib/locales/en/active_record.yml +17 -0
  152. data/lib/locales/en/commands.yml +165 -0
  153. data/lib/locales/en/miscellaneous.yml +29 -0
  154. data/lib/locales/en/presenters.yml +19 -0
  155. data/lib/locales/en/services.yml +14 -0
  156. data/lib/locales/en/subcommands.yml +786 -0
  157. data/lib/seed_data/0/.todo +5 -0
  158. data/lib/seed_data/20230613121411/.doto +8 -0
  159. data/lib/seed_data/20230613121411/doto/migration_version.json +3 -0
  160. data/lib/seed_data/20230613121411/doto/themes/cherry.json +79 -0
  161. data/lib/seed_data/20230613121411/doto/themes/christmas.json +79 -0
  162. data/lib/seed_data/20230613121411/doto/themes/default.json +79 -0
  163. data/lib/seed_data/20230613121411/doto/themes/lemon.json +79 -0
  164. data/lib/seed_data/20230613121411/doto/themes/light.json +79 -0
  165. data/lib/seed_data/20230613121411/doto/themes/matrix.json +79 -0
  166. data/lib/seed_data/20230613121411/doto/themes/whiteout.json +79 -0
  167. data/lib/seed_data/20240210161248/.doto +9 -0
  168. data/lib/seed_data/20240210161248/doto/current_project.json +4 -0
  169. data/lib/seed_data/20240210161248/doto/migration_version.json +3 -0
  170. data/lib/seed_data/20240210161248/doto/projects/default/project.json +5 -0
  171. data/lib/seed_data/20240210161248/doto/themes/cherry.json +79 -0
  172. data/lib/seed_data/20240210161248/doto/themes/christmas.json +79 -0
  173. data/lib/seed_data/20240210161248/doto/themes/default.json +79 -0
  174. data/lib/seed_data/20240210161248/doto/themes/lemon.json +79 -0
  175. data/lib/seed_data/20240210161248/doto/themes/light.json +79 -0
  176. data/lib/seed_data/20240210161248/doto/themes/matrix.json +79 -0
  177. data/lib/seed_data/20240210161248/doto/themes/whiteout.json +79 -0
  178. data/sig/dsu.rbs +4 -0
  179. metadata +406 -0
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../env'
4
+ require_relative 'version'
5
+
6
+ module Doto
7
+ module Migration
8
+ class Migrator
9
+ class << self
10
+ def migrate_if!(migration_services: [])
11
+ return if migration_services.any? do |migration_service|
12
+ migration_service.migrate_if!
13
+ migration_service.class.migrates_to_latest_migration_version?
14
+ end
15
+
16
+ raise I18n.t('migrations.error.missing_current_migration_service', migration_version: Migration::VERSION)
17
+ rescue StandardError => e
18
+ puts I18n.t('migrations.error.failed', message: e.message)
19
+ exit 1 unless Doto.env.test?
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doto
4
+ module Migration
5
+ module RawHelpers
6
+ module ColorThemeHash
7
+ def to_h
8
+ read.merge(version: version)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doto
4
+ module Migration
5
+ module RawHelpers
6
+ module ConfigurationHash
7
+ attr_accessor :default_project
8
+
9
+ def to_h
10
+ read.merge(version: version, default_project: default_project)
11
+ end
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doto
4
+ module Migration
5
+ module RawHelpers
6
+ module EntryGroupHash
7
+ def to_h
8
+ read.merge(version: version)
9
+ end
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../crud/json_file'
4
+
5
+ module Doto
6
+ module Migration
7
+ class RawJsonFile < Crud::JsonFile
8
+ public :read, :read!, :version=
9
+
10
+ def to_h
11
+ read.merge(version: version)
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,56 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'fileutils'
4
+ require 'pathname'
5
+
6
+ require_relative 'raw_json_file'
7
+
8
+ module Doto
9
+ module Migration
10
+ class RawJsonFiles
11
+ attr_reader :folder
12
+
13
+ def initialize(folder)
14
+ @folder = folder
15
+ end
16
+
17
+ def each_file(regex: //)
18
+ return unless folder_exist?
19
+
20
+ Pathname.new(folder).children.each do |child|
21
+ next unless child.file? && child.to_s.match?(regex)
22
+
23
+ yield RawJsonFile.new(child)
24
+ end
25
+ end
26
+
27
+ def folder_exist?
28
+ self.class.folder_exist?(folder: folder)
29
+ end
30
+
31
+ class << self
32
+ def folder_exist?(folder:)
33
+ Dir.exist?(folder)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ attr_writer :folder
40
+
41
+ def safe_cp_r(source, destination)
42
+ Pathname.new(source).find do |source_path|
43
+ next if source_path.directory?
44
+
45
+ relative_path = source_path.relative_path_from(Pathname.new(source))
46
+ target_path = Pathname.new(destination).join(relative_path)
47
+
48
+ next if target_path.exist?
49
+
50
+ FileUtils.mkdir_p(target_path.dirname)
51
+ FileUtils.cp(source_path, target_path)
52
+ end
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,94 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../base_service'
4
+ require_relative '../raw_helpers/entry_group_hash'
5
+ require_relative '../version'
6
+
7
+ module Doto
8
+ module Migration
9
+ module V20230613121411
10
+ class Service < BaseService
11
+ class << self
12
+ def from_migration_version
13
+ 0
14
+ end
15
+
16
+ def to_migration_version
17
+ 20230613121411 # rubocop:disable Style/NumericLiterals
18
+ end
19
+ end
20
+
21
+ private
22
+
23
+ def run_migration!
24
+ super
25
+
26
+ raise_backup_folder_does_not_exist_error_if!
27
+
28
+ delete_old_config_file
29
+ delete_old_themes_folder
30
+
31
+ copy_new_doto_folders
32
+ copy_new_doto_configuration
33
+ migrate_entry_groups
34
+ end
35
+
36
+ def config_file_from
37
+ File.join(root_folder, '.doto')
38
+ end
39
+
40
+ def doto_folder_from
41
+ File.join(root_folder, 'doto')
42
+ end
43
+
44
+ def entries_folder_from
45
+ File.join(doto_folder_from, 'entries')
46
+ end
47
+
48
+ def themes_folder_from
49
+ File.join(doto_folder_from, 'themes')
50
+ end
51
+
52
+ def copy_new_doto_folders
53
+ puts 'Copying new doto folders...'
54
+
55
+ FileUtils.cp_r(File.join(seed_data_folder, '.'), File.join(root_folder, 'doto')) unless pretend?
56
+ end
57
+
58
+ def copy_new_doto_configuration
59
+ puts 'Copying new doto configuration...'
60
+
61
+ FileUtils.cp(seed_data_configuration, config_folder) unless pretend?
62
+ end
63
+
64
+ def migrate_entry_groups
65
+ puts 'Migrating entry groups...'
66
+
67
+ return if pretend?
68
+
69
+ puts "\tUpdating entry group version..."
70
+
71
+ RawJsonFiles.new(entries_folder_from).each_file(regex: /\d{4}-\d{2}-\d{2}.json/) do |raw_entry_group|
72
+ raw_entry_group.extend(RawHelpers::EntryGroupHash)
73
+ raw_entry_group.version = to_migration_version
74
+ raw_entry_group.save!
75
+ end
76
+ end
77
+
78
+ def delete_old_config_file
79
+ puts 'Deleting old configuration file...'
80
+
81
+ return if pretend?
82
+
83
+ File.delete(config_file_from) if File.file?(config_file_from)
84
+ end
85
+
86
+ def delete_old_themes_folder
87
+ puts 'Deleting old themes folder...'
88
+
89
+ FileUtils.rm_rf(themes_folder_from) unless pretend?
90
+ end
91
+ end
92
+ end
93
+ end
94
+ end
@@ -0,0 +1,148 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../base_service'
4
+ require_relative '../raw_helpers/color_theme_hash'
5
+ require_relative '../raw_helpers/configuration_hash'
6
+ require_relative '../raw_helpers/entry_group_hash'
7
+ require_relative '../raw_json_file'
8
+ require_relative '../version'
9
+
10
+ module Doto
11
+ module Migration
12
+ module V20240210161248
13
+ class Service < BaseService
14
+ class << self
15
+ def from_migration_version
16
+ 20230613121411 # rubocop:disable Style/NumericLiterals
17
+ end
18
+
19
+ def to_migration_version
20
+ 20240210161248 # rubocop:disable Style/NumericLiterals
21
+ end
22
+ end
23
+
24
+ private
25
+
26
+ def run_migration!
27
+ super
28
+
29
+ raise_backup_folder_does_not_exist_error_if!
30
+
31
+ add_new_color_themes
32
+ create_default_project
33
+ create_current_project_file
34
+ update_configuration
35
+ update_entry_groups
36
+ update_color_themes
37
+ delete_old_entry_folder
38
+
39
+ puts 'Migration completed successfully.'
40
+ end
41
+
42
+ def doto_folder_from
43
+ File.join(root_folder, 'doto')
44
+ end
45
+
46
+ def entries_folder_from
47
+ File.join(doto_folder_from, 'entries')
48
+ end
49
+
50
+ def themes_folder_from
51
+ File.join(doto_folder_from, 'themes')
52
+ end
53
+
54
+ def add_new_color_themes
55
+ puts 'Copying new color themes...'
56
+
57
+ FileUtils.mkdir_p(themes_folder_from) unless pretend?
58
+
59
+ %w[light.json christmas.json].each do |theme_file|
60
+ destination_theme_file_path = File.join(themes_folder_from, theme_file)
61
+ # Don't skip these theme files because they were deployed in the previous
62
+ # doto version with bugs. We need to overwrite them with this new version.
63
+ # next if File.exist?(destination_theme_file_path)
64
+
65
+ source_theme_file_path = File.join(seed_data_folder, 'themes', theme_file)
66
+ FileUtils.cp(source_theme_file_path, destination_theme_file_path) unless pretend?
67
+ end
68
+ end
69
+
70
+ def create_default_project
71
+ puts "Creating default project \"#{default_project_name}\"..."
72
+
73
+ return if pretend?
74
+
75
+ FileUtils.cp_r(File.join(seed_data_folder, 'projects', '.'),
76
+ File.join(doto_folder, 'projects'))
77
+ end
78
+
79
+ def create_current_project_file
80
+ puts 'Creating current project file...'
81
+
82
+ return if pretend?
83
+
84
+ # NOTE: doto_folder won't change and is safe to use here.
85
+ FileUtils.cp(File.join(seed_data_folder, 'current_project.json'), doto_folder)
86
+ end
87
+
88
+ def update_configuration
89
+ puts 'Updating configuration...'
90
+
91
+ return if pretend?
92
+
93
+ # NOTE: config_path won't change and is safe to use here.
94
+ RawJsonFile.new(config_path).tap do |configuration_file|
95
+ configuration_file.extend(RawHelpers::ConfigurationHash)
96
+ configuration_file.version = to_migration_version
97
+ configuration_file.default_project = default_project_name
98
+ end.save!
99
+ end
100
+
101
+ def update_entry_groups
102
+ puts 'Updating entry groups...'
103
+
104
+ return if pretend?
105
+
106
+ puts "\tCopying entries to default project \"#{default_project_name}\"..."
107
+
108
+ entries_folder_to = File.join(doto_folder, 'projects', default_project_name, 'entries')
109
+ FileUtils.cp_r(File.join(entries_folder_from, '.'), entries_folder_to)
110
+
111
+ puts "\tUpdating entry group version..."
112
+
113
+ RawJsonFiles.new(entries_folder_to).each_file(regex: /\d{4}-\d{2}-\d{2}.json/) do |raw_entry_group|
114
+ raw_entry_group.extend(RawHelpers::EntryGroupHash)
115
+ raw_entry_group.version = to_migration_version
116
+ raw_entry_group.save!
117
+ end
118
+ end
119
+
120
+ def update_color_themes
121
+ puts 'Updating color themes...'
122
+
123
+ FileUtils.cp_r(File.join(backup_folder, 'themes', '.'), themes_folder) unless pretend?
124
+
125
+ puts "\tUpdating color theme version..."
126
+
127
+ themes_folder_to = File.join(doto_folder, 'themes')
128
+
129
+ RawJsonFiles.new(themes_folder_to).each_file(regex: /.+.json/) do |raw_entry_group|
130
+ raw_entry_group.extend(RawHelpers::ColorThemeHash)
131
+ raw_entry_group.version = to_migration_version
132
+ raw_entry_group.save! unless pretend?
133
+ end
134
+ end
135
+
136
+ def delete_old_entry_folder
137
+ puts 'Cleaning up old entries...'
138
+
139
+ FileUtils.rm_rf(File.join(entries_folder_from)) unless pretend?
140
+ end
141
+
142
+ def default_project_name
143
+ 'default'
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doto
4
+ module Migration
5
+ VERSION = 20240210161248 # rubocop:disable Style/NumericLiterals
6
+ end
7
+ end
@@ -0,0 +1,224 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../crud/json_file'
4
+ require_relative '../migration/version'
5
+ require_relative '../support/color_themable'
6
+ require_relative '../support/descriptable'
7
+ require_relative '../support/fileable'
8
+ require_relative '../support/presentable'
9
+ require_relative '../validators/color_theme_validator'
10
+ require_relative '../validators/description_validator'
11
+ require_relative '../validators/version_validator'
12
+ require_relative 'configuration'
13
+
14
+ module Doto
15
+ module Models
16
+ # This class represents a doto color theme.
17
+ class ColorTheme < Crud::JsonFile
18
+ include Support::ColorThemable
19
+ include Support::Descriptable
20
+ include Support::Fileable
21
+ include Support::Presentable
22
+
23
+ THEME_FILE_NAME_REGEX = /.+.json/
24
+
25
+ VERSION = Migration::VERSION
26
+
27
+ DEFAULT_THEME_NAME = 'default'
28
+ # Theme colors key/value pair format:
29
+ # <key>: { color: <color> [, mode: <mode>] [, background: <background>] }
30
+ # Where <color> (required) == any color represented in the colorize gem `String.colors` array.
31
+ # <mode> (optional, default is :default) == any mode represented in the colorize gem `String.modes` array.
32
+ # <background> (optional, default is :default) == any color represented in the colorize gem
33
+ # `String.colors` array.
34
+ DEFAULT_THEME_COLORS = {
35
+ help: { color: :cyan },
36
+ doto_header: { color: :white, mode: :bold, background: :cyan },
37
+ doto_footer: { color: :cyan },
38
+ header: { color: :cyan, mode: :bold },
39
+ subheader: { color: :cyan, mode: :underline },
40
+ body: { color: :cyan },
41
+ footer: { color: :light_cyan },
42
+ date: { color: :cyan, mode: :bold },
43
+ index: { color: :light_cyan },
44
+ # Status colors.
45
+ info: { color: :cyan },
46
+ success: { color: :green },
47
+ warning: { color: :yellow },
48
+ error: { color: :light_yellow, background: :red },
49
+ # Prompts
50
+ prompt: { color: :cyan, mode: :bold },
51
+ prompt_options: { color: :white, mode: :bold }
52
+ }.freeze
53
+ DEFAULT_THEME = {
54
+ version: VERSION,
55
+ description: 'Default theme.'
56
+ }.merge(DEFAULT_THEME_COLORS).freeze
57
+
58
+ MIN_DESCRIPTION_LENGTH = 2
59
+ MAX_DESCRIPTION_LENGTH = 256
60
+
61
+ # TODO: Validate other attrs.
62
+ validates_with Validators::DescriptionValidator
63
+ validates_with Validators::ColorThemeValidator
64
+ validates_with Validators::VersionValidator
65
+
66
+ attr_reader :theme_name, :options
67
+
68
+ def initialize(theme_name:, theme_hash: nil, options: {})
69
+ raise ArgumentError, 'theme_name is nil.' if theme_name.nil?
70
+ raise ArgumentError, "theme_name is the wrong object type: \"#{theme_name}\"." unless theme_name.is_a?(String)
71
+ unless theme_hash.is_a?(Hash) || theme_hash.nil?
72
+ raise ArgumentError, "theme_hash is the wrong object type: \"#{theme_hash}\"."
73
+ end
74
+
75
+ FileUtils.mkdir_p themes_folder
76
+
77
+ @theme_name = theme_name
78
+ @options = options || {}
79
+
80
+ super(self.class.send(:themes_path, theme_name: @theme_name))
81
+
82
+ theme_hash ||= DEFAULT_THEME.merge(description: "#{@theme_name.capitalize} theme")
83
+
84
+ # Color themes I expect will change a lot, so we're using
85
+ # a little meta-programming here to dynamically create
86
+ # public attr_readers and private attr_writers based on the
87
+ # keys in DEFAULT_THEME, then assign those attributes from
88
+ # the values in theme_hash. theme_hash will be guaranteed to
89
+ # have the same keys as DEFAULT_THEME.keys at this point
90
+ # because we called ensure_theme_hash! above.
91
+ DEFAULT_THEME.each_key do |attr|
92
+ self.class.class_eval do
93
+ attr_reader attr
94
+ attr_writer attr
95
+ private :"#{attr}="
96
+ end
97
+ attr_value = theme_hash[attr]
98
+ attr_value = attr_value.merge_default_colors if default_theme_color_keys.include?(attr)
99
+ send(:"#{attr}=", attr_value)
100
+ end
101
+ end
102
+
103
+ def delete
104
+ self.class.delete(theme_name: theme_name)
105
+ end
106
+
107
+ def delete!
108
+ self.class.delete!(theme_name: theme_name)
109
+ end
110
+
111
+ def exist?
112
+ self.class.exist?(theme_name: theme_name)
113
+ end
114
+
115
+ class << self
116
+ delegate :themes_folder, :themes_path, to: Support::Fileable
117
+
118
+ def all
119
+ Dir.glob("#{themes_folder}/*").map do |file_path|
120
+ theme_name = File.basename(file_path, '.*')
121
+ find(theme_name: theme_name)
122
+ end
123
+ end
124
+
125
+ def configuration
126
+ Models::Configuration.new
127
+ end
128
+
129
+ def current
130
+ theme_name = configuration.theme_name
131
+ return unless exist?(theme_name: theme_name)
132
+
133
+ find(theme_name: theme_name)
134
+ end
135
+
136
+ # Returns the current color theme if it exists; otherwise,
137
+ # it returns the default color theme.
138
+ def current_or_default
139
+ current || default
140
+ end
141
+
142
+ def default
143
+ new(theme_name: DEFAULT_THEME_NAME, theme_hash: DEFAULT_THEME)
144
+ end
145
+
146
+ def delete(theme_name:)
147
+ superclass.delete(file_path: themes_path(theme_name: theme_name))
148
+ end
149
+
150
+ def delete!(theme_name:)
151
+ superclass.delete!(file_path: themes_path(theme_name: theme_name))
152
+ end
153
+
154
+ def ensure_color_theme_color_defaults_for(theme_hash: DEFAULT_THEME)
155
+ theme_hash = theme_hash.dup
156
+
157
+ theme_hash.each_pair do |key, value|
158
+ next unless default_theme_color_keys.include?(key)
159
+
160
+ theme_hash[key] = value.merge_default_colors
161
+ end
162
+ theme_hash
163
+ end
164
+
165
+ def exist?(theme_name:)
166
+ superclass.file_exist?(file_path: themes_path(theme_name: theme_name))
167
+ end
168
+
169
+ def find(theme_name:)
170
+ theme_hash = read!(file_path: themes_path(theme_name: theme_name))
171
+ Services::ColorTheme::HydratorService.new(theme_name: theme_name, theme_hash: theme_hash).call
172
+ end
173
+
174
+ def find_or_create(theme_name:)
175
+ return find(theme_name: theme_name) if exist?(theme_name: theme_name)
176
+
177
+ new(theme_name: theme_name).tap(&:write!)
178
+ end
179
+
180
+ def find_or_initialize(theme_name:)
181
+ return find(theme_name: theme_name) if exist?(theme_name: theme_name)
182
+
183
+ new(theme_name: theme_name)
184
+ end
185
+
186
+ private
187
+
188
+ def default_theme_color_keys
189
+ DEFAULT_THEME_COLORS.keys
190
+ end
191
+ end
192
+
193
+ def to_h
194
+ {}.tap do |hash|
195
+ DEFAULT_THEME.each_key do |key|
196
+ hash[key] = public_send(key)
197
+ end
198
+ end
199
+ end
200
+
201
+ def ==(other)
202
+ return false unless other.is_a?(self.class)
203
+ return false unless other.theme_name == theme_name
204
+
205
+ DEFAULT_THEME.keys.all? { |key| public_send(key) == other.public_send(key) }
206
+ end
207
+ alias eql? ==
208
+
209
+ def hash
210
+ DEFAULT_THEME.keys.map { |key| public_send(key) }.tap do |hashes|
211
+ hashes << theme_name.hash
212
+ end.hash
213
+ end
214
+
215
+ private
216
+
217
+ attr_writer :theme_name, :description
218
+
219
+ def default_theme_color_keys
220
+ @default_theme_color_keys ||= self.class.send(:default_theme_color_keys)
221
+ end
222
+ end
223
+ end
224
+ end