dsu 0.1.0.alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +7 -0
  2. data/.reek.yml +20 -0
  3. data/.rspec +3 -0
  4. data/.rubocop.yml +192 -0
  5. data/.ruby-version +1 -0
  6. data/CHANGELOG.md +2 -0
  7. data/CODE_OF_CONDUCT.md +84 -0
  8. data/Gemfile +19 -0
  9. data/Gemfile.lock +133 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +128 -0
  12. data/Rakefile +12 -0
  13. data/bin/console +15 -0
  14. data/bin/setup +8 -0
  15. data/exe/dsu +11 -0
  16. data/lib/dsu/cli.rb +178 -0
  17. data/lib/dsu/command_services/add_entry_service.rb +61 -0
  18. data/lib/dsu/models/entry.rb +49 -0
  19. data/lib/dsu/models/entry_group.rb +70 -0
  20. data/lib/dsu/services/configuration_loader_service.rb +34 -0
  21. data/lib/dsu/services/entry_group_deleter_service.rb +31 -0
  22. data/lib/dsu/services/entry_group_hydrator_service.rb +43 -0
  23. data/lib/dsu/services/entry_group_reader_service.rb +36 -0
  24. data/lib/dsu/services/entry_group_writer_service.rb +45 -0
  25. data/lib/dsu/services/entry_hydrator_service.rb +35 -0
  26. data/lib/dsu/subcommands/config.rb +49 -0
  27. data/lib/dsu/support/ask.rb +38 -0
  28. data/lib/dsu/support/colorable.rb +13 -0
  29. data/lib/dsu/support/commander/command.rb +130 -0
  30. data/lib/dsu/support/commander/command_help.rb +62 -0
  31. data/lib/dsu/support/commander/subcommand.rb +45 -0
  32. data/lib/dsu/support/configuration.rb +89 -0
  33. data/lib/dsu/support/entry_group_fileable.rb +41 -0
  34. data/lib/dsu/support/entry_group_loadable.rb +52 -0
  35. data/lib/dsu/support/field_errors.rb +11 -0
  36. data/lib/dsu/support/folder_locations.rb +21 -0
  37. data/lib/dsu/support/interactive/cli.rb +161 -0
  38. data/lib/dsu/support/say.rb +40 -0
  39. data/lib/dsu/support/time_formatable.rb +42 -0
  40. data/lib/dsu/validators/entries_validator.rb +64 -0
  41. data/lib/dsu/validators/time_validator.rb +34 -0
  42. data/lib/dsu/version.rb +5 -0
  43. data/lib/dsu/views/entry_group/show.rb +60 -0
  44. data/lib/dsu.rb +38 -0
  45. data/sig/dsu.rbs +4 -0
  46. metadata +199 -0
