dsu 2.3.2 → 2.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/Gemfile.lock +32 -5
- data/README.md +40 -2
- data/lib/dsu/cli.rb +5 -0
- data/lib/dsu/models/color_theme.rb +3 -0
- data/lib/dsu/models/entry.rb +3 -0
- data/lib/dsu/presenters/import/all_presenter.rb +62 -0
- data/lib/dsu/presenters/import/dates_presenter.rb +72 -0
- data/lib/dsu/presenters/import/import_file.rb +25 -0
- data/lib/dsu/presenters/import/messages.rb +42 -0
- data/lib/dsu/presenters/import/service_callable.rb +21 -0
- data/lib/dsu/services/entry_group/importer_service.rb +84 -0
- data/lib/dsu/subcommands/export.rb +2 -1
- data/lib/dsu/subcommands/import.rb +68 -0
- data/lib/dsu/support/command_options/dsu_times.rb +1 -1
- data/lib/dsu/validators/description_validator.rb +16 -5
- data/lib/dsu/version.rb +1 -1
- data/lib/dsu/views/import.rb +29 -0
- data/lib/locales/en/commands.yml +4 -0
- data/lib/locales/en/subcommands.yml +92 -0
- metadata +20 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz: '
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: '049f61bd321689f9690d42ba2cb72e0901c63e78a1c6454ee8fe0126e8dc0396'
|
4
|
+
data.tar.gz: e1687331fa05a352b29800f86b43de93b780a8efa50d74e1be13ce83fa331b23
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 2d5547b1ce5e805ec2ea8399ed01b76a7c03180b3ddc14b1ad9225cbacec79dbc963b37b24224fac6397b0a88b51772741e0a2840db70064f37f179b2e1d7aa5
|
7
|
+
data.tar.gz: a0146a22e7fe3404ee63de7f26d1ce66d945685ec21e41fd1b20eaed4b5cea037920552a5dc52be26c6394dfdf90ff9382d8702ffcfe3b9de571e9b1c0d59e6d
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
## [2.4.0] 2024-01-01
|
2
|
+
|
3
|
+
Enhancements
|
4
|
+
|
5
|
+
- Add `dsu import` command to import DSU entries from a comma-delimited csv file. See `dsu help import` for more information.
|
6
|
+
- Update README.md to reflect new `dsu import` command.
|
7
|
+
|
8
|
+
Changes
|
9
|
+
|
10
|
+
- Update ruby gems.
|
11
|
+
|
1
12
|
## [2.3.2] 2023-12-30
|
2
13
|
|
3
14
|
Changes
|
data/Gemfile.lock
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
PATH
|
2
2
|
remote: .
|
3
3
|
specs:
|
4
|
-
dsu (2.
|
4
|
+
dsu (2.4.0)
|
5
5
|
activemodel (>= 7.0.8, < 8.0)
|
6
6
|
activesupport (>= 7.0.8, < 8.0)
|
7
7
|
colorize (>= 0.8.1, < 1.0)
|
@@ -37,13 +37,38 @@ GEM
|
|
37
37
|
dotenv (2.8.1)
|
38
38
|
drb (2.2.0)
|
39
39
|
ruby2_keywords
|
40
|
-
|
40
|
+
dry-configurable (1.1.0)
|
41
|
+
dry-core (~> 1.0, < 2)
|
42
|
+
zeitwerk (~> 2.6)
|
43
|
+
dry-core (1.0.1)
|
44
|
+
concurrent-ruby (~> 1.0)
|
45
|
+
zeitwerk (~> 2.6)
|
46
|
+
dry-inflector (1.0.0)
|
47
|
+
dry-initializer (3.1.1)
|
48
|
+
dry-logic (1.5.0)
|
49
|
+
concurrent-ruby (~> 1.0)
|
50
|
+
dry-core (~> 1.0, < 2)
|
51
|
+
zeitwerk (~> 2.6)
|
52
|
+
dry-schema (1.13.3)
|
53
|
+
concurrent-ruby (~> 1.0)
|
54
|
+
dry-configurable (~> 1.0, >= 1.0.1)
|
55
|
+
dry-core (~> 1.0, < 2)
|
56
|
+
dry-initializer (~> 3.0)
|
57
|
+
dry-logic (>= 1.4, < 2)
|
58
|
+
dry-types (>= 1.7, < 2)
|
59
|
+
zeitwerk (~> 2.6)
|
60
|
+
dry-types (1.7.1)
|
61
|
+
concurrent-ruby (~> 1.0)
|
62
|
+
dry-core (~> 1.0)
|
63
|
+
dry-inflector (~> 1.0)
|
64
|
+
dry-logic (~> 1.4)
|
65
|
+
zeitwerk (~> 2.6)
|
66
|
+
factory_bot (6.4.5)
|
41
67
|
activesupport (>= 5.0.0)
|
42
68
|
ffaker (2.23.0)
|
43
69
|
i18n (1.14.1)
|
44
70
|
concurrent-ruby (~> 1.0)
|
45
71
|
json (2.7.1)
|
46
|
-
kwalify (0.7.2)
|
47
72
|
language_server-protocol (3.17.0.3)
|
48
73
|
method_source (1.0.0)
|
49
74
|
minitest (5.20.0)
|
@@ -62,10 +87,11 @@ GEM
|
|
62
87
|
racc (1.7.3)
|
63
88
|
rainbow (3.1.1)
|
64
89
|
rake (13.1.0)
|
65
|
-
reek (6.
|
66
|
-
|
90
|
+
reek (6.2.0)
|
91
|
+
dry-schema (~> 1.13.0)
|
67
92
|
parser (~> 3.2.0)
|
68
93
|
rainbow (>= 2.0, < 4.0)
|
94
|
+
rexml (~> 3.1)
|
69
95
|
regexp_parser (2.8.3)
|
70
96
|
rexml (3.2.6)
|
71
97
|
rspec (3.12.0)
|
@@ -118,6 +144,7 @@ GEM
|
|
118
144
|
tzinfo (2.0.6)
|
119
145
|
concurrent-ruby (~> 1.0)
|
120
146
|
unicode-display_width (2.5.0)
|
147
|
+
zeitwerk (2.6.12)
|
121
148
|
|
122
149
|
PLATFORMS
|
123
150
|
x86_64-darwin-19
|
data/README.md
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# `dsu`- Streamline Your Daily Stand-Up Meeting Participation!
|
2
2
|
|
3
3
|
[![Ruby](https://github.com/gangelo/dsu/actions/workflows/ruby.yml/badge.svg)](https://github.com/gangelo/dsu/actions/workflows/ruby.yml)
|
4
|
-
[![GitHub version](http://badge.fury.io/gh/gangelo%2Fdsu.svg?refresh=
|
5
|
-
[![Gem Version](https://badge.fury.io/rb/dsu.svg?refresh=
|
4
|
+
[![GitHub version](http://badge.fury.io/gh/gangelo%2Fdsu.svg?refresh=4)](https://badge.fury.io/gh/gangelo%2Fdsu)
|
5
|
+
[![Gem Version](https://badge.fury.io/rb/dsu.svg?refresh=4)](https://badge.fury.io/rb/dsu)
|
6
6
|
[![Documentation](http://img.shields.io/badge/docs-rdoc.info-blue.svg)](http://www.rubydoc.info/gems/dsu/)
|
7
7
|
[![Report Issues](https://img.shields.io/badge/report-issues-red.svg)](https://github.com/gangelo/dsu/issues)
|
8
8
|
[![License](http://img.shields.io/badge/license-MIT-yellowgreen.svg)](#license)
|
@@ -44,6 +44,7 @@ Commands:
|
|
44
44
|
dsu edit|e SUBCOMMAND # Edit DSU entries...
|
45
45
|
dsu export|x SUBCOMMAND # Export DSU entries...
|
46
46
|
dsu help [COMMAND] # Describe available...
|
47
|
+
dsu import|m SUBCOMMAND # Imports DSU entries...
|
47
48
|
dsu info|i # Displays information...
|
48
49
|
dsu list|l SUBCOMMAND # Displays DSU entries...
|
49
50
|
dsu theme|t SUBCOMMAND # Manage DSU themes...
|
@@ -413,6 +414,43 @@ The following command, when run on December 25, 2023, at 20:15:46...
|
|
413
414
|
|
414
415
|
For more information, see `dsu` help (`$ dsu export` or `dsu help export`) for more information.
|
415
416
|
|
417
|
+
## Importing DSU Entries
|
418
|
+
|
419
|
+
`dsu` provides a means to import entry group entry data from a previously exported `csv` file (see [Exporting DSU Entries](#exporting-dsu-entries)).
|
420
|
+
|
421
|
+
If you want to import a previously expoeted `csv` file, you can import `dsu` entries from a `csv` file by using any of the following commands:
|
422
|
+
|
423
|
+
- `$ dsu import all`
|
424
|
+
- `$ dsu m a` # Equivalent to the above, only using shortcuts
|
425
|
+
- `$ dsu import dates OPTIONS`
|
426
|
+
- `$ dsu m dd OPTIONS` # Equivalent to the above, only using shortcuts
|
427
|
+
|
428
|
+
**NOTE:** Each `import` command will prompt you to confirm the import. If confirmed, `dsu` will import the entry group entry data from the `csv` file into `dsu`.
|
429
|
+
|
430
|
+
### For example
|
431
|
+
|
432
|
+
### Importing all entries from a `csv` file
|
433
|
+
You can import _all_ entry group entries from a `csv` file.
|
434
|
+
|
435
|
+
The following command will import all the `dsu` entries from the given `csv` file, and merge the imported entries with any existing entry group entries you may have:
|
436
|
+
|
437
|
+
`$ dsu import all -i ~/Downloads/dsu-20231225201546-2023-01-01-thru-2024-01-01.csv`
|
438
|
+
|
439
|
+
The following command will import all the `dsu` entries from the given `csv` file, and **_overwrite_** all entry groups entries with the same entry group date using the `dsu export all` shortcut command:
|
440
|
+
|
441
|
+
`$ dsu m a -m false -i ~/Downloads/dsu-20231225201546-2023-01-01-thru-2024-01-01.csv`
|
442
|
+
|
443
|
+
### Importing specific entries from a `csv` file
|
444
|
+
You can import _specific_ entry group entries from a `csv` file for a date range.
|
445
|
+
|
446
|
+
The following command will import the `dsu` entries from the given `csv` file for the given date range, and merge the imported entries with any existing entry group entries you may have:
|
447
|
+
|
448
|
+
`$ dsu import dates --from 1/1/2023 --to 12/31/2023 -i ~/Downloads/dsu-20231225201546-2023-01-01-thru-2024-01-01.csv`
|
449
|
+
|
450
|
+
The following command will import the `dsu` entries from the given `csv` file for the given date range, and **_overwrite_** all entry groups entries with the same entry group date using the `dsu import dates` shortcut command:
|
451
|
+
|
452
|
+
`$ dsu m dd -m false -f 1/1/2023 -t 12/31/2023 -i ~/Downloads/dsu-20231225201546-2023-01-01-thru-2024-01-01.csv`
|
453
|
+
|
416
454
|
## Customizing the `dsu` Configuration File
|
417
455
|
To customize the `dsu` configuration file, you may follow the instructions outlined here. It is only recommended that you customize the `dsu` configuration file *only* if you are working with an official release (`n.n.n.n`).
|
418
456
|
|
data/lib/dsu/cli.rb
CHANGED
@@ -8,6 +8,7 @@ require_relative 'subcommands/config'
|
|
8
8
|
require_relative 'subcommands/delete'
|
9
9
|
require_relative 'subcommands/edit'
|
10
10
|
require_relative 'subcommands/export'
|
11
|
+
require_relative 'subcommands/import'
|
11
12
|
require_relative 'subcommands/list'
|
12
13
|
require_relative 'subcommands/theme'
|
13
14
|
|
@@ -21,6 +22,7 @@ module Dsu
|
|
21
22
|
map I18n.t('commands.edit.key_mappings') => :edit
|
22
23
|
map I18n.t('commands.export.key_mappings') => :export
|
23
24
|
map I18n.t('commands.help.key_mappings') => :help
|
25
|
+
map I18n.t('commands.import.key_mappings') => :import
|
24
26
|
map I18n.t('commands.info.key_mappings') => :info
|
25
27
|
map I18n.t('commands.list.key_mappings') => :list
|
26
28
|
map I18n.t('commands.theme.key_mappings') => :theme
|
@@ -68,6 +70,9 @@ module Dsu
|
|
68
70
|
desc I18n.t('commands.theme.desc'), I18n.t('commands.theme.usage')
|
69
71
|
subcommand :theme, Subcommands::Theme
|
70
72
|
|
73
|
+
desc I18n.t('commands.import.desc'), I18n.t('commands.import.usage')
|
74
|
+
subcommand :import, Subcommands::Import
|
75
|
+
|
71
76
|
desc I18n.t('commands.info.desc'), I18n.t('commands.info.usage')
|
72
77
|
def info
|
73
78
|
configuration_version = Models::Configuration::VERSION
|
@@ -54,6 +54,9 @@ module Dsu
|
|
54
54
|
description: 'Default theme.'
|
55
55
|
}.merge(DEFAULT_THEME_COLORS).freeze
|
56
56
|
|
57
|
+
MIN_DESCRIPTION_LENGTH = 2
|
58
|
+
MAX_DESCRIPTION_LENGTH = 256
|
59
|
+
|
57
60
|
# TODO: Validate other attrs.
|
58
61
|
validates_with Validators::DescriptionValidator
|
59
62
|
validates_with Validators::ColorThemeValidator
|
data/lib/dsu/models/entry.rb
CHANGED
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/entry_group'
|
4
|
+
require_relative '../../services/entry_group/importer_service'
|
5
|
+
require_relative '../../support/ask'
|
6
|
+
require_relative '../base_presenter_ex'
|
7
|
+
require_relative 'import_file'
|
8
|
+
require_relative 'messages'
|
9
|
+
require_relative 'service_callable'
|
10
|
+
|
11
|
+
module Dsu
|
12
|
+
module Presenters
|
13
|
+
module Import
|
14
|
+
class AllPresenter < BasePresenterEx
|
15
|
+
include ImportFile
|
16
|
+
include Messages
|
17
|
+
include ServiceCallable
|
18
|
+
include Support::Ask
|
19
|
+
|
20
|
+
def initialize(import_file_path:, options: {})
|
21
|
+
super(options: options)
|
22
|
+
|
23
|
+
@import_file_path = import_file_path
|
24
|
+
end
|
25
|
+
|
26
|
+
def render(response:)
|
27
|
+
return display_cancelled_message unless response
|
28
|
+
|
29
|
+
display_import_messages importer_service_call
|
30
|
+
end
|
31
|
+
|
32
|
+
def display_import_prompt
|
33
|
+
yes?(prompt_with_options(prompt: import_prompt, options: import_prompt_options), options: options)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
attr_reader :import_file_path, :options
|
39
|
+
|
40
|
+
def import_entry_groups
|
41
|
+
@import_entry_groups ||= CSV.foreach(import_file_path,
|
42
|
+
headers: true).with_object({}) do |entry_group_entry, entry_groups_hash|
|
43
|
+
next unless entry_group_entry['version'].to_i == Dsu::Migration::VERSION
|
44
|
+
|
45
|
+
Date.parse(entry_group_entry['entry_group']).to_s.tap do |time|
|
46
|
+
entry_groups_hash[time] = [] unless entry_groups_hash.key?(time)
|
47
|
+
entry_groups_hash[time] << entry_group_entry['entry_group_entry']
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def import_prompt
|
53
|
+
I18n.t('subcommands.import.prompts.import_all_confirm', count: import_entry_groups.count)
|
54
|
+
end
|
55
|
+
|
56
|
+
def import_prompt_options
|
57
|
+
I18n.t('subcommands.import.prompts.options')
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../models/entry_group'
|
4
|
+
require_relative '../../services/entry_group/importer_service'
|
5
|
+
require_relative '../../support/ask'
|
6
|
+
require_relative '../base_presenter_ex'
|
7
|
+
require_relative 'import_file'
|
8
|
+
require_relative 'messages'
|
9
|
+
require_relative 'service_callable'
|
10
|
+
|
11
|
+
module Dsu
|
12
|
+
module Presenters
|
13
|
+
module Import
|
14
|
+
class DatesPresenter < BasePresenterEx
|
15
|
+
include ImportFile
|
16
|
+
include Messages
|
17
|
+
include ServiceCallable
|
18
|
+
include Support::Ask
|
19
|
+
|
20
|
+
def initialize(from:, to:, import_file_path:, options: {})
|
21
|
+
super(options: options)
|
22
|
+
|
23
|
+
@from = from.beginning_of_day
|
24
|
+
@to = to.end_of_day
|
25
|
+
@import_file_path = import_file_path
|
26
|
+
end
|
27
|
+
|
28
|
+
def render(response:)
|
29
|
+
return display_cancelled_message unless response
|
30
|
+
|
31
|
+
display_import_messages importer_service_call
|
32
|
+
end
|
33
|
+
|
34
|
+
def display_import_prompt
|
35
|
+
yes?(prompt_with_options(prompt: import_prompt, options: import_prompt_options), options: options)
|
36
|
+
end
|
37
|
+
|
38
|
+
private
|
39
|
+
|
40
|
+
attr_reader :from, :to, :import_file_path, :options
|
41
|
+
|
42
|
+
def import_entry_groups
|
43
|
+
@import_entry_groups ||= CSV.foreach(import_file_path,
|
44
|
+
headers: true).with_object({}) do |entry_group_entry, entry_groups_hash|
|
45
|
+
next unless entry_group_entry['version'].to_i == Dsu::Migration::VERSION
|
46
|
+
|
47
|
+
entry_group_time = middle_of_day_for(entry_group_entry['entry_group'])
|
48
|
+
next unless entry_group_time.to_date.between?(from.to_date, to.to_date)
|
49
|
+
|
50
|
+
entry_group_time.to_date.to_s.tap do |time|
|
51
|
+
entry_groups_hash[time] = [] unless entry_groups_hash.key?(time)
|
52
|
+
entry_groups_hash[time] << entry_group_entry['entry_group_entry']
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def import_prompt
|
58
|
+
I18n.t('subcommands.import.prompts.import_dates_confirm',
|
59
|
+
from: from.to_date, to: to.to_date, count: import_entry_groups.keys.count)
|
60
|
+
end
|
61
|
+
|
62
|
+
def import_prompt_options
|
63
|
+
I18n.t('subcommands.import.prompts.options')
|
64
|
+
end
|
65
|
+
|
66
|
+
def middle_of_day_for(date_string)
|
67
|
+
Time.parse(date_string).in_time_zone.middle_of_day
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dsu
|
4
|
+
module Presenters
|
5
|
+
module Import
|
6
|
+
module ImportFile
|
7
|
+
def import_file_path_exist?
|
8
|
+
File.exist? import_file_path
|
9
|
+
end
|
10
|
+
|
11
|
+
def nothing_to_import?
|
12
|
+
return true unless import_file_path_exist?
|
13
|
+
|
14
|
+
import_entry_groups.empty?
|
15
|
+
end
|
16
|
+
|
17
|
+
def import_entry_groups
|
18
|
+
# Should return a Hash of entry group entries
|
19
|
+
# Example: { '2023-12-32' => ['Entry description 1', 'Entry description 2', ...] }
|
20
|
+
raise NotImplementedError
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Dsu
|
4
|
+
module Presenters
|
5
|
+
module Import
|
6
|
+
module Messages
|
7
|
+
def display_import_prompt
|
8
|
+
raise NotImplementedError
|
9
|
+
end
|
10
|
+
|
11
|
+
def display_import_file_not_exist_message
|
12
|
+
puts apply_theme(I18n.t('subcommands.import.messages.file_not_exist',
|
13
|
+
file_path: import_file_path), theme_color: color_theme.info)
|
14
|
+
end
|
15
|
+
|
16
|
+
def display_nothing_to_import_message
|
17
|
+
puts apply_theme(I18n.t('subcommands.import.messages.nothing_to_import'), theme_color: color_theme.info)
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def display_cancelled_message
|
23
|
+
puts apply_theme(I18n.t('subcommands.import.messages.cancelled'), theme_color: color_theme.info)
|
24
|
+
end
|
25
|
+
|
26
|
+
def display_import_messages(import_results)
|
27
|
+
import_results.each_pair do |entry_group_date, errors|
|
28
|
+
if errors.empty?
|
29
|
+
puts apply_theme(I18n.t('subcommands.import.messages.import_success',
|
30
|
+
date: entry_group_date), theme_color: color_theme.success)
|
31
|
+
else
|
32
|
+
errors.each do |error|
|
33
|
+
puts apply_theme(I18n.t('subcommands.import.messages.import_error',
|
34
|
+
date: entry_group_date, error: error), theme_color: color_theme.error)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../../services/entry_group/importer_service'
|
4
|
+
|
5
|
+
module Dsu
|
6
|
+
module Presenters
|
7
|
+
module Import
|
8
|
+
module ServiceCallable
|
9
|
+
private
|
10
|
+
|
11
|
+
def importer_service_call
|
12
|
+
@importer_service_call ||= begin
|
13
|
+
importer_service = Services::EntryGroup::ImporterService.new(import_entry_groups: import_entry_groups,
|
14
|
+
options: options)
|
15
|
+
importer_service.call
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'csv'
|
4
|
+
require_relative '../../models/entry_group'
|
5
|
+
|
6
|
+
module Dsu
|
7
|
+
module Services
|
8
|
+
module EntryGroup
|
9
|
+
# Expects a hash having the following format:
|
10
|
+
# {
|
11
|
+
# "2023-12-29" => ["Entry 1 description", "Entry 2 description", ...],
|
12
|
+
# "2023-12-30" => ["Entry 1 description", ...],
|
13
|
+
# "2023-12-31" => ["Entry 1 description", ...]
|
14
|
+
# }
|
15
|
+
class ImporterService
|
16
|
+
include Support::Fileable
|
17
|
+
|
18
|
+
def initialize(import_entry_groups:, options: {})
|
19
|
+
raise ArgumentError, 'Argument import_entry_groups is blank' if import_entry_groups.blank?
|
20
|
+
|
21
|
+
@import_entry_groups = import_entry_groups
|
22
|
+
@options = options
|
23
|
+
end
|
24
|
+
|
25
|
+
def call
|
26
|
+
import!
|
27
|
+
end
|
28
|
+
|
29
|
+
private
|
30
|
+
|
31
|
+
attr_reader :import_entry_groups, :options
|
32
|
+
|
33
|
+
def import!
|
34
|
+
import_entry_groups.each_pair do |entry_group_date, entry_descriptions|
|
35
|
+
entry_group_for(entry_group_date).tap do |entry_group|
|
36
|
+
entry_descriptions.each do |entry_description|
|
37
|
+
add_entry_group_entry_if(entry_group: entry_group, entry_description: entry_description)
|
38
|
+
end
|
39
|
+
|
40
|
+
import_messages[entry_group.time_yyyy_mm_dd] = []
|
41
|
+
|
42
|
+
unless entry_group.save
|
43
|
+
entry_group.errors.full_messages.each do |error|
|
44
|
+
import_messages[entry_group.time_yyyy_mm_dd] << error
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
import_messages
|
51
|
+
end
|
52
|
+
|
53
|
+
def entry_group_for(entry_group_date)
|
54
|
+
time = Time.parse(entry_group_date).in_time_zone
|
55
|
+
if merge?
|
56
|
+
Models::EntryGroup.find_or_initialize(time: time)
|
57
|
+
else
|
58
|
+
Models::EntryGroup.new(time: time, options: options)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def add_entry_group_entry_if(entry_group:, entry_description:)
|
63
|
+
entry = Models::Entry.new(description: entry_description)
|
64
|
+
return entry_group.entries << entry if replace?
|
65
|
+
return if entry_group.entries.include?(entry)
|
66
|
+
|
67
|
+
entry_group.entries << entry
|
68
|
+
end
|
69
|
+
|
70
|
+
def merge?
|
71
|
+
options.fetch(:merge, true)
|
72
|
+
end
|
73
|
+
|
74
|
+
def replace?
|
75
|
+
!merge?
|
76
|
+
end
|
77
|
+
|
78
|
+
def import_messages
|
79
|
+
@import_messages ||= {}
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../presenters/export/all_presenter'
|
4
|
+
require_relative '../presenters/export/dates_presenter'
|
3
5
|
require_relative '../support/command_options/dsu_times'
|
4
6
|
require_relative '../support/command_options/time_mnemonic'
|
5
7
|
require_relative '../support/time_formatable'
|
@@ -39,7 +41,6 @@ module Dsu
|
|
39
41
|
return
|
40
42
|
end
|
41
43
|
|
42
|
-
times = times_sort(times: times, entries_display_order: options[:entries_display_order])
|
43
44
|
Views::Export.new(presenter: dates_presenter_for(from: times.min, to: times.max, options: options)).render
|
44
45
|
rescue ArgumentError => e
|
45
46
|
Views::Shared::Error.new(messages: e.message).render
|
@@ -0,0 +1,68 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../presenters/import/all_presenter'
|
4
|
+
require_relative '../presenters/import/dates_presenter'
|
5
|
+
require_relative '../support/command_options/dsu_times'
|
6
|
+
require_relative '../support/command_options/time_mnemonic'
|
7
|
+
require_relative '../support/time_formatable'
|
8
|
+
require_relative '../views/import'
|
9
|
+
require_relative '../views/shared/error'
|
10
|
+
require_relative 'base_subcommand'
|
11
|
+
|
12
|
+
module Dsu
|
13
|
+
module Subcommands
|
14
|
+
class Import < BaseSubcommand
|
15
|
+
include Support::CommandOptions::TimeMnemonic
|
16
|
+
include Support::TimeFormatable
|
17
|
+
|
18
|
+
# TODO: I18n.
|
19
|
+
map %w[a] => :all
|
20
|
+
map %w[dd] => :dates
|
21
|
+
|
22
|
+
desc I18n.t('subcommands.import.all.desc'), I18n.t('subcommands.import.all.usage')
|
23
|
+
long_desc I18n.t('subcommands.import.all.long_desc')
|
24
|
+
option :import_file, type: :string, required: true, aliases: '-i', banner: 'IMPORT_CVS_FILE'
|
25
|
+
option :merge, type: :boolean, default: true, aliases: '-m'
|
26
|
+
option :prompts, type: :hash, default: {}, hide: true, aliases: '-p'
|
27
|
+
def all
|
28
|
+
Views::Import.new(presenter: all_presenter(import_file_path: options[:import_file],
|
29
|
+
options: options)).render
|
30
|
+
end
|
31
|
+
|
32
|
+
desc I18n.t('subcommands.import.dates.desc'), I18n.t('subcommands.import.dates.usage')
|
33
|
+
long_desc I18n.t('subcommands.import.dates.long_desc',
|
34
|
+
date_option_description: date_option_description,
|
35
|
+
mnemonic_option_description: mnemonic_option_description)
|
36
|
+
option :from, type: :string, required: true, aliases: '-f', banner: 'DATE|MNEMONIC'
|
37
|
+
option :to, type: :string, required: true, aliases: '-t', banner: 'DATE|MNEMONIC'
|
38
|
+
option :import_file, type: :string, required: true, aliases: '-i', banner: 'IMPORT_CVS_FILE'
|
39
|
+
option :merge, type: :boolean, default: true, aliases: '-m'
|
40
|
+
option :prompts, type: :hash, default: {}, hide: true, aliases: '-p'
|
41
|
+
def dates
|
42
|
+
options = configuration.to_h.merge(self.options).with_indifferent_access
|
43
|
+
times, errors = Support::CommandOptions::DsuTimes.dsu_times_for(from_option: options[:from], to_option: options[:to]) # rubocop:disable Layout/LineLength
|
44
|
+
if errors.any?
|
45
|
+
Views::Shared::Error.new(messages: errors).render
|
46
|
+
return
|
47
|
+
end
|
48
|
+
|
49
|
+
Views::Import.new(presenter: dates_presenter_for(from: times.min,
|
50
|
+
to: times.max,
|
51
|
+
import_file_path: options[:import_file],
|
52
|
+
options: options)).render
|
53
|
+
rescue ArgumentError => e
|
54
|
+
Views::Shared::Error.new(messages: e.message).render
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def all_presenter(import_file_path:, options:)
|
60
|
+
Presenters::Import::AllPresenter.new(import_file_path: import_file_path, options: options)
|
61
|
+
end
|
62
|
+
|
63
|
+
def dates_presenter_for(from:, to:, import_file_path:, options:)
|
64
|
+
Presenters::Import::DatesPresenter.new(from: from, to: to, import_file_path: import_file_path, options: options)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
@@ -21,7 +21,7 @@ module Dsu
|
|
21
21
|
errors << I18n.t('errors.to_option_invalid', to_option: to_option) if to_time.nil?
|
22
22
|
return [[], errors] if errors.any?
|
23
23
|
|
24
|
-
min_time, max_time = [from_time, to_time].
|
24
|
+
min_time, max_time = [from_time, to_time].minmax
|
25
25
|
[(min_time.to_date..max_time.to_date).map(&:to_time), []]
|
26
26
|
end
|
27
27
|
|
@@ -12,6 +12,7 @@ module Dsu
|
|
12
12
|
end
|
13
13
|
|
14
14
|
unless description.is_a?(String)
|
15
|
+
# TODO: I18n.
|
15
16
|
record.errors.add(:description, 'is the wrong object type. ' \
|
16
17
|
"\"String\" was expected, but \"#{description.class}\" was received.")
|
17
18
|
return
|
@@ -25,16 +26,26 @@ module Dsu
|
|
25
26
|
def validate_description(record)
|
26
27
|
description = record.description
|
27
28
|
|
28
|
-
return if description.length.between?(
|
29
|
+
return if description.length.between?(min_description_length(record), max_description_length(record))
|
29
30
|
|
30
|
-
if description.length <
|
31
|
+
if description.length < min_description_length(record)
|
31
32
|
# TODO: I18n.
|
32
|
-
record.errors.add(:description, "is too short: \"#{record.short_description}\"
|
33
|
-
|
33
|
+
record.errors.add(:description, "is too short: \"#{record.short_description}\" " \
|
34
|
+
"(minimum is #{min_description_length(record)} characters).")
|
35
|
+
elsif description.length > max_description_length(record)
|
34
36
|
# TODO: I18n.
|
35
|
-
record.errors.add(:description, "is too long: \"#{record.short_description}\"
|
37
|
+
record.errors.add(:description, "is too long: \"#{record.short_description}\" " \
|
38
|
+
"(maximum is #{max_description_length(record)} characters).")
|
36
39
|
end
|
37
40
|
end
|
41
|
+
|
42
|
+
def min_description_length(record)
|
43
|
+
record.class::MIN_DESCRIPTION_LENGTH
|
44
|
+
end
|
45
|
+
|
46
|
+
def max_description_length(record)
|
47
|
+
record.class::MAX_DESCRIPTION_LENGTH
|
48
|
+
end
|
38
49
|
end
|
39
50
|
end
|
40
51
|
end
|
data/lib/dsu/version.rb
CHANGED
@@ -0,0 +1,29 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../models/color_theme'
|
4
|
+
require_relative '../models/configuration'
|
5
|
+
require_relative '../support/color_themable'
|
6
|
+
|
7
|
+
module Dsu
|
8
|
+
module Views
|
9
|
+
class Import
|
10
|
+
include Support::ColorThemable
|
11
|
+
|
12
|
+
def initialize(presenter:)
|
13
|
+
@presenter = presenter
|
14
|
+
end
|
15
|
+
|
16
|
+
def render
|
17
|
+
return presenter.display_import_file_not_exist_message unless presenter.import_file_path_exist?
|
18
|
+
return presenter.display_nothing_to_import_message if presenter.nothing_to_import?
|
19
|
+
|
20
|
+
response = presenter.display_import_prompt
|
21
|
+
presenter.render response: response
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
attr_reader :presenter
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
data/lib/locales/en/commands.yml
CHANGED
@@ -69,6 +69,10 @@ en:
|
|
69
69
|
key_mappings: x
|
70
70
|
desc: export|x SUBCOMMAND
|
71
71
|
usage: Export DSU entries for the given SUBCOMMAND
|
72
|
+
import:
|
73
|
+
key_mappings: m
|
74
|
+
desc: import|m SUBCOMMAND
|
75
|
+
usage: Imports DSU entries for the given SUBCOMMAND
|
72
76
|
info:
|
73
77
|
key_mappings: i
|
74
78
|
desc: info|i
|
@@ -288,6 +288,98 @@ en:
|
|
288
288
|
options:
|
289
289
|
- y
|
290
290
|
- N
|
291
|
+
import:
|
292
|
+
dates:
|
293
|
+
desc: dates|dd OPTIONS
|
294
|
+
usage: Imports the DSU entries given the OPTIONS provided
|
295
|
+
long_desc: |
|
296
|
+
Imports the DSU entries for the given OPTIONS provided.
|
297
|
+
|
298
|
+
$ dsu import dates OPTIONS
|
299
|
+
|
300
|
+
$ dsu m dd OPTIONS
|
301
|
+
|
302
|
+
OPTIONS:
|
303
|
+
|
304
|
+
-i|--import-file IMPORT_CVS_FILE: The IMPORT_CVS_FILE file to import. IMPORT_CVS_FILE should be a fully qualified path to a file that was previously created as a result of running `dsu export`. see `dsu help export`.
|
305
|
+
|
306
|
+
-m|--merge true|false (default: true): If true, imported entries will be added to the entry group if the entry group already exists. If false, the imported entries will replace all existing entries for the entry group if the entry group already exists. If the entry group does not exist, it will be created using the imported entries.
|
307
|
+
|
308
|
+
-f|--from DATE|MNEMONIC: The DATE or MNEMONIC that represents the start of the range of DSU dates to import. If a relative mnemonic is used (+/-n, e.g +1, -1, etc.), the date calculated will be relative to the current date (e.g. `<MNEMONIC>.to_i.days.from_now(Time.now)`).
|
309
|
+
|
310
|
+
-t|--to DATE|MNEMONIC: The DATE or MNEMONIC that represents the end of the range of DSU dates to import. If a relative mnemonic is used (+/-n, e.g +1, -1, etc.), the date calculated will be relative to the date that resulting from the `--from` option date calculation.
|
311
|
+
|
312
|
+
%{date_option_description}
|
313
|
+
|
314
|
+
%{mnemonic_option_description}
|
315
|
+
|
316
|
+
EXAMPLES:
|
317
|
+
|
318
|
+
NOTE: All examples can substitute their respective short form options (e.g. `-f`, `-t`, etc. for `--from`, `--to`, etc.).
|
319
|
+
|
320
|
+
The below will import the DSU entries for the range of dates from 1/1 to 1/4 for the current year, from the import file, and replace all the entries for the respective entry groups imported:
|
321
|
+
|
322
|
+
$ dsu import dates --from 1/1 --to +3 -i /path/to/import.csv -m false
|
323
|
+
|
324
|
+
This will import the DSU entries for the range of dates from 1/2 to 1/5 for the year 2022, from the import file, and merge all the entries for the respective entry groups imported:
|
325
|
+
|
326
|
+
$ dsu m dd --from 1/5/2022 --to -3 -i /path/to/import.csv
|
327
|
+
|
328
|
+
This (assuming "today" is 1/10) will import the DSU entries for the last week 1/10 to 1/3 of the current year, from the import file, and merge all the entries for the respective entry groups imported:
|
329
|
+
|
330
|
+
$ dsu import dates --from today --to -7 -i /path/to/import.csv -m true
|
331
|
+
|
332
|
+
This (assuming "today" is 5/23) will import the DSU entries for the last week 5/16 to 5/22.
|
333
|
+
This example simply illustrates the fact that you can use relative mnemonics for
|
334
|
+
both `--from` and `--to` options; this doesn't mean you should do so...
|
335
|
+
|
336
|
+
While you can use relative mnemonics for both `--from` and `--to` options,
|
337
|
+
there is always a more intuitive way. The below example basically imports one week of DSU entries back 1 week from yesterday's date, from the import file, and merge all the entries for the respective entry groups imported:
|
338
|
+
|
339
|
+
$ dsu import dates --from -7 --to +6 -i /path/to/import.csv
|
340
|
+
|
341
|
+
The above can be accomplished MUCH easier by simply using the `yesterday` mnemonic...
|
342
|
+
|
343
|
+
This (assuming "today" is 5/23) will import the DSU entries back 1 week from yesterday's date 5/16 to 5/22, from the import file, and merge all the entries for the respective entry groups imported:
|
344
|
+
|
345
|
+
$ dsu m dd --from yesterday --to -6 -i /path/to/import.csv
|
346
|
+
all:
|
347
|
+
desc: all|a OPTIONS
|
348
|
+
usage: Imports all DSU entries from a given DSU export .csv file
|
349
|
+
long_desc: |
|
350
|
+
Imports all DSU entries from a given DSU export .csv file.
|
351
|
+
|
352
|
+
$ dsu import all OPTIONS
|
353
|
+
|
354
|
+
$ dsu m a OPTIONS
|
355
|
+
|
356
|
+
OPTIONS:
|
357
|
+
|
358
|
+
-i|--import-file IMPORT_CVS_FILE: The IMPORT_CVS_FILE file to import. IMPORT_CVS_FILE should be a fully qualified path to a file that was previously created as a result of running `dsu export`. see `dsu help export`.
|
359
|
+
|
360
|
+
-m|--merge true|false (default: true): If true, imported entries will be added to the entry group if the entry group already exists. If false, the imported entries will replace all existing entries for the entry group if the entry group already exists. If the entry group does not exist, it will be created using the imported entries.
|
361
|
+
|
362
|
+
EXAMPLES:
|
363
|
+
|
364
|
+
This will import all the DSU entries from the import file, and replace all the entries for the respective entry groups imported:
|
365
|
+
|
366
|
+
$ dsu import all -i /path/to/import.csv -m false
|
367
|
+
|
368
|
+
This will import all the DSU entries from the import file, and merge all the entries for the respective entry groups imported:
|
369
|
+
|
370
|
+
$ dsu import all -i /path/to/import.csv
|
371
|
+
messages:
|
372
|
+
import_success: Entry group for %{date} imported successfully.
|
373
|
+
import_error: "Entry group for %{date} imported with an error: %{error}."
|
374
|
+
nothing_to_import: No entry groups to import.
|
375
|
+
cancelled: Cancelled.
|
376
|
+
file_not_exist: Import file %{file_path} does not exist.
|
377
|
+
prompts:
|
378
|
+
import_all_confirm: Import all entry groups (%{count} entry groups)?
|
379
|
+
import_dates_confirm: Import all the entry groups for %{from} thru %{to} (%{count} entry groups)?
|
380
|
+
options:
|
381
|
+
- y
|
382
|
+
- N
|
291
383
|
list:
|
292
384
|
date:
|
293
385
|
desc: date|d DATE|MNEMONIC
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: dsu
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 2.
|
4
|
+
version: 2.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Gene M. Angelo, Jr.
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-01-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: activesupport
|
@@ -190,6 +190,11 @@ files:
|
|
190
190
|
- lib/dsu/presenters/export/messages.rb
|
191
191
|
- lib/dsu/presenters/export/nothing_to_export.rb
|
192
192
|
- lib/dsu/presenters/export/service_callable.rb
|
193
|
+
- lib/dsu/presenters/import/all_presenter.rb
|
194
|
+
- lib/dsu/presenters/import/dates_presenter.rb
|
195
|
+
- lib/dsu/presenters/import/import_file.rb
|
196
|
+
- lib/dsu/presenters/import/messages.rb
|
197
|
+
- lib/dsu/presenters/import/service_callable.rb
|
193
198
|
- lib/dsu/services/color_theme/hydrator_service.rb
|
194
199
|
- lib/dsu/services/configuration/hydrator_service.rb
|
195
200
|
- lib/dsu/services/entry/hydrator_service.rb
|
@@ -199,6 +204,7 @@ files:
|
|
199
204
|
- lib/dsu/services/entry_group/editor_service.rb
|
200
205
|
- lib/dsu/services/entry_group/exporter_service.rb
|
201
206
|
- lib/dsu/services/entry_group/hydrator_service.rb
|
207
|
+
- lib/dsu/services/entry_group/importer_service.rb
|
202
208
|
- lib/dsu/services/migration_version/hydrator_service.rb
|
203
209
|
- lib/dsu/services/stderr_redirector_service.rb
|
204
210
|
- lib/dsu/services/stdout_redirector_service.rb
|
@@ -210,6 +216,7 @@ files:
|
|
210
216
|
- lib/dsu/subcommands/delete.rb
|
211
217
|
- lib/dsu/subcommands/edit.rb
|
212
218
|
- lib/dsu/subcommands/export.rb
|
219
|
+
- lib/dsu/subcommands/import.rb
|
213
220
|
- lib/dsu/subcommands/list.rb
|
214
221
|
- lib/dsu/subcommands/theme.rb
|
215
222
|
- lib/dsu/support/ask.rb
|
@@ -246,6 +253,7 @@ files:
|
|
246
253
|
- lib/dsu/views/entry_group/shared/no_entries_to_display_for_year_of.rb
|
247
254
|
- lib/dsu/views/entry_group/show.rb
|
248
255
|
- lib/dsu/views/export.rb
|
256
|
+
- lib/dsu/views/import.rb
|
249
257
|
- lib/dsu/views/shared/error.rb
|
250
258
|
- lib/dsu/views/shared/info.rb
|
251
259
|
- lib/dsu/views/shared/message.rb
|
@@ -282,28 +290,25 @@ post_install_message: |
|
|
282
290
|
View the dsu README.md here: https://github.com/gangelo/dsu
|
283
291
|
View the dsu CHANGELOG.md: https://github.com/gangelo/dsu/blob/main/CHANGELOG.md
|
284
292
|
|
293
|
+
Dsu now has a import command! Try it out by running `dsu import help`.
|
285
294
|
Dsu now has a export command! Try it out by running `dsu export help`.
|
286
295
|
Dsu now has a browse command! Try it out by running `dsu browse help`.
|
287
|
-
|
288
|
-
Dsu now has a festive "christmas" theme! Try it out by running `dsu theme use christmas`.
|
289
296
|
Dsu now has a "light" theme for light background terminals! Try it out by running `dsu theme use light`.
|
290
|
-
|
291
297
|
Dsu now has a delete command! Try it out by running `dsu delete help`.
|
292
298
|
|
293
299
|
Try a dsu theme by running `dsu theme list` and then `dsu theme use THEME_NAME` where THEME_NAME is the name of the theme you want to try.
|
294
300
|
|
295
301
|
:)
|
296
302
|
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
|||
|
303
|
+
Happy New Year 2024 from dsu!
|
304
|
+
|
305
|
+
* * *
|
306
|
+
* * . * . *
|
307
|
+
* * * * * *
|
308
|
+
* * * * *
|
309
|
+
* * *
|
310
|
+
|
311
|
+
May your year be filled with sparks of joy and innovation!
|
307
312
|
rdoc_options: []
|
308
313
|
require_paths:
|
309
314
|
- lib
|