dsu 0.1.0.alpha.1

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