@@ -0,0 +1,130 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'command_help'
4
+ require_relative 'subcommand'
5
+
6
+ module Dsu
7
+ module Support
8
+ module Commander
9
+ # https://www.toptal.com/ruby/ruby-dsl-metaprogramming-guide
10
+ module Command
11
+ class << self
12
+ def included(base)
13
+ base.extend ClassMethods
14
+ base.engine.command_namespace to_command_namespace_symbol base.name
15
+ base.engine.command_prompt base.engine.command_namespace
16
+ binding.pry
17
+ base.singleton_class.delegate :command_namespace, :command_namespaces, :commands,
18
+ :command_add, :command_subcommand_add, :command_prompt, :command_parent,
19
+ :help, to: :engine
20
+ end
21
+
22
+ private
23
+
24
+ def to_command_namespace_symbol(namespace, join_token: '_')
25
+ namespace.delete(':').split(/(?=[A-Z])/).join(join_token).downcase
26
+ end
27
+ end
28
+
29
+ module ClassMethods
30
+ def command_subcommand_create(command_parent:)
31
+ new.tap do |subcommand|
32
+ subcommand.extend Subcommand
33
+ subcommand.command_parent command_parent
34
+ end
35
+ end
36
+
37
+ def engine
38
+ @engine ||= Engine.new(owning_command: self)
39
+ end
40
+
41
+ class Engine
42
+ include CommandHelp
43
+
44
+ attr_reader :owning_command
45
+
46
+ def initialize(owning_command:)
47
+ @owning_command = owning_command
48
+ end
49
+
50
+ def command_add(command:, desc:, long_desc: nil, options: {}, commands: [])
51
+ self.commands[command_namespace] ||= {}
52
+ self.commands[command_namespace][command] = {
53
+ desc: desc,
54
+ long_desc: long_desc,
55
+ options: options,
56
+ commands: commands,
57
+ help: command_help_for(command: command, desc: desc,
58
+ long_desc: long_desc, options: options, commands: commands)
59
+ }
60
+ end
61
+
62
+ def command_subcommand_add(subcommand, command_parent: nil)
63
+ command_parent ||= @owning_command
64
+ subcommand = subcommand.command_subcommand_create command_parent: command_parent
65
+ commands[command_namespace] ||= {}
66
+ binding.pry
67
+ subcommand.command_namespaces.each_with_index do |namespace, index|
68
+ next if index.zero?
69
+
70
+ target = commands.dig(*subcommand.command_namespaces[1..index])
71
+ target ||= commands.dig(*subcommand.command_namespaces[0..index - 1])
72
+ target[namespace] ||= {}
73
+ end
74
+ commands.dig(*subcommand.command_namespaces[0..])[subcommand.command_namespaces.last] = subcommand
75
+
76
+ # subcommand.commands.each do |command_namespace, command|
77
+ # command.each do |subcommand_command, data|
78
+ # commands[self.command_namespace][command_namespace] ||= {}
79
+ # commands[self.command_namespace][command_namespace][subcommand_command] = {
80
+ # desc: data[:desc],
81
+ # long_desc: data[:long_desc],
82
+ # options: data[:options],
83
+ # commands: data[:commands],
84
+ # help: command_help_for(command: subcommand_command, namespaces: subcommand.command_namespaces, desc: data[:desc],
85
+ # long_desc: data[:long_desc], options: data[:options], commands: data[:commands])
86
+ # }
87
+ # end
88
+ # end
89
+ end
90
+
91
+ def command_namespaces(namespaces = [])
92
+ command_parent&.command_namespaces(namespaces)
93
+
94
+ namespaces << command_namespace
95
+ namespaces
96
+ end
97
+
98
+ def command_namespace(namespace = nil)
99
+ return @command_namespace || name if namespace.nil?
100
+
101
+ @command_namespace = namespace
102
+ end
103
+
104
+ def command_prompt(value = nil)
105
+ return @command_prompt || name if value.nil?
106
+
107
+ @command_prompt = value
108
+ end
109
+
110
+ def command_parent(parent = nil)
111
+ return @command_parent if parent.nil?
112
+
113
+ @command_parent = parent
114
+ end
115
+
116
+ def commands
117
+ @commands ||= {}
118
+ end
119
+
120
+ def help
121
+ commands.each do |_command, command_data|
122
+ puts "#{command_namespaces.join(' ')} #{command_data[:help]}"
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
128
+ end
129
+ end
130
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsu
4
+ module Support
5
+ module Commander
6
+ module CommandHelp
7
+ private
8
+
9
+ # rubocop:disable Lint/UnusedMethodArgument
10
+ def command_help_for(command:, desc:, namespaces: nil, long_desc: nil, options: {}, commands: [])
11
+ namespaces ||= command_namespaces
12
+ help =
13
+ <<~HELP
14
+ #{namespaces&.join(' ')} #{command}#{' [OPTIONS]' if options&.any?} - #{desc}
15
+ #{'OPTIONS:' if options&.any?}
16
+ #{options_help_for options}
17
+ #{'OPTION ALIASES:' if any_option_aliases_for?(options)}
18
+ #{options_aliases_help_for options}
19
+ #{'---' unless long_desc.blank?}
20
+ #{long_desc}
21
+ HELP
22
+ help.gsub(/\n{2,}/, "\n")
23
+ end
24
+ # rubocop:enable Lint/UnusedMethodArgument
25
+
26
+ def options_help_for(options)
27
+ return [] if options.blank?
28
+
29
+ options.map do |option, data|
30
+ type = option_to_a(data[:type])&.join(' | ')
31
+ type = :boolean if type.blank?
32
+ "#{option} <#{type}>, default: #{data[:default]}"
33
+ end.join("\n")
34
+ end
35
+
36
+ def options_aliases_help_for(options)
37
+ return unless any_option_aliases_for?(options)
38
+
39
+ options.filter_map do |option, data|
40
+ aliases = option_to_a(data[:aliases])&.join(' | ')
41
+ <<~HELP
42
+ #{option} aliases: [#{aliases}]
43
+ HELP
44
+ end.join("\n")
45
+ end
46
+
47
+ def any_option_aliases_for?(options)
48
+ return false if options.blank?
49
+
50
+ options.keys.any? { |key| options.dig(key, :aliases).any? }
51
+ end
52
+
53
+ def option_to_a(option)
54
+ return [] if option.blank?
55
+ return option if option.is_a? Array
56
+
57
+ [option]
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsu
4
+ module Support
5
+ module Commander
6
+ # Subcommands should extend this module once they are instantiated
7
+ # so that the Subcommand instance has access to all necessary
8
+ # class methods for this subcommand to work.
9
+ module Subcommand
10
+ class << self
11
+ def extended(mod)
12
+ mod.singleton_class.delegate :command_namespace, :commands,
13
+ :command_add, :command_subcommand_add, :command_prompt, :help, to: mod.class
14
+ end
15
+ end
16
+
17
+ # Subcommand-specific instance methods.
18
+ #
19
+ # Define Subcommand-specific method equivalents of the Command class
20
+ # methods needed to make this Subcommand instance unique.
21
+
22
+ # def command_namespace(namespace = nil)
23
+ # return @command_namespace || name if namespace.nil?
24
+
25
+ # @command_namespace = namespace
26
+ # end
27
+
28
+ # Subcommands can be used by any Command, so the :command_parent needs
29
+ # to be unique to this Subcommand instance.
30
+ def command_parent(parent = nil)
31
+ return @command_prompt if parent.nil?
32
+
33
+ @command_prompt = parent
34
+ end
35
+
36
+ def command_namespaces(namespaces = [])
37
+ command_parent&.command_namespaces(namespaces)
38
+
39
+ namespaces << command_namespace
40
+ namespaces
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,89 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorize'
4
+ require 'fileutils'
5
+ require 'yaml'
6
+ require_relative 'colorable'
7
+ require_relative 'folder_locations'
8
+ require_relative 'say'
9
+
10
+ module Dsu
11
+ module Support
12
+ module Configuration
13
+ include Colorable
14
+ include FolderLocations
15
+ include Say
16
+
17
+ CONFIG_FILENAME = '.dsu'
18
+
19
+ # rubocop:disable Style/StringHashKeys - YAML writing/loading necessitates this
20
+ DEFAULT_DSU_OPTIONS = {
21
+ # The order by which entries should be displayed by default:
22
+ # asc or desc, ascending or descending, respectively.
23
+ 'entries_display_order' => 'desc',
24
+ 'entries_file_name' => '%Y-%m-%d.json',
25
+ 'entries_folder' => "#{FolderLocations.root_folder}/dsu/entries",
26
+ }.freeze
27
+ # rubocop:enable Style/StringHashKeys
28
+
29
+ def config_file
30
+ File.join(root_folder, CONFIG_FILENAME)
31
+ end
32
+
33
+ def config_file?
34
+ File.exist? config_file
35
+ end
36
+
37
+ def create_config_file!
38
+ create_config_file config_file: config_file
39
+ print_config_file
40
+ end
41
+
42
+ def delete_config_file!
43
+ delete_config_file config_file: config_file
44
+ end
45
+
46
+ def print_config_file
47
+ if config_file?
48
+ say "Config file (#{config_file}) contents:", SUCCESS
49
+ hash = YAML.safe_load(File.open(config_file))
50
+ say hash.to_yaml.gsub("\n-", "\n\n-"), SUCCESS
51
+ else
52
+ say "Config file (#{config_file}) does not exist.", WARNING
53
+ end
54
+ end
55
+
56
+ private
57
+
58
+ def create_config_file(config_file:)
59
+ folder = File.dirname(config_file)
60
+ unless Dir.exist?(folder)
61
+ say "Destination folder for configuration file (#{folder}) does not exist", ERROR
62
+ return false
63
+ end
64
+
65
+ if File.exist?(config_file)
66
+ say "Configuration file (#{config_file}) already exists", WARNING
67
+ return false
68
+ end
69
+
70
+ File.write(config_file, DEFAULT_DSU_OPTIONS.to_yaml)
71
+ say "Configuration file (#{config_file}) created.", SUCCESS
72
+
73
+ true
74
+ end
75
+
76
+ def delete_config_file(config_file:)
77
+ unless File.exist?(config_file)
78
+ say "Configuration file (#{config_file}) does not exist", WARNING
79
+ return false
80
+ end
81
+
82
+ File.delete config_file
83
+ say "Configuration file (#{config_file}) deleted", SUCCESS
84
+
85
+ true
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,41 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../services/configuration_loader_service'
4
+
5
+ module Dsu
6
+ module Support
7
+ module EntryGroupFileable
8
+ module_function
9
+
10
+ def entry_group_file_exists?
11
+ File.exist?(entry_group_file_path)
12
+ end
13
+
14
+ def entry_group_path_exists?
15
+ Dir.exist?(entries_folder)
16
+ end
17
+
18
+ def entry_group_file_path
19
+ File.join(entries_folder, entries_file_name)
20
+ end
21
+
22
+ def entries_folder
23
+ @entries_folder ||= configuration[:entries_folder]
24
+ end
25
+
26
+ def entries_file_name
27
+ @entries_file_name ||= time.strftime(configuration[:entries_file_name])
28
+ end
29
+
30
+ def create_entry_group_path_if!
31
+ FileUtils.mkdir_p(entries_folder) unless entry_group_path_exists?
32
+ end
33
+
34
+ private
35
+
36
+ def configuration
37
+ @configuration ||= options[:configuration] || Services::ConfigurationLoaderService.new.call
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require_relative '../services/entry_group_reader_service'
5
+ require_relative '../models/entry'
6
+
7
+ module Dsu
8
+ module Support
9
+ module EntryGroupLoadable
10
+ module_function
11
+
12
+ # returns a Hash having :time and :entries
13
+ # where entries == an Array of Entry Hashes
14
+ # representing the JSON Entry objects for :time.
15
+ def entry_group_hash_for(time:)
16
+ entry_group_json = Services::EntryGroupReaderService.new(time: time).call
17
+ if entry_group_json.present?
18
+ return JSON.parse(entry_group_json, symbolize_names: true).tap do |hash|
19
+ hash[:time] = Time.parse(hash[:time])
20
+ end
21
+ end
22
+
23
+ {
24
+ time: time,
25
+ entries: []
26
+ }
27
+ end
28
+
29
+ private
30
+
31
+ # Accepts an entry group hash and returns a
32
+ # hydrated entry group hash:
33
+ #
34
+ # {
35
+ # time: <Time object>,
36
+ # entries [
37
+ # <Entry object 0>,
38
+ # <Entry object 1>,
39
+ # ...
40
+ # ]
41
+ # }
42
+ def hydrate_entry_group_hash(entry_group_hash:, time:)
43
+ time = entry_group_hash.fetch(:time, time)
44
+ time = Time.parse(time) unless time.is_a? Time
45
+ entries = entry_group_hash.fetch(:entries, [])
46
+ entries = entries.map { |entry_hash| Models::Entry.new(**entry_hash) }
47
+
48
+ { time: time, entries: entries }
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsu
4
+ module Support
5
+ module FieldErrors
6
+ FIELD_FORMAT_ERROR = :field_format_error
7
+ FIELD_TYPE_ERROR = :field_type_error
8
+ FIELD_DUPLICATE_ERROR = :field_duplicate_error
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'active_support/core_ext/object/blank'
4
+ require 'colorize'
5
+ require 'pathname'
6
+
7
+ module Dsu
8
+ module Support
9
+ module FolderLocations
10
+ module_function
11
+
12
+ def root_folder
13
+ Dir.home
14
+ end
15
+
16
+ def temp_folder
17
+ Dir.tmpdir
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,161 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../ask'
4
+ require_relative '../colorable'
5
+ require_relative '../say'
6
+
7
+ module Dsu
8
+ module Interactive
9
+ class Cli
10
+ include Support::Colorable
11
+ include Support::Ask
12
+ include Support::Say
13
+
14
+ BACK_COMMANDS = %w[b].freeze
15
+ EXIT_COMMANDS = %w[x].freeze
16
+ HELP_COMMANDS = %w[?].freeze
17
+ PROMPT_TOKEN = '>'
18
+
19
+ attr_reader :name, :parent, :prompt
20
+
21
+ def initialize(name:, parent: nil, **options)
22
+ @name = name
23
+ @parent = parent
24
+ @prompt = options[:prompt]
25
+ end
26
+
27
+ # Starts our interactive loop.
28
+ def start
29
+ help
30
+ process_commands
31
+ end
32
+
33
+ def process(command:)
34
+ if command.cancelled?
35
+ nil
36
+ elsif help?(command.command)
37
+ help
38
+ elsif back_or_exit?(command.command)
39
+ parent&.help
40
+ else
41
+ unrecognized_command command.command
42
+ end
43
+ end
44
+
45
+ # Dispays the full help; header and body.
46
+ def help
47
+ help_header
48
+ help_body
49
+ end
50
+
51
+ private
52
+
53
+ # This is our interaction loop. Commands that are NOT help or
54
+ # back or exit commands are yielded to the subclass to execute. Help
55
+ # commands simply display help; back (or exit) commands transfer control
56
+ # back to the parent cli (if parent? is true) or exits the current
57
+ # cli (if parent? is false) respectfully.
58
+ def process_commands
59
+ loop do
60
+ command = wrap_command(ask)
61
+ process(command: command)
62
+ next if command.cancelled?
63
+ break if back_or_exit?(command.command)
64
+ end
65
+ say 'Done.', ABORTED unless parent?
66
+ end
67
+
68
+ def wrap_command(command)
69
+ Struct.new(:command, :args, :cancelled, keyword_init: true) do
70
+ def cancelled?
71
+ cancelled
72
+ end
73
+
74
+ def cancelled!
75
+ self[:cancelled] = true
76
+ end
77
+ end.new(
78
+ command: command.split[0],
79
+ args: command.split[1..],
80
+ cancelled: false
81
+ )
82
+ end
83
+
84
+ # This is the full prompt that needs to be displayed that includes
85
+ # all parent prompts, right down to the current prompt.
86
+ def full_prompt
87
+ prompts = full_prompt_build prompts: []
88
+ prompt_token = "#{PROMPT_TOKEN} "
89
+ "#{prompts.join prompt_token}#{prompt_token}"
90
+ end
91
+
92
+ def parent?
93
+ !parent.nil?
94
+ end
95
+
96
+ def back?(command)
97
+ back_commands.include? command
98
+ end
99
+
100
+ def back_commands
101
+ @back_commands ||= BACK_COMMANDS
102
+ end
103
+
104
+ def exit?(command)
105
+ exit_commands.include? command
106
+ end
107
+
108
+ def exit_commands
109
+ @exit_commands ||= EXIT_COMMANDS
110
+ end
111
+
112
+ def back_or_exit?(command)
113
+ (back_commands + exit_commands).include? command
114
+ end
115
+
116
+ def help?(command)
117
+ help_commands.include? command
118
+ end
119
+
120
+ # Returns what are considered to be commands associated with
121
+ # displaying help.
122
+ def help_commands
123
+ @help_commands ||= HELP_COMMANDS
124
+ end
125
+
126
+ # Displays the help header; override this if you want to customize
127
+ # your own help header in your subclass.
128
+ def help_header
129
+ say "#{name} Help", HIGHLIGHT
130
+ say '---', HIGHLIGHT
131
+ end
132
+
133
+ # Override this in your subclass and call super AFTER you've
134
+ # displayed your subclass' help body.
135
+ def help_body
136
+ say "[#{HELP_COMMANDS.join(' | ')}] Display help", HIGHLIGHT
137
+ say "[#{BACK_COMMANDS.join(' | ')}] Go back", HIGHLIGHT if parent?
138
+ say "[#{EXIT_COMMANDS.join(' | ')}] Exit", HIGHLIGHT unless parent?
139
+ end
140
+
141
+ # This simply outputs our prompt and accepts user input.
142
+ def ask
143
+ super full_prompt
144
+ end
145
+
146
+ def unrecognized_command(command)
147
+ say "Unrecognized command (\"#{command}\"). Try again.", ERROR
148
+ end
149
+
150
+ # Builds the full prompt to be used which amounts to:
151
+ # <parent cli prompt> PROMPT_TOKEN <child cli 1 prompt>
152
+ # PROMPT_TOKEN <child 2 cli prompt> ...
153
+ def full_prompt_build(prompts:)
154
+ parent.send(:full_prompt_build, prompts: prompts) if parent?
155
+
156
+ prompts << prompt
157
+ prompts.flatten
158
+ end
159
+ end
160
+ end
161
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'colorized_string'
4
+
5
+ module Dsu
6
+ module Support
7
+ module Say
8
+ class << self
9
+ def say(text, color = nil)
10
+ puts say_string_for(text, color)
11
+ end
12
+
13
+ def say_string_for(text, color = nil)
14
+ unless color.nil? || color.is_a?(Symbol)
15
+ raise ':color is the wrong type. "Symbol" was expected, but ' \
16
+ "\"#{color.class}\" was returned."
17
+ end
18
+
19
+ return text if color.nil?
20
+
21
+ text.public_send(color)
22
+ end
23
+ end
24
+
25
+ def say(text, color = nil)
26
+ Say.say(text, color)
27
+ end
28
+
29
+ # NOTE: some modes (ColorizedString.modes) will cancel out each other if
30
+ # overriden in a block. For example, if you set a string to be bold
31
+ # (i.e. mode: :bold) and then override it in a block (e.g. string.underline)
32
+ # the string will not be bold and underlined, it will just be underlined.
33
+ def colorize_string(string:, color: :default, mode: :default)
34
+ colorized_string = ColorizedString[string].colorize(color: color, mode: mode)
35
+ colorized_string = yield colorized_string if block_given?
36
+ colorized_string
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'time'
4
+ require 'active_support/core_ext/numeric/time'
5
+
6
+ module Dsu
7
+ module Support
8
+ # This module provides functions for formatting Time objects
9
+ # to display in the console.
10
+ module TimeFormatable
11
+ module_function
12
+
13
+ def formatted_time(time:)
14
+ time = time.localtime if time.utc?
15
+
16
+ today_yesterday_or_tomorrow = if today?(time: time)
17
+ 'Today'
18
+ elsif yesterday?(time: time)
19
+ 'Yesterday'
20
+ elsif tomorrow?(time: time)
21
+ 'Tomorrow'
22
+ end
23
+
24
+ return time.strftime('%A, %Y-%m-%d') unless today_yesterday_or_tomorrow
25
+
26
+ time.strftime("%A, (#{today_yesterday_or_tomorrow}) %Y-%m-%d")
27
+ end
28
+
29
+ def today?(time:)
30
+ time.strftime('%Y%m%d') == Time.now.strftime('%Y%m%d')
31
+ end
32
+
33
+ def yesterday?(time:)
34
+ time.strftime('%Y%m%d') == 1.day.ago(Time.now).strftime('%Y%m%d')
35
+ end
36
+
37
+ def tomorrow?(time:)
38
+ time.strftime('%Y%m%d') == 1.day.from_now(Time.now).strftime('%Y%m%d')
39
+ end
40
+ end
41
+ end
42
+ end