csv_plus_plus 0.0.4 → 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 +7 -0
- data/README.md +8 -3
- data/bin/csv++ +1 -37
- data/bin/csvpp +6 -0
- data/lib/csv_plus_plus/cli.rb +84 -0
- data/lib/csv_plus_plus/google_api_client.rb +20 -0
- 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/modifier.rb +45 -7
- data/lib/csv_plus_plus/options.rb +0 -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 +3 -0
- data/lib/csv_plus_plus/writer/file_backer_upper.rb +56 -0
- data/lib/csv_plus_plus/writer/google_sheets.rb +21 -34
- data/lib/csv_plus_plus.rb +5 -1
- metadata +26 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: c4f5c9b5a342102fa8e1c31b319fb05f19ad55278bc633435129254b5b2511a8
|
4
|
+
data.tar.gz: 0c2656d4d7b22d0b3b3311745ed5a70b789277c622f0f2d09d2b8a144de71a8d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 90965e2275cc7988f4054b59e30523cd645b21c1adb89797828e86a767217b09ac174bbc17094604dd608b93defd79f4c9a74ccf7e2f20dd17995f8c1b5dadac
|
7
|
+
data.tar.gz: 291011896232e88a7bd0e252887459986812d2877c1d0383458250e1c30cf735d8b2bdaf5dd759f44fa5b8b33b08f74d780df7960622ab774e9f6d856628508f
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -21,12 +21,17 @@ def profit() (price * quantity) - fees
|
|
21
21
|
![[expand]],[[format=bold]],,,"=PROFIT()",$$fees
|
22
22
|
```
|
23
23
|
|
24
|
-
##
|
24
|
+
## Variables
|
25
25
|
|
26
|
-
|
26
|
+
Variables can be defined in the code section by giving a name (a combination of letters, numbers and underscores ) the expression `:=` and followed with a value.
|
27
27
|
|
28
|
-
|
28
|
+
### Built-in Variables
|
29
29
|
|
30
|
+
* `$$rownum` - The current row number. The first row of the spreadsheet starts at 1. Can be used anywhere and it's value will evaluate to the current row being processed.
|
31
|
+
|
32
|
+
## Functions
|
33
|
+
|
34
|
+
### Built-in Functions
|
30
35
|
* `cellref(CELL)` - Returns a reference to the `CELL` relative to the current row. If the current `$$rownum` is `2`, then `CELLREF("C")` returns a reference to cell `C2`.
|
31
36
|
|
32
37
|
## Modifiers
|
data/bin/csv++
CHANGED
@@ -1,42 +1,6 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
# frozen_string_literal: true
|
3
3
|
|
4
|
-
require 'optparse'
|
5
4
|
require_relative '../lib/csv_plus_plus'
|
6
5
|
|
7
|
-
|
8
|
-
|
9
|
-
option_parser =
|
10
|
-
::OptionParser.new do |parser|
|
11
|
-
parser.on('-h', '--help', 'Show help information') do
|
12
|
-
puts(parser)
|
13
|
-
exit
|
14
|
-
end
|
15
|
-
|
16
|
-
::SUPPORTED_CSVPP_FLAGS.each do |flag|
|
17
|
-
parser.on(flag.short_flag, flag.long_flag, flag.description) do |v|
|
18
|
-
flag.handler.call(options, v)
|
19
|
-
end
|
20
|
-
end
|
21
|
-
end
|
22
|
-
|
23
|
-
option_parser.parse!
|
24
|
-
|
25
|
-
error_message = options.validate
|
26
|
-
unless error_message.nil?
|
27
|
-
warn(error_message)
|
28
|
-
puts(option_parser)
|
29
|
-
exit(1)
|
30
|
-
end
|
31
|
-
|
32
|
-
begin
|
33
|
-
::CSVPlusPlus.apply_template_to_sheet!(::ARGF.read, ::ARGF.filename, options)
|
34
|
-
rescue ::CSVPlusPlus::Error => e
|
35
|
-
if e.is_a?(::CSVPlusPlus::Language::SyntaxError)
|
36
|
-
warn(options.verbose ? e.to_verbose_trace : e.to_trace)
|
37
|
-
else
|
38
|
-
warn(e.message)
|
39
|
-
end
|
40
|
-
|
41
|
-
exit(1)
|
42
|
-
end
|
6
|
+
::CSVPlusPlus::CLI.launch_compiler!
|
data/bin/csvpp
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
module CSVPlusPlus
|
6
|
+
# Handle running the application with the given CLI flags
|
7
|
+
class CLI
|
8
|
+
# handle any CLI flags and launch the compiler
|
9
|
+
def self.launch_compiler!
|
10
|
+
cli = new
|
11
|
+
cli.compile!
|
12
|
+
rescue ::StandardError => e
|
13
|
+
cli.handle_error(e)
|
14
|
+
exit(1)
|
15
|
+
end
|
16
|
+
|
17
|
+
# initialize
|
18
|
+
def initialize
|
19
|
+
parse_options!
|
20
|
+
end
|
21
|
+
|
22
|
+
# compile the given template, using the given CLI flags
|
23
|
+
def compile!
|
24
|
+
::CSVPlusPlus.apply_template_to_sheet!(::ARGF.read, ::ARGF.filename, @options)
|
25
|
+
end
|
26
|
+
|
27
|
+
# (nicely) handle a given error. how it's handled depends on if it's our error and if @options.verbose
|
28
|
+
def handle_error(error)
|
29
|
+
case error
|
30
|
+
when ::CSVPlusPlus::Error
|
31
|
+
handle_internal_error(error)
|
32
|
+
when ::Google::Apis::ClientError
|
33
|
+
handle_google_error(error)
|
34
|
+
else
|
35
|
+
# TODO: more if verbose?
|
36
|
+
warn(error.message)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def handle_internal_error(error)
|
43
|
+
if error.is_a?(::CSVPlusPlus::Language::SyntaxError)
|
44
|
+
warn(@options.verbose ? error.to_verbose_trace : error.to_trace)
|
45
|
+
else
|
46
|
+
warn(error.message)
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def handle_google_error(error)
|
51
|
+
warn("Error making Google Sheets API request: #{error.message}")
|
52
|
+
return unless @options.verbose
|
53
|
+
|
54
|
+
warn("#{error.status_code} Error making Google API request [#{error.message}]: #{error.body}")
|
55
|
+
end
|
56
|
+
|
57
|
+
def parse_options!
|
58
|
+
@options = ::CSVPlusPlus::Options.new
|
59
|
+
option_parser.parse!
|
60
|
+
validate_options
|
61
|
+
end
|
62
|
+
|
63
|
+
def validate_options
|
64
|
+
error_message = @options.validate
|
65
|
+
return if error_message.nil?
|
66
|
+
|
67
|
+
puts(option_parser)
|
68
|
+
raise(::CSVPlusPlus::Error, error_message)
|
69
|
+
end
|
70
|
+
|
71
|
+
def option_parser
|
72
|
+
::OptionParser.new do |parser|
|
73
|
+
parser.on('-h', '--help', 'Show help information') do
|
74
|
+
puts(parser)
|
75
|
+
exit
|
76
|
+
end
|
77
|
+
|
78
|
+
::SUPPORTED_CSVPP_FLAGS.each do |f|
|
79
|
+
parser.on(f.short_flag, f.long_flag, f.description) { |v| f.handler.call(@options, v) }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSVPlusPlus
|
4
|
+
# A convenience wrapper around Google's REST API client
|
5
|
+
module GoogleApiClient
|
6
|
+
# Get a +::Google::Apis::SheetsV4::SheetsService+ instance connected to the sheets API
|
7
|
+
def self.sheets_client
|
8
|
+
::Google::Apis::SheetsV4::SheetsService.new.tap do |s|
|
9
|
+
s.authorization = ::Google::Auth.get_application_default(['https://www.googleapis.com/auth/spreadsheets'].freeze)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# Get a +::Google::Apis::DriveV3::DriveService+ instance connected to the drive API
|
14
|
+
def self.drive_client
|
15
|
+
::Google::Apis::DriveV3::DriveService.new.tap do |d|
|
16
|
+
d.authorization = ::Google::Auth.get_application_default(['https://www.googleapis.com/auth/drive.file'].freeze)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -5,24 +5,24 @@ require_relative './entity'
|
|
5
5
|
module CSVPlusPlus
|
6
6
|
module Language
|
7
7
|
module Entities
|
8
|
-
##
|
9
8
|
# A boolean value
|
10
9
|
class Boolean < Entity
|
11
10
|
attr_reader :value
|
12
11
|
|
13
12
|
# initialize
|
13
|
+
# @param value [String, Boolean]
|
14
14
|
def initialize(value)
|
15
15
|
super(:boolean)
|
16
16
|
# TODO: probably can do a lot better in general on type validation
|
17
17
|
@value = value.is_a?(::String) ? (value.downcase == 'true') : value
|
18
18
|
end
|
19
19
|
|
20
|
-
#
|
20
|
+
# @return [String]
|
21
21
|
def to_s
|
22
22
|
@value.to_s.upcase
|
23
23
|
end
|
24
24
|
|
25
|
-
#
|
25
|
+
# @return [Boolean]
|
26
26
|
def ==(other)
|
27
27
|
super && value == other.value
|
28
28
|
end
|
@@ -9,13 +9,14 @@ module CSVPlusPlus
|
|
9
9
|
class Entity
|
10
10
|
attr_reader :id, :type
|
11
11
|
|
12
|
-
#
|
12
|
+
# @param type [String, Symbol]
|
13
|
+
# @param id [String]
|
13
14
|
def initialize(type, id: nil)
|
14
15
|
@type = type.to_sym
|
15
16
|
@id = id.downcase.to_sym if id
|
16
17
|
end
|
17
18
|
|
18
|
-
#
|
19
|
+
# @return [Boolean]
|
19
20
|
def ==(other)
|
20
21
|
self.class == other.class && @type == other.type && @id == other.id
|
21
22
|
end
|
@@ -30,7 +31,8 @@ module CSVPlusPlus
|
|
30
31
|
end
|
31
32
|
end
|
32
33
|
|
33
|
-
#
|
34
|
+
# Respond to predicates by type (entity.boolean?, entity.string?, etc)
|
35
|
+
# @return [Boolean]
|
34
36
|
def respond_to_missing?(method_name, *_arguments)
|
35
37
|
(method_name =~ /^(\w+)\?$/ && a_type?(::Regexp.last_match(1))) || super
|
36
38
|
end
|
@@ -42,17 +44,19 @@ module CSVPlusPlus
|
|
42
44
|
end
|
43
45
|
end
|
44
46
|
|
45
|
-
# An entity that can take arguments
|
47
|
+
# An entity that can take other entities as arguments
|
46
48
|
class EntityWithArguments < Entity
|
47
49
|
attr_reader :arguments
|
48
50
|
|
49
|
-
#
|
51
|
+
# @param type [String, Symbol]
|
52
|
+
# @param id [String]
|
53
|
+
# @param arguments [Array<Entity>]
|
50
54
|
def initialize(type, id: nil, arguments: [])
|
51
55
|
super(type, id:)
|
52
56
|
@arguments = arguments
|
53
57
|
end
|
54
58
|
|
55
|
-
#
|
59
|
+
# @return [Boolean]
|
56
60
|
def ==(other)
|
57
61
|
super && @arguments == other.arguments
|
58
62
|
end
|
@@ -7,12 +7,30 @@ require_relative './language/syntax_error'
|
|
7
7
|
|
8
8
|
module CSVPlusPlus
|
9
9
|
# A container representing the operations that can be applied to a cell or row
|
10
|
+
#
|
11
|
+
# @attr expand [Expand]
|
12
|
+
# @attr fontfamily [String]
|
13
|
+
# @attr fontsize [String]
|
14
|
+
# @attr halign ['left', 'center', 'right']
|
15
|
+
# @attr valign ['top', 'center', 'bottom']
|
16
|
+
# @attr note [String]
|
17
|
+
# @attr numberformat [String]
|
18
|
+
# @attr row_level [Boolean]
|
19
|
+
# @attr validation [Object]
|
20
|
+
#
|
21
|
+
# @attr_writer borderstyle [String]
|
22
|
+
#
|
23
|
+
# @attr_reader bordercolor [String]
|
24
|
+
# @attr_reader borders [Array<String>]
|
25
|
+
# @attr_reader color [Color]
|
26
|
+
# @attr_reader fontcolor [Color]
|
27
|
+
# @attr_reader formats [Array<String>]
|
10
28
|
class Modifier
|
11
29
|
attr_reader :bordercolor, :borders, :color, :fontcolor, :formats
|
12
30
|
attr_writer :borderstyle
|
13
31
|
attr_accessor :expand, :fontfamily, :fontsize, :halign, :valign, :note, :numberformat, :row_level, :validation
|
14
32
|
|
15
|
-
#
|
33
|
+
# @param row_level [Boolean] Whether or not this modifier applies to the entire row
|
16
34
|
def initialize(row_level: false)
|
17
35
|
@row_level = row_level
|
18
36
|
@freeze = false
|
@@ -20,47 +38,59 @@ module CSVPlusPlus
|
|
20
38
|
@formats = ::Set.new
|
21
39
|
end
|
22
40
|
|
23
|
-
# Set the color
|
41
|
+
# Set the color
|
42
|
+
# @param hex_value [String]
|
24
43
|
def color=(hex_value)
|
25
44
|
@color = ::CSVPlusPlus::Color.new(hex_value)
|
26
45
|
end
|
27
46
|
|
28
|
-
# Assign a border
|
47
|
+
# Assign a border
|
48
|
+
# @param side ['top', 'left', 'bottom', 'right', 'all']
|
29
49
|
def border=(side)
|
30
50
|
@borders << side
|
31
51
|
end
|
32
52
|
|
33
53
|
# Does this have a border along +side+?
|
54
|
+
# @param side ['top', 'left', 'bottom', 'right', 'all']
|
55
|
+
# @return [Boolean]
|
34
56
|
def border_along?(side)
|
35
|
-
|
57
|
+
@borders.include?('all') || @borders.include?(side)
|
36
58
|
end
|
37
59
|
|
38
60
|
# Does this have a border along all sides?
|
61
|
+
# @return [Boolean]
|
39
62
|
def border_all?
|
40
|
-
@borders.include?('all')
|
63
|
+
@borders.include?('all') \
|
64
|
+
|| (border_along?('top') && border_along?('bottom') && border_along?('left') && border_along?('right'))
|
41
65
|
end
|
42
66
|
|
43
67
|
# Set the bordercolor
|
68
|
+
# @param hex_value [String] formatted as '#000000', '#000' or '000000'
|
44
69
|
def bordercolor=(hex_value)
|
45
70
|
@bordercolor = ::CSVPlusPlus::Color.new(hex_value)
|
46
71
|
end
|
47
72
|
|
48
73
|
# Are there any borders set?
|
74
|
+
# @return [Boolean]
|
49
75
|
def any_border?
|
50
76
|
!@borders.empty?
|
51
77
|
end
|
52
78
|
|
53
79
|
# Set the fontcolor
|
80
|
+
# @param hex_value [String] formatted as '#000000', '#000' or '000000'
|
54
81
|
def fontcolor=(hex_value)
|
55
82
|
@fontcolor = ::CSVPlusPlus::Color.new(hex_value)
|
56
83
|
end
|
57
84
|
|
58
|
-
# Set a format
|
85
|
+
# Set a text format (bolid, italic, underline or strikethrough)
|
86
|
+
# @param value ['bold', 'italic', 'underline', 'strikethrough']
|
59
87
|
def format=(value)
|
60
88
|
@formats << value
|
61
89
|
end
|
62
90
|
|
63
91
|
# Is the given format set?
|
92
|
+
# @param type ['bold', 'italic', 'underline', 'strikethrough']
|
93
|
+
# @return [Boolean]
|
64
94
|
def formatted?(type)
|
65
95
|
@formats.include?(type)
|
66
96
|
end
|
@@ -71,6 +101,7 @@ module CSVPlusPlus
|
|
71
101
|
end
|
72
102
|
|
73
103
|
# Is the row forzen?
|
104
|
+
# @return [Boolean]
|
74
105
|
def frozen?
|
75
106
|
@frozen
|
76
107
|
end
|
@@ -81,21 +112,24 @@ module CSVPlusPlus
|
|
81
112
|
end
|
82
113
|
|
83
114
|
# Is this a row-level modifier?
|
115
|
+
# @return [Boolean]
|
84
116
|
def row_level?
|
85
117
|
@row_level
|
86
118
|
end
|
87
119
|
|
88
120
|
# Is this a cell-level modifier?
|
121
|
+
# @return [Boolean]
|
89
122
|
def cell_level?
|
90
123
|
!@row_level
|
91
124
|
end
|
92
125
|
|
93
126
|
# Style of border
|
127
|
+
# @return [String]
|
94
128
|
def borderstyle
|
95
129
|
@borderstyle || 'solid'
|
96
130
|
end
|
97
131
|
|
98
|
-
#
|
132
|
+
# @return [String]
|
99
133
|
def to_s
|
100
134
|
# TODO... I dunno, not sure how to manage this
|
101
135
|
"Modifier(row_level: #{@row_level} halign: #{@halign} valign: #{@valign} format: #{@formats} " \
|
@@ -103,8 +137,12 @@ module CSVPlusPlus
|
|
103
137
|
end
|
104
138
|
|
105
139
|
# Create a new modifier instance, with all values defaulted from +other+
|
140
|
+
# @param other [Modifier]
|
106
141
|
def take_defaults_from!(other)
|
107
142
|
other.instance_variables.each do |property|
|
143
|
+
# don't propagate row-specific values
|
144
|
+
next if property == :@row_level
|
145
|
+
|
108
146
|
value = other.instance_variable_get(property)
|
109
147
|
instance_variable_set(property, value.clone)
|
110
148
|
end
|
@@ -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,11 +1,14 @@
|
|
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(
|
@@ -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,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,7 +22,7 @@ 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
27
|
fetch_spreadsheet!
|
30
28
|
fetch_spreadsheet_values!
|
@@ -32,13 +30,18 @@ module CSVPlusPlus
|
|
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,12 +53,7 @@ module CSVPlusPlus
|
|
50
53
|
end
|
51
54
|
|
52
55
|
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)
|
56
|
+
format_range('A1:Z1000')
|
59
57
|
end
|
60
58
|
|
61
59
|
def fetch_spreadsheet_values!
|
@@ -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
|
@@ -95,7 +93,7 @@ module CSVPlusPlus
|
|
95
93
|
end
|
96
94
|
|
97
95
|
def fetch_spreadsheet!
|
98
|
-
@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
|
data/lib/csv_plus_plus.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require_relative 'csv_plus_plus/cli'
|
3
4
|
require_relative 'csv_plus_plus/error'
|
4
5
|
require_relative 'csv_plus_plus/language/compiler'
|
5
6
|
require_relative 'csv_plus_plus/options'
|
@@ -15,7 +16,10 @@ module CSVPlusPlus
|
|
15
16
|
template = c.parse_template
|
16
17
|
|
17
18
|
output = ::CSVPlusPlus::Writer.writer(options)
|
18
|
-
c.outputting!
|
19
|
+
c.outputting! do
|
20
|
+
output.write_backup if options.backup
|
21
|
+
output.write(template)
|
22
|
+
end
|
19
23
|
end
|
20
24
|
end
|
21
25
|
end
|
metadata
CHANGED
@@ -1,15 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: csv_plus_plus
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.5
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Patrick Carroll
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2023-02-
|
11
|
+
date: 2023-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: google-apis-drive_v3
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0.3'
|
13
27
|
- !ruby/object:Gem::Dependency
|
14
28
|
name: google-apis-sheets_v4
|
15
29
|
requirement: !ruby/object:Gem::Requirement
|
@@ -100,19 +114,23 @@ description: "A programming language built on top of CSV. You can define functi
|
|
100
114
|
email: patrick@patrickomatic.com
|
101
115
|
executables:
|
102
116
|
- csv++
|
117
|
+
- csvpp
|
103
118
|
extensions: []
|
104
119
|
extra_rdoc_files: []
|
105
120
|
files:
|
106
121
|
- CHANGELOG.md
|
107
122
|
- README.md
|
108
123
|
- bin/csv++
|
124
|
+
- bin/csvpp
|
109
125
|
- lib/csv_plus_plus.rb
|
110
126
|
- lib/csv_plus_plus/cell.rb
|
127
|
+
- lib/csv_plus_plus/cli.rb
|
111
128
|
- lib/csv_plus_plus/cli_flag.rb
|
112
129
|
- lib/csv_plus_plus/code_section.rb
|
113
130
|
- lib/csv_plus_plus/color.rb
|
114
131
|
- lib/csv_plus_plus/error.rb
|
115
132
|
- lib/csv_plus_plus/expand.rb
|
133
|
+
- lib/csv_plus_plus/google_api_client.rb
|
116
134
|
- lib/csv_plus_plus/google_options.rb
|
117
135
|
- lib/csv_plus_plus/graph.rb
|
118
136
|
- lib/csv_plus_plus/language/cell_value.tab.rb
|
@@ -145,6 +163,7 @@ files:
|
|
145
163
|
- lib/csv_plus_plus/writer/base_writer.rb
|
146
164
|
- lib/csv_plus_plus/writer/csv.rb
|
147
165
|
- lib/csv_plus_plus/writer/excel.rb
|
166
|
+
- lib/csv_plus_plus/writer/file_backer_upper.rb
|
148
167
|
- lib/csv_plus_plus/writer/google_sheet_builder.rb
|
149
168
|
- lib/csv_plus_plus/writer/google_sheet_modifier.rb
|
150
169
|
- lib/csv_plus_plus/writer/google_sheets.rb
|
@@ -155,13 +174,13 @@ homepage: https://github.com/patrickomatic/csv-plus-plus
|
|
155
174
|
licenses:
|
156
175
|
- MIT
|
157
176
|
metadata:
|
158
|
-
rubygems_mfa_required: 'true'
|
159
177
|
bug_tracker_uri: https://github.com/patrickomatic/csv-plus-plus/issues
|
160
178
|
documentation_uri: https://www.rubydoc.info/gems/csv_plus_plus/
|
161
|
-
github_repo: git://github.com/patrickomatic/
|
162
|
-
homepage_uri: https://github.com/patrickomatic/
|
163
|
-
source_code_uri: https://github.com/patrickomatic/
|
164
|
-
changelog_uri: https://github.com/patrickomatic/
|
179
|
+
github_repo: git://github.com/patrickomatic/csv-plus-plus
|
180
|
+
homepage_uri: https://github.com/patrickomatic/csv-plus-plus
|
181
|
+
source_code_uri: https://github.com/patrickomatic/csv-plus-plus
|
182
|
+
changelog_uri: https://github.com/patrickomatic/csv-plus-plus/blob/main/CHANGELOG.md
|
183
|
+
rubygems_mfa_required: 'true'
|
165
184
|
post_install_message:
|
166
185
|
rdoc_options: []
|
167
186
|
require_paths:
|