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.
- checksums.yaml +7 -0
- data/.env.test +1 -0
- data/.reek.yml +20 -0
- data/.rspec +3 -0
- data/.rubocop.yml +206 -0
- data/.ruby-version +1 -0
- data/CHANGELOG.md +7 -0
- data/CODE_OF_CONDUCT.md +84 -0
- data/Gemfile +30 -0
- data/Gemfile.lock +179 -0
- data/LICENSE.txt +21 -0
- data/README.md +38 -0
- data/Rakefile +16 -0
- data/bin/console +36 -0
- data/bin/doto +3 -0
- data/bin/setup +18 -0
- data/exe/doto +33 -0
- data/lib/core/ruby/color_theme_colors.rb +16 -0
- data/lib/core/ruby/color_theme_mode.rb +42 -0
- data/lib/core/ruby/wrap_and_join.rb +31 -0
- data/lib/doto/base_cli.rb +56 -0
- data/lib/doto/cli.rb +131 -0
- data/lib/doto/command_services/add_entry_service.rb +50 -0
- data/lib/doto/crud/json_file.rb +161 -0
- data/lib/doto/env.rb +44 -0
- data/lib/doto/migration/base_service.rb +118 -0
- data/lib/doto/migration/migrator.rb +24 -0
- data/lib/doto/migration/raw_helpers/color_theme_hash.rb +13 -0
- data/lib/doto/migration/raw_helpers/configuration_hash.rb +15 -0
- data/lib/doto/migration/raw_helpers/entry_group_hash.rb +13 -0
- data/lib/doto/migration/raw_json_file.rb +15 -0
- data/lib/doto/migration/raw_json_files.rb +56 -0
- data/lib/doto/migration/v20230613121411/service.rb +94 -0
- data/lib/doto/migration/v20240210161248/service.rb +148 -0
- data/lib/doto/migration/version.rb +7 -0
- data/lib/doto/models/color_theme.rb +224 -0
- data/lib/doto/models/configuration.rb +185 -0
- data/lib/doto/models/entry.rb +63 -0
- data/lib/doto/models/entry_group.rb +223 -0
- data/lib/doto/models/migration_version.rb +49 -0
- data/lib/doto/models/project.rb +295 -0
- data/lib/doto/presenters/base_presenter.rb +32 -0
- data/lib/doto/presenters/base_presenter_ex.rb +15 -0
- data/lib/doto/presenters/color_theme_presenter.rb +52 -0
- data/lib/doto/presenters/color_theme_show_presenter.rb +55 -0
- data/lib/doto/presenters/configuration_presenter.rb +50 -0
- data/lib/doto/presenters/entry_group/list/date_presenter.rb +77 -0
- data/lib/doto/presenters/entry_group/list/dates_presenter.rb +60 -0
- data/lib/doto/presenters/entry_group/list/messages.rb +15 -0
- data/lib/doto/presenters/entry_group/list/nothing_to_list.rb +15 -0
- data/lib/doto/presenters/entry_group_presenter.rb +35 -0
- data/lib/doto/presenters/entry_presenter.rb +25 -0
- data/lib/doto/presenters/export/all_presenter.rb +44 -0
- data/lib/doto/presenters/export/dates_presenter.rb +55 -0
- data/lib/doto/presenters/import/all_presenter.rb +57 -0
- data/lib/doto/presenters/import/dates_presenter.rb +70 -0
- data/lib/doto/presenters/import/import_entry.rb +22 -0
- data/lib/doto/presenters/import/import_file.rb +33 -0
- data/lib/doto/presenters/project/create_presenter.rb +44 -0
- data/lib/doto/presenters/project/defaultable.rb +15 -0
- data/lib/doto/presenters/project/delete_by_number_presenter.rb +54 -0
- data/lib/doto/presenters/project/delete_presenter.rb +53 -0
- data/lib/doto/presenters/project/list_presenter.rb +24 -0
- data/lib/doto/presenters/project/rename_by_number_presenter.rb +63 -0
- data/lib/doto/presenters/project/rename_presenter.rb +57 -0
- data/lib/doto/presenters/project/use_by_number_presenter.rb +57 -0
- data/lib/doto/presenters/project/use_presenter.rb +56 -0
- data/lib/doto/services/color_theme/hydrator_service.rb +42 -0
- data/lib/doto/services/configuration/hydrator_service.rb +42 -0
- data/lib/doto/services/entry/hydrator_service.rb +33 -0
- data/lib/doto/services/entry_group/browse_service.rb +100 -0
- data/lib/doto/services/entry_group/counter_service.rb +32 -0
- data/lib/doto/services/entry_group/deleter_service.rb +35 -0
- data/lib/doto/services/entry_group/editor_service.rb +103 -0
- data/lib/doto/services/entry_group/exporter_service.rb +98 -0
- data/lib/doto/services/entry_group/hydrator_service.rb +37 -0
- data/lib/doto/services/entry_group/importer_service.rb +117 -0
- data/lib/doto/services/migration_version/hydrator_service.rb +36 -0
- data/lib/doto/services/project/hydrator_service.rb +40 -0
- data/lib/doto/services/project/rename_service.rb +70 -0
- data/lib/doto/services/stderr_redirector_service.rb +27 -0
- data/lib/doto/services/stdout_redirector_service.rb +27 -0
- data/lib/doto/services/temp_file/reader_service.rb +33 -0
- data/lib/doto/services/temp_file/writer_service.rb +35 -0
- data/lib/doto/subcommands/base_subcommand.rb +12 -0
- data/lib/doto/subcommands/browse.rb +49 -0
- data/lib/doto/subcommands/config.rb +81 -0
- data/lib/doto/subcommands/delete.rb +108 -0
- data/lib/doto/subcommands/edit.rb +48 -0
- data/lib/doto/subcommands/export.rb +62 -0
- data/lib/doto/subcommands/import.rb +72 -0
- data/lib/doto/subcommands/list.rb +95 -0
- data/lib/doto/subcommands/project.rb +146 -0
- data/lib/doto/subcommands/theme.rb +131 -0
- data/lib/doto/support/ask.rb +44 -0
- data/lib/doto/support/color_themable.rb +36 -0
- data/lib/doto/support/command_help_colorizeable.rb +34 -0
- data/lib/doto/support/command_hookable.rb +71 -0
- data/lib/doto/support/command_options/doto_times.rb +48 -0
- data/lib/doto/support/command_options/time.rb +84 -0
- data/lib/doto/support/command_options/time_mnemonic.rb +108 -0
- data/lib/doto/support/command_options/time_mnemonics.rb +16 -0
- data/lib/doto/support/descriptable.rb +29 -0
- data/lib/doto/support/entry_group_browsable.rb +104 -0
- data/lib/doto/support/field_errors.rb +11 -0
- data/lib/doto/support/fileable.rb +136 -0
- data/lib/doto/support/presentable.rb +11 -0
- data/lib/doto/support/project_file_system.rb +118 -0
- data/lib/doto/support/short_string.rb +24 -0
- data/lib/doto/support/time_comparable.rb +21 -0
- data/lib/doto/support/time_formatable.rb +65 -0
- data/lib/doto/support/times_sortable.rb +71 -0
- data/lib/doto/support/transform_project_name.rb +24 -0
- data/lib/doto/support/utils.rb +11 -0
- data/lib/doto/validators/color_theme_validator.rb +74 -0
- data/lib/doto/validators/description_validator.rb +51 -0
- data/lib/doto/validators/entries_validator.rb +77 -0
- data/lib/doto/validators/project_name_validator.rb +58 -0
- data/lib/doto/validators/time_validator.rb +25 -0
- data/lib/doto/validators/version_validator.rb +29 -0
- data/lib/doto/version.rb +6 -0
- data/lib/doto/views/base_list_view.rb +41 -0
- data/lib/doto/views/color_theme/index.rb +62 -0
- data/lib/doto/views/color_theme/show.rb +107 -0
- data/lib/doto/views/configuration/show.rb +41 -0
- data/lib/doto/views/entry_group/edit.rb +121 -0
- data/lib/doto/views/entry_group/list.rb +23 -0
- data/lib/doto/views/entry_group/shared/no_entries_to_display.rb +53 -0
- data/lib/doto/views/entry_group/shared/no_entries_to_display_for_month_of.rb +32 -0
- data/lib/doto/views/entry_group/shared/no_entries_to_display_for_week_of.rb +33 -0
- data/lib/doto/views/entry_group/shared/no_entries_to_display_for_year_of.rb +33 -0
- data/lib/doto/views/entry_group/show.rb +63 -0
- data/lib/doto/views/export.rb +82 -0
- data/lib/doto/views/import.rb +105 -0
- data/lib/doto/views/import_dates.rb +17 -0
- data/lib/doto/views/project/create.rb +87 -0
- data/lib/doto/views/project/delete.rb +96 -0
- data/lib/doto/views/project/delete_by_number.rb +19 -0
- data/lib/doto/views/project/list.rb +115 -0
- data/lib/doto/views/project/rename.rb +98 -0
- data/lib/doto/views/project/rename_by_number.rb +21 -0
- data/lib/doto/views/project/use.rb +97 -0
- data/lib/doto/views/project/use_by_number.rb +19 -0
- data/lib/doto/views/shared/error.rb +17 -0
- data/lib/doto/views/shared/info.rb +17 -0
- data/lib/doto/views/shared/message.rb +85 -0
- data/lib/doto/views/shared/model_errors.rb +32 -0
- data/lib/doto/views/shared/success.rb +17 -0
- data/lib/doto/views/shared/warning.rb +17 -0
- data/lib/doto.rb +33 -0
- data/lib/locales/en/active_record.yml +17 -0
- data/lib/locales/en/commands.yml +165 -0
- data/lib/locales/en/miscellaneous.yml +29 -0
- data/lib/locales/en/presenters.yml +19 -0
- data/lib/locales/en/services.yml +14 -0
- data/lib/locales/en/subcommands.yml +786 -0
- data/lib/seed_data/0/.todo +5 -0
- data/lib/seed_data/20230613121411/.doto +8 -0
- data/lib/seed_data/20230613121411/doto/migration_version.json +3 -0
- data/lib/seed_data/20230613121411/doto/themes/cherry.json +79 -0
- data/lib/seed_data/20230613121411/doto/themes/christmas.json +79 -0
- data/lib/seed_data/20230613121411/doto/themes/default.json +79 -0
- data/lib/seed_data/20230613121411/doto/themes/lemon.json +79 -0
- data/lib/seed_data/20230613121411/doto/themes/light.json +79 -0
- data/lib/seed_data/20230613121411/doto/themes/matrix.json +79 -0
- data/lib/seed_data/20230613121411/doto/themes/whiteout.json +79 -0
- data/lib/seed_data/20240210161248/.doto +9 -0
- data/lib/seed_data/20240210161248/doto/current_project.json +4 -0
- data/lib/seed_data/20240210161248/doto/migration_version.json +3 -0
- data/lib/seed_data/20240210161248/doto/projects/default/project.json +5 -0
- data/lib/seed_data/20240210161248/doto/themes/cherry.json +79 -0
- data/lib/seed_data/20240210161248/doto/themes/christmas.json +79 -0
- data/lib/seed_data/20240210161248/doto/themes/default.json +79 -0
- data/lib/seed_data/20240210161248/doto/themes/lemon.json +79 -0
- data/lib/seed_data/20240210161248/doto/themes/light.json +79 -0
- data/lib/seed_data/20240210161248/doto/themes/matrix.json +79 -0
- data/lib/seed_data/20240210161248/doto/themes/whiteout.json +79 -0
- data/sig/dsu.rbs +4 -0
- 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
|