Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ AionCLI is a collection of scripts mainly for handling csv files.

## Installation

Make sure you have rbenv installed. This can be done through homebrew.
This project has been modernized for current Ruby and is intended to be Ruby 4-ready.
At the moment it is verified in this repository with Ruby 3.2.x and updated gems that are compatible with newer Ruby/OpenSSL behavior.

Make sure you have `rbenv` installed. This can be done through Homebrew.

Follow the instructions on how to setup homebrew:
https://brew.sh/index_da
Expand All @@ -16,26 +19,26 @@ After this, install rbenv using the following command:
Follow the instructions on how to setup rbenv:
https://github.com/rbenv/rbenv#homebrew-on-macos

Install a ruby version to use:
Install a recent Ruby version to use:

$ rbenv install 2.5.3
$ rbenv install 3.2.2

Clone the repository to some folder, and navigate to it:

$ git clone https://github.com/aion-dk/aion_cli.git
$ cd aion_cli

Specify an installed ruby version to use:
Specify an installed Ruby version to use:

$ rbenv local 2.5.3
$ rbenv local 3.2.2

Install bundler, pull gems and install the aion script:
Install Bundler, install gems, and install the `aion` script:

$ gem install bundler
$ bundle install
$ bundle exec aion install

You should be ready to go :-)
You should now be ready to go.

## Usage

Expand Down
23 changes: 11 additions & 12 deletions aion_cli.gemspec
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
# coding: utf-8
lib = File.expand_path('../lib', __FILE__)
lib = File.expand_path('lib', __dir__)
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
require 'aion_cli/version'

Expand All @@ -9,11 +8,12 @@ Gem::Specification.new do |spec|
spec.authors = ['Michael Andersen']
spec.email = ['michael.andersen.85@gmail.com']

spec.summary = %q{A small collection of scripts used by Aion Aps}
spec.description = %q{A small collection of scripts used by Aion Aps. Mainly for handling csv files.}
spec.summary = 'A small collection of scripts used by Aion Aps'
spec.description = 'A small collection of scripts used by Aion Aps. Mainly for handling csv files.'
spec.homepage = 'https://github.com/aion-dk/aion_cli'

spec.license = 'Aion'
spec.required_ruby_version = '>= 3.2'

# Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
# to allow pushing to a single host or delete this section to allow pushing to any host.
Expand All @@ -28,16 +28,15 @@ Gem::Specification.new do |spec|

spec.require_paths = ['lib']

spec.add_dependency 'activesupport', '~> 5.2.1'
spec.add_dependency 'activesupport', '~> 7.2'
spec.add_dependency 'aion-s3', '~> 1.0'
spec.add_dependency 'charlock_holmes', '~> 0.7.6'
spec.add_dependency 'faker', '~> 1.9.1'
spec.add_dependency 'faker', '~> 3.6'
spec.add_dependency 'http', '~> 5.0.4'
spec.add_dependency 'roo', '~> 2.7.1'
spec.add_dependency 'thor', '~> 0.20.3'
spec.add_dependency 'write_xlsx', '~> 0.85.5'
spec.add_dependency 'roo', '~> 3.0'
spec.add_dependency 'thor', '~> 1.4'
spec.add_dependency 'write_xlsx', '~> 1.15'

#spec.add_development_dependency 'bundler', '~> 1.17.1'
#spec.add_development_dependency 'rake', '~> 12.3.1'
#spec.add_development_dependency 'minitest', '~> 5.11.3'
spec.add_development_dependency 'minitest', '~> 5.27'
spec.add_development_dependency 'rake', '~> 13.3'
end
10 changes: 4 additions & 6 deletions lib/aion_cli/commands/add.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
require 'securerandom'
require 'date'
require 'set'
require 'aion_cli/helpers/application_helper'
require 'aion_cli/helpers/unique_string_generator'
Expand Down Expand Up @@ -52,6 +53,8 @@ def unique_numbers(path)
min = ask_natural_number('Pick the minimum')
max = ask_natural_number('Pick the maximum')

