dsu 1.2.1 → 2.0.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 +4 -4
- data/CHANGELOG.md +65 -21
- data/Gemfile.lock +7 -7
- data/README.md +28 -35
- data/bin/console +23 -1
- data/bin/dsu +3 -0
- data/bin/setup +14 -3
- data/exe/dsu +23 -1
- data/exe/dsu_migrate.rb +43 -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/not_today.rb +7 -0
- data/lib/core/ruby/wrap_and_join.rb +31 -0
- data/lib/dsu/base_cli.rb +19 -23
- data/lib/dsu/cli.rb +47 -37
- data/lib/dsu/command_services/add_entry_service.rb +10 -21
- data/lib/dsu/crud/json_file.rb +139 -0
- data/lib/dsu/crud/raw_json_file.rb +51 -0
- data/lib/dsu/env.rb +21 -0
- data/lib/dsu/migration/service.rb +196 -0
- data/lib/dsu/migration/version.rb +7 -0
- data/lib/dsu/models/color_theme.rb +270 -0
- data/lib/dsu/models/configuration.rb +160 -0
- data/lib/dsu/models/entry.rb +6 -2
- data/lib/dsu/models/entry_group.rb +143 -42
- data/lib/dsu/models/migration_version.rb +48 -0
- data/lib/dsu/presenters/base_presenter.rb +32 -0
- data/lib/dsu/presenters/color_theme_presenter.rb +50 -0
- data/lib/dsu/presenters/color_theme_show_presenter.rb +49 -0
- data/lib/dsu/presenters/configuration_presenter.rb +45 -0
- data/lib/dsu/presenters/entry_group_presenter.rb +35 -0
- data/lib/dsu/presenters/entry_presenter.rb +25 -0
- data/lib/dsu/services/color_theme/hydrator_service.rb +42 -0
- data/lib/dsu/services/configuration/hydrator_service.rb +42 -0
- data/lib/dsu/services/entry/hydrator_service.rb +33 -0
- data/lib/dsu/services/entry_group/editor_service.rb +107 -0
- data/lib/dsu/services/entry_group/hydrator_service.rb +37 -0
- data/lib/dsu/services/migration_version/hydrator_service.rb +36 -0
- data/lib/dsu/services/stderr_redirector_service.rb +27 -0
- data/lib/dsu/services/temp_file/reader_service.rb +33 -0
- data/lib/dsu/services/temp_file/writer_service.rb +35 -0
- data/lib/dsu/subcommands/base_subcommand.rb +14 -0
- data/lib/dsu/subcommands/config.rb +92 -32
- data/lib/dsu/subcommands/edit.rb +3 -3
- data/lib/dsu/subcommands/list.rb +70 -93
- data/lib/dsu/subcommands/theme.rb +159 -0
- data/lib/dsu/support/ask.rb +14 -19
- data/lib/dsu/support/color_themable.rb +34 -0
- data/lib/dsu/support/command_help_colorizeable.rb +27 -0
- data/lib/dsu/support/command_hookable.rb +60 -0
- data/lib/dsu/support/command_options/dsu_times.rb +32 -21
- data/lib/dsu/support/command_options/time.rb +7 -1
- data/lib/dsu/support/command_options/time_mneumonic.rb +7 -1
- data/lib/dsu/support/descriptable.rb +6 -4
- data/lib/dsu/support/entry_group_viewable.rb +28 -4
- data/lib/dsu/support/fileable.rb +94 -0
- data/lib/dsu/support/presentable.rb +11 -0
- data/lib/dsu/support/subcommand_help_colorizeable.rb +27 -0
- data/lib/dsu/support/time_comparable.rb +19 -0
- data/lib/dsu/support/time_formatable.rb +12 -0
- data/lib/dsu/support/times_sortable.rb +48 -14
- data/lib/dsu/support/utils.rb +11 -0
- data/lib/dsu/validators/color_theme_validator.rb +74 -0
- data/lib/dsu/validators/entries_validator.rb +4 -8
- data/lib/dsu/validators/version_validator.rb +29 -0
- data/lib/dsu/version.rb +2 -1
- data/lib/dsu/views/color_theme/index.rb +62 -0
- data/lib/dsu/views/color_theme/show.rb +106 -0
- data/lib/dsu/views/configuration/show.rb +41 -0
- data/lib/dsu/views/entry_group/edit.rb +3 -5
- data/lib/dsu/views/entry_group/shared/no_entries_to_display.rb +41 -0
- data/lib/dsu/views/entry_group/show.rb +16 -15
- data/lib/dsu/views/shared/error.rb +17 -0
- data/lib/dsu/views/shared/info.rb +17 -0
- data/lib/dsu/views/shared/message.rb +85 -0
- data/lib/dsu/views/shared/model_errors.rb +31 -0
- data/lib/dsu/views/shared/success.rb +17 -0
- data/lib/dsu/views/shared/warning.rb +17 -0
- data/lib/dsu.rb +22 -1
- data/lib/seed_data/themes/cherry.json +79 -0
- data/lib/seed_data/themes/default.json +79 -0
- data/lib/seed_data/themes/lemon.json +79 -0
- data/lib/seed_data/themes/matrix.json +79 -0
- data/lib/seed_data/themes/whiteout.json +79 -0
- metadata +68 -23
- data/lib/dsu/core/ruby/not_today.rb +0 -11
- data/lib/dsu/services/ai/tense_translator_service.rb +0 -63
- data/lib/dsu/services/configuration_loader_service.rb +0 -55
- data/lib/dsu/services/entry_group_deleter_service.rb +0 -31
- data/lib/dsu/services/entry_group_editor_service.rb +0 -96
- data/lib/dsu/services/entry_group_hydrator_service.rb +0 -43
- data/lib/dsu/services/entry_group_reader_service.rb +0 -36
- data/lib/dsu/services/entry_group_writer_service.rb +0 -46
- data/lib/dsu/services/entry_hydrator_service.rb +0 -35
- data/lib/dsu/services/temp_file_reader_service.rb +0 -31
- data/lib/dsu/services/temp_file_writer_service.rb +0 -33
- data/lib/dsu/support/colorable.rb +0 -14
- data/lib/dsu/support/configurable.rb +0 -15
- data/lib/dsu/support/configuration.rb +0 -112
- data/lib/dsu/support/entry_group_fileable.rb +0 -49
- data/lib/dsu/support/entry_group_loadable.rb +0 -49
- data/lib/dsu/support/folder_locations.rb +0 -21
- data/lib/dsu/support/say.rb +0 -40
- data/lib/dsu/views/edited_entries/shared/errors.rb +0 -39
- data/lib/dsu/views/shared/messages.rb +0 -56
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'colorized_string'
|
|
4
|
+
|
|
5
|
+
module Dsu
|
|
6
|
+
module Support
|
|
7
|
+
module ColorThemable
|
|
8
|
+
def prompt_with_options(prompt:, options:)
|
|
9
|
+
options = "[#{options.join('/')}]"
|
|
10
|
+
"#{apply_theme(prompt, theme_color: self.prompt)} " \
|
|
11
|
+
"#{apply_theme(options, theme_color: prompt_options)}" \
|
|
12
|
+
"#{apply_theme('>', theme_color: self.prompt)}"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
module_function
|
|
16
|
+
|
|
17
|
+
def apply_theme(input, theme_color:)
|
|
18
|
+
if input.is_a?(Array)
|
|
19
|
+
return input.map do |string|
|
|
20
|
+
colorize_string(string, theme_color: theme_color)
|
|
21
|
+
end.join("\n")
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
colorize_string(input, theme_color: theme_color)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def colorize_string(input, theme_color:)
|
|
30
|
+
ColorizedString[input].colorize(**theme_color)
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../models/color_theme'
|
|
4
|
+
require_relative '../support/color_themable'
|
|
5
|
+
|
|
6
|
+
module Dsu
|
|
7
|
+
module Support
|
|
8
|
+
module CommandHelpColorizable
|
|
9
|
+
class << self
|
|
10
|
+
def included(base)
|
|
11
|
+
base.extend(ClassMethods)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
def help(shell, subcommand = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
16
|
+
help_text = Services::StdoutRedirectorService.call { super }
|
|
17
|
+
puts apply_theme(help_text, theme_color: color_theme.help)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def color_theme
|
|
21
|
+
@color_theme ||= Models::ColorTheme.current_or_default
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../models/color_theme'
|
|
4
|
+
require_relative '../services/stderr_redirector_service'
|
|
5
|
+
require_relative '../views/shared/error'
|
|
6
|
+
require_relative 'color_themable'
|
|
7
|
+
|
|
8
|
+
module Dsu
|
|
9
|
+
module Support
|
|
10
|
+
module CommandHookable
|
|
11
|
+
class << self
|
|
12
|
+
def included(base)
|
|
13
|
+
base.extend(ColorThemable)
|
|
14
|
+
base.extend(ClassMethods)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
module ClassMethods
|
|
19
|
+
def start(args = ARGV, options = {})
|
|
20
|
+
display_dsu_header unless suspend_header?(args, options)
|
|
21
|
+
stderror = Services::StderrRedirectorService.call do
|
|
22
|
+
super
|
|
23
|
+
end
|
|
24
|
+
display_errors_if(stderror)
|
|
25
|
+
display_dsu_footer
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def display_dsu_header
|
|
29
|
+
puts apply_theme("Dsu v#{Dsu::VERSION}", theme_color: color_theme.dsu_header)
|
|
30
|
+
puts
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def suspend_header?(args, _options)
|
|
36
|
+
return unless args.count > 1
|
|
37
|
+
return true if args[0] == 'theme' && %w[use delete].include?(args[1])
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def display_dsu_footer
|
|
41
|
+
puts apply_theme('_' * 35, theme_color: color_theme.dsu_footer)
|
|
42
|
+
footer = apply_theme("Theme: #{color_theme.theme_name}", theme_color: color_theme.dsu_footer)
|
|
43
|
+
puts footer
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def display_errors_if(stderror_string)
|
|
47
|
+
stderror_string = stderror_string.strip
|
|
48
|
+
return unless stderror_string.present?
|
|
49
|
+
|
|
50
|
+
errors = stderror_string.split("\n").map(&:strip)
|
|
51
|
+
Views::Shared::Error.new(messages: errors, options: options.merge({ ordered_list: false })).render
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def color_theme
|
|
55
|
+
Models::ColorTheme.current_or_default
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
end
|
|
@@ -2,34 +2,45 @@
|
|
|
2
2
|
|
|
3
3
|
require_relative 'time'
|
|
4
4
|
require_relative 'time_mneumonic'
|
|
5
|
-
require_relative 'time_mneumonics'
|
|
6
5
|
|
|
7
6
|
module Dsu
|
|
8
7
|
module Support
|
|
9
8
|
module CommandOptions
|
|
10
9
|
module DsuTimes
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
#
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
10
|
+
module_function
|
|
11
|
+
|
|
12
|
+
# Returns an array of Time objects. The first element is the "from" time.
|
|
13
|
+
# The second element is the "to" time. Both arguments are expected to be
|
|
14
|
+
# command options that are time strings, time or relative time mneumonics.
|
|
15
|
+
def dsu_times_for(from_option:, to_option:)
|
|
16
|
+
from_time = dsu_from_time_for(from_option: from_option)
|
|
17
|
+
to_time = dsu_to_time_for(to_option: to_option, from_time: from_time)
|
|
18
|
+
|
|
19
|
+
errors = []
|
|
20
|
+
errors << "Option -f, [--from=DATE|MNEMONIC] value is invalid [\"#{from_option}\"]" if from_time.nil?
|
|
21
|
+
errors << "Option -t, [--to=DATE|MNEMONIC] value is invalid [\"#{to_option}\"]" if to_time.nil?
|
|
22
|
+
return [[], errors] if errors.any?
|
|
23
|
+
|
|
24
|
+
min_time, max_time = [from_time, to_time].sort
|
|
25
|
+
[(min_time.to_date..max_time.to_date).map(&:to_time), []]
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def dsu_from_time_for(from_option:)
|
|
29
|
+
return if from_option.nil?
|
|
30
|
+
|
|
31
|
+
from_time = if TimeMneumonic.time_mneumonic?(from_option)
|
|
32
|
+
TimeMneumonic.time_from_mneumonic(command_option: from_option)
|
|
30
33
|
end
|
|
34
|
+
from_time || Time.time_from_date_string(command_option: from_option)
|
|
35
|
+
end
|
|
31
36
|
|
|
32
|
-
|
|
37
|
+
def dsu_to_time_for(to_option:, from_time:)
|
|
38
|
+
to_time = if TimeMneumonic.relative_time_mneumonic?(to_option)
|
|
39
|
+
TimeMneumonic.time_from_mneumonic(command_option: to_option, relative_time: from_time)
|
|
40
|
+
elsif TimeMneumonic.time_mneumonic?(to_option)
|
|
41
|
+
TimeMneumonic.time_from_mneumonic(command_option: to_option)
|
|
42
|
+
end
|
|
43
|
+
to_time || Time.time_from_date_string(command_option: to_option)
|
|
33
44
|
end
|
|
34
45
|
end
|
|
35
46
|
end
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
module Dsu
|
|
4
4
|
module Support
|
|
5
5
|
module CommandOptions
|
|
6
|
+
# TODO: Make this into an ActiveModel class that uses validations.
|
|
7
|
+
#
|
|
6
8
|
# The purpose of this module is to take a command option that is a string and return a Time object.
|
|
7
9
|
# The command option is expected to be a date in the format of [M]M/[D]D[/YYYY]. MM and DD with
|
|
8
10
|
# leading zeroes is optional (i.e. only M and D are required), YYYY is optionl and will be replaced
|
|
@@ -10,6 +12,8 @@ module Dsu
|
|
|
10
12
|
module Time
|
|
11
13
|
DATE_CAPTURE_REGEX = %r{\A(?<month>0?[1-9]|1[0-2])/(?<day>0?[1-9]|1\d|2\d|3[01])(?:/(?<year>\d{4}))?\z}
|
|
12
14
|
|
|
15
|
+
module_function
|
|
16
|
+
|
|
13
17
|
def time_from_date_string!(command_option:)
|
|
14
18
|
raise ArgumentError, 'command_option is nil.' if command_option.nil?
|
|
15
19
|
raise ArgumentError, 'command_option is blank.' if command_option.blank?
|
|
@@ -35,7 +39,7 @@ module Dsu
|
|
|
35
39
|
nil
|
|
36
40
|
end
|
|
37
41
|
|
|
38
|
-
|
|
42
|
+
# private_class_methods go here.
|
|
39
43
|
|
|
40
44
|
# This method returns the time parts for the given time string in a hash
|
|
41
45
|
# (i.e. month, day, year) IF the time string matches the DATE_CAPTURE_REGEX
|
|
@@ -71,6 +75,8 @@ module Dsu
|
|
|
71
75
|
time_parts[:year] = ::Time.now.year if time_parts[:year].nil?
|
|
72
76
|
"#{time_parts[:year]}/#{time_parts[:month]}/#{time_parts[:day]}"
|
|
73
77
|
end
|
|
78
|
+
|
|
79
|
+
private_class_method :time_parts_for, :time_parts?, :valid_time!, :time_string_for
|
|
74
80
|
end
|
|
75
81
|
end
|
|
76
82
|
end
|
|
@@ -10,6 +10,8 @@ module Dsu
|
|
|
10
10
|
module TimeMneumonic
|
|
11
11
|
include TimeMneumonics
|
|
12
12
|
|
|
13
|
+
module_function
|
|
14
|
+
|
|
13
15
|
def time_from_mneumonic(command_option:, relative_time: nil)
|
|
14
16
|
time_from_mneumonic!(command_option: command_option, relative_time: relative_time)
|
|
15
17
|
rescue ArgumentError
|
|
@@ -45,7 +47,7 @@ module Dsu
|
|
|
45
47
|
mneumonic.match?(RELATIVE_REGEX)
|
|
46
48
|
end
|
|
47
49
|
|
|
48
|
-
|
|
50
|
+
# Add private_class_methods here.
|
|
49
51
|
|
|
50
52
|
# Returns a Time object from a mneumonic.
|
|
51
53
|
def time_for_mneumonic(mneumonic:, relative_time:)
|
|
@@ -96,6 +98,10 @@ module Dsu
|
|
|
96
98
|
raise ArgumentError, "#{command_option_name} is an invalid mneumonic: \"#{command_option}\"."
|
|
97
99
|
end
|
|
98
100
|
end
|
|
101
|
+
|
|
102
|
+
private_class_method :time_for_mneumonic, :relative_time_for,
|
|
103
|
+
:mneumonic?, :today_mneumonic?, :tomorrow_mneumonic?,
|
|
104
|
+
:yesterday_mneumonic?, :validate_argument!
|
|
99
105
|
end
|
|
100
106
|
end
|
|
101
107
|
end
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
module Dsu
|
|
4
4
|
module Support
|
|
5
5
|
module Descriptable
|
|
6
|
+
DESCRIPTION_MAX_COUNT = 25
|
|
7
|
+
|
|
6
8
|
class << self
|
|
7
|
-
def included(
|
|
8
|
-
|
|
9
|
+
def included(base)
|
|
10
|
+
base.extend(ClassMethods)
|
|
9
11
|
end
|
|
10
12
|
end
|
|
11
13
|
|
|
@@ -16,7 +18,7 @@ module Dsu
|
|
|
16
18
|
end
|
|
17
19
|
|
|
18
20
|
module ClassMethods
|
|
19
|
-
def short_description(string:, count:
|
|
21
|
+
def short_description(string:, count: DESCRIPTION_MAX_COUNT, elipsis: '...')
|
|
20
22
|
return elipsis unless string.is_a?(String)
|
|
21
23
|
|
|
22
24
|
elipsis_length = elipsis.length
|
|
@@ -27,7 +29,7 @@ module Dsu
|
|
|
27
29
|
tokens = string.split
|
|
28
30
|
string = ''
|
|
29
31
|
|
|
30
|
-
return "#{tokens.first[0
|
|
32
|
+
return "#{tokens.first[0...(count - elipsis_length)]}#{elipsis}" if tokens.count == 1
|
|
31
33
|
|
|
32
34
|
tokens.each do |token|
|
|
33
35
|
break if string.length + token.length + elipsis_length > count
|
|
@@ -4,6 +4,9 @@ module Dsu
|
|
|
4
4
|
module Support
|
|
5
5
|
module EntryGroupViewable
|
|
6
6
|
def view_entry_groups(times:, options: {})
|
|
7
|
+
raise ArgumentError, 'times must be an Array' unless times.is_a?(Array)
|
|
8
|
+
raise ArgumentError, 'options must be a Hash' unless options.is_a?(Hash)
|
|
9
|
+
|
|
7
10
|
total_viewable_entry_groups = 0
|
|
8
11
|
|
|
9
12
|
times.each do |time|
|
|
@@ -13,25 +16,46 @@ module Dsu
|
|
|
13
16
|
end
|
|
14
17
|
end
|
|
15
18
|
|
|
16
|
-
|
|
19
|
+
total_unviewable_entry_groups = times.size - total_viewable_entry_groups
|
|
20
|
+
yield total_viewable_entry_groups, total_unviewable_entry_groups if block_given?
|
|
17
21
|
end
|
|
18
22
|
|
|
19
23
|
def view_entry_group(time:, options: {})
|
|
24
|
+
raise ArgumentError, 'time must be a Time object' unless time.is_a?(Time)
|
|
25
|
+
raise ArgumentError, 'options must be a Hash' unless options.is_a?(Hash)
|
|
26
|
+
|
|
20
27
|
return unless show_entry_group?(time: time, options: options)
|
|
21
28
|
|
|
22
|
-
entry_group = Models::EntryGroup.
|
|
29
|
+
entry_group = Models::EntryGroup.find_or_initialize(time: time)
|
|
23
30
|
Views::EntryGroup::Show.new(entry_group: entry_group).render
|
|
24
31
|
|
|
25
32
|
yield if block_given?
|
|
26
33
|
end
|
|
27
34
|
|
|
35
|
+
# This method will unconditionally display the FIRST and LAST entry groups
|
|
36
|
+
# associated with the times provided by the <times> argument. All other
|
|
37
|
+
# entry groups will be conditionally displayed based on the :include_all
|
|
38
|
+
# value in the <options> argument.
|
|
39
|
+
def view_list_for(times:, options:)
|
|
40
|
+
configuration = Models::Configuration.new unless defined?(configuration) && configuration
|
|
41
|
+
options = configuration.to_h.merge(options).with_indifferent_access
|
|
42
|
+
times_first_and_last = [times.first, times.last]
|
|
43
|
+
times.each do |time|
|
|
44
|
+
view_options = options.dup
|
|
45
|
+
view_options[:include_all] = true if times_first_and_last.include?(time)
|
|
46
|
+
view_entry_group(time: time, options: view_options) do
|
|
47
|
+
puts
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
28
52
|
private
|
|
29
53
|
|
|
30
54
|
def show_entry_group?(time:, options:)
|
|
31
|
-
Models::EntryGroup.
|
|
55
|
+
Models::EntryGroup.exist?(time: time) || options[:include_all]
|
|
32
56
|
end
|
|
33
57
|
|
|
34
|
-
module_function :view_entry_group, :view_entry_groups, :show_entry_group?
|
|
58
|
+
module_function :view_entry_group, :view_entry_groups, :view_list_for, :show_entry_group?
|
|
35
59
|
end
|
|
36
60
|
end
|
|
37
61
|
end
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dsu
|
|
4
|
+
module Support
|
|
5
|
+
module Fileable
|
|
6
|
+
MIGRATION_VERSION_FILE_NAME = 'migration_version.json'
|
|
7
|
+
|
|
8
|
+
def dsu_folder
|
|
9
|
+
File.join(root_folder, 'dsu')
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
# Configuration
|
|
13
|
+
|
|
14
|
+
def config_folder
|
|
15
|
+
root_folder
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def config_file_name
|
|
19
|
+
'.dsu'
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def config_path
|
|
23
|
+
File.join(config_folder, config_file_name)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# Entries
|
|
27
|
+
|
|
28
|
+
def entries_folder
|
|
29
|
+
File.join(dsu_folder, 'entries')
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def entries_file_name(time:, file_name_format: nil)
|
|
33
|
+
file_name_format ||= '%Y-%m-%d.json'
|
|
34
|
+
time.strftime(file_name_format)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def entries_path(time:, file_name_format: nil)
|
|
38
|
+
File.join(entries_folder, entries_file_name(time: time, file_name_format: file_name_format))
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Themes
|
|
42
|
+
|
|
43
|
+
def themes_folder
|
|
44
|
+
File.join(dsu_folder, 'themes')
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def themes_path(theme_name:)
|
|
48
|
+
File.join(themes_folder, theme_file_name(theme_name: theme_name))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def theme_file_name(theme_name:)
|
|
52
|
+
"#{theme_name}.json"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Migration
|
|
56
|
+
|
|
57
|
+
def migration_version_folder
|
|
58
|
+
File.join(dsu_folder)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def migration_version_path
|
|
62
|
+
File.join(migration_version_folder, MIGRATION_VERSION_FILE_NAME)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Base folders
|
|
66
|
+
|
|
67
|
+
def root_folder
|
|
68
|
+
Dir.home
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def temp_folder
|
|
72
|
+
Dir.tmpdir
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def gem_dir
|
|
76
|
+
Gem.loaded_specs['dsu'].gem_dir
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Back up folder
|
|
80
|
+
|
|
81
|
+
def backup_folder(version:)
|
|
82
|
+
File.join(dsu_folder, 'backup', version.to_s)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
# Seed data folders
|
|
86
|
+
|
|
87
|
+
def seed_data_folder
|
|
88
|
+
File.join(gem_dir, 'lib/seed_data')
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
extend self # rubocop:disable Style/ModuleFunction
|
|
92
|
+
end
|
|
93
|
+
end
|
|
94
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../models/color_theme'
|
|
4
|
+
require_relative '../support/color_themable'
|
|
5
|
+
|
|
6
|
+
module Dsu
|
|
7
|
+
module Support
|
|
8
|
+
module SubcommandHelpColorizable
|
|
9
|
+
class << self
|
|
10
|
+
def included(base)
|
|
11
|
+
base.extend(ClassMethods)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module ClassMethods
|
|
15
|
+
def command_help(shell, subcommand = false) # rubocop:disable Style/OptionalBooleanParameter
|
|
16
|
+
help_text = Services::StdoutRedirectorService.call { super }
|
|
17
|
+
puts apply_theme(help_text, theme_color: color_theme.help)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def color_theme
|
|
21
|
+
@color_theme ||= Models::ColorTheme.current_or_default
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Dsu
|
|
4
|
+
module Support
|
|
5
|
+
module TimeComparable
|
|
6
|
+
TIME_COMPARABLE_FORMAT_SPECIFIER = '%Y%m%d'
|
|
7
|
+
|
|
8
|
+
def time_equal?(other_time:)
|
|
9
|
+
time_equal_compare_string_for(time: time) == time_equal_compare_string_for(time: other_time)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def time_equal_compare_string_for(time:)
|
|
13
|
+
time = time.localtime if time.utc?
|
|
14
|
+
|
|
15
|
+
time.strftime(TIME_COMPARABLE_FORMAT_SPECIFIER)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -28,9 +28,21 @@ module Dsu
|
|
|
28
28
|
time.strftime("%A, (#{today_yesterday_or_tomorrow}) %Y-%m-%d #{time_zone}")
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
+
def mm_dd(time:, separator: '/')
|
|
32
|
+
time.strftime("%m#{separator}%d")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def mm_dd_yyyy(time:, separator: '/')
|
|
36
|
+
time.strftime("%m#{separator}%d#{separator}%Y")
|
|
37
|
+
end
|
|
38
|
+
|
|
31
39
|
def timezone_for(time:)
|
|
32
40
|
time.zone
|
|
33
41
|
end
|
|
42
|
+
|
|
43
|
+
def yyyy_mm_dd(time:, separator: '-')
|
|
44
|
+
time.strftime("%Y#{separator}%m#{separator}%d")
|
|
45
|
+
end
|
|
34
46
|
end
|
|
35
47
|
end
|
|
36
48
|
end
|
|
@@ -3,29 +3,41 @@
|
|
|
3
3
|
module Dsu
|
|
4
4
|
module Support
|
|
5
5
|
module TimesSortable
|
|
6
|
-
|
|
6
|
+
def sorted_dsu_times_for(times:)
|
|
7
|
+
configuration = Models::Configuration.new unless defined?(configuration) && configuration
|
|
8
|
+
entries_display_order = configuration.entries_display_order
|
|
9
|
+
times_sort(times: times_for(times: times), entries_display_order: entries_display_order)
|
|
10
|
+
end
|
|
7
11
|
|
|
8
12
|
def times_sort(times:, entries_display_order: nil)
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
raise "Invalid entries_display_order: #{entries_display_order}"
|
|
12
|
-
end
|
|
13
|
+
times = times.dup
|
|
14
|
+
entries_display_order ||= :asc
|
|
13
15
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
16
|
+
validate_times_argument!(times: times)
|
|
17
|
+
validate_entries_display_order_argument!(entries_display_order: entries_display_order)
|
|
18
|
+
|
|
19
|
+
return times if times.one?
|
|
20
|
+
|
|
21
|
+
# NOTE: The times array needs to be sorted unconditionally because if
|
|
22
|
+
# the sort is ascending, then the times array needs to be returned
|
|
23
|
+
# in ascending order. If the sort is descending, then in order to
|
|
24
|
+
# properly reverse the times array, it needs to first be sorted in
|
|
25
|
+
# ascending order before being reversed.
|
|
26
|
+
times.sort!
|
|
27
|
+
times.reverse! if entries_display_order == :desc
|
|
28
|
+
|
|
29
|
+
times
|
|
19
30
|
end
|
|
20
31
|
|
|
21
|
-
# TODO: Do we have something else we can use here?
|
|
22
32
|
def times_for(times:)
|
|
33
|
+
times = times.dup
|
|
34
|
+
validate_times_argument!(times: times)
|
|
35
|
+
|
|
23
36
|
start_date = times.max
|
|
24
37
|
return times unless start_date.monday? || start_date.on_weekend?
|
|
25
38
|
|
|
26
|
-
# If the
|
|
27
|
-
#
|
|
28
|
-
# Monday.
|
|
39
|
+
# If the start date is a weekend or a Monday then we need to look back
|
|
40
|
+
# to include the preceeding Friday upto and including the start date.
|
|
29
41
|
(0..3).filter_map do |num|
|
|
30
42
|
time = start_date - num.days
|
|
31
43
|
next unless time == start_date || time.on_weekend? || time.friday?
|
|
@@ -33,6 +45,28 @@ module Dsu
|
|
|
33
45
|
time
|
|
34
46
|
end
|
|
35
47
|
end
|
|
48
|
+
|
|
49
|
+
private
|
|
50
|
+
|
|
51
|
+
def validate_times_argument!(times:)
|
|
52
|
+
raise ArgumentError, "times is the wrong object type: \"#{times.class}\"" unless times.is_a?(Array)
|
|
53
|
+
raise ArgumentError, 'times is empty' if times.empty?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def validate_entries_display_order_argument!(entries_display_order:)
|
|
57
|
+
unless entries_display_order.nil? || entries_display_order.is_a?(Symbol)
|
|
58
|
+
raise ArgumentError, "entries_display_order is the wrong object type: \"#{entries_display_order.class}\""
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
unless %i[asc desc].include?(entries_display_order)
|
|
62
|
+
raise ArgumentError, "entries_display_order is invalid: \":#{entries_display_order}\""
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# NOTE: This, as opposed to using module_function, so that we can
|
|
67
|
+
# invoke .validate_times_sort_arguments! from the .times_sort
|
|
68
|
+
# method with module as the receiver AND when included as a mixin.
|
|
69
|
+
extend self
|
|
36
70
|
end
|
|
37
71
|
end
|
|
38
72
|
end
|