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
data/exe/doto
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require 'rubygems'
|
|
5
|
+
|
|
6
|
+
lib_dir = File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
|
7
|
+
$LOAD_PATH << lib_dir unless $LOAD_PATH.include?(lib_dir)
|
|
8
|
+
|
|
9
|
+
if File.exist?('.env.development')
|
|
10
|
+
# This loads our development environment when running dev.
|
|
11
|
+
require 'dotenv'
|
|
12
|
+
Dotenv.load('.env.development')
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
require 'doto'
|
|
16
|
+
|
|
17
|
+
if Doto.env.development?
|
|
18
|
+
# This simply allows us to use a folder other than doto folder for the production
|
|
19
|
+
# release, so we don't mess with our current production doto folder.
|
|
20
|
+
module Doto
|
|
21
|
+
module Support
|
|
22
|
+
module Fileable
|
|
23
|
+
def root_folder
|
|
24
|
+
File.join(gem_dir, '.development_home')
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
puts "ATTENTION: Doto root folder is: #{Doto::Support::Fileable.root_folder}!"
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
Doto::CLI.start(ARGV)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ColorThemeColors
|
|
4
|
+
DEFAULT_THEME_COLORS = { color: :default, mode: :default, background: :default }.freeze
|
|
5
|
+
|
|
6
|
+
# Ensures that default colors and mode are represented in the returned Hash.
|
|
7
|
+
def merge_default_colors
|
|
8
|
+
# TODO: Error checking.
|
|
9
|
+
DEFAULT_THEME_COLORS.merge(dup)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def merge_default_colors!
|
|
13
|
+
# TODO: Error checking.
|
|
14
|
+
merge!(merge_default_colors)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
# TODO: Error checking.
|
|
4
|
+
# TODO: Keep <method>! (bang)?
|
|
5
|
+
module ColorThemeMode
|
|
6
|
+
def default!
|
|
7
|
+
dup.merge({ mode: :default })
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def bold!
|
|
11
|
+
dup.merge({ mode: :bold })
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def italic!
|
|
15
|
+
dup.merge({ mode: :italic })
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def underline!
|
|
19
|
+
dup.merge({ mode: :underline })
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def blink!
|
|
23
|
+
dup.merge({ mode: :blink })
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def swap!
|
|
27
|
+
dup.merge({ mode: :swap })
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def hide!
|
|
31
|
+
dup.merge({ mode: :hide })
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def mode!(mode)
|
|
35
|
+
dup.merge({ mode: mode })
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def light!
|
|
39
|
+
light_color = :"light_#{self[:color].to_s.gsub('light_', '')}"
|
|
40
|
+
dup.merge({ color: light_color })
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module WrapAndJoin
|
|
4
|
+
class << self
|
|
5
|
+
def included(base)
|
|
6
|
+
base.const_set(:WRAP_AND_JOIN_JOIN_TOKEN, ', ')
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def wrap_and_join(wrapper: %w["], join: Array::WRAP_AND_JOIN_JOIN_TOKEN)
|
|
11
|
+
validate_wrapper!(wrapper)
|
|
12
|
+
validate_join!(join)
|
|
13
|
+
|
|
14
|
+
wrapper << wrapper.first if wrapper.count == 1
|
|
15
|
+
map { |element| "#{wrapper[0]}#{element}#{wrapper[1]}" }.join(join)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
private
|
|
19
|
+
|
|
20
|
+
def validate_wrapper!(wrapper)
|
|
21
|
+
raise ArgumentError, 'wrapper is nil' if wrapper.nil?
|
|
22
|
+
raise ArgumentError, 'wrapper must be an Array' unless wrapper.is_a?(Array)
|
|
23
|
+
raise ArgumentError, 'wrapper must be an Array of 1 or 2 wrapper elements' unless wrapper.count.between?(1, 2)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def validate_join!(join)
|
|
27
|
+
return if join.nil?
|
|
28
|
+
|
|
29
|
+
raise ArgumentError, 'join must be a String' unless join.is_a?(String)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'bundler'
|
|
4
|
+
require 'thor'
|
|
5
|
+
require_relative 'command_services/add_entry_service'
|
|
6
|
+
require_relative 'models/color_theme'
|
|
7
|
+
require_relative 'models/configuration'
|
|
8
|
+
require_relative 'models/entry_group'
|
|
9
|
+
require_relative 'services/stdout_redirector_service'
|
|
10
|
+
require_relative 'support/color_themable'
|
|
11
|
+
require_relative 'support/command_help_colorizeable'
|
|
12
|
+
require_relative 'support/command_hookable'
|
|
13
|
+
require_relative 'support/times_sortable'
|
|
14
|
+
require_relative 'version'
|
|
15
|
+
require_relative 'views/entry_group/show'
|
|
16
|
+
|
|
17
|
+
module Doto
|
|
18
|
+
class BaseCLI < ::Thor
|
|
19
|
+
include Support::ColorThemable
|
|
20
|
+
include Support::CommandHelpColorizable
|
|
21
|
+
include Support::CommandHookable
|
|
22
|
+
include Support::TimesSortable
|
|
23
|
+
|
|
24
|
+
class_option :debug, type: :boolean, default: false
|
|
25
|
+
|
|
26
|
+
default_command :help
|
|
27
|
+
|
|
28
|
+
def initialize(*args)
|
|
29
|
+
super
|
|
30
|
+
|
|
31
|
+
@configuration = Models::Configuration.new
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class << self
|
|
35
|
+
def exit_on_failure?
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def date_option_description
|
|
40
|
+
I18n.t('options.date_option_description')
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def mnemonic_option_description
|
|
44
|
+
I18n.t('options.mnemonic_option_description')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
private
|
|
49
|
+
|
|
50
|
+
attr_reader :configuration
|
|
51
|
+
|
|
52
|
+
def color_theme
|
|
53
|
+
Models::ColorTheme.current_or_default
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
data/lib/doto/cli.rb
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'time'
|
|
5
|
+
require_relative 'base_cli'
|
|
6
|
+
require_relative 'presenters/entry_group/list/date_presenter'
|
|
7
|
+
require_relative 'subcommands/browse'
|
|
8
|
+
require_relative 'subcommands/config'
|
|
9
|
+
require_relative 'subcommands/delete'
|
|
10
|
+
require_relative 'subcommands/edit'
|
|
11
|
+
require_relative 'subcommands/export'
|
|
12
|
+
require_relative 'subcommands/import'
|
|
13
|
+
require_relative 'subcommands/list'
|
|
14
|
+
require_relative 'subcommands/project'
|
|
15
|
+
require_relative 'subcommands/theme'
|
|
16
|
+
require_relative 'support/command_options/time_mnemonic'
|
|
17
|
+
require_relative 'support/time_formatable'
|
|
18
|
+
require_relative 'views/entry_group/list'
|
|
19
|
+
|
|
20
|
+
module Doto
|
|
21
|
+
# The `doto` command.
|
|
22
|
+
class CLI < BaseCLI
|
|
23
|
+
include Support::CommandOptions::TimeMnemonic
|
|
24
|
+
include Support::TimeFormatable
|
|
25
|
+
|
|
26
|
+
map I18n.t('commands.add.key_mappings') => :add
|
|
27
|
+
map I18n.t('commands.browse.key_mappings') => :browse
|
|
28
|
+
map I18n.t('commands.config.key_mappings') => :config
|
|
29
|
+
map I18n.t('commands.delete.key_mappings') => :delete
|
|
30
|
+
map I18n.t('commands.edit.key_mappings') => :edit
|
|
31
|
+
map I18n.t('commands.export.key_mappings') => :export
|
|
32
|
+
map I18n.t('commands.help.key_mappings') => :help
|
|
33
|
+
map I18n.t('commands.import.key_mappings') => :import
|
|
34
|
+
map I18n.t('commands.info.key_mappings') => :info
|
|
35
|
+
map I18n.t('commands.list.key_mappings') => :list
|
|
36
|
+
map I18n.t('commands.project.key_mappings') => :project
|
|
37
|
+
map I18n.t('commands.theme.key_mappings') => :theme
|
|
38
|
+
map I18n.t('commands.version.key_mappings') => :version
|
|
39
|
+
|
|
40
|
+
desc I18n.t('commands.add.desc'), I18n.t('commands.add.usage')
|
|
41
|
+
long_desc I18n.t('commands.add.long_desc',
|
|
42
|
+
date_option_description: date_option_description, mnemonic_option_description: mnemonic_option_description)
|
|
43
|
+
option I18n.t('options.date_or_mnemonic.name'), aliases: I18n.t('options.date_or_mnemonic.aliases'),
|
|
44
|
+
type: :string, banner: I18n.t('options.date_or_mnemonic.banner')
|
|
45
|
+
option I18n.t('options.tomorrow.name'), aliases: I18n.t('options.tomorrow.aliases'), type: :boolean
|
|
46
|
+
option I18n.t('options.yesterday.name'), aliases: I18n.t('options.yesterday.aliases'), type: :boolean
|
|
47
|
+
option I18n.t('options.today.name'), aliases: I18n.t('options.today.aliases'), type: :boolean, default: true
|
|
48
|
+
def add(description)
|
|
49
|
+
date_or_mnemonic = if options[I18n.t('options.date.name')].present?
|
|
50
|
+
options[I18n.t('options.date.name')]
|
|
51
|
+
elsif options[I18n.t('options.tomorrow.name')].present?
|
|
52
|
+
I18n.t('options.tomorrow.name')
|
|
53
|
+
elsif options[I18n.t('options.yesterday.name')].present?
|
|
54
|
+
I18n.t('options.yesterday.name')
|
|
55
|
+
elsif options[I18n.t('options.today.name')].present?
|
|
56
|
+
I18n.t('options.today.name')
|
|
57
|
+
end
|
|
58
|
+
time = if time_mnemonic?(date_or_mnemonic)
|
|
59
|
+
time_from_mnemonic(command_option: date_or_mnemonic)
|
|
60
|
+
else
|
|
61
|
+
Time.parse(date_or_mnemonic)
|
|
62
|
+
end
|
|
63
|
+
entry = Models::Entry.new(description: description)
|
|
64
|
+
CommandServices::AddEntryService.new(entry: entry, time: time).call
|
|
65
|
+
presenter = Presenters::EntryGroup::List::DatePresenter.new(times: [time], options: options)
|
|
66
|
+
# TODO: Refactor View::EntryGroup::Show to accept a presenter and use it here
|
|
67
|
+
Views::EntryGroup::List.new(presenter: presenter).render
|
|
68
|
+
rescue ArgumentError => e
|
|
69
|
+
Views::Shared::Error.new(messages: e.message).render
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
desc I18n.t('commands.browse.desc'), I18n.t('commands.browse.usage')
|
|
73
|
+
subcommand :browse, Subcommands::Browse
|
|
74
|
+
|
|
75
|
+
desc I18n.t('commands.list.desc'), I18n.t('commands.list.usage')
|
|
76
|
+
subcommand :list, Subcommands::List
|
|
77
|
+
|
|
78
|
+
desc I18n.t('commands.project.desc'), I18n.t('commands.project.usage')
|
|
79
|
+
subcommand :project, Subcommands::Project
|
|
80
|
+
|
|
81
|
+
desc I18n.t('commands.config.desc'), I18n.t('commands.config.usage')
|
|
82
|
+
subcommand :config, Subcommands::Config
|
|
83
|
+
|
|
84
|
+
desc I18n.t('commands.delete.desc'), I18n.t('commands.delete.usage')
|
|
85
|
+
subcommand :delete, Subcommands::Delete
|
|
86
|
+
|
|
87
|
+
desc I18n.t('commands.edit.desc'), I18n.t('commands.edit.usage')
|
|
88
|
+
subcommand :edit, Subcommands::Edit
|
|
89
|
+
|
|
90
|
+
desc I18n.t('commands.export.desc'), I18n.t('commands.export.usage')
|
|
91
|
+
subcommand :export, Subcommands::Export
|
|
92
|
+
|
|
93
|
+
desc I18n.t('commands.theme.desc'), I18n.t('commands.theme.usage')
|
|
94
|
+
subcommand :theme, Subcommands::Theme
|
|
95
|
+
|
|
96
|
+
desc I18n.t('commands.import.desc'), I18n.t('commands.import.usage')
|
|
97
|
+
subcommand :import, Subcommands::Import
|
|
98
|
+
|
|
99
|
+
desc I18n.t('commands.info.desc'), I18n.t('commands.info.usage')
|
|
100
|
+
def info
|
|
101
|
+
configuration_version = Models::Configuration::VERSION
|
|
102
|
+
entry_group_version = Models::EntryGroup::VERSION
|
|
103
|
+
color_theme_version = Models::ColorTheme::VERSION
|
|
104
|
+
info = I18n.t('commands.info.info',
|
|
105
|
+
doto_version: doto_version,
|
|
106
|
+
configuration_version: configuration_version,
|
|
107
|
+
entry_group_version: entry_group_version,
|
|
108
|
+
color_theme_version: color_theme_version,
|
|
109
|
+
config_folder: Support::Fileable.config_path,
|
|
110
|
+
root_folder: Support::Fileable.root_folder,
|
|
111
|
+
entries_folder: Support::Fileable.entries_folder,
|
|
112
|
+
themes_folder: Support::Fileable.themes_folder,
|
|
113
|
+
gem_folder: Support::Fileable.gem_dir,
|
|
114
|
+
temp_folder: Support::Fileable.temp_folder,
|
|
115
|
+
migration_version_folder: Support::Fileable.migration_version_folder,
|
|
116
|
+
migration_file_folder: Support::Fileable.migration_version_path)
|
|
117
|
+
puts apply_theme(info, theme_color: color_theme.body)
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
desc I18n.t('commands.version.desc'), I18n.t('commands.version.usage')
|
|
121
|
+
def version
|
|
122
|
+
puts apply_theme(doto_version, theme_color: color_theme.body)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
private
|
|
126
|
+
|
|
127
|
+
def doto_version
|
|
128
|
+
"v#{VERSION}"
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
end
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../models/entry'
|
|
4
|
+
require_relative '../support/color_themable'
|
|
5
|
+
require_relative '../support/descriptable'
|
|
6
|
+
require_relative '../support/fileable'
|
|
7
|
+
require_relative '../views/shared/error'
|
|
8
|
+
|
|
9
|
+
module Doto
|
|
10
|
+
module CommandServices
|
|
11
|
+
# This class adds (does NOT update) an entry to an entry group by
|
|
12
|
+
# writing it to the appropriate entry group json file.
|
|
13
|
+
class AddEntryService
|
|
14
|
+
include Support::ColorThemable
|
|
15
|
+
include Support::Descriptable
|
|
16
|
+
include Support::Fileable
|
|
17
|
+
|
|
18
|
+
attr_reader :entry, :entry_group, :time
|
|
19
|
+
|
|
20
|
+
delegate :description, to: :entry
|
|
21
|
+
|
|
22
|
+
# :entry is an Entry object
|
|
23
|
+
# :time is a Time object; the time of the entry group.
|
|
24
|
+
def initialize(entry:, time:)
|
|
25
|
+
raise ArgumentError, 'entry is nil' if entry.nil?
|
|
26
|
+
raise ArgumentError, 'entry is the wrong object type' unless entry.is_a?(Models::Entry)
|
|
27
|
+
raise ArgumentError, 'time is nil' if time.nil?
|
|
28
|
+
raise ArgumentError, 'time is the wrong object type' unless time.is_a?(Time)
|
|
29
|
+
|
|
30
|
+
@entry = entry
|
|
31
|
+
@time = time
|
|
32
|
+
@entry_group = Models::EntryGroup.find_or_initialize(time: time)
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def call
|
|
36
|
+
entry.validate!
|
|
37
|
+
entry_group.entries << entry
|
|
38
|
+
entry_group.save!
|
|
39
|
+
entry
|
|
40
|
+
rescue ActiveModel::ValidationError => e
|
|
41
|
+
header = I18n.t('headers.entry.could_not_be_added')
|
|
42
|
+
Views::Shared::Error.new(messages: e.message, header: header).render
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
attr_writer :entry, :entry_group, :time
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_model'
|
|
4
|
+
require 'json'
|
|
5
|
+
require_relative '../migration/version'
|
|
6
|
+
|
|
7
|
+
module Doto
|
|
8
|
+
module Crud
|
|
9
|
+
class JsonFile
|
|
10
|
+
include ActiveModel::Model
|
|
11
|
+
|
|
12
|
+
attr_reader :file_path
|
|
13
|
+
|
|
14
|
+
def initialize(file_path)
|
|
15
|
+
@file_path = file_path
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def update_version!
|
|
19
|
+
@version = Migration::VERSION
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def delete
|
|
23
|
+
self.class.delete(file_path: file_path)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def delete!
|
|
27
|
+
self.class.delete!(file_path: file_path)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def file_exist?
|
|
31
|
+
self.class.file_exist?(file_path: file_path)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def persisted?
|
|
35
|
+
file_exist?
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Override this method to reload data from the file
|
|
39
|
+
def reload
|
|
40
|
+
@version = read_version
|
|
41
|
+
|
|
42
|
+
self
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def to_h
|
|
46
|
+
raise NotImplementedError, 'You must implement this method in a your subclass'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def to_model
|
|
50
|
+
self
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def version
|
|
54
|
+
@version ||= read_version
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def write
|
|
58
|
+
return false unless valid?
|
|
59
|
+
|
|
60
|
+
self.class.write(file_data: to_h, file_path: file_path)
|
|
61
|
+
true
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def write!
|
|
65
|
+
validate!
|
|
66
|
+
|
|
67
|
+
self.class.write(file_data: to_h, file_path: file_path)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
alias save write
|
|
71
|
+
alias save! write!
|
|
72
|
+
|
|
73
|
+
class << self
|
|
74
|
+
def file_exist?(file_path:)
|
|
75
|
+
File.exist?(file_path)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def delete(file_path:)
|
|
79
|
+
return false unless file_exist?(file_path: file_path)
|
|
80
|
+
|
|
81
|
+
File.delete(file_path)
|
|
82
|
+
|
|
83
|
+
true
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def delete!(file_path:)
|
|
87
|
+
raise file_does_not_exist_message(file_path: file_path) unless delete(file_path: file_path)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def parse(json)
|
|
91
|
+
return if json.nil?
|
|
92
|
+
|
|
93
|
+
JSON.parse(json, symbolize_names: true)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def read(file_path:)
|
|
97
|
+
json = File.read(file_path) if file_exist?(file_path: file_path)
|
|
98
|
+
hash = parse(json)
|
|
99
|
+
return yield hash if hash && block_given?
|
|
100
|
+
|
|
101
|
+
hash
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def read!(file_path:)
|
|
105
|
+
raise file_does_not_exist_message(file_path: file_path) unless file_exist?(file_path: file_path)
|
|
106
|
+
|
|
107
|
+
hash = read(file_path: file_path)
|
|
108
|
+
return yield hash if hash && block_given?
|
|
109
|
+
|
|
110
|
+
hash
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def write(file_data:, file_path:)
|
|
114
|
+
raise ArgumentError, 'file_data is nil' if file_data.nil?
|
|
115
|
+
raise ArgumentError, "file_data is the wrong object type:\"#{file_data}\"" unless file_data.is_a?(Hash)
|
|
116
|
+
|
|
117
|
+
file_data = JSON.pretty_generate(file_data)
|
|
118
|
+
File.write(file_path, file_data)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def write!(file_data:, file_path:)
|
|
122
|
+
# TODO: Should we be raising an error if the file does not exist?
|
|
123
|
+
# raise file_does_not_exist_message(file_path: file_path) unless file_exist?(file_path: file_path)
|
|
124
|
+
|
|
125
|
+
write(file_data: file_data, file_path: file_path)
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def file_does_not_exist_message(file_path:)
|
|
129
|
+
"File \"#{file_path}\" does not exist"
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
attr_writer :file_path, :version
|
|
136
|
+
|
|
137
|
+
def read
|
|
138
|
+
hash = self.class.read(file_path: file_path)
|
|
139
|
+
return yield hash if block_given?
|
|
140
|
+
|
|
141
|
+
hash
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def read!
|
|
145
|
+
hash = self.class.read!(file_path: file_path)
|
|
146
|
+
return yield hash if hash && block_given?
|
|
147
|
+
|
|
148
|
+
hash
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def read_version
|
|
152
|
+
return 0 unless file_exist?
|
|
153
|
+
|
|
154
|
+
hash = read
|
|
155
|
+
return 0 if hash.nil?
|
|
156
|
+
|
|
157
|
+
hash.fetch(:version, 0).to_i
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
data/lib/doto/env.rb
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doto
|
|
4
|
+
class << self
|
|
5
|
+
def env # rubocop:disable Metrics/MethodLength
|
|
6
|
+
@env ||= Struct.new(:env) do
|
|
7
|
+
def test?
|
|
8
|
+
env.fetch('DOTO_ENV', nil) == 'test'
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def development?
|
|
12
|
+
env.fetch('DOTO_ENV', nil) == 'development'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def local?
|
|
16
|
+
test? || development?
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def production?
|
|
20
|
+
env.fetch('DOTO_ENV', 'production') == 'production'
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def screen_shot_mode?
|
|
24
|
+
development? && (env.fetch('SCREEN_SHOT_USERNAME', '').present? ||
|
|
25
|
+
env.fetch('SCREEN_SHOT_HOSTNAME', '').present?)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def screen_shot_prompt
|
|
29
|
+
username = screen_shot_username
|
|
30
|
+
hostname = screen_shot_hostname
|
|
31
|
+
"#{username}@#{hostname}:~ $"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def screen_shot_username
|
|
35
|
+
env.fetch('SCREEN_SHOT_USERNAME', 'username')
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def screen_shot_hostname
|
|
39
|
+
env.fetch('SCREEN_SHOT_HOSTNAME', 'hostname')
|
|
40
|
+
end
|
|
41
|
+
end.new(ENV)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../models/migration_version'
|
|
4
|
+
require_relative '../support/fileable'
|
|
5
|
+
require_relative 'version'
|
|
6
|
+
|
|
7
|
+
module Doto
|
|
8
|
+
module Migration
|
|
9
|
+
class BaseService
|
|
10
|
+
include Support::Fileable
|
|
11
|
+
|
|
12
|
+
def initialize(options: {})
|
|
13
|
+
@options = options || {}
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
class << self
|
|
17
|
+
def migrates_to_latest_migration_version?
|
|
18
|
+
to_migration_version == Migration::VERSION
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# The migration version that this migration is upgrading from.
|
|
22
|
+
def from_migration_version
|
|
23
|
+
raise NotImplementedError, 'You must implement the #from_migration_version method in your subclass'
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# The migration version that this migration is upgrading to.
|
|
27
|
+
def to_migration_version
|
|
28
|
+
raise NotImplementedError, 'You must implement the #to_migration_version method in your subclass'
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def migrate_if!
|
|
33
|
+
return unless run_migration?
|
|
34
|
+
|
|
35
|
+
puts "Running migrations #{from_migration_version} -> #{to_migration_version}..."
|
|
36
|
+
puts "\tpretend?: #{pretend?}" if pretend?
|
|
37
|
+
|
|
38
|
+
run_migration!
|
|
39
|
+
update_migration_version!
|
|
40
|
+
|
|
41
|
+
puts "\tMigration #{from_migration_version} -> #{to_migration_version} complete."
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
attr_accessor :options
|
|
47
|
+
|
|
48
|
+
# You must implement your own migration logic in your subclass and call this method.
|
|
49
|
+
def run_migration!
|
|
50
|
+
create_backup
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def pretend?
|
|
54
|
+
options.fetch(:pretend, true)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def run_migration?
|
|
58
|
+
migration_version == from_migration_version
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# The migration version that this migration is upgrading from.
|
|
62
|
+
def from_migration_version
|
|
63
|
+
self.class.from_migration_version
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# The migration version that this migration is upgrading to.
|
|
67
|
+
def to_migration_version
|
|
68
|
+
self.class.to_migration_version
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# The migration version before running this migration.
|
|
72
|
+
def migration_version
|
|
73
|
+
# Typically we should not be using models in any of the migration services
|
|
74
|
+
# because if these change, the migrations will break. However, using
|
|
75
|
+
# the MigrationVersion model is an exception because it is a very simple
|
|
76
|
+
# model and is unlikely to change.
|
|
77
|
+
Models::MigrationVersion.new.version
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def create_backup
|
|
81
|
+
puts "Creating backup #{backup_folder}..."
|
|
82
|
+
|
|
83
|
+
return puts "\tSkipping: backup already exists." if backup_exist?
|
|
84
|
+
|
|
85
|
+
FileUtils.cp_r(doto_folder, backup_folder)
|
|
86
|
+
FileUtils.cp(config_path, backup_folder)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def backup_exist?
|
|
90
|
+
Dir.exist?(backup_folder)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def backup_folder
|
|
94
|
+
@backup_folder ||= backup_folder_for(migration_version: from_migration_version)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def update_migration_version!
|
|
98
|
+
puts 'Updating migration version...'
|
|
99
|
+
|
|
100
|
+
return if pretend? || migration_version == to_migration_version
|
|
101
|
+
|
|
102
|
+
Models::MigrationVersion.new(version: to_migration_version).save!
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def seed_data_folder
|
|
106
|
+
seed_data_doto_folder_for(migration_version: to_migration_version)
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def seed_data_configuration
|
|
110
|
+
seed_data_doto_configuration_for(migration_version: to_migration_version)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def raise_backup_folder_does_not_exist_error_if!
|
|
114
|
+
raise "Backup folder #{backup_folder} does not exist, cannot continue" unless backup_exist?
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|