csv_plus_plus 0.0.4 → 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +9 -3
- data/bin/csv++ +1 -37
- data/bin/csvpp +6 -0
- data/lib/csv_plus_plus/cell.rb +24 -8
- data/lib/csv_plus_plus/cli.rb +97 -0
- data/lib/csv_plus_plus/cli_flag.rb +10 -2
- data/lib/csv_plus_plus/code_section.rb +22 -3
- data/lib/csv_plus_plus/color.rb +19 -5
- data/lib/csv_plus_plus/google_api_client.rb +20 -0
- data/lib/csv_plus_plus/google_options.rb +6 -2
- data/lib/csv_plus_plus/graph.rb +0 -1
- data/lib/csv_plus_plus/language/ast_builder.rb +68 -0
- data/lib/csv_plus_plus/language/benchmarked_compiler.rb +65 -0
- data/lib/csv_plus_plus/language/builtins.rb +46 -0
- data/lib/csv_plus_plus/language/cell_value.tab.rb +1 -2
- data/lib/csv_plus_plus/language/code_section.tab.rb +1 -2
- data/lib/csv_plus_plus/language/compiler.rb +74 -86
- data/lib/csv_plus_plus/language/entities/boolean.rb +5 -4
- data/lib/csv_plus_plus/language/entities/cell_reference.rb +10 -3
- data/lib/csv_plus_plus/language/entities/entity.rb +22 -6
- data/lib/csv_plus_plus/language/entities/function.rb +6 -4
- data/lib/csv_plus_plus/language/entities/function_call.rb +4 -3
- data/lib/csv_plus_plus/language/entities/number.rb +6 -4
- data/lib/csv_plus_plus/language/entities/runtime_value.rb +9 -8
- data/lib/csv_plus_plus/language/entities/string.rb +6 -4
- data/lib/csv_plus_plus/language/references.rb +22 -5
- data/lib/csv_plus_plus/language/runtime.rb +80 -22
- data/lib/csv_plus_plus/language/scope.rb +29 -38
- data/lib/csv_plus_plus/language/syntax_error.rb +10 -5
- data/lib/csv_plus_plus/lexer/lexer.rb +25 -12
- data/lib/csv_plus_plus/lexer/tokenizer.rb +35 -11
- data/lib/csv_plus_plus/modifier.rb +71 -8
- data/lib/csv_plus_plus/modifier.tab.rb +2 -2
- data/lib/csv_plus_plus/options.rb +17 -3
- data/lib/csv_plus_plus/row.rb +15 -4
- data/lib/csv_plus_plus/template.rb +10 -6
- 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 +5 -9
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +58 -0
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +8 -10
- data/lib/csv_plus_plus/writer/google_sheets.rb +22 -41
- data/lib/csv_plus_plus/writer/rubyxl_builder.rb +23 -15
- data/lib/csv_plus_plus/writer/rubyxl_modifier.rb +15 -8
- data/lib/csv_plus_plus.rb +26 -4
- metadata +29 -7
@@ -17,6 +17,7 @@ module_eval(<<'...end modifier.y/module_eval...', 'modifier.y', 121)
|
|
17
17
|
|
18
18
|
include ::CSVPlusPlus::Lexer
|
19
19
|
|
20
|
+
# @param cell_modifier
|
20
21
|
def initialize(cell_modifier:, row_modifier:)
|
21
22
|
super()
|
22
23
|
|
@@ -42,11 +43,10 @@ module_eval(<<'...end modifier.y/module_eval...', 'modifier.y', 121)
|
|
42
43
|
'modifier'
|
43
44
|
end
|
44
45
|
|
45
|
-
def tokenizer
|
46
|
+
def tokenizer
|
46
47
|
::CSVPlusPlus::Lexer::Tokenizer.new(
|
47
48
|
catchall: /\w+/,
|
48
49
|
ignore: /\s+/,
|
49
|
-
input:,
|
50
50
|
stop_fn: lambda do |scanner|
|
51
51
|
return false unless scanner.scan(/\]\]/)
|
52
52
|
|
@@ -4,7 +4,16 @@ require_relative './cli_flag'
|
|
4
4
|
require_relative './google_options'
|
5
5
|
|
6
6
|
module CSVPlusPlus
|
7
|
-
# The options a user can supply
|
7
|
+
# The options a user can supply (via CLI flags)
|
8
|
+
#
|
9
|
+
# @attr backup [boolean] Create a backup of the spreadsheet before writing
|
10
|
+
# @attr create_if_not_exists [boolean] Create the spreadsheet if it does not exist?
|
11
|
+
# @attr key_values [Hash] Additional variables that can be supplied to the template
|
12
|
+
# @attr offset [Array<Integer>] An [x, y] offset (array with two integers)
|
13
|
+
# @attr output_filename [String] The file to write our compiled results to
|
14
|
+
# @attr sheet_name [String] The name of the spreadsheet to write to
|
15
|
+
# @attr verbose [boolean] Include extra verbose output?
|
16
|
+
# @attr_reader google [GoogleOptions] Options that are specific to the Google Sheets writer
|
8
17
|
class Options
|
9
18
|
attr_accessor :backup, :create_if_not_exists, :key_values, :offset, :output_filename, :sheet_name, :verbose
|
10
19
|
attr_reader :google
|
@@ -15,16 +24,20 @@ module CSVPlusPlus
|
|
15
24
|
@create_if_not_exists = false
|
16
25
|
@key_values = {}
|
17
26
|
@verbose = false
|
18
|
-
# TODO: switch to true? probably a safer choice
|
19
27
|
@backup = false
|
20
28
|
end
|
21
29
|
|
22
30
|
# Set the Google Sheet ID
|
31
|
+
#
|
32
|
+
# @param sheet_id [String] The identifier used by Google's API to reference the sheet. You can find it in the URL
|
33
|
+
# for the sheet
|
34
|
+
# @return [String]
|
23
35
|
def google_sheet_id=(sheet_id)
|
24
36
|
@google = ::CSVPlusPlus::GoogleOptions.new(sheet_id)
|
25
37
|
end
|
26
38
|
|
27
39
|
# Returns an error string or nil if there are no validation problems
|
40
|
+
# @return [String, nil]
|
28
41
|
def validate
|
29
42
|
return if @google || @output_filename
|
30
43
|
|
@@ -32,6 +45,7 @@ module CSVPlusPlus
|
|
32
45
|
end
|
33
46
|
|
34
47
|
# Return a string with a verbose description of what we're doing with the options
|
48
|
+
# @return [String]
|
35
49
|
def verbose_summary
|
36
50
|
<<~SUMMARY
|
37
51
|
#{summary_divider}
|
@@ -56,7 +70,7 @@ module CSVPlusPlus
|
|
56
70
|
SUMMARY
|
57
71
|
end
|
58
72
|
|
59
|
-
#
|
73
|
+
# @return [String]
|
60
74
|
def to_s
|
61
75
|
"Options(create_if_not_exists: #{@create_if_not_exists}, google: #{@google}, key_values: #{@key_values}, " \
|
62
76
|
"offset: #{@offset}, sheet_name: #{@sheet_name}, verbose: #{@verbose})"
|
data/lib/csv_plus_plus/row.rb
CHANGED
@@ -4,37 +4,48 @@ require_relative 'cell'
|
|
4
4
|
require_relative 'modifier.tab'
|
5
5
|
|
6
6
|
module CSVPlusPlus
|
7
|
-
##
|
8
7
|
# A row of a template
|
8
|
+
#
|
9
|
+
# @attr_reader cells [Array<Cell>]
|
10
|
+
# @attr_reader index [Integer] The index of this row
|
11
|
+
# @attr_reader modifier [Modifier] The modifier to apply to all cells in this row
|
9
12
|
class Row
|
10
13
|
attr_reader :cells, :index, :modifier
|
11
14
|
|
12
|
-
#
|
15
|
+
# @param index [Integer] The index of this row (starts at 0)
|
16
|
+
# @param cells [Array<Cell>] The cells belonging to this row
|
17
|
+
# @param modifier [Modifier] The modifier to apply to all cells in this row
|
13
18
|
def initialize(index, cells, modifier)
|
14
19
|
@cells = cells
|
15
20
|
@modifier = modifier
|
16
21
|
@index = index
|
17
22
|
end
|
18
23
|
|
19
|
-
# Set the row index
|
24
|
+
# Set the row's +index+ and update the +row_index+ of all affected cells
|
25
|
+
#
|
26
|
+
# @param index [Integer] The index of this row (starts at 0)
|
20
27
|
def index=(index)
|
21
28
|
@index = index
|
22
29
|
@cells.each { |cell| cell.row_index = index }
|
23
30
|
end
|
24
31
|
|
25
32
|
# How much this row will expand itself, if at all (0)
|
33
|
+
#
|
34
|
+
# @return [Integer]
|
26
35
|
def expand_amount
|
27
36
|
return 0 unless @modifier.expand
|
28
37
|
|
29
38
|
@modifier.expand.repetitions || (1000 - @index)
|
30
39
|
end
|
31
40
|
|
32
|
-
#
|
41
|
+
# @return [String]
|
33
42
|
def to_s
|
34
43
|
"Row(index: #{index}, modifier: #{modifier}, cells: #{cells})"
|
35
44
|
end
|
36
45
|
|
37
46
|
# Return a deep copy of this row
|
47
|
+
#
|
48
|
+
# @return [Row]
|
38
49
|
def deep_clone
|
39
50
|
::Marshal.load(::Marshal.dump(self))
|
40
51
|
end
|
@@ -2,21 +2,23 @@
|
|
2
2
|
|
3
3
|
module CSVPlusPlus
|
4
4
|
# Contains the flow and data from a code section and CSV section
|
5
|
+
#
|
6
|
+
# @attr_reader rows [Array<Row>] The +Row+s that comprise this +Template+
|
5
7
|
class Template
|
6
|
-
attr_reader :rows
|
8
|
+
attr_reader :rows
|
7
9
|
|
8
|
-
#
|
9
|
-
def initialize(rows
|
10
|
+
# @param rows [Array<Row>] The +Row+s that comprise this +Template+
|
11
|
+
def initialize(rows:)
|
10
12
|
@rows = rows
|
11
|
-
@scope = scope
|
12
13
|
end
|
13
14
|
|
14
|
-
#
|
15
|
+
# @return [String]
|
15
16
|
def to_s
|
16
|
-
"Template(rows: #{@rows}
|
17
|
+
"Template(rows: #{@rows})"
|
17
18
|
end
|
18
19
|
|
19
20
|
# Apply any expand= modifiers to the parsed template
|
21
|
+
# @return [Array<Row>]
|
20
22
|
def expand_rows!
|
21
23
|
expanded_rows = []
|
22
24
|
row_index = 0
|
@@ -32,6 +34,8 @@ module CSVPlusPlus
|
|
32
34
|
end
|
33
35
|
|
34
36
|
# Make sure that the template has a valid amount of infinite expand modifiers
|
37
|
+
#
|
38
|
+
# @param runtime [Runtime] The compiler's current runtime
|
35
39
|
def validate_infinite_expands(runtime)
|
36
40
|
infinite_expand_rows = @rows.filter { |r| r.modifier.expand&.infinite? }
|
37
41
|
return unless infinite_expand_rows.length > 1
|
@@ -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,25 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative './file_backer_upper'
|
3
4
|
require_relative './rubyxl_builder'
|
4
5
|
|
5
6
|
module CSVPlusPlus
|
6
7
|
module Writer
|
7
8
|
# A class that can output a +Template+ to an Excel file
|
8
9
|
class Excel < ::CSVPlusPlus::Writer::BaseWriter
|
10
|
+
include ::CSVPlusPlus::Writer::FileBackerUpper
|
11
|
+
|
9
12
|
# write the +template+ to an Excel file
|
10
13
|
def write(template)
|
11
14
|
::CSVPlusPlus::Writer::RubyXLBuilder.new(
|
12
|
-
|
15
|
+
input_filename: @options.output_filename,
|
13
16
|
rows: template.rows,
|
14
17
|
sheet_name: @options.sheet_name
|
15
|
-
).write
|
16
|
-
end
|
17
|
-
|
18
|
-
protected
|
19
|
-
|
20
|
-
def load_requires
|
21
|
-
require('rubyXL')
|
22
|
-
require('rubyXL/convenience_methods')
|
18
|
+
).build_workbook.write(@options.output_filename)
|
23
19
|
end
|
24
20
|
end
|
25
21
|
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'fileutils'
|
4
|
+
require 'pathname'
|
5
|
+
|
6
|
+
module CSVPlusPlus
|
7
|
+
module Writer
|
8
|
+
# A module that can be mixed into any Writer that needs to back up it's @output_filename (all of them except Google
|
9
|
+
# Sheets)
|
10
|
+
module FileBackerUpper
|
11
|
+
# I don't want to include a bunch of second/millisecond stuff in the filename unless we
|
12
|
+
# really need to. so try a less specifically formatted filename then get more specific
|
13
|
+
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
|
14
|
+
private_constant :DESIRED_BACKUP_FORMATS
|
15
|
+
|
16
|
+
# Assuming the underlying spreadsheet is file-based, create a backup of it
|
17
|
+
def write_backup
|
18
|
+
return unless ::File.exist?(@options.output_filename)
|
19
|
+
|
20
|
+
# TODO: also don't do anything if the current backups contents haven't changed (do a md5sum or something)
|
21
|
+
|
22
|
+
attempt_backups.tap do |backed_up_to|
|
23
|
+
warn("Backed up #{@options.output_filename} to #{backed_up_to}") if @options.verbose
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
private
|
28
|
+
|
29
|
+
def attempt_backups
|
30
|
+
attempted =
|
31
|
+
# rubocop:disable Lint/ConstantResolution
|
32
|
+
DESIRED_BACKUP_FORMATS.map do |file_format|
|
33
|
+
# rubocop:enable Lint/ConstantResolution
|
34
|
+
filename = format_backup_filename(file_format)
|
35
|
+
backed_up_to = backup(filename)
|
36
|
+
|
37
|
+
next filename unless backed_up_to
|
38
|
+
|
39
|
+
return backed_up_to
|
40
|
+
end
|
41
|
+
|
42
|
+
raise(::CSVPlusPlus::Error, "Unable to write backup file despite trying these: #{attempted.join(', ')}")
|
43
|
+
end
|
44
|
+
|
45
|
+
def backup(filename)
|
46
|
+
return if ::File.exist?(filename)
|
47
|
+
|
48
|
+
::FileUtils.cp(@options.output_filename, filename)
|
49
|
+
filename
|
50
|
+
end
|
51
|
+
|
52
|
+
def format_backup_filename(file_format)
|
53
|
+
pn = ::Pathname.new(@options.output_filename)
|
54
|
+
pn.sub_ext("-#{::Time.now.strftime(file_format)}" + pn.extname)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -7,7 +7,7 @@ module CSVPlusPlus
|
|
7
7
|
# Given +rows+ from a +Template+, build requests compatible with Google Sheets Ruby API
|
8
8
|
# rubocop:disable Metrics/ClassLength
|
9
9
|
class GoogleSheetBuilder
|
10
|
-
#
|
10
|
+
# @param current_sheet_values
|
11
11
|
def initialize(current_sheet_values:, sheet_id:, rows:, column_index: 0, row_index: 0)
|
12
12
|
@current_sheet_values = current_sheet_values
|
13
13
|
@sheet_id = sheet_id
|
@@ -17,6 +17,8 @@ module CSVPlusPlus
|
|
17
17
|
end
|
18
18
|
|
19
19
|
# Build a Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest
|
20
|
+
#
|
21
|
+
# @return [Google::Apis::SheetsV4::BatchUpdateSpreadsheetRequest]
|
20
22
|
def batch_update_spreadsheet_request
|
21
23
|
build_batch_request(@rows)
|
22
24
|
end
|
@@ -42,20 +44,16 @@ module CSVPlusPlus
|
|
42
44
|
end
|
43
45
|
end
|
44
46
|
|
45
|
-
# rubocop:disable Metrics/AbcSize
|
46
47
|
def build_cell_format(mod)
|
47
48
|
sheets_ns::CellFormat.new.tap do |cf|
|
48
49
|
cf.text_format = mod.text_format
|
49
50
|
|
50
|
-
cf.horizontal_alignment = mod.halign
|
51
|
-
cf.vertical_alignment = mod.valign
|
52
|
-
|
53
|
-
cf.
|
54
|
-
|
55
|
-
cf.number_format = mod.numberformat if mod.numberformat
|
51
|
+
cf.horizontal_alignment = mod.halign
|
52
|
+
cf.vertical_alignment = mod.valign
|
53
|
+
cf.background_color = mod.color
|
54
|
+
cf.number_format = mod.numberformat
|
56
55
|
end
|
57
56
|
end
|
58
|
-
# rubocop:enable Metrics/AbcSize
|
59
57
|
|
60
58
|
def grid_range_for_cell(cell)
|
61
59
|
sheets_ns::GridRange.new(
|
@@ -68,7 +66,7 @@ module CSVPlusPlus
|
|
68
66
|
end
|
69
67
|
|
70
68
|
def current_value(row_index, cell_index)
|
71
|
-
@
|
69
|
+
@current_sheet_values[row_index][cell_index]
|
72
70
|
rescue ::StandardError
|
73
71
|
nil
|
74
72
|
end
|
@@ -1,20 +1,18 @@
|
|
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
|
|
17
|
-
#
|
15
|
+
# @param options [Options]
|
18
16
|
def initialize(options)
|
19
17
|
super(options)
|
20
18
|
|
@@ -23,8 +21,10 @@ module CSVPlusPlus
|
|
23
21
|
end
|
24
22
|
|
25
23
|
# write a +template+ to Google Sheets
|
24
|
+
#
|
25
|
+
# @param template [Template]
|
26
26
|
def write(template)
|
27
|
-
|
27
|
+
@sheets_client = ::CSVPlusPlus::GoogleApiClient.sheets_client
|
28
28
|
|
29
29
|
fetch_spreadsheet!
|
30
30
|
fetch_spreadsheet_values!
|
@@ -32,15 +32,12 @@ module CSVPlusPlus
|
|
32
32
|
create_sheet! if @options.create_if_not_exists
|
33
33
|
|
34
34
|
update_cells!(template)
|
35
|
-
rescue ::Google::Apis::ClientError => e
|
36
|
-
handle_google_error(e)
|
37
35
|
end
|
38
36
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
require('googleauth')
|
37
|
+
# write a backup of the google sheet
|
38
|
+
def write_backup
|
39
|
+
drive_client = ::CSVPlusPlus::GoogleApiClient.drive_client
|
40
|
+
drive_client.copy_file(@sheet_id)
|
44
41
|
end
|
45
42
|
|
46
43
|
private
|
@@ -50,12 +47,7 @@ module CSVPlusPlus
|
|
50
47
|
end
|
51
48
|
|
52
49
|
def full_range
|
53
|
-
format_range(
|
54
|
-
end
|
55
|
-
|
56
|
-
def auth!
|
57
|
-
@gs ||= sheets_ns::SheetsService.new
|
58
|
-
@gs.authorization = ::Google::Auth.get_application_default(::AUTH_SCOPES)
|
50
|
+
format_range('A1:Z1000')
|
59
51
|
end
|
60
52
|
|
61
53
|
def fetch_spreadsheet_values!
|
@@ -85,7 +77,7 @@ module CSVPlusPlus
|
|
85
77
|
end
|
86
78
|
|
87
79
|
def get_all_spreadsheet_values(render_option)
|
88
|
-
@
|
80
|
+
@sheets_client.get_spreadsheet_values(@sheet_id, full_range, value_render_option: render_option)
|
89
81
|
end
|
90
82
|
|
91
83
|
def sheet
|
@@ -95,7 +87,7 @@ module CSVPlusPlus
|
|
95
87
|
end
|
96
88
|
|
97
89
|
def fetch_spreadsheet!
|
98
|
-
@spreadsheet = @
|
90
|
+
@spreadsheet = @sheets_client.get_spreadsheet(@sheet_id)
|
99
91
|
|
100
92
|
return unless @sheet_name.nil?
|
101
93
|
|
@@ -105,34 +97,23 @@ module CSVPlusPlus
|
|
105
97
|
def create_sheet!
|
106
98
|
return if sheet
|
107
99
|
|
108
|
-
@
|
109
|
-
|
100
|
+
@sheets_client.create_spreadsheet(@sheet_name)
|
101
|
+
fetch_spreadsheet!
|
110
102
|
@sheet_name = @spreadsheet.sheets.last.properties.title
|
111
103
|
end
|
112
104
|
|
113
105
|
def update_cells!(template)
|
114
|
-
builder
|
106
|
+
@sheets_client.batch_update_spreadsheet(@sheet_id, builder(template).batch_update_spreadsheet_request)
|
107
|
+
end
|
108
|
+
|
109
|
+
def builder(template)
|
110
|
+
::CSVPlusPlus::Writer::GoogleSheetBuilder.new(
|
115
111
|
rows: template.rows,
|
116
112
|
sheet_id: sheet&.properties&.sheet_id,
|
117
113
|
column_index: @options.offset[1],
|
118
114
|
row_index: @options.offset[0],
|
119
|
-
current_sheet_values: @
|
115
|
+
current_sheet_values: @current_values
|
120
116
|
)
|
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
117
|
end
|
137
118
|
end
|
138
119
|
end
|
@@ -5,21 +5,29 @@ require_relative './rubyxl_modifier'
|
|
5
5
|
module CSVPlusPlus
|
6
6
|
module Writer
|
7
7
|
# Build a RubyXL workbook formatted according to the given +rows+
|
8
|
+
#
|
9
|
+
# @attr_reader input_filename [String] The filename being written to
|
10
|
+
# @attr_reader rows [Array<Row>] The rows being written
|
8
11
|
class RubyXLBuilder
|
9
|
-
attr_reader :
|
12
|
+
attr_reader :input_filename, :rows
|
10
13
|
|
11
|
-
#
|
12
|
-
|
14
|
+
# @param input_filename [String] The file to write to
|
15
|
+
# @param rows [Array<Row>] The rows to write
|
16
|
+
# @param sheet_name [String] The name of the sheet within the workbook to write to
|
17
|
+
def initialize(input_filename:, rows:, sheet_name:)
|
13
18
|
@rows = rows
|
14
|
-
@
|
15
|
-
@
|
16
|
-
@worksheet = @workbook[sheet_name]
|
19
|
+
@input_filename = input_filename
|
20
|
+
@sheet_name = sheet_name
|
17
21
|
end
|
18
22
|
|
19
|
-
#
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
+
# Build a +RubyXL::Workbook+ with the given +@rows+ in +sheet_name+
|
24
|
+
#
|
25
|
+
# @return [RubyXL::Workbook]
|
26
|
+
def build_workbook
|
27
|
+
open_workbook.tap do |workbook|
|
28
|
+
@worksheet = workbook[@sheet_name]
|
29
|
+
build_workbook!
|
30
|
+
end
|
23
31
|
end
|
24
32
|
|
25
33
|
private
|
@@ -96,14 +104,14 @@ module CSVPlusPlus
|
|
96
104
|
end
|
97
105
|
end
|
98
106
|
|
99
|
-
def open_workbook
|
100
|
-
if ::File.exist?(@
|
101
|
-
::RubyXL::Parser.parse(@
|
102
|
-
workbook.add_worksheet(sheet_name) unless workbook[sheet_name]
|
107
|
+
def open_workbook
|
108
|
+
if ::File.exist?(@input_filename)
|
109
|
+
::RubyXL::Parser.parse(@input_filename).tap do |workbook|
|
110
|
+
workbook.add_worksheet(@sheet_name) unless workbook[@sheet_name]
|
103
111
|
end
|
104
112
|
else
|
105
113
|
::RubyXL::Workbook.new.tap do |workbook|
|
106
|
-
workbook.worksheets[0].sheet_name = sheet_name
|
114
|
+
workbook.worksheets[0].sheet_name = @sheet_name
|
107
115
|
end
|
108
116
|
end
|
109
117
|
end
|
@@ -1,7 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
module CSVPlusPlus
|
4
|
-
# Writer
|
5
4
|
module Writer
|
6
5
|
# Build a RubyXL-decorated Modifier class adds some support for Excel
|
7
6
|
class RubyXLModifier < ::SimpleDelegator
|
@@ -32,21 +31,29 @@ module CSVPlusPlus
|
|
32
31
|
}.freeze
|
33
32
|
private_constant :BORDER_STYLES
|
34
33
|
|
34
|
+
# The excel-specific border weight
|
35
|
+
#
|
36
|
+
# @return [Integer]
|
37
|
+
def border_weight
|
38
|
+
return unless borderstyle
|
39
|
+
|
40
|
+
# rubocop:disable Lint/ConstantResolution
|
41
|
+
BORDER_STYLES[borderstyle.to_sym]
|
42
|
+
# rubocop:enable Lint/ConstantResolution
|
43
|
+
end
|
44
|
+
|
35
45
|
# The excel-specific number format code
|
46
|
+
#
|
47
|
+
# @return [String]
|
36
48
|
def number_format_code
|
49
|
+
return unless numberformat
|
50
|
+
|
37
51
|
::RubyXL::NumberFormats::DEFAULT_NUMBER_FORMATS.find_by_format_id(
|
38
52
|
# rubocop:disable Lint/ConstantResolution
|
39
53
|
NUM_FMT_IDS[numberformat.to_sym]
|
40
54
|
# rubocop:enable Lint/ConstantResolution
|
41
55
|
).format_code
|
42
56
|
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
57
|
end
|
51
58
|
end
|
52
59
|
end
|
data/lib/csv_plus_plus.rb
CHANGED
@@ -1,21 +1,43 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require 'google/apis/drive_v3'
|
4
|
+
require 'google/apis/sheets_v4'
|
5
|
+
require 'googleauth'
|
6
|
+
require 'rubyXL'
|
7
|
+
require 'rubyXL/convenience_methods'
|
8
|
+
|
9
|
+
require_relative 'csv_plus_plus/cli'
|
3
10
|
require_relative 'csv_plus_plus/error'
|
11
|
+
require_relative 'csv_plus_plus/language/builtins'
|
4
12
|
require_relative 'csv_plus_plus/language/compiler'
|
13
|
+
require_relative 'csv_plus_plus/language/runtime'
|
5
14
|
require_relative 'csv_plus_plus/options'
|
6
15
|
require_relative 'csv_plus_plus/writer'
|
7
16
|
|
8
|
-
# A language for writing rich CSV
|
17
|
+
# A programming language for writing rich CSV files
|
9
18
|
module CSVPlusPlus
|
10
19
|
# Parse the input into a +Template+ and write it to the desired format
|
20
|
+
#
|
21
|
+
# @param input [String] The csvpp input to compile
|
22
|
+
# @param filename [String, nil] The filename the input was read from. +nil+ if it is read from stdin.
|
23
|
+
# @param options [Options] The various options to compile with
|
24
|
+
#
|
25
|
+
# rubocop:disable Metrics/MethodLength
|
11
26
|
def self.apply_template_to_sheet!(input, filename, options)
|
12
27
|
warn(options.verbose_summary) if options.verbose
|
13
28
|
|
14
|
-
::CSVPlusPlus::Language::Compiler.with_compiler(
|
15
|
-
|
29
|
+
::CSVPlusPlus::Language::Compiler.with_compiler(
|
30
|
+
options:,
|
31
|
+
runtime: ::CSVPlusPlus::Language::Runtime.new(input:, filename:)
|
32
|
+
) do |c|
|
33
|
+
template = c.compile_template
|
16
34
|
|
17
35
|
output = ::CSVPlusPlus::Writer.writer(options)
|
18
|
-
c.outputting!
|
36
|
+
c.outputting! do
|
37
|
+
output.write_backup if options.backup
|
38
|
+
output.write(template)
|
39
|
+
end
|
19
40
|
end
|
20
41
|
end
|
42
|
+
# rubocop:enable Metrics/MethodLength
|
21
43
|
end
|