csv_plus_plus 0.0.3 → 0.0.5
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/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
|