raise Thor::Error, 'Maximum must be greater than minimum' unless max > min

generator = UniqueStringGenerator.new do
(min + SecureRandom.random_number(max - min)).to_s
end
Expand All @@ -67,12 +70,7 @@ def unique_numbers(path)
desc 'age CSV_FILE', 'Add age calculated from CPR birthdate to selected date. DATE defaults to "Today"'
def age(path)
date_input = ask_date_string('Input date to calculate age from. (Date defaults "Today" if blank)', true)
date = nil
if date_input == ""
date = Date.today
else
date = Date.parse(date_input)
end
date = date_input.empty? ? Date.today : Date.parse(date_input)
headers, *rows = read_spreadsheet(path)
index_cpr = ask_header_index(headers, 'Specify CPR column.')

Expand Down
2 changes: 1 addition & 1 deletion lib/aion_cli/commands/avx.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ def credentials_aggregate(*paths)


paths_rest.each do |path|
headers, *rows = read_csv(path)
_headers, *rows = read_csv(path)

rows.each do |row|
id = row[index]
Expand Down
5 changes: 3 additions & 2 deletions lib/aion_cli/commands/data.rb
Original file line number Diff line number Diff line change
Expand Up @@ -77,9 +77,10 @@ def stats_3f(path, json_path)
index_department = ask_header_index(headers, 'Specify the column with the DEPARTMENT NUMBER')
index_profession = ask_header_index(headers, 'Specify the column with the PROFESSION NUMBER')
index_overall_groups = ask_header_index(headers, 'Specify the column with the GROUP MAPPING')
voting_round_reference = ask("Specify the VOTING ROUNDS REFERENCE to use for voter totals from the eligible voters file (ex. 'voting_round_1'): ")

file = File.read(json_path)
total_json = JSON.parse(file).first.last # Value of first voting round
json_file = JSON.parse(File.read(json_path))
total_json = json_file.fetch(voting_round_reference, json_file.first.last)

index_option_label = get_column_index(headers, 'option_label')

Expand Down
7 changes: 4 additions & 3 deletions lib/aion_cli/commands/excel.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,20 +6,21 @@
module AionCLI
module CLI
class Excel < Thor
include AionCLI::ApplicationHelper

desc 'to_csv', 'Convert Excel file to CSV'
long_desc <<-LONG_DESC
Convert Excel file, default_sheet (first sheet) to csv\n
With rejection of top and left padding cells.
LONG_DESC

option :sheet, aliasses: '-s', banner: 'Sheet number'
option :sheet, aliases: '-s', banner: 'Sheet number'
def to_csv(path)
absolute_path = File.absolute_path(path)
xlsx = Roo::Spreadsheet.open(absolute_path)

unless options[:s].nil?
sheet_number = options[:s].to_i
unless options[:sheet].nil?
sheet_number = options[:sheet].to_i
xlsx.default_sheet = xlsx.sheets[sheet_number]
end

Expand Down
4 changes: 2 additions & 2 deletions lib/aion_cli/commands/table.rb
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ def join(path_a, path_b)
next
end

raise Thor::Error, "Duplicate keys in join column for ADDITIONAL_FILE (key: #{key})" if join_b_hash.has_key?(key)
raise Thor::Error, "Duplicate keys in join column for ADDITIONAL_FILE (key: #{key})" if join_b_hash.key?(key)
join_b_hash[key] = values
end

Expand Down Expand Up @@ -182,7 +182,7 @@ def vlookup(path_a, path_b)
raise Thor::Error, 'Empty value in join column for ADDITIONAL_FILE' unless allow_empty
end

raise Thor::Error, "Duplicate key combination in join columns for ADDITIONAL_FILE (key: #{key})" if join_b_hash.has_key?(key)
raise Thor::Error, "Duplicate key combination in join columns for ADDITIONAL_FILE (key: #{key})" if join_b_hash.key?(key)

