dsu 0.1.0.alpha.1 → 0.1.0.alpha.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +20 -0
- data/Gemfile +1 -1
- data/Gemfile.lock +1 -1
- data/README.md +159 -23
- data/bin/console +0 -1
- data/lib/dsu/base_cli.rb +60 -0
- data/lib/dsu/cli.rb +65 -123
- data/lib/dsu/models/entry.rb +4 -13
- data/lib/dsu/models/entry_group.rb +20 -0
- data/lib/dsu/services/configuration_loader_service.rb +13 -9
- data/lib/dsu/services/temp_file_reader_service.rb +31 -0
- data/lib/dsu/services/temp_file_writer_service.rb +33 -0
- data/lib/dsu/subcommands/config.rb +34 -4
- data/lib/dsu/subcommands/edit.rb +132 -0
- data/lib/dsu/subcommands/list.rb +70 -0
- data/lib/dsu/support/configuration.rb +12 -1
- data/lib/dsu/support/entry_group_viewable.rb +19 -0
- data/lib/dsu/support/time_formatable.rb +8 -14
- data/lib/dsu/support/times_sortable.rb +40 -0
- data/lib/dsu/validators/entries_validator.rb +9 -12
- data/lib/dsu/version.rb +1 -1
- data/lib/dsu/views/entry_group/edit.rb +70 -0
- data/lib/dsu/views/entry_group/show.rb +3 -7
- data/lib/dsu.rb +0 -28
- metadata +10 -2
@@ -2,7 +2,9 @@
|
|
2
2
|
|
3
3
|
require 'deco_lite'
|
4
4
|
require_relative '../support/entry_group_loadable'
|
5
|
+
require_relative '../services/entry_group_deleter_service'
|
5
6
|
require_relative '../services/entry_group_reader_service'
|
7
|
+
require_relative '../services/entry_group_writer_service'
|
6
8
|
require_relative '../validators/entries_validator'
|
7
9
|
require_relative '../validators/time_validator'
|
8
10
|
|
@@ -30,10 +32,16 @@ module Dsu
|
|
30
32
|
end
|
31
33
|
|
32
34
|
class << self
|
35
|
+
def delete(time:, options: {})
|
36
|
+
Services::EntryGroupDeleterService.new(time: time, options: options).call
|
37
|
+
end
|
38
|
+
|
33
39
|
def exists?(time:)
|
34
40
|
Dsu::Services::EntryGroupReaderService.entry_group_file_exists?(time: time)
|
35
41
|
end
|
36
42
|
|
43
|
+
# Loads the EntryGroup from the file system and returns an
|
44
|
+
# instantiated EntryGroup object.
|
37
45
|
def load(time: nil)
|
38
46
|
new(**hydrated_entry_group_hash_for(time: time))
|
39
47
|
end
|
@@ -51,6 +59,18 @@ module Dsu
|
|
51
59
|
%i[time entries]
|
52
60
|
end
|
53
61
|
|
62
|
+
def save!
|
63
|
+
validate!
|
64
|
+
Services::EntryGroupWriterService.new(entry_group: self).call
|
65
|
+
self
|
66
|
+
end
|
67
|
+
|
68
|
+
# Deletes the entry group file from the file system.
|
69
|
+
def delete
|
70
|
+
self.class.delete(time: time)
|
71
|
+
self
|
72
|
+
end
|
73
|
+
|
54
74
|
def to_h
|
55
75
|
super.tap do |hash|
|
56
76
|
hash[:entries] = hash[:entries].dup
|
@@ -11,23 +11,27 @@ module Dsu
|
|
11
11
|
|
12
12
|
attr_reader :default_options
|
13
13
|
|
14
|
-
def initialize(default_options:
|
15
|
-
|
14
|
+
def initialize(default_options: nil)
|
15
|
+
unless default_options.nil? ||
|
16
|
+
default_options.is_a?(Hash) ||
|
17
|
+
default_options.is_a?(ActiveSupport::HashWithIndifferentAccess)
|
18
|
+
raise ArgumentError, 'default_options must be a Hash or ActiveSupport::HashWithIndifferentAccess'
|
19
|
+
end
|
20
|
+
|
21
|
+
@default_options ||= default_options || {}
|
22
|
+
@default_options = @default_options.with_indifferent_access if @default_options.is_a?(Hash)
|
16
23
|
end
|
17
24
|
|
18
25
|
def call
|
19
|
-
|
26
|
+
config_options.merge(default_options).with_indifferent_access
|
20
27
|
end
|
21
28
|
|
22
29
|
private
|
23
30
|
|
24
|
-
|
31
|
+
def config_options
|
32
|
+
return Support::Configuration::DEFAULT_DSU_OPTIONS unless config_file?
|
25
33
|
|
26
|
-
|
27
|
-
return {} unless config_file?
|
28
|
-
|
29
|
-
yaml_options = File.read(config_file)
|
30
|
-
YAML.safe_load ERB.new(yaml_options).result
|
34
|
+
@config_options ||= YAML.safe_load(ERB.new(File.read(config_file)).result)
|
31
35
|
end
|
32
36
|
end
|
33
37
|
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dsu
|
4
|
+
module Services
|
5
|
+
class TempFileReaderService
|
6
|
+
def initialize(temp_file_path:, options: {})
|
7
|
+
raise ArgumentError, 'temp_file_path is nil' if temp_file_path.nil?
|
8
|
+
raise ArgumentError, 'temp_file_path is the wrong object type' unless temp_file_path.is_a?(String)
|
9
|
+
raise ArgumentError, 'temp_file_path is empty' if temp_file_path.empty?
|
10
|
+
raise ArgumentError, 'temp_file_path does not exist' unless File.exist?(temp_file_path)
|
11
|
+
raise ArgumentError, 'options is nil' if options.nil?
|
12
|
+
raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
|
13
|
+
|
14
|
+
@temp_file_path = temp_file_path
|
15
|
+
@options = options || {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
raise ArgumentError, 'no block given' unless block_given?
|
20
|
+
|
21
|
+
File.foreach(temp_file_path) do |line|
|
22
|
+
yield line.strip
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
attr_reader :temp_file_path, :options
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'tempfile'
|
4
|
+
|
5
|
+
module Dsu
|
6
|
+
module Services
|
7
|
+
class TempFileWriterService
|
8
|
+
def initialize(temp_file_content:, options: {})
|
9
|
+
raise ArgumentError, 'temp_file_content is nil' if temp_file_content.nil?
|
10
|
+
raise ArgumentError, 'temp_file_content is the wrong object type' unless temp_file_content.is_a?(String)
|
11
|
+
raise ArgumentError, 'options is nil' if options.nil?
|
12
|
+
raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
|
13
|
+
|
14
|
+
@temp_file_content = temp_file_content
|
15
|
+
@options = options || {}
|
16
|
+
end
|
17
|
+
|
18
|
+
def call
|
19
|
+
raise ArgumentError, 'no block given' unless block_given?
|
20
|
+
|
21
|
+
Tempfile.new('dsu').tap do |file|
|
22
|
+
file.write("#{temp_file_content}\n")
|
23
|
+
file.close
|
24
|
+
yield file.path
|
25
|
+
end.unlink
|
26
|
+
end
|
27
|
+
|
28
|
+
private
|
29
|
+
|
30
|
+
attr_reader :temp_file_content, :options
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -33,14 +33,44 @@ module Dsu
|
|
33
33
|
desc 'init', 'Creates and initializes a .dsu file in your home folder'
|
34
34
|
long_desc <<-LONG_DESC
|
35
35
|
NAME
|
36
|
-
|
37
|
-
`dsu config init` -- will create and initialize a .dsu file
|
38
|
-
in the "#{Dsu::Support::FolderLocations.root_folder}" folder.
|
36
|
+
|
37
|
+
`dsu config init` -- will create and initialize a .dsu file ("#{Dsu::Support::FolderLocations.root_folder}/.dsu") that you may edit. Otherwise, the default configuration will be used.
|
39
38
|
|
40
39
|
SYNOPSIS
|
41
|
-
|
40
|
+
|
42
41
|
dsu config init
|
42
|
+
|
43
|
+
CONFIGURATION FILE OPTIONS
|
44
|
+
|
45
|
+
The following configuration file options are available:
|
46
|
+
|
47
|
+
editor:
|
48
|
+
|
49
|
+
The default editor to use when editing entry groups if the EDITOR environment variable on your system is not set. The default is 'nano'. You'll need to change the default editor on Windows systems.
|
50
|
+
|
51
|
+
entries_display_order:
|
52
|
+
|
53
|
+
The order by which entries will be displayed, 'asc' or 'desc' (ascending or descending, respectively).
|
54
|
+
|
55
|
+
Default: 'desc'
|
56
|
+
|
57
|
+
entries_file_name:
|
58
|
+
|
59
|
+
The entries file name format. It is recommended that you do not change this. The file name must include `%Y`, `%m` and `%d` `Time` formatting specifiers to make sure the file name is unique and able to be located by `dsu` functions. For example, the default file name is `%Y-%m-%d.json`; however, something like `%m-%d-%Y.json` or `entry-group-%m-%d-%Y.json` would work as well.
|
60
|
+
|
61
|
+
ATTENTION: Please keep in mind that if you change this value `dsu` will not recognize entry files using a different format. You would (at this time), have to manually rename any existing entry file names to the new format.
|
62
|
+
|
63
|
+
Default: '%Y-%m-%d.json'
|
64
|
+
|
65
|
+
entries_folder:
|
66
|
+
|
67
|
+
This is the folder where `dsu` stores entry files. You may change this to anything you want. `dsu` will create this folder for you, as long as your system's write permissions allow this.
|
68
|
+
|
69
|
+
ATTENTION: Please keep in mind that if you change this value `dsu` will not be able to find entry files in any previous folder. You would (at this time), have to manually mode any existing entry files to this new folder.
|
70
|
+
|
71
|
+
Default: "'#{Dsu::Support::FolderLocations.root_folder}/dsu/entries'"
|
43
72
|
LONG_DESC
|
73
|
+
|
44
74
|
def init
|
45
75
|
create_config_file!
|
46
76
|
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'English'
|
4
|
+
require_relative '../base_cli'
|
5
|
+
require_relative '../models/entry_group'
|
6
|
+
require_relative '../services/temp_file_reader_service'
|
7
|
+
require_relative '../services/temp_file_writer_service'
|
8
|
+
require_relative '../support/time_formatable'
|
9
|
+
require_relative '../views/entry_group/edit'
|
10
|
+
require_relative '../views/entry_group/show'
|
11
|
+
|
12
|
+
module Dsu
|
13
|
+
module Subcommands
|
14
|
+
class Edit < Dsu::BaseCLI
|
15
|
+
include Support::TimeFormatable
|
16
|
+
|
17
|
+
map %w[d] => :date
|
18
|
+
map %w[n] => :today
|
19
|
+
map %w[t] => :tomorrow
|
20
|
+
map %w[y] => :yesterday
|
21
|
+
|
22
|
+
desc 'today, n',
|
23
|
+
'Edits the DSU entries for today.'
|
24
|
+
long_desc <<-LONG_DESC
|
25
|
+
Edits the DSU entries for today.
|
26
|
+
LONG_DESC
|
27
|
+
def today
|
28
|
+
Views::EntryGroup::Show.new(entry_group: edit_entry_group(time: Time.now)).render
|
29
|
+
end
|
30
|
+
|
31
|
+
desc 'tomorrow, t',
|
32
|
+
'Edits the DSU entries for tomorrow.'
|
33
|
+
long_desc <<-LONG_DESC
|
34
|
+
Edits the DSU entries for tomorrow.
|
35
|
+
LONG_DESC
|
36
|
+
def tomorrow
|
37
|
+
Views::EntryGroup::Show.new(entry_group: edit_entry_group(time: Time.now.tomorrow)).render
|
38
|
+
end
|
39
|
+
|
40
|
+
desc 'yesterday, y',
|
41
|
+
'Edits the DSU entries for yesterday.'
|
42
|
+
long_desc <<-LONG_DESC
|
43
|
+
Edits the DSU entries for yesterday.
|
44
|
+
LONG_DESC
|
45
|
+
def yesterday
|
46
|
+
Views::EntryGroup::Show.new(entry_group: edit_entry_group(time: Time.now.yesterday)).render
|
47
|
+
end
|
48
|
+
|
49
|
+
desc 'date, d DATE',
|
50
|
+
'Edits the DSU entries for DATE.'
|
51
|
+
long_desc <<-LONG_DESC
|
52
|
+
Edits the DSU entries for DATE.
|
53
|
+
|
54
|
+
\x5 #{date_option_description}
|
55
|
+
LONG_DESC
|
56
|
+
def date(date)
|
57
|
+
Views::EntryGroup::Show.new(entry_group: edit_entry_group(time: Time.parse(date))).render
|
58
|
+
rescue ArgumentError => e
|
59
|
+
say "Error: #{e.message}", ERROR
|
60
|
+
exit 1
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def edit_entry_group(time:)
|
66
|
+
formatted_time = formatted_time(time: time)
|
67
|
+
unless Models::EntryGroup.exists?(time: time)
|
68
|
+
say "No DSU entries exist for #{formatted_time}"
|
69
|
+
exit 1
|
70
|
+
end
|
71
|
+
|
72
|
+
say "Editing DSU entries for #{formatted_time}..."
|
73
|
+
entry_group = Models::EntryGroup.load(time: time)
|
74
|
+
|
75
|
+
# This renders the view to a string...
|
76
|
+
output = capture_stdxxx do
|
77
|
+
Views::EntryGroup::Edit.new(entry_group: entry_group).render
|
78
|
+
end
|
79
|
+
# ...which is then written to a temp file.
|
80
|
+
Services::TempFileWriterService.new(temp_file_content: output).call do |temp_file_path|
|
81
|
+
unless system("${EDITOR:-#{configuration[:editor]}} #{temp_file_path}")
|
82
|
+
say "Failed to open temporary file in editor '#{configuration[:editor]}';" \
|
83
|
+
"the system error returned was: '#{$CHILD_STATUS}'.", ERROR
|
84
|
+
say 'Either set the EDITOR environment variable ' \
|
85
|
+
'or set the dsu editor configuration option (`$ dsu config init`).', ERROR
|
86
|
+
say 'Run `$ dsu help config` for more information.', ERROR
|
87
|
+
system('dsu help config')
|
88
|
+
exit 1
|
89
|
+
end
|
90
|
+
entries = []
|
91
|
+
Services::TempFileReaderService.new(temp_file_path: temp_file_path).call do |temp_file_line|
|
92
|
+
# Skip comments and blank lines.
|
93
|
+
next if ['#', nil].include? temp_file_line[0]
|
94
|
+
|
95
|
+
match_data = temp_file_line.match(/(\S+)\s(.+)/)
|
96
|
+
# TODO: Error handling if match_data is nil.
|
97
|
+
entry_sha = match_data[1]
|
98
|
+
entry_description = match_data[2]
|
99
|
+
|
100
|
+
next if %w[- d delete].include?(entry_sha) # delete the entry
|
101
|
+
|
102
|
+
entry_sha = nil if %w[+ a add].include?(entry_sha) # add the new entry
|
103
|
+
entries << Models::Entry.new(uuid: entry_sha, description: entry_description)
|
104
|
+
end
|
105
|
+
|
106
|
+
entry_group.entries = entries
|
107
|
+
|
108
|
+
return entry_group.delete if entries.empty?
|
109
|
+
|
110
|
+
entry_group.save!
|
111
|
+
end
|
112
|
+
entry_group
|
113
|
+
end
|
114
|
+
|
115
|
+
# https://stackoverflow.com/questions/4459330/how-do-i-temporarily-redirect-stderr-in-ruby/4459463#4459463
|
116
|
+
def capture_stdxxx
|
117
|
+
# The output stream must be an IO-like object. In this case we capture it in
|
118
|
+
# an in-memory IO object so we can return the string value. You can assign any
|
119
|
+
# IO object here.
|
120
|
+
string_io = StringIO.new
|
121
|
+
prev_stdout, $stdout = $stdout, string_io # rubocop:disable Style/ParallelAssignment
|
122
|
+
prev_stderr, $stderr = $stderr, string_io # rubocop:disable Style/ParallelAssignment
|
123
|
+
yield
|
124
|
+
string_io.string
|
125
|
+
ensure
|
126
|
+
# Restore the previous value of stderr and stdout (typically equal to STDERR).
|
127
|
+
$stdout = prev_stdout
|
128
|
+
$stderr = prev_stderr
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../base_cli'
|
4
|
+
|
5
|
+
module Dsu
|
6
|
+
module Subcommands
|
7
|
+
class List < Dsu::BaseCLI
|
8
|
+
map %w[d] => :date
|
9
|
+
map %w[n] => :today
|
10
|
+
map %w[t] => :tomorrow
|
11
|
+
map %w[y] => :yesterday
|
12
|
+
|
13
|
+
desc 'today, n',
|
14
|
+
'Displays the DSU entries for today'
|
15
|
+
long_desc <<-LONG_DESC
|
16
|
+
Displays the DSU entries for today. This command has no options.
|
17
|
+
LONG_DESC
|
18
|
+
def today
|
19
|
+
time = Time.now
|
20
|
+
sorted_dsu_times_for(times: [time, time.yesterday]).each do |t|
|
21
|
+
view_entry_group(time: t)
|
22
|
+
puts
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
desc 'tomorrow, t',
|
27
|
+
'Displays the DSU entries for tomorrow'
|
28
|
+
long_desc <<-LONG_DESC
|
29
|
+
Displays the DSU entries for tomorrow. This command has no options.
|
30
|
+
LONG_DESC
|
31
|
+
def tomorrow
|
32
|
+
time = Time.now
|
33
|
+
sorted_dsu_times_for(times: [time.tomorrow, time]).each do |t|
|
34
|
+
view_entry_group(time: t)
|
35
|
+
puts
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
desc 'yesterday, y',
|
40
|
+
'Displays the DSU entries for yesterday'
|
41
|
+
long_desc <<-LONG_DESC
|
42
|
+
Displays the DSU entries for yesterday. This command has no options.
|
43
|
+
LONG_DESC
|
44
|
+
def yesterday
|
45
|
+
time = Time.now
|
46
|
+
sorted_dsu_times_for(times: [time.yesterday, time.yesterday.yesterday]).each do |t|
|
47
|
+
view_entry_group(time: t)
|
48
|
+
puts
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
desc 'date, d DATE',
|
53
|
+
'Displays the DSU entries for DATE'
|
54
|
+
long_desc <<-LONG_DESC
|
55
|
+
Displays the DSU entries for DATE.
|
56
|
+
\x5 #{date_option_description}
|
57
|
+
LONG_DESC
|
58
|
+
def date(date)
|
59
|
+
time = Time.parse(date)
|
60
|
+
sorted_dsu_times_for(times: [time, time.yesterday]).each do |t|
|
61
|
+
view_entry_group(time: t)
|
62
|
+
puts
|
63
|
+
end
|
64
|
+
rescue ArgumentError => e
|
65
|
+
say "Error: #{e.message}", ERROR
|
66
|
+
exit 1
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -18,11 +18,16 @@ module Dsu
|
|
18
18
|
|
19
19
|
# rubocop:disable Style/StringHashKeys - YAML writing/loading necessitates this
|
20
20
|
DEFAULT_DSU_OPTIONS = {
|
21
|
+
# The default editor to use when editing entry groups if the EDITOR
|
22
|
+
# environment variable on your system is not set. On nix systmes,
|
23
|
+
# the default editor is`nano`. You need to change this default on
|
24
|
+
# Windows systems.
|
25
|
+
'editor' => 'nano',
|
21
26
|
# The order by which entries should be displayed by default:
|
22
27
|
# asc or desc, ascending or descending, respectively.
|
23
28
|
'entries_display_order' => 'desc',
|
24
29
|
'entries_file_name' => '%Y-%m-%d.json',
|
25
|
-
'entries_folder' => "#{FolderLocations.root_folder}/dsu/entries"
|
30
|
+
'entries_folder' => "#{FolderLocations.root_folder}/dsu/entries"
|
26
31
|
}.freeze
|
27
32
|
# rubocop:enable Style/StringHashKeys
|
28
33
|
|
@@ -43,6 +48,7 @@ module Dsu
|
|
43
48
|
delete_config_file config_file: config_file
|
44
49
|
end
|
45
50
|
|
51
|
+
# TODO: Move this to a view (e.g. views/configuration/show.rb)
|
46
52
|
def print_config_file
|
47
53
|
if config_file?
|
48
54
|
say "Config file (#{config_file}) contents:", SUCCESS
|
@@ -50,6 +56,11 @@ module Dsu
|
|
50
56
|
say hash.to_yaml.gsub("\n-", "\n\n-"), SUCCESS
|
51
57
|
else
|
52
58
|
say "Config file (#{config_file}) does not exist.", WARNING
|
59
|
+
say ''
|
60
|
+
say 'The default configuration is being used:'
|
61
|
+
DEFAULT_DSU_OPTIONS.each_with_index do |config_entry, index|
|
62
|
+
say "#{index + 1}. #{config_entry[0]}: '#{config_entry[1]}'"
|
63
|
+
end
|
53
64
|
end
|
54
65
|
end
|
55
66
|
|
@@ -0,0 +1,19 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dsu
|
4
|
+
module Support
|
5
|
+
module EntryGroupViewable
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def view_entry_group(time:)
|
9
|
+
entry_group = if Models::EntryGroup.exists?(time: time)
|
10
|
+
entry_group_json = Services::EntryGroupReaderService.new(time: time).call
|
11
|
+
Services::EntryGroupHydratorService.new(entry_group_json: entry_group_json).call
|
12
|
+
else
|
13
|
+
Models::EntryGroup.new(time: time)
|
14
|
+
end
|
15
|
+
Views::EntryGroup::Show.new(entry_group: entry_group).render
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -13,29 +13,23 @@ module Dsu
|
|
13
13
|
def formatted_time(time:)
|
14
14
|
time = time.localtime if time.utc?
|
15
15
|
|
16
|
-
today_yesterday_or_tomorrow = if today?
|
16
|
+
today_yesterday_or_tomorrow = if time.today?
|
17
17
|
'Today'
|
18
|
-
elsif yesterday?
|
18
|
+
elsif time.yesterday?
|
19
19
|
'Yesterday'
|
20
|
-
elsif tomorrow?
|
20
|
+
elsif time.tomorrow?
|
21
21
|
'Tomorrow'
|
22
22
|
end
|
23
23
|
|
24
|
-
|
24
|
+
time_zone = timezone_for(time: time)
|
25
25
|
|
26
|
-
time.strftime("%A,
|
27
|
-
end
|
28
|
-
|
29
|
-
def today?(time:)
|
30
|
-
time.strftime('%Y%m%d') == Time.now.strftime('%Y%m%d')
|
31
|
-
end
|
26
|
+
return time.strftime("%A, %Y-%m-%d #{time_zone}") unless today_yesterday_or_tomorrow
|
32
27
|
|
33
|
-
|
34
|
-
time.strftime('%Y%m%d') == 1.day.ago(Time.now).strftime('%Y%m%d')
|
28
|
+
time.strftime("%A, (#{today_yesterday_or_tomorrow}) %Y-%m-%d #{time_zone}")
|
35
29
|
end
|
36
30
|
|
37
|
-
def
|
38
|
-
time.
|
31
|
+
def timezone_for(time:)
|
32
|
+
time.zone
|
39
33
|
end
|
40
34
|
end
|
41
35
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dsu
|
4
|
+
module Support
|
5
|
+
module TimesSortable
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def times_sort(times:, entries_display_order: nil)
|
9
|
+
entries_display_order ||= 'asc'
|
10
|
+
unless %w[asc desc].include? entries_display_order
|
11
|
+
raise "Invalid entries_display_order: #{entries_display_order}"
|
12
|
+
end
|
13
|
+
|
14
|
+
if entries_display_order == 'asc'
|
15
|
+
times.sort # sort ascending
|
16
|
+
elsif entries_display_order == 'desc'
|
17
|
+
times.sort.reverse # sort descending
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def times_for(times:)
|
22
|
+
start_date = times.max
|
23
|
+
return times unless start_date.monday? || start_date.on_weekend?
|
24
|
+
|
25
|
+
# (0..3).map { |num| start_date - num.days }
|
26
|
+
# (start_date..-start_date.friday?).map { |time| time }
|
27
|
+
# (0..3).map { |num| start_date - num.days if start_date.on_weekend? || start_date.monday? }
|
28
|
+
# If the start_date is a weekend or a Monday, then we need to include
|
29
|
+
# start_date along with all the dates up to and including the previous
|
30
|
+
# Monday.
|
31
|
+
(0..3).filter_map do |num|
|
32
|
+
time = start_date - num.days
|
33
|
+
next unless time == start_date || time.on_weekend? || time.friday?
|
34
|
+
|
35
|
+
time
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -12,14 +12,14 @@ module Dsu
|
|
12
12
|
def validate(record)
|
13
13
|
raise 'options[:fields] is not defined.' unless options.key? :fields
|
14
14
|
raise 'options[:fields] is not an Array.' unless options[:fields].is_a? Array
|
15
|
-
raise 'options[:fields] elements are not Symbols.' unless options[:fields].all?
|
15
|
+
raise 'options[:fields] elements are not Symbols.' unless options[:fields].all?(Symbol)
|
16
16
|
|
17
17
|
options[:fields].each do |field|
|
18
18
|
entries = record.send(field)
|
19
19
|
|
20
20
|
unless entries.is_a?(Array)
|
21
21
|
record.errors.add(field, 'is the wrong object type. ' \
|
22
|
-
|
22
|
+
"\"Array\" was expected, but \"#{entries.class}\" was received.")
|
23
23
|
next
|
24
24
|
end
|
25
25
|
|
@@ -35,7 +35,7 @@ module Dsu
|
|
35
35
|
next if entry.is_a? Dsu::Models::Entry
|
36
36
|
|
37
37
|
record.errors.add(field, 'entry Array element is the wrong object type. ' \
|
38
|
-
|
38
|
+
"\"Entry\" was expected, but \"#{entry.class}\" was received.",
|
39
39
|
type: Dsu::Support::FieldErrors::FIELD_TYPE_ERROR)
|
40
40
|
|
41
41
|
next
|
@@ -47,16 +47,13 @@ module Dsu
|
|
47
47
|
|
48
48
|
entry_objects = entries.select { |entry| entry.is_a?(Dsu::Models::Entry) }
|
49
49
|
|
50
|
-
entry_objects.map(&:uuid)
|
51
|
-
|
52
|
-
end
|
50
|
+
uuids = entry_objects.map(&:uuid)
|
51
|
+
return if uuids.uniq.length == uuids.length
|
53
52
|
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
type: Dsu::Support::FieldErrors::FIELD_DUPLICATE_ERROR)
|
59
|
-
end
|
53
|
+
non_unique_uuids = uuids.select { |element| uuids.count(element) > 1 }.uniq
|
54
|
+
if non_unique_uuids.any?
|
55
|
+
record.errors.add(field, "contains duplicate UUIDs: #{non_unique_uuids.join(', ')}.",
|
56
|
+
type: Dsu::Support::FieldErrors::FIELD_DUPLICATE_ERROR)
|
60
57
|
end
|
61
58
|
end
|
62
59
|
end
|
data/lib/dsu/version.rb
CHANGED
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'time'
|
4
|
+
require 'active_support/core_ext/numeric/time'
|
5
|
+
require_relative '../../models/entry_group'
|
6
|
+
require_relative '../../support/colorable'
|
7
|
+
require_relative '../../support/say'
|
8
|
+
require_relative '../../support/time_formatable'
|
9
|
+
|
10
|
+
module Dsu
|
11
|
+
module Views
|
12
|
+
module EntryGroup
|
13
|
+
class Edit
|
14
|
+
include Support::Colorable
|
15
|
+
include Support::Say
|
16
|
+
include Support::TimeFormatable
|
17
|
+
|
18
|
+
def initialize(entry_group:, options: {})
|
19
|
+
raise ArgumentError, 'entry_group is nil' if entry_group.nil?
|
20
|
+
raise ArgumentError, 'entry_group is the wrong object type' unless entry_group.is_a?(Models::EntryGroup)
|
21
|
+
raise ArgumentError, 'options is nil' if options.nil?
|
22
|
+
raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
|
23
|
+
|
24
|
+
@entry_group = entry_group
|
25
|
+
@options = options || {}
|
26
|
+
end
|
27
|
+
|
28
|
+
def call
|
29
|
+
# Just in case the entry group is invalid, we'll
|
30
|
+
# validate it before displaying it.
|
31
|
+
entry_group.validate!
|
32
|
+
render_entry_group!
|
33
|
+
rescue ActiveModel::ValidationError
|
34
|
+
puts "Error(s) encountered: #{entry_group.errors.full_messages}"
|
35
|
+
raise
|
36
|
+
end
|
37
|
+
alias render call
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
attr_reader :entry_group, :options
|
42
|
+
|
43
|
+
def render_entry_group!
|
44
|
+
say "# Editing DSU Entries for #{formatted_time(time: entry_group.time)}"
|
45
|
+
say('(no entries available for this day)') and return if entry_group.entries.empty?
|
46
|
+
|
47
|
+
say ''
|
48
|
+
say '# [SHA/COMMAND] [DESCRIPTION]'
|
49
|
+
|
50
|
+
entry_group.entries.each do |entry|
|
51
|
+
say "#{entry.uuid} #{entry.description}"
|
52
|
+
end
|
53
|
+
|
54
|
+
say ''
|
55
|
+
say '# INSTRUCTIONS:'
|
56
|
+
say '# ADD a DSU entry: use one of the following commands: [+|a|add] ' \
|
57
|
+
'followed by the description.'
|
58
|
+
say '# EDIT a DSU entry: change the description.'
|
59
|
+
say '# DELETE a DSU entry: delete the entry or replace the sha with one ' \
|
60
|
+
'of the following commands: [-|d|delete].'
|
61
|
+
say '# NOTE: deleting all the entries will delete the entry group file; '
|
62
|
+
say '# this is preferable if this is what you want to do :)'
|
63
|
+
say '# REORDER a DSU entry: reorder the DSU entries in order preference.'
|
64
|
+
say '#'
|
65
|
+
say '# *** When you are done, save and close your editor ***'
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|