Skip to content

Commit

Permalink
Add dsu import command
Browse files Browse the repository at this point in the history
  • Loading branch information
gangelo committed Jan 1, 2024
1 parent 10fc787 commit b8ffaa8
Show file tree
Hide file tree
Showing 24 changed files with 573 additions and 34 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ todo.txt
/spec/.tmp

migration_version.yml

.DS_Store
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
## [2.4.0] 2023-nn-nn

Enhancements

- Add `dsu import` command to import DSU entries from a comma-delimited csv file. See `dsu help import` for more information.

## [2.3.2] 2023-12-30

Changes
Expand Down
22 changes: 5 additions & 17 deletions Gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: .
specs:
dsu (2.3.2)
dsu (2.4.0)
activemodel (>= 7.0.8, < 8.0)
activesupport (>= 7.0.8, < 8.0)
colorize (>= 0.8.1, < 1.0)
Expand All @@ -12,32 +12,22 @@ PATH
GEM
remote: https://rubygems.org/
specs:
activemodel (7.1.2)
activesupport (= 7.1.2)
activesupport (7.1.2)
base64
bigdecimal
activemodel (7.0.8)
activesupport (= 7.0.8)
activesupport (7.0.8)
concurrent-ruby (~> 1.0, >= 1.0.2)
connection_pool (>= 2.2.5)
drb
i18n (>= 1.6, < 2)
minitest (>= 5.1)
mutex_m
tzinfo (~> 2.0)
ast (2.4.2)
base64 (0.2.0)
bigdecimal (3.1.5)
byebug (11.1.3)
coderay (1.1.3)
colorize (0.8.1)
concurrent-ruby (1.2.2)
connection_pool (2.4.1)
diff-lcs (1.5.0)
docile (1.4.0)
dotenv (2.8.1)
drb (2.2.0)
ruby2_keywords
factory_bot (6.4.2)
factory_bot (6.4.5)
activesupport (>= 5.0.0)
ffaker (2.23.0)
i18n (1.14.1)
Expand All @@ -47,7 +37,6 @@ GEM
language_server-protocol (3.17.0.3)
method_source (1.0.0)
minitest (5.20.0)
mutex_m (0.2.0)
os (1.1.4)
parallel (1.24.0)
parser (3.2.2.4)
Expand Down Expand Up @@ -106,7 +95,6 @@ GEM
rubocop-capybara (~> 2.17)
rubocop-factory_bot (~> 2.22)
ruby-progressbar (1.13.0)
ruby2_keywords (0.0.5)
simplecov (0.22.0)
docile (~> 1.1)
simplecov-html (~> 0.11)
Expand Down
5 changes: 5 additions & 0 deletions lib/dsu/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
require_relative 'subcommands/delete'
require_relative 'subcommands/edit'
require_relative 'subcommands/export'
require_relative 'subcommands/import'
require_relative 'subcommands/list'
require_relative 'subcommands/theme'

Expand All @@ -21,6 +22,7 @@ class CLI < BaseCLI
map I18n.t('commands.edit.key_mappings') => :edit
map I18n.t('commands.export.key_mappings') => :export
map I18n.t('commands.help.key_mappings') => :help
map I18n.t('commands.import.key_mappings') => :import
map I18n.t('commands.info.key_mappings') => :info
map I18n.t('commands.list.key_mappings') => :list
map I18n.t('commands.theme.key_mappings') => :theme
Expand Down Expand Up @@ -68,6 +70,9 @@ def add(description)
desc I18n.t('commands.theme.desc'), I18n.t('commands.theme.usage')
subcommand :theme, Subcommands::Theme

desc I18n.t('commands.import.desc'), I18n.t('commands.import.usage')
subcommand :import, Subcommands::Import

desc I18n.t('commands.info.desc'), I18n.t('commands.info.usage')
def info
configuration_version = Models::Configuration::VERSION
Expand Down
3 changes: 3 additions & 0 deletions lib/dsu/models/color_theme.rb
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ class ColorTheme < Crud::JsonFile
description: 'Default theme.'
}.merge(DEFAULT_THEME_COLORS).freeze

MIN_DESCRIPTION_LENGTH = 2
MAX_DESCRIPTION_LENGTH = 256

# TODO: Validate other attrs.
validates_with Validators::DescriptionValidator
validates_with Validators::ColorThemeValidator
Expand Down
3 changes: 3 additions & 0 deletions lib/dsu/models/entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ class Entry
include Support::Descriptable
include Support::Presentable

MIN_DESCRIPTION_LENGTH = 2
MAX_DESCRIPTION_LENGTH = 256

validates_with Validators::DescriptionValidator

attr_reader :description, :options
Expand Down
68 changes: 68 additions & 0 deletions lib/dsu/presenters/import/all_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# frozen_string_literal: true

require_relative '../../models/entry_group'
require_relative '../../services/entry_group/importer_service'
require_relative '../../support/ask'
require_relative '../base_presenter_ex'
require_relative 'import_file'
require_relative 'messages'
require_relative 'service_callable'

module Dsu
module Presenters
module Import
class AllPresenter < BasePresenterEx
include ImportFile
include Messages
include ServiceCallable
include Support::Ask

def initialize(import_file_path:, options: {})
super(options: options)

@import_file_path = import_file_path
end

def render(response:)
return display_cancelled_message unless response