join_b_hash[key] = row[lookup_index_b]
end
Expand Down
9 changes: 5 additions & 4 deletions lib/aion_cli/commands/text.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
require 'aion_cli/helpers/application_helper'
require 'aion_cli/helpers/cpr_parser'
require 'csv'

module AionCLI
module CLI
Expand All @@ -15,7 +16,7 @@ def to_csv(path, column_widths = nil)



if column_widths and column_widths =~ /^\d+(,\d+)*$/
if column_widths && column_widths.match?(/^\d+(,\d+)*$/)
_column_widths = column_widths.split(',').map(&:to_i)

absolute_path = File.absolute_path(path)
Expand Down Expand Up @@ -65,7 +66,7 @@ def filter_multi_lines(path)
begin
start_regex = Regexp.new(ask("Specify the regex to start line extract"))
say "Interpreted as: #{start_regex.inspect}", :green
rescue
rescue RegexpError
start_regex = ""
say "Could not parse regex", :yellow
end
Expand All @@ -74,7 +75,7 @@ def filter_multi_lines(path)
begin
stop_regex = Regexp.new(ask("Specify the regex to end line extract"))
say "Interpreted as: #{stop_regex.inspect}", :green
rescue
rescue RegexpError
stop_regex = ""
say "Could not parse regex", :yellow
end
Expand Down Expand Up @@ -140,7 +141,7 @@ def cpr_to_csv(path)
ask_output do |csv|
csv << ['cpr'] + selected_attributes.map(&:to_s)
records.each do |cpr_no, attributes|
csv << [cpr_no] + selected_attributes.map{ |attr_name| attributes[attr_name].try(:strip) }
csv << [cpr_no] + selected_attributes.map { |attr_name| attributes[attr_name]&.strip }
end
end
end
Expand Down
32 changes: 15 additions & 17 deletions lib/aion_cli/helpers/application_helper.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
require 'csv'
require 'charlock_holmes'
require 'fileutils'
require 'roo'

module AionCLI
module ApplicationHelper
Expand All @@ -8,14 +10,14 @@ module ApplicationHelper

def read_spreadsheet(path)
absolute_path = File.expand_path(path)
case File.extname(path)
case File.extname(absolute_path)
when '.csv'
table = read_csv(path)
when '.xlsx'
size = File.size(absolute_path)
size_mb = (size / 1024.0 / 1024.0).round
if size_mb > 5
say("The XSLT file #{absolute_path} is ~ #{size_mb}MB.")
say("The XLSX file #{absolute_path} is ~ #{size_mb}MB.")
say('To improve the speed, consider converting the file to a csv.')
unless yes?('Would you like to continue?', :yellow)
say('Done', :green)
Expand All @@ -41,14 +43,15 @@ def read_csv(path, options = {})
# Remove BOM from contents
content.sub!("\xEF\xBB\xBF", '')
options[:col_sep] ||= detect_col_sep(content)
CSV.parse(content, options)
CSV.parse(content, **options)
end

# CSV Helpers
def read_file(path)
content = File.read(path)
detection = CharlockHolmes::EncodingDetector.detect(content)
CharlockHolmes::Converter.convert(content, detection[:encoding], 'UTF-8')
source_encoding = detection&.dig(:encoding) || 'UTF-8'
CharlockHolmes::Converter.convert(content, source_encoding, 'UTF-8')
end

def ask_header_index(headers, message)
Expand Down Expand Up @@ -150,7 +153,7 @@ def ask_date_string(message, allow_blank = false)

date = ask("Date (YYYY-MM-DD):")

