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,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../env'
4
+ require_relative '../models/color_theme'
5
+ require_relative '../support/color_themable'
6
+
7
+ module Doto
8
+ module Views
9
+ class BaseListView
10
+ include Support::ColorThemable
11
+
12
+ attr_reader :presenter
13
+
14
+ def initialize(presenter:, options: {})
15
+ @presenter = presenter
16
+ @options = options&.dup || {}
17
+ @color_theme = Models::ColorTheme.find(theme_name: theme_name)
18
+ end
19
+
20
+ def render
21
+ yield
22
+ rescue StandardError => e
23
+ puts apply_theme(e.message, theme_color: color_theme.error)
24
+ puts apply_theme(e.backtrace_locations.join("\n"), theme_color: color_theme.error) if Doto.env.local?
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :color_theme, :options
30
+
31
+ def theme_name
32
+ @theme_name ||= options.fetch(:theme_name, Models::Configuration.new.theme_name)
33
+ end
34
+
35
+ def formatted_index(index:)
36
+ apply_theme("#{format('%02s', index + 1)}. ",
37
+ theme_color: color_theme.index)
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../models/configuration'
4
+ require_relative '../../models/color_theme'
5
+
6
+ module Doto
7
+ module Views
8
+ module ColorTheme
9
+ class Index
10
+ include Support::ColorThemable
11
+
12
+ def render
13
+ render!
14
+ end
15
+
16
+ private
17
+
18
+ def render!
19
+ presenter = color_theme.presenter
20
+ puts presenter.header
21
+ puts
22
+ render_theme_details
23
+ puts
24
+ puts presenter.footer
25
+ end
26
+
27
+ def render_theme_details
28
+ themes_folder = Models::Configuration.new.themes_folder
29
+ theme_file_names = Dir.glob("#{themes_folder}/*").map { |theme_path| File.basename(theme_path, '.*') }
30
+ theme_file_names << default_theme_name unless theme_file_names.include?(default_theme_name)
31
+ theme_file_names.sort.each_with_index do |theme_file, index|
32
+ color_theme = if theme_file == default_theme_name
33
+ default_theme
34
+ else
35
+ Models::ColorTheme.find(theme_name: theme_file)
36
+ end
37
+ presenter = color_theme.presenter
38
+ puts presenter.detail_with_index(index: index)
39
+ end
40
+ end
41
+
42
+ # When getting the default theme, if it exists on disk, use that; otherwise,
43
+ # use the in-memory default theme.
44
+ def default_theme
45
+ if Models::ColorTheme.exist?(theme_name: default_theme_name)
46
+ Models::ColorTheme.find(theme_name: default_theme_name)
47
+ else
48
+ Models::ColorTheme.default
49
+ end
50
+ end
51
+
52
+ def default_theme_name
53
+ Doto::Models::ColorTheme::DEFAULT_THEME_NAME
54
+ end
55
+
56
+ def color_theme
57
+ @color_theme ||= Models::ColorTheme.current_or_default
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,107 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../models/color_theme'
4
+ require_relative '../../models/configuration'
5
+ require_relative '../entry_group/show'
6
+ require_relative '../shared/error'
7
+ require_relative '../shared/info'
8
+ require_relative '../shared/success'
9
+ require_relative '../shared/warning'
10
+
11
+ module Doto
12
+ module Views
13
+ module ColorTheme
14
+ # TODO: I18n this class.
15
+ class Show
16
+ include Support::ColorThemable
17
+
18
+ def initialize(theme_name:, options: {})
19
+ @theme_name = theme_name
20
+ @options = options || {}
21
+ end
22
+
23
+ def render
24
+ render!
25
+ end
26
+
27
+ private
28
+
29
+ attr_reader :theme_name, :options
30
+
31
+ def color_theme
32
+ @color_theme ||= Models::ColorTheme.find_or_initialize(theme_name: theme_name)
33
+ end
34
+
35
+ def presenter
36
+ @presenter ||= Doto::Presenters::ColorThemeShowPresenter.new(color_theme, options: options)
37
+ end
38
+
39
+ def render!
40
+ puts presenter.header
41
+ puts
42
+
43
+ presenter.detail
44
+ puts
45
+
46
+ display_entry_group_example
47
+ puts
48
+
49
+ display_configuration_example
50
+ puts
51
+
52
+ display_messages_example
53
+ puts
54
+
55
+ puts presenter.footer
56
+ end
57
+
58
+ def display_entry_group_example
59
+ puts apply_theme('`doto list` example', theme_color: color_theme.subheader)
60
+ puts
61
+
62
+ options = custom_options
63
+ entry_group = mock_entry_group(options)
64
+ EntryGroup::Show.new(entry_group: entry_group, options: options).render
65
+ end
66
+
67
+ def display_configuration_example
68
+ puts apply_theme('`doto config info` example', theme_color: color_theme.subheader)
69
+ puts
70
+
71
+ Views::Configuration::Show.new(config: Models::Configuration.new(options: custom_options)).render
72
+ end
73
+
74
+ def display_messages_example
75
+ options = custom_options
76
+ messages = ['Example 1', 'Example 2', 'Example 3']
77
+
78
+ puts apply_theme('Message examples', theme_color: color_theme.subheader)
79
+ puts
80
+
81
+ Shared::Error.new(messages: messages, header: 'Errors example', options: options).render
82
+ puts
83
+
84
+ Shared::Info.new(messages: messages, header: 'Info example', options: options).render
85
+ puts
86
+
87
+ Shared::Success.new(messages: messages, header: 'Success example', options: options).render
88
+ puts
89
+
90
+ Shared::Warning.new(messages: messages, header: 'Warning example', options: options).render
91
+ end
92
+
93
+ def mock_entry_group(options)
94
+ @mock_entry_group ||= Doto::Models::EntryGroup.new(time: Time.now, entries: [
95
+ Doto::Models::Entry.new(description: 'Doto entry 1', options: options),
96
+ Doto::Models::Entry.new(description: 'Doto entry 2', options: options),
97
+ Doto::Models::Entry.new(description: 'Doto entry 3', options: options)
98
+ ], options: options)
99
+ end
100
+
101
+ def custom_options
102
+ options.merge(theme_name: color_theme.theme_name, output_stream: $stdout)
103
+ end
104
+ end
105
+ end
106
+ end
107
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'active_support/core_ext/numeric/time'
5
+ require_relative '../../models/configuration'
6
+ require_relative '../../support/color_themable'
7
+
8
+ module Doto
9
+ module Views
10
+ module Configuration
11
+ class Show
12
+ include Support::ColorThemable
13
+
14
+ def initialize(config:, options: {})
15
+ raise ArgumentError, 'config is nil' if config.nil?
16
+ raise ArgumentError, 'config is the wrong object type' unless config.is_a?(Models::Configuration)
17
+ raise ArgumentError, 'options is nil' if options.nil?
18
+ raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
19
+
20
+ @config = config
21
+ @options = options || {}
22
+ end
23
+
24
+ def call
25
+ render!
26
+ end
27
+ alias render call
28
+
29
+ private
30
+
31
+ attr_reader :config, :options
32
+
33
+ def render!
34
+ presenter = config.presenter
35
+ puts presenter.configuration_header
36
+ puts presenter.configuration_details
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,121 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../models/configuration'
4
+ require_relative '../../models/entry_group'
5
+
6
+ module Doto
7
+ module Views
8
+ module EntryGroup
9
+ # TODO: I18n this class.
10
+ class Edit
11
+ def initialize(entry_group:, options: {})
12
+ raise ArgumentError, 'entry_group is nil' if entry_group.nil?
13
+ raise ArgumentError, 'entry_group is the wrong object type' unless entry_group.is_a?(Models::EntryGroup)
14
+ raise ArgumentError, 'options is nil' if options.nil?
15
+ raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
16
+
17
+ @entry_group = entry_group
18
+ @options = options || {}
19
+ end
20
+
21
+ def render
22
+ puts render_as_string
23
+ end
24
+
25
+ def render_as_string
26
+ # Just in case the entry group is invalid, we'll validate it before displaying it.
27
+ entry_group.validate!
28
+
29
+ <<~EDIT_VIEW
30
+ #{banner_line}
31
+ # Editing TODO Entries for #{entry_group.time_formatted}
32
+ #{banner_line}
33
+
34
+ #{entry_group_view&.chomp}
35
+
36
+ #{banner_line}
37
+ # INSTRUCTIONS
38
+ #{banner_line}
39
+ # ADD a TODO entry: type an ENTRY DESCRIPTION on a new line.
40
+ # EDIT a TODO entry: change the existing ENTRY DESCRIPTION.
41
+ # DELETE a TODO entry: delete the ENTRY DESCRIPTION.
42
+ # NOTE: deleting all of the ENTRY DESCRIPTIONs will delete the entry group file;
43
+ # this is preferable if this is what you want to do :)
44
+ # REORDER a TODO entry: reorder the ENTRY DESCRIPTIONs in order preference.
45
+ #
46
+ # *** When you are done, save and close your editor ***
47
+ #{banner_line}
48
+ EDIT_VIEW
49
+ end
50
+
51
+ private
52
+
53
+ attr_reader :entry_group, :options
54
+
55
+ def time
56
+ @time ||= entry_group.time
57
+ end
58
+
59
+ def banner_line
60
+ '#' * 80
61
+ end
62
+
63
+ def entry_group_view
64
+ return entry_group_entry_lines if entry_group.entries.any?
65
+ return previous_entry_group_entry_lines if carry_over_entries_to_today? && previous_entry_group?
66
+
67
+ <<~EDIT_VIEW
68
+ #{banner_line}
69
+ # ENTER TODO ENTRIES BELOW
70
+ #{banner_line}
71
+
72
+ EDIT_VIEW
73
+ end
74
+
75
+ def entry_group_entry_lines
76
+ raise 'No entries in entry group' if entry_group.entries.empty?
77
+
78
+ <<~EDIT_VIEW
79
+ #{banner_line}
80
+ # TODO ENTRIES
81
+ #{banner_line}
82
+
83
+ #{entry_group.entries.map(&:description).join("\n").chomp}
84
+ EDIT_VIEW
85
+ end
86
+
87
+ def previous_entry_group_entry_lines
88
+ raise 'carry_over_entries_to_today? is false' unless carry_over_entries_to_today?
89
+ raise 'Entries exist in entry_group' if entry_group.entries.any?
90
+ raise 'No previous entry group exists' unless previous_entry_group?
91
+
92
+ <<~EDIT_VIEW
93
+ #{banner_line}
94
+ # PREVIOUS TODO ENTRIES FROM #{previous_entry_group.time_formatted}
95
+ #{banner_line}
96
+
97
+ #{previous_entry_group.entries.map(&:description).join("\n").chomp}
98
+ EDIT_VIEW
99
+ end
100
+
101
+ def previous_entry_group?
102
+ previous_entry_group&.entries&.present?
103
+ end
104
+
105
+ def previous_entry_group
106
+ # Go back a max of 7 days to find the previous entry group.
107
+ # TODO: Make this configurable or accept an option?
108
+ @previous_entry_group ||= (1..7).each do |days|
109
+ t = time.days_ago(days)
110
+ return Models::EntryGroup.find(time: t) if Models::EntryGroup.exist?(time: t)
111
+ end
112
+ nil
113
+ end
114
+
115
+ def carry_over_entries_to_today?
116
+ Models::Configuration.new.merge(options).carry_over_entries_to_today?
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Doto
4
+ module Views
5
+ module EntryGroup
6
+ class List
7
+ def initialize(presenter:)
8
+ @presenter = presenter
9
+ end
10
+
11
+ def render
12
+ return presenter.display_nothing_to_list_message if presenter.nothing_to_list?
13
+
14
+ presenter.render
15
+ end
16
+
17
+ private
18
+
19
+ attr_reader :presenter
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,53 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../../../models/color_theme'
4
+ require_relative '../../../support/color_themable'
5
+ require_relative '../../../support/time_formatable'
6
+
7
+ module Doto
8
+ module Views
9
+ module EntryGroup
10
+ module Shared
11
+ class NoEntriesToDisplay
12
+ include Support::ColorThemable
13
+ include Support::TimeFormatable
14
+
15
+ def initialize(times:, options: {})
16
+ raise ArgumentError, 'times must be an Array' unless times.is_a?(Array)
17
+ raise ArgumentError, 'times must contain Time objects' unless times.all?(Time)
18
+ raise ArgumentError, 'options must be a Hash' unless options.is_a?(Hash) || options.nil?
19
+
20
+ @times = times
21
+ @options = options || {}
22
+ end
23
+
24
+ # TODO: I18n.
25
+ def render
26
+ puts render_as_string
27
+ end
28
+
29
+ def render_as_string
30
+ apply_theme(message, theme_color: color_theme.info)
31
+ end
32
+
33
+ private
34
+
35
+ attr_reader :times, :options
36
+
37
+ def message
38
+ "(nothing to display for #{time_range})"
39
+ end
40
+
41
+ def time_range
42
+ "#{formatted_time(time: times.min)} " \
43
+ "through #{formatted_time(time: times.max)}"
44
+ end
45
+
46
+ def color_theme
47
+ @color_theme ||= Models::ColorTheme.current_or_default
48
+ end
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'no_entries_to_display'
4
+
5
+ module Doto
6
+ module Views
7
+ module EntryGroup
8
+ module Shared
9
+ class NoEntriesToDisplayForMonthOf < NoEntriesToDisplay
10
+ def initialize(time:, options: {})
11
+ super(times: [time.beginning_of_month, time.end_of_month], options: options)
12
+
13
+ @time = time
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :time
19
+
20
+ # TODO: I18n.
21
+ def message
22
+ "(nothing to display for the month of #{month_string}, #{time_range})"
23
+ end
24
+
25
+ def month_string
26
+ I18n.l(time, format: '%B')
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'no_entries_to_display'
4
+
5
+ module Doto
6
+ module Views
7
+ module EntryGroup
8
+ module Shared
9
+ class NoEntriesToDisplayForWeekOf < NoEntriesToDisplay
10
+ def initialize(time:, options: {})
11
+ super(times: [time.beginning_of_week, time.end_of_week], options: options)
12
+
13
+ @time = time
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :time
19
+
20
+ # TODO: I18n.
21
+ def message
22
+ "(nothing to display for week of #{week_of_string}, #{time_range})"
23
+ end
24
+
25
+ # TODO: I18n.
26
+ def week_of_string
27
+ time.to_date
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'no_entries_to_display'
4
+
5
+ module Doto
6
+ module Views
7
+ module EntryGroup
8
+ module Shared
9
+ class NoEntriesToDisplayForYearOf < NoEntriesToDisplay
10
+ def initialize(time:, options: {})
11
+ super(times: [time.beginning_of_year, time.end_of_year], options: options)
12
+
13
+ @time = time
14
+ end
15
+
16
+ private
17
+
18
+ attr_reader :time
19
+
20
+ # TODO: I18n.
21
+ def message
22
+ "(nothing to display for the year of #{year_string}, #{time_range})"
23
+ end
24
+
25
+ # TODO: I18n.
26
+ def year_string
27
+ time.year
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'active_support/core_ext/numeric/time'
5
+ require_relative '../../models/entry_group'
6
+ require_relative '../../support/color_themable'
7
+ require_relative '../../support/time_formatable'
8
+
9
+ module Doto
10
+ module Views
11
+ module EntryGroup
12
+ class Show
13
+ include Support::ColorThemable
14
+ include Support::TimeFormatable
15
+
16
+ def initialize(entry_group:, options: {})
17
+ raise ArgumentError, 'entry_group is nil' if entry_group.nil?
18
+ raise ArgumentError, 'entry_group is the wrong object type' unless entry_group.is_a?(Models::EntryGroup)
19
+ raise ArgumentError, 'options is nil' if options.nil?
20
+ raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
21
+
22
+ @entry_group = entry_group
23
+ @options = options || {}
24
+ end
25
+
26
+ def call
27
+ render!
28
+ end
29
+ alias render call
30
+
31
+ private
32
+
33
+ attr_reader :entry_group, :options
34
+
35
+ def render!
36
+ puts presenter.formatted_time
37
+
38
+ entry_group.validate!
39
+ if entry_group.entries.empty?
40
+ puts presenter.no_entries_available
41
+
42
+ return
43
+ end
44
+
45
+ entry_group.entries.each_with_index do |entry, index|
46
+ entry_presenter = entry.presenter
47
+ puts entry_presenter.formatted_description_with_index(index: index)
48
+ end
49
+ rescue ActiveModel::ValidationError
50
+ puts apply_theme(errors(entry_group), theme_color: presenter.color_theme.error)
51
+ end
52
+
53
+ def errors(model)
54
+ model.errors.full_messages.join(', ')
55
+ end
56
+
57
+ def presenter
58
+ @presenter ||= entry_group.presenter
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../models/color_theme'
4
+ require_relative '../support/ask'
5
+ require_relative '../support/color_themable'
6
+
7
+ module Doto
8
+ module Views
9
+ class Export
10
+ include Support::Ask
11
+ include Support::ColorThemable
12
+
13
+ def initialize(presenter:, options:)
14
+ @presenter = presenter
15
+ @options = options&.dup || {}
16
+ @color_theme = Models::ColorTheme.find(theme_name: theme_name)
17
+ end
18
+
19
+ def render
20
+ return display_nothing_to_export_message if presenter.nothing_to_export?
21
+
22
+ response = display_export_prompt
23
+ if presenter.respond response: response
24
+ display_exported_message
25
+ display_exported_to_message(file_path: presenter.export_file_path)
26
+ else
27
+ display_cancelled_message
28
+ end
29
+ rescue StandardError => e
30
+ puts apply_theme(e.message, theme_color: color_theme.error)
31
+ puts apply_theme(e.backtrace_locations.join("\n"), theme_color: color_theme.error) if Doto.env.local?
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :presenter, :color_theme, :options
37
+
38
+ def project_name
39
+ presenter.project_name
40
+ end
41
+
42
+ def display_export_prompt
43
+ response = ask_while(prompt_with_options(prompt: export_prompt,
44
+ options: export_prompt_options), options: options) do |input|
45
+ message = I18n.t('information.input.try_again', options: export_prompt_options.join(','))
46
+ puts apply_theme(message, theme_color: color_theme.info) unless export_prompt_options.include?(input)
47
+ export_prompt_options.include?(input)
48
+ end
49
+ response == export_prompt_options.first
50
+ end
51
+
52
+ def display_cancelled_message
53
+ puts apply_theme(I18n.t('subcommands.export.messages.cancelled'), theme_color: color_theme.info)
54
+ end
55
+
56
+ def display_exported_message
57
+ puts apply_theme(I18n.t('subcommands.export.messages.exported'), theme_color: color_theme.success)
58
+ end
59
+
60
+ def display_exported_to_message(file_path:)
61
+ puts apply_theme(I18n.t('subcommands.export.messages.exported_to', file_path: file_path),
62
+ theme_color: color_theme.success)
63
+ end
64
+
65
+ def display_nothing_to_export_message
66
+ puts apply_theme(I18n.t('subcommands.export.messages.nothing_to_export'), theme_color: color_theme.info)
67
+ end
68
+
69
+ def export_prompt
70
+ I18n.t('subcommands.export.prompts.export_all_confirm', count: presenter.entry_group_count)
71
+ end
72
+
73
+ def export_prompt_options
74
+ I18n.t('subcommands.export.prompts.options')
75
+ end
76
+
77
+ def theme_name
78
+ @theme_name ||= options.fetch(:theme_name, Models::Configuration.new.theme_name)
79
+ end
80
+ end
81
+ end
82
+ end