doto 0.0.1.pre.alpha.1

Sign up to get free protection for your applications and to get access to all the features.
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