csv_plus_plus 0.0.3 → 0.0.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +9 -4
- data/bin/csv++ +1 -78
- data/bin/csvpp +6 -0
- data/lib/csv_plus_plus/cli.rb +84 -0
- data/lib/csv_plus_plus/cli_flag.rb +83 -0
- data/lib/csv_plus_plus/color.rb +45 -11
- data/lib/csv_plus_plus/error.rb +7 -0
- data/lib/csv_plus_plus/google_api_client.rb +20 -0
- data/lib/csv_plus_plus/graph.rb +0 -5
- data/lib/csv_plus_plus/language/compiler.rb +0 -1
- data/lib/csv_plus_plus/language/entities/boolean.rb +3 -3
- data/lib/csv_plus_plus/language/entities/entity.rb +10 -6
- data/lib/csv_plus_plus/language/scope.rb +0 -2
- data/lib/csv_plus_plus/language/syntax_error.rb +1 -1
- data/lib/csv_plus_plus/modifier.rb +49 -22
- data/lib/csv_plus_plus/modifier.tab.rb +367 -381
- data/lib/csv_plus_plus/options.rb +1 -1
- data/lib/csv_plus_plus/version.rb +1 -1
- data/lib/csv_plus_plus/writer/base_writer.rb +0 -1
- data/lib/csv_plus_plus/writer/csv.rb +4 -1
- data/lib/csv_plus_plus/writer/excel.rb +18 -2
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +56 -0
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +12 -31
- data/lib/csv_plus_plus/writer/google_sheet_modifier.rb +56 -0
- data/lib/csv_plus_plus/writer/google_sheets.rb +25 -38
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +112 -0
- data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +52 -0
- data/lib/csv_plus_plus/writer.rb +2 -3
- data/lib/csv_plus_plus.rb +6 -1
- metadata +45 -15
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './cli_flag'
|
3
4
|
require_relative './google_options'
|
4
5
|
|
5
6
|
module CSVPlusPlus
|
@@ -14,7 +15,6 @@ module CSVPlusPlus
|
|
14
15
|
@create_if_not_exists = false
|
15
16
|
@key_values = {}
|
16
17
|
@verbose = false
|
17
|
-
# TODO: switch to true? probably a safer choice
|
18
18
|
@backup = false
|
19
19
|
end
|
20
20
|
|
@@ -1,10 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './file_backer_upper'
|
4
|
+
|
3
5
|
module CSVPlusPlus
|
4
6
|
module Writer
|
5
|
-
##
|
6
7
|
# A class that can output a +Template+ to CSV
|
7
8
|
class CSV < ::CSVPlusPlus::Writer::BaseWriter
|
9
|
+
include ::CSVPlusPlus::Writer::FileBackerUpper
|
10
|
+
|
8
11
|
# write a +template+ to CSV
|
9
12
|
def write(template)
|
10
13
|
# TODO: also read it and merge the results
|
@@ -1,12 +1,28 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './file_backer_upper'
|
4
|
+
require_relative './rubyxl_builder'
|
5
|
+
|
3
6
|
module CSVPlusPlus
|
4
7
|
module Writer
|
5
8
|
# A class that can output a +Template+ to an Excel file
|
6
9
|
class Excel < ::CSVPlusPlus::Writer::BaseWriter
|
7
|
-
|
10
|
+
include ::CSVPlusPlus::Writer::FileBackerUpper
|
11
|
+
|
12
|
+
# write the +template+ to an Excel file
|
8
13
|
def write(template)
|
9
|
-
|
14
|
+
::CSVPlusPlus::Writer::RubyXLBuilder.new(
|
15
|
+
output_filename: @options.output_filename,
|
16
|
+
rows: template.rows,
|
17
|
+
sheet_name: @options.sheet_name
|
18
|
+
).write
|
19
|
+
end
|
20
|
+
|
21
|
+
protected
|
22
|
+
|
23
|
+
def load_requires
|
24
|
+
require('rubyXL')
|
25
|
+
require('rubyXL/convenience_methods')
|
10
26
|
end
|
11
27
|
end
|
12
28
|
end
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module CSVPlusPlus
|
7
|
+
module Writer
|
8
|
+
# A mixin that can
|
9
|
+
module FileBackerUpper
|
10
|
+
# we don't want to include a bunch of second/millisecond stuff in the filename unless we
|
11
|
+
# really need to. so try a less specifically formatted filename then get more specific
|
12
|
+
DESIRED_BACKUP_FORMATS = [%(%Y_%m_%d-%I_%M%p), %(%Y_%m_%d-%I_%M_%S%p), %(%Y_%m_%d-%I_%M_%S_%L%p)].freeze
|
13
|
+
private_constant :DESIRED_BACKUP_FORMATS
|
14
|
+
|
15
|
+
# Assuming the underlying spreadsheet is file-based, create a backup of it
|
16
|
+
# rubocop:disable Metrics/MethodLength
|
17
|
+
def write_backup
|
18
|
+
return unless ::File.exist?(@options.output_filename)
|
19
|
+
|
20
|
+
attempted = []
|
21
|
+
backed_up_to = nil
|
22
|
+
|
23
|
+
# rubocop:disable Lint/ConstantResolution
|
24
|
+
DESIRED_BACKUP_FORMATS.find do |file_format|
|
25
|
+
# rubocop:enable Lint/ConstantResolution
|
26
|
+
filename = format_backup_filename(file_format)
|
27
|
+
attempted << filename
|
28
|
+
backed_up_to = backup(filename)
|
29
|
+
|
30
|
+
break if backed_up_to
|
31
|
+
end
|
32
|
+
|
33
|
+
unless backed_up_to
|
34
|
+
raise(::CSVPlusPlus::Error, "Unable to write backup file despite trying these: #{attempted.join(', ')}")
|
35
|
+
end
|
36
|
+
|
37
|
+
warn("Backed up #{@options.output_filename} to #{backed_up_to}") if @options.verbose
|
38
|
+
end
|
39
|
+
# rubocop:enable Metrics/MethodLength
|
40
|
+
|
41
|
+
private
|
42
|
+
|
43
|
+
def backup(filename)
|
44
|
+
return if ::File.exist?(filename)
|
45
|
+
|
46
|
+
::FileUtils.cp(@options.output_filename, filename)
|
47
|
+
filename
|
48
|
+
end
|
49
|
+
|
50
|
+
def format_backup_filename(file_format)
|
51
|
+
pn = ::Pathname.new(@options.output_filename)
|
52
|
+
pn.sub_ext("-#{::Time.now.strftime(file_format)}" + pn.extname)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -1,8 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './google_sheet_modifier'
|
4
|
+
|
3
5
|
module CSVPlusPlus
|
4
6
|
module Writer
|
5
|
-
##
|
6
7
|
# Given +rows+ from a +Template+, build requests compatible with Google Sheets Ruby API
|
7
8
|
# rubocop:disable Metrics/ClassLength
|
8
9
|
class GoogleSheetBuilder
|
@@ -26,9 +27,7 @@ module CSVPlusPlus
|
|
26
27
|
::Google::Apis::SheetsV4
|
27
28
|
end
|
28
29
|
|
29
|
-
def sheets_color(color)
|
30
|
-
sheets_ns::Color.new(red: color.red, green: color.green, blue: color.blue)
|
31
|
-
end
|
30
|
+
def sheets_color(color); end
|
32
31
|
|
33
32
|
def set_extended_value_type!(extended_value, value)
|
34
33
|
v = value || ''
|
@@ -43,38 +42,20 @@ module CSVPlusPlus
|
|
43
42
|
end
|
44
43
|
end
|
45
44
|
|
46
|
-
|
47
|
-
sheets_ns::TextFormat.new(
|
48
|
-
bold: mod.formatted?('bold') || nil,
|
49
|
-
italic: mod.formatted?('italic') || nil,
|
50
|
-
strikethrough: mod.formatted?('strikethrough') || nil,
|
51
|
-
underline: mod.formatted?('underline') || nil,
|
52
|
-
|
53
|
-
font_family: mod.fontfamily,
|
54
|
-
font_size: mod.fontsize,
|
55
|
-
|
56
|
-
foreground_color: mod.fontcolor ? sheets_color(mod.fontcolor) : nil
|
57
|
-
)
|
58
|
-
end
|
59
|
-
|
60
|
-
# rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
45
|
+
# rubocop:disable Metrics/AbcSize
|
61
46
|
def build_cell_format(mod)
|
62
47
|
sheets_ns::CellFormat.new.tap do |cf|
|
63
|
-
cf.text_format =
|
48
|
+
cf.text_format = mod.text_format
|
64
49
|
|
65
|
-
|
66
|
-
cf.
|
67
|
-
cf.horizontal_alignment = 'RIGHT' if mod.aligned?('right')
|
68
|
-
cf.horizontal_alignment = 'CENTER' if mod.aligned?('center')
|
69
|
-
cf.vertical_alignment = 'TOP' if mod.aligned?('top')
|
70
|
-
cf.vertical_alignment = 'BOTTOM' if mod.aligned?('bottom')
|
50
|
+
cf.horizontal_alignment = mod.halign if mod.halign
|
51
|
+
cf.vertical_alignment = mod.valign if mod.valign
|
71
52
|
|
72
|
-
cf.background_color =
|
53
|
+
cf.background_color = mod.color if mod.color
|
73
54
|
|
74
|
-
cf.number_format =
|
55
|
+
cf.number_format = mod.numberformat if mod.numberformat
|
75
56
|
end
|
76
57
|
end
|
77
|
-
# rubocop:enable Metrics/AbcSize
|
58
|
+
# rubocop:enable Metrics/AbcSize
|
78
59
|
|
79
60
|
def grid_range_for_cell(cell)
|
80
61
|
sheets_ns::GridRange.new(
|
@@ -106,10 +87,10 @@ module CSVPlusPlus
|
|
106
87
|
end
|
107
88
|
|
108
89
|
def build_cell_data(cell)
|
109
|
-
mod = cell.modifier
|
90
|
+
mod = ::CSVPlusPlus::Writer::GoogleSheetModifier.new(cell.modifier)
|
110
91
|
|
111
92
|
sheets_ns::CellData.new.tap do |cd|
|
112
|
-
cd.user_entered_format = build_cell_format(
|
93
|
+
cd.user_entered_format = build_cell_format(mod)
|
113
94
|
cd.note = mod.note if mod.note
|
114
95
|
|
115
96
|
# XXX apply data validation
|
@@ -0,0 +1,56 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSVPlusPlus
|
4
|
+
module Writer
|
5
|
+
# Decorate a Modifier so it can be written to the Google Sheets API
|
6
|
+
class GoogleSheetModifier < ::SimpleDelegator
|
7
|
+
# Format the halign for Google Sheets
|
8
|
+
def halign
|
9
|
+
super&.upcase
|
10
|
+
end
|
11
|
+
|
12
|
+
# Format the valign for Google Sheets
|
13
|
+
def valign
|
14
|
+
super&.upcase
|
15
|
+
end
|
16
|
+
|
17
|
+
# Format the color for Google Sheets
|
18
|
+
def color
|
19
|
+
google_sheets_color(super) if super
|
20
|
+
end
|
21
|
+
|
22
|
+
# Format the fontcolor for Google Sheets
|
23
|
+
def fontcolor
|
24
|
+
google_sheets_color(super) if super
|
25
|
+
end
|
26
|
+
|
27
|
+
# Format the numberformat for Google Sheets
|
28
|
+
def numberformat
|
29
|
+
::Google::Apis::SheetsV4::NumberFormat.new(type: super) if super
|
30
|
+
end
|
31
|
+
|
32
|
+
# Builds a SheetsV4::TextFormat with the underlying Modifier
|
33
|
+
def text_format
|
34
|
+
::Google::Apis::SheetsV4::TextFormat.new(
|
35
|
+
bold: formatted?('bold') || nil,
|
36
|
+
italic: formatted?('italic') || nil,
|
37
|
+
strikethrough: formatted?('strikethrough') || nil,
|
38
|
+
underline: formatted?('underline') || nil,
|
39
|
+
font_family: fontfamily,
|
40
|
+
font_size: fontsize,
|
41
|
+
foreground_color: fontcolor
|
42
|
+
)
|
43
|
+
end
|
44
|
+
|
45
|
+
private
|
46
|
+
|
47
|
+
def google_sheets_color(color)
|
48
|
+
::Google::Apis::SheetsV4::Color.new(
|
49
|
+
red: color.red_percent,
|
50
|
+
green: color.green_percent,
|
51
|
+
blue: color.blue_percent
|
52
|
+
)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -1,16 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative '../google_api_client'
|
3
4
|
require_relative 'base_writer'
|
4
5
|
require_relative 'google_sheet_builder'
|
5
6
|
|
6
|
-
AUTH_SCOPES = ['https://www.googleapis.com/auth/spreadsheets'].freeze
|
7
|
-
FULL_RANGE = 'A1:Z1000'
|
8
|
-
|
9
7
|
module CSVPlusPlus
|
10
8
|
module Writer
|
11
|
-
# A class that can
|
9
|
+
# A class that can write a +Template+ to Google Sheets (via their API)
|
12
10
|
class GoogleSheets < ::CSVPlusPlus::Writer::BaseWriter
|
13
|
-
#
|
11
|
+
# TODO: it would be nice to raise this but we shouldn't expand out more than necessary for our data
|
14
12
|
SPREADSHEET_INFINITY = 1000
|
15
13
|
public_constant :SPREADSHEET_INFINITY
|
16
14
|
|
@@ -24,21 +22,26 @@ module CSVPlusPlus
|
|
24
22
|
|
25
23
|
# write a +template+ to Google Sheets
|
26
24
|
def write(template)
|
27
|
-
|
25
|
+
@sheets_client = ::CSVPlusPlus::GoogleApiClient.sheets_client
|
28
26
|
|
29
|
-
|
30
|
-
|
27
|
+
fetch_spreadsheet!
|
28
|
+
fetch_spreadsheet_values!
|
31
29
|
|
32
30
|
create_sheet! if @options.create_if_not_exists
|
33
31
|
|
34
32
|
update_cells!(template)
|
35
|
-
|
36
|
-
|
33
|
+
end
|
34
|
+
|
35
|
+
# write a backup of the google sheet
|
36
|
+
def write_backup
|
37
|
+
drive_client = ::CSVPlusPlus::GoogleApiClient.drive_client
|
38
|
+
drive_client.copy_file(@sheet_id)
|
37
39
|
end
|
38
40
|
|
39
41
|
protected
|
40
42
|
|
41
43
|
def load_requires
|
44
|
+
require('google/apis/drive_v3')
|
42
45
|
require('google/apis/sheets_v4')
|
43
46
|
require('googleauth')
|
44
47
|
end
|
@@ -50,15 +53,10 @@ module CSVPlusPlus
|
|
50
53
|
end
|
51
54
|
|
52
55
|
def full_range
|
53
|
-
format_range(
|
56
|
+
format_range('A1:Z1000')
|
54
57
|
end
|
55
58
|
|
56
|
-
def
|
57
|
-
@gs ||= sheets_ns::SheetsService.new
|
58
|
-
@gs.authorization = ::Google::Auth.get_application_default(::AUTH_SCOPES)
|
59
|
-
end
|
60
|
-
|
61
|
-
def save_spreadsheet_values!
|
59
|
+
def fetch_spreadsheet_values!
|
62
60
|
formatted_values = get_all_spreadsheet_values('FORMATTED_VALUE')
|
63
61
|
formula_values = get_all_spreadsheet_values('FORMULA')
|
64
62
|
|
@@ -85,7 +83,7 @@ module CSVPlusPlus
|
|
85
83
|
end
|
86
84
|
|
87
85
|
def get_all_spreadsheet_values(render_option)
|
88
|
-
@
|
86
|
+
@sheets_client.get_spreadsheet_values(@sheet_id, full_range, value_render_option: render_option)
|
89
87
|
end
|
90
88
|
|
91
89
|
def sheet
|
@@ -94,8 +92,8 @@ module CSVPlusPlus
|
|
94
92
|
@spreadsheet.sheets.find { |s| s.properties.title.strip == @sheet_name.strip }
|
95
93
|
end
|
96
94
|
|
97
|
-
def
|
98
|
-
@spreadsheet = @
|
95
|
+
def fetch_spreadsheet!
|
96
|
+
@spreadsheet = @sheets_client.get_spreadsheet(@sheet_id)
|
99
97
|
|
100
98
|
return unless @sheet_name.nil?
|
101
99
|
|
@@ -105,34 +103,23 @@ module CSVPlusPlus
|
|
105
103
|
def create_sheet!
|
106
104
|
return if sheet
|
107
105
|
|
108
|
-
@
|
109
|
-
|
106
|
+
@sheets_client.create_spreadsheet(@sheet_name)
|
107
|
+
fetch_spreadsheet!
|
110
108
|
@sheet_name = @spreadsheet.sheets.last.properties.title
|
111
109
|
end
|
112
110
|
|
113
111
|
def update_cells!(template)
|
114
|
-
builder
|
112
|
+
@sheets_client.batch_update_spreadsheet(@sheet_id, builder(template).batch_update_spreadsheet_request)
|
113
|
+
end
|
114
|
+
|
115
|
+
def builder(template)
|
116
|
+
::CSVPlusPlus::Writer::GoogleSheetBuilder.new(
|
115
117
|
rows: template.rows,
|
116
118
|
sheet_id: sheet&.properties&.sheet_id,
|
117
119
|
column_index: @options.offset[1],
|
118
120
|
row_index: @options.offset[0],
|
119
121
|
current_sheet_values: @current_sheet_values
|
120
122
|
)
|
121
|
-
@gs.batch_update_spreadsheet(@sheet_id, builder.batch_update_spreadsheet_request)
|
122
|
-
rescue ::Google::Apis::ClientError => e
|
123
|
-
handle_google_error(e)
|
124
|
-
end
|
125
|
-
|
126
|
-
def sheets_ns
|
127
|
-
::Google::Apis::SheetsV4
|
128
|
-
end
|
129
|
-
|
130
|
-
def handle_google_error(error)
|
131
|
-
if @options.verbose
|
132
|
-
warn("#{error.status_code} Error making Google Sheets API request [#{error.message}]: #{error.body}")
|
133
|
-
else
|
134
|
-
warn("Error making Google Sheets API request: #{error.message}")
|
135
|
-
end
|
136
123
|
end
|
137
124
|
end
|
138
125
|
end
|
@@ -0,0 +1,112 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './rubyxl_modifier'
|
4
|
+
|
5
|
+
module CSVPlusPlus
|
6
|
+
module Writer
|
7
|
+
# Build a RubyXL workbook formatted according to the given +rows+
|
8
|
+
class RubyXLBuilder
|
9
|
+
attr_reader :output_filename, :rows
|
10
|
+
|
11
|
+
# initialize
|
12
|
+
def initialize(output_filename:, rows:, sheet_name:)
|
13
|
+
@rows = rows
|
14
|
+
@output_filename = output_filename
|
15
|
+
@workbook = open_workbook(sheet_name)
|
16
|
+
@worksheet = @workbook[sheet_name]
|
17
|
+
end
|
18
|
+
|
19
|
+
# write the given @rows in +sheet_name+ to +@output_filename+
|
20
|
+
def write
|
21
|
+
build_workbook!
|
22
|
+
@workbook.write(@output_filename)
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def build_workbook!
|
28
|
+
@rows.each_with_index do |row, x|
|
29
|
+
row.cells.each_with_index do |cell, y|
|
30
|
+
modifier = ::CSVPlusPlus::Writer::RubyXLModifier.new(cell.modifier)
|
31
|
+
|
32
|
+
@worksheet.add_cell(x, y, cell.to_csv)
|
33
|
+
format_cell!(x, y, modifier)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def do_alignments!(cell, modifier)
|
39
|
+
cell.change_horizontal_alignment(modifier.halign) if modifier.halign
|
40
|
+
cell.change_vertical_alignment(modifier.valign) if modifier.valign
|
41
|
+
end
|
42
|
+
|
43
|
+
# rubocop:disable Metrics/MethodLength
|
44
|
+
def do_borders!(cell, modifier)
|
45
|
+
return unless modifier.any_border?
|
46
|
+
|
47
|
+
color = modifier.bordercolor
|
48
|
+
weight = modifier.border_weight
|
49
|
+
|
50
|
+
if modifier.border_all?
|
51
|
+
%i[top bottom left right].each do |direction|
|
52
|
+
# TODO: I can't support a weight and a color?
|
53
|
+
cell.change_border(direction, color || weight)
|
54
|
+
end
|
55
|
+
else
|
56
|
+
modifier.borders.each do |direction|
|
57
|
+
cell.change_border(direction, color || weight)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
# rubocop:enable Metrics/MethodLength
|
62
|
+
|
63
|
+
def do_fill!(cell, modifier)
|
64
|
+
cell.change_fill(modifier.color.to_hex) if modifier.color
|
65
|
+
end
|
66
|
+
|
67
|
+
def do_formats!(cell, modifier)
|
68
|
+
cell.change_font_bold(true) if modifier.formatted?('bold')
|
69
|
+
cell.change_font_italics(true) if modifier.formatted?('italic')
|
70
|
+
cell.change_font_underline(true) if modifier.formatted?('underline')
|
71
|
+
cell.change_font_strikethrough(true) if modifier.formatted?('strikethrough')
|
72
|
+
end
|
73
|
+
|
74
|
+
def do_fonts!(cell, modifier)
|
75
|
+
cell.change_font_color(modifier.fontcolor.to_hex) if modifier.fontcolor
|
76
|
+
cell.change_font_name(modifier.fontfamily) if modifier.fontfamily
|
77
|
+
cell.change_font_size(modifier.fontsize) if modifier.fontsize
|
78
|
+
end
|
79
|
+
|
80
|
+
def do_number_formats!(cell, modifier)
|
81
|
+
return unless modifier.numberformat
|
82
|
+
|
83
|
+
cell.set_number_format(modifier.number_format_code)
|
84
|
+
# TODO: this is annoying... we have to set the contents with the correct type of object
|
85
|
+
cell.change_contents(cell.value)
|
86
|
+
end
|
87
|
+
|
88
|
+
def format_cell!(row_index, cell_index, modifier)
|
89
|
+
@worksheet.sheet_data[row_index][cell_index].tap do |cell|
|
90
|
+
do_alignments!(cell, modifier)
|
91
|
+
do_borders!(cell, modifier)
|
92
|
+
do_fill!(cell, modifier)
|
93
|
+
do_fonts!(cell, modifier)
|
94
|
+
do_formats!(cell, modifier)
|
95
|
+
do_number_formats!(cell, modifier)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def open_workbook(sheet_name)
|
100
|
+
if ::File.exist?(@output_filename)
|
101
|
+
::RubyXL::Parser.parse(@output_filename).tap do |workbook|
|
102
|
+
workbook.add_worksheet(sheet_name) unless workbook[sheet_name]
|
103
|
+
end
|
104
|
+
else
|
105
|
+
::RubyXL::Workbook.new.tap do |workbook|
|
106
|
+
workbook.worksheets[0].sheet_name = sheet_name
|
107
|
+
end
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
end
|
112
|
+
end
|
@@ -0,0 +1,52 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSVPlusPlus
|
4
|
+
# Writer
|
5
|
+
module Writer
|
6
|
+
# Build a RubyXL-decorated Modifier class adds some support for Excel
|
7
|
+
class RubyXLModifier < ::SimpleDelegator
|
8
|
+
# https://www.rubydoc.info/gems/rubyXL/RubyXL/NumberFormats
|
9
|
+
# https://support.microsoft.com/en-us/office/number-format-codes-5026bbd6-04bc-48cd-bf33-80f18b4eae68
|
10
|
+
NUM_FMT_IDS = {
|
11
|
+
currency: 5,
|
12
|
+
date: 14,
|
13
|
+
date_time: 22,
|
14
|
+
number: 1,
|
15
|
+
percent: 9,
|
16
|
+
text: 49,
|
17
|
+
time: 21,
|
18
|
+
scientific: 48
|
19
|
+
}.freeze
|
20
|
+
private_constant :NUM_FMT_IDS
|
21
|
+
|
22
|
+
# https://www.rubydoc.info/gems/rubyXL/2.3.0/RubyXL
|
23
|
+
# ST_BorderStyle = %w{ none thin medium dashed dotted thick double hair mediumDashed dashDot mediumDashDot
|
24
|
+
# dashDotDot slantDashDot }
|
25
|
+
BORDER_STYLES = {
|
26
|
+
dashed: 'dashed',
|
27
|
+
dotted: 'dotted',
|
28
|
+
double: 'double',
|
29
|
+
solid: 'thin',
|
30
|
+
solid_medium: 'medium',
|
31
|
+
solid_thick: 'thick'
|
32
|
+
}.freeze
|
33
|
+
private_constant :BORDER_STYLES
|
34
|
+
|
35
|
+
# The excel-specific number format code
|
36
|
+
def number_format_code
|
37
|
+
::RubyXL::NumberFormats::DEFAULT_NUMBER_FORMATS.find_by_format_id(
|
38
|
+
# rubocop:disable Lint/ConstantResolution
|
39
|
+
NUM_FMT_IDS[numberformat.to_sym]
|
40
|
+
# rubocop:enable Lint/ConstantResolution
|
41
|
+
).format_code
|
42
|
+
end
|
43
|
+
|
44
|
+
# The excel-specific border weight
|
45
|
+
def border_weight
|
46
|
+
# rubocop:disable Lint/ConstantResolution
|
47
|
+
BORDER_STYLES[borderstyle.to_sym]
|
48
|
+
# rubocop:enable Lint/ConstantResolution
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
data/lib/csv_plus_plus/writer.rb
CHANGED
@@ -7,7 +7,6 @@ require_relative './writer/google_sheets'
|
|
7
7
|
require_relative './writer/open_document'
|
8
8
|
|
9
9
|
module CSVPlusPlus
|
10
|
-
##
|
11
10
|
# Various strategies for writing to various formats (excel, google sheets, CSV, OpenDocument)
|
12
11
|
module Writer
|
13
12
|
# Return an instance of a writer depending on the given +options+
|
@@ -17,8 +16,8 @@ module CSVPlusPlus
|
|
17
16
|
case options.output_filename
|
18
17
|
when /\.csv$/ then ::CSVPlusPlus::Writer::CSV.new(options)
|
19
18
|
when /\.ods$/ then ::CSVPlusPlus::Writer::OpenDocument.new(options)
|
20
|
-
when /\.
|
21
|
-
else raise(::
|
19
|
+
when /\.xl(sx|sm|tx|tm)$/ then ::CSVPlusPlus::Writer::Excel.new(options)
|
20
|
+
else raise(::CSVPlusPlus::Error, "Unsupported file extension: #{options.output_filename}")
|
22
21
|
end
|
23
22
|
end
|
24
23
|
end
|
data/lib/csv_plus_plus.rb
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'csv_plus_plus/cli'
|
4
|
+
require_relative 'csv_plus_plus/error'
|
3
5
|
require_relative 'csv_plus_plus/language/compiler'
|
4
6
|
require_relative 'csv_plus_plus/options'
|
5
7
|
require_relative 'csv_plus_plus/writer'
|
@@ -14,7 +16,10 @@ module CSVPlusPlus
|
|
14
16
|
template = c.parse_template
|
15
17
|
|
16
18
|
output = ::CSVPlusPlus::Writer.writer(options)
|
17
|
-
c.outputting!
|
19
|
+
c.outputting! do
|
20
|
+
output.write_backup if options.backup
|
21
|
+
output.write(template)
|
22
|
+
end
|
18
23
|
end
|
19
24
|
end
|
20
25
|
end
|