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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '00852090ac540e878e139eb7abf19fbb28c62876c75ff5e4f94f7eeec2ab63df'
4
- data.tar.gz: 911334c622bbfe95ed7a424089f02e641fa1908528201370013ad0ba97f51802
3
+ metadata.gz: '049f61bd321689f9690d42ba2cb72e0901c63e78a1c6454ee8fe0126e8dc0396'
4
+ data.tar.gz: e1687331fa05a352b29800f86b43de93b780a8efa50d74e1be13ce83fa331b23
5
5
  SHA512:
6
- metadata.gz: 9337dd2eed163e9a19981b001eeb90b48576dc229d62c14ff0f5a5fffd08b41a666d2ab2c4395db035cc439e96ad5c069e66c92b60fb69cb92fe994285bcc1ad
7
- data.tar.gz: 26cc17c475cca588a06f2065427e98dd981f830f936729554fd66fb57c57344741a0792e452fab564babd49f8b14c0e25af619b65d32ecb76165b5ce139c3332
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.3.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
- factory_bot (6.4.2)
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.1.4)
66
- kwalify (~> 0.7.0)
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=3)](https://badge.fury.io/gh/gangelo%2Fdsu)
5
- [![Gem Version](https://badge.fury.io/rb/dsu.svg?refresh=3)](https://badge.fury.io/rb/dsu)
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
@@ -14,6 +14,9 @@ module Dsu
14
14
  include Support::Descriptable
15
15
  include Support::Presentable
16
16
 
17
+ MIN_DESCRIPTION_LENGTH = 2
18
+ MAX_DESCRIPTION_LENGTH = 256
19
+
17
20
  validates_with Validators::DescriptionValidator
18
21
 
19
22
  attr_reader :description, :options
@@ -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].sort
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?(2, 256)
29
+ return if description.length.between?(min_description_length(record), max_description_length(record))
29
30
 
30
- if description.length < 2
31
+ if description.length < min_description_length(record)
31
32
  # TODO: I18n.
32
- record.errors.add(:description, "is too short: \"#{record.short_description}\" (minimum is 2 characters).")
33
- elsif description.length > 256
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}\" (maximum is 256 characters).")
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
@@ -2,5 +2,5 @@
2
2
 
3
3
  module Dsu
4
4
  VERSION_REGEX = /\A\d+\.\d+\.\d+(\.(alpha|rc)\.\d+)?\z/
5
- VERSION = '2.3.2'
5
+ VERSION = '2.4.0'
6
6
  end
@@ -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
@@ -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.3.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: 2023-12-30 00:00:00.000000000 Z
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
- Merry CHRISTmas, New Years and Happy holidays from dsu!
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