importer_service_call.tap do |import_results|
if import_results.values.all?(&:empty?)
display_import_success_message
else
display_import_error_message import_results
end
end
end

def display_import_prompt
yes?(prompt_with_options(prompt: import_prompt, options: import_prompt_options), options: options)
end

private

attr_reader :import_file_path, :options

def import_entry_groups
@import_entry_groups ||= CSV.foreach(import_file_path,
headers: true).with_object({}) do |entry_group_entry, entry_groups_hash|
next unless entry_group_entry['version'].to_i == Dsu::Migration::VERSION

Date.parse(entry_group_entry['entry_group']).to_s.tap do |time|
entry_groups_hash[time] = [] unless entry_groups_hash.key?(time)
entry_groups_hash[time] << entry_group_entry['entry_group_entry']
end
end
end

def import_prompt
I18n.t('subcommands.import.prompts.import_all_confirm', count: import_entry_groups.count)
end

def import_prompt_options
I18n.t('subcommands.import.prompts.options')
end
end
end
end
end
78 changes: 78 additions & 0 deletions lib/dsu/presenters/import/dates_presenter.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
# frozen_string_literal: true

require_relative '../../models/entry_group'
require_relative '../../services/entry_group/importer_service'
require_relative '../../support/ask'
require_relative '../base_presenter_ex'
require_relative 'import_file'
require_relative 'messages'
require_relative 'service_callable'

module Dsu
module Presenters
module Import
class DatesPresenter < BasePresenterEx
include ImportFile
include Messages
include ServiceCallable
include Support::Ask

def initialize(from:, to:, import_file_path:, options: {})
super(options: options)

@from = from.beginning_of_day
@to = to.end_of_day
@import_file_path = import_file_path
end

def render(response:)
return display_cancelled_message unless response

importer_service_call.tap do |import_results|
if import_results.values.all?(&:empty?)
display_import_success_message
else
display_import_error_message import_results
end
end
end

def display_import_prompt
yes?(prompt_with_options(prompt: import_prompt, options: import_prompt_options), options: options)
end

private

attr_reader :from, :to, :import_file_path, :options

def import_entry_groups
@import_entry_groups ||= CSV.foreach(import_file_path,
headers: true).with_object({}) do |entry_group_entry, entry_groups_hash|
next unless entry_group_entry['version'].to_i == Dsu::Migration::VERSION

entry_group_time = middle_of_day_for(entry_group_entry['entry_group'])
next unless entry_group_time.to_date.between?(from.to_date, to.to_date)

entry_group_time.to_date.to_s.tap do |time|
entry_groups_hash[time] = [] unless entry_groups_hash.key?(time)
entry_groups_hash[time] << entry_group_entry['entry_group_entry']
end
end
end

def import_prompt
I18n.t('subcommands.import.prompts.import_dates_confirm',
from: from.to_date, to: to.to_date, count: import_entry_groups.keys.count)
end

def import_prompt_options
I18n.t('subcommands.import.prompts.options')
end

def middle_of_day_for(date_string)
Time.parse(date_string).in_time_zone.middle_of_day
end
end
end
end
end
25 changes: 25 additions & 0 deletions lib/dsu/presenters/import/import_file.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# frozen_string_literal: true

module Dsu
module Presenters
module Import
module ImportFile
def import_file_path_exist?
File.exist? import_file_path
end

def nothing_to_import?
return true unless import_file_path_exist?

import_entry_groups.empty?
end

def import_entry_groups
# Should return a Hash of entry group entries
# Example: { '2023-12-32' => ['Entry description 1', 'Entry description 2', ...] }
raise NotImplementedError
end
end
end
end
end
47 changes: 47 additions & 0 deletions lib/dsu/presenters/import/messages.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# frozen_string_literal: true

module Dsu
module Presenters
module Import
module Messages
def display_import_prompt
raise NotImplementedError
end

def display_import_file_not_exist_message
puts apply_theme(I18n.t('subcommands.import.messages.file_not_exist',
file_path: import_file_path), theme_color: color_theme.info)
end

def display_nothing_to_import_message
puts apply_theme(I18n.t('subcommands.import.messages.nothing_to_import'), theme_color: color_theme.info)
end

private

def display_cancelled_message
puts apply_theme(I18n.t('subcommands.import.messages.cancelled'), theme_color: color_theme.info)
end

def display_import_success_message
puts apply_theme(I18n.t('subcommands.import.messages.import_success'),
theme_color: color_theme.success)
end

def display_import_error_message(import_results)
import_results.each_pair do |entry_group_date, errors|
if errors.empty?
puts apply_theme(I18n.t('subcommands.import.messages.import_success',
date: entry_group_date), theme_color: color_theme.success)
else
errors.each do |error|
puts apply_theme(I18n.t('subcommands.import.messages.import_error',
date: entry_group_date, error: error), theme_color: color_theme.error)
end
end
end
end
end
end
end
end
21 changes: 21 additions & 0 deletions lib/dsu/presenters/import/service_callable.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# frozen_string_literal: true

require_relative '../../services/entry_group/importer_service'

module Dsu
module Presenters
module Import
module ServiceCallable
private

def importer_service_call
@importer_service_call ||= begin
importer_service = Services::EntryGroup::ImporterService.new(import_entry_groups: import_entry_groups,
options: options)
importer_service.call
end
end
end
end
end
end
Loading

0 comments on commit b8ffaa8

Please sign in to comment.