unless allow_blank || date.match(/^\d{4}\-\d{2}\-\d{2}$/)
unless allow_blank || date.match?(/^\d{4}-\d{2}-\d{2}$/)
say("'#{date}' is invalid. Date must have this format -> YYYY-MM-DD", :red)
next
end
Expand All @@ -177,7 +180,7 @@ def ask_output_path(extname = '.csv', default_name = 'out', message = 'Pick a na

absolute_output_path = File.expand_path(output_path)

if File.exists?(absolute_output_path)
if File.exist?(absolute_output_path)
if yes?('The file already exists. Would you like to overwrite?', :yellow)
File.unlink(absolute_output_path)
else
Expand All @@ -196,7 +199,7 @@ def ask_output_dir(default_name = 'out', mkdir: false)

absolute_output_dir = File.expand_path(output_dir)

if File.exists?(absolute_output_dir)
if File.exist?(absolute_output_dir)
say("Already exists: #{absolute_output_dir}", :red)
next
end
Expand Down Expand Up @@ -225,25 +228,20 @@ def ask_unpack_path(source_path)
end

def get_column_index(headers, column_name)
headers.each_with_index do |header, index|
if header == column_name
return index
end
end

headers.index(column_name)
end

private

def detect_col_sep(contents)
test_contents = contents.lines.first.chomp
test_results = [',',';',"\t"].map { |col_sep| [count_col_sep(test_contents, col_sep), col_sep ] }
test_contents = contents.lines.first.to_s.chomp
test_results = [',', ';', "\t"].map { |col_sep| [count_col_sep(test_contents, col_sep), col_sep] }
test_results.sort.last.last
end

def count_col_sep(test_contents, col_sep)
CSV.parse(test_contents, col_sep: col_sep).first.size
rescue
CSV.parse(test_contents, col_sep: col_sep).first&.size.to_i
rescue StandardError
0
end

Expand Down
2 changes: 2 additions & 0 deletions lib/aion_cli/helpers/cpr_parser.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'set'

class CPRParser
SUPPORTED_RECORD_TYPES = [
'001', # Personoplysninger
Expand Down
7 changes: 4 additions & 3 deletions lib/aion_cli/helpers/dawa_client.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
require 'singleton'
require 'http'
require 'json'
require 'stringio'

module AionCLI
class DAWAClient
Expand Down Expand Up @@ -76,8 +77,8 @@ def municipalities
# @return a list of names
# @raise StandardError if a name was not found
def municipality_names(codes)
pairs = Hash[ municipalities.map { |o| [o['kode'].to_i, o['navn']] } ]
codes.map { |code| pairs[code] or raise 'Name not found' }
pairs = municipalities.to_h { |municipality| [municipality['kode'].to_i, municipality['navn']] }
codes.map { |code| pairs.fetch(code) { raise 'Name not found' } }
end


Expand All @@ -88,7 +89,7 @@ def request(url, params: {}, max_retries: 3)
return JSON.parse(dawa_response)
rescue JSON::ParserError => e
$stderr << "Error occured when attempting to parse response from DAWA: #{e.message}\nRetrying...\n"
rescue => e
rescue StandardError => e
$stderr << "Error occured: #{e.message}\nRetrying...\n"
end

Expand Down
9 changes: 7 additions & 2 deletions lib/aion_cli/helpers/preparation_helper.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@

require 'date'
require 'uri'

module AionCLI
module PreparationHelper

Expand Down Expand Up @@ -39,14 +42,16 @@ def clean_phone(phone)
end

def clean_dob(date,format)
begin Date.strptime(date, format).strftime('%d%m%y') rescue nil end
Date.strptime(date, format).strftime('%d%m%y')
rescue ArgumentError
nil
end

def slice_file(rows, indexes, prefix, suffix)
file_name = "#{prefix}_#{suffix}.csv"
output_path = File.expand_path(file_name)

if File.exists?(output_path)
if File.exist?(output_path)
if yes?("The file #{file_name} already exists. Would you like to overwrite?", :yellow)
File.unlink(output_path)
else
Expand Down
2 changes: 2 additions & 0 deletions lib/aion_cli/helpers/unique_string_generator.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
require 'set'

class UniqueStringGenerator

def initialize(used_values = [], &block)
Expand Down
Loading