csv_plus_plus 0.0.3 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/README.md +1 -1
- data/bin/csv++ +13 -54
- 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/graph.rb +0 -5
- data/lib/csv_plus_plus/language/compiler.rb +0 -1
- 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 +4 -15
- data/lib/csv_plus_plus/modifier.tab.rb +367 -381
- data/lib/csv_plus_plus/options.rb +1 -0
- data/lib/csv_plus_plus/version.rb +1 -1
- data/lib/csv_plus_plus/writer/excel.rb +15 -2
- 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 +4 -4
- 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 +1 -0
- metadata +26 -15
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: ea5b029c524b401348cc42399514c9fb689b13bfcf7298664429f69c85ae6607
|
4
|
+
data.tar.gz: 5bb79395742dcd89bf3b4ca7ede92a9a41a2c3ddefc4dff1b4232c285117373d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4a1bf4ccf98486b64ed69f34e701e9c4c69aea2b971c569aab7f6345ca721ccbde01570449033be23eeea37ec59d244dbb2692f27bfbfaae6069c84e23ea988a
|
7
|
+
data.tar.gz: 8cef1aa204255588787b3e3138ec282a9b45c174b83e647a91d0476aca199ee4ee8640f11c40418cded6026016a4383e1e1df7600cdfa5c5524d25a6c953fb76
|
data/CHANGELOG.md
CHANGED
data/README.md
CHANGED
@@ -9,7 +9,7 @@ A tool that allows you to programatically author spreadsheets in your favorite t
|
|
9
9
|
A `csvpp` file consists of a (optional) code section and a CSV section separated by `---`. In the code section you can define variables and functions that can be used in the CSV below it. For example:
|
10
10
|
|
11
11
|
```
|
12
|
-
fees := 0.
|
12
|
+
fees := 0.50 # my broker charges $0.50 a trade
|
13
13
|
|
14
14
|
price := cellref(C)
|
15
15
|
quantity := cellref(D)
|
data/bin/csv++
CHANGED
@@ -7,64 +7,18 @@ require_relative '../lib/csv_plus_plus'
|
|
7
7
|
options = ::CSVPlusPlus::Options.new
|
8
8
|
|
9
9
|
option_parser =
|
10
|
-
# rubocop:disable Metrics/BlockLength
|
11
10
|
::OptionParser.new do |parser|
|
12
|
-
parser.on('-b', '--backup', 'Create a backup of the spreadsheet before applying changes.') do
|
13
|
-
options.backup = true
|
14
|
-
end
|
15
|
-
|
16
|
-
parser.on(
|
17
|
-
'-g SHEET_ID',
|
18
|
-
'--google-sheet-id SHEET_ID',
|
19
|
-
'The id of the sheet - you can extract this from the URL: ' \
|
20
|
-
'https://docs.google.com/spreadsheets/d/< ... SHEET_ID ... >/edit#gid=0'
|
21
|
-
) do |v|
|
22
|
-
options.google_sheet_id = v
|
23
|
-
end
|
24
|
-
|
25
|
-
parser.on('-c', '--create', "Create the sheet if it doesn't exist. It will use --sheet-name if specified") do
|
26
|
-
options.create_if_not_exists = true
|
27
|
-
end
|
28
|
-
|
29
|
-
parser.on(
|
30
|
-
'-k KEY_VALUES',
|
31
|
-
'--key-values KEY_VALUES',
|
32
|
-
'A comma-separated list of key=values which will be made available to the template'
|
33
|
-
) do |v|
|
34
|
-
options.key_values =
|
35
|
-
begin
|
36
|
-
[v.split('=')].to_h
|
37
|
-
rescue ::StandardError
|
38
|
-
{}
|
39
|
-
end
|
40
|
-
end
|
41
|
-
|
42
|
-
parser.on('-n SHEET_NAME', '--sheet-name SHEET_NAME', 'The name of the sheet to apply the template to') do |v|
|
43
|
-
options.sheet_name = v
|
44
|
-
end
|
45
|
-
|
46
|
-
parser.on('-o OUTPUT_FILE', '--output OUTPUT_FILE', 'The file to write to (must be .csv, .ods, .xls)') do |v|
|
47
|
-
options.output_filename = v
|
48
|
-
end
|
49
|
-
|
50
|
-
parser.on('-v', '--verbose', 'Enable verbose output') do
|
51
|
-
options.verbose = true
|
52
|
-
end
|
53
|
-
|
54
|
-
parser.on('-x OFFSET', '--offset-columns OFFSET', 'Apply the template offset by OFFSET cells') do |v|
|
55
|
-
options.offset[0] = v
|
56
|
-
end
|
57
|
-
|
58
|
-
parser.on('-y OFFSET', '--offset-rows OFFSET', 'Apply the template offset by OFFSET rows') do |v|
|
59
|
-
options.offset[1] = v
|
60
|
-
end
|
61
|
-
|
62
11
|
parser.on('-h', '--help', 'Show help information') do
|
63
12
|
puts(parser)
|
64
13
|
exit
|
65
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
|
66
21
|
end
|
67
|
-
# rubocop:enable Metrics/BlockLength
|
68
22
|
|
69
23
|
option_parser.parse!
|
70
24
|
|
@@ -77,7 +31,12 @@ end
|
|
77
31
|
|
78
32
|
begin
|
79
33
|
::CSVPlusPlus.apply_template_to_sheet!(::ARGF.read, ::ARGF.filename, options)
|
80
|
-
rescue ::CSVPlusPlus::
|
81
|
-
|
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
|
+
|
82
41
|
exit(1)
|
83
42
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './google_options'
|
4
|
+
|
5
|
+
module CSVPlusPlus
|
6
|
+
# Individual CLI flags that a user can supply
|
7
|
+
class CliFlag
|
8
|
+
attr_reader :short_flag, :long_flag, :description, :handler
|
9
|
+
|
10
|
+
# initialize
|
11
|
+
def initialize(short_flag, long_flag, description, handler)
|
12
|
+
@short_flag = short_flag
|
13
|
+
@long_flag = long_flag
|
14
|
+
@description = description
|
15
|
+
@handler = handler
|
16
|
+
end
|
17
|
+
|
18
|
+
# to_s
|
19
|
+
def to_s
|
20
|
+
"#{@short_flag}, #{@long_flag} #{@description}"
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
SUPPORTED_CSVPP_FLAGS = [
|
26
|
+
::CSVPlusPlus::CliFlag.new(
|
27
|
+
'-b',
|
28
|
+
'--backup',
|
29
|
+
'Create a backup of the spreadsheet before applying changes.',
|
30
|
+
->(options, _v) { options.backup = true }
|
31
|
+
),
|
32
|
+
::CSVPlusPlus::CliFlag.new(
|
33
|
+
'-c',
|
34
|
+
'--create',
|
35
|
+
"Create the sheet if it doesn't exist. It will use --sheet-name if specified",
|
36
|
+
->(options, _v) { options.create_if_not_exists = true }
|
37
|
+
),
|
38
|
+
::CSVPlusPlus::CliFlag.new(
|
39
|
+
'-g SHEET_ID',
|
40
|
+
'--google-sheet-id SHEET_ID',
|
41
|
+
'The id of the sheet - you can extract this from the URL: ' \
|
42
|
+
'https://docs.google.com/spreadsheets/d/< ... SHEET_ID ... >/edit#gid=0',
|
43
|
+
->(options, v) { options.google_sheet_id = v }
|
44
|
+
),
|
45
|
+
::CSVPlusPlus::CliFlag.new(
|
46
|
+
'-k',
|
47
|
+
'--key-values KEY_VALUES',
|
48
|
+
'A comma-separated list of key=values which will be made available to the template',
|
49
|
+
lambda do |options, v|
|
50
|
+
options.key_values =
|
51
|
+
begin
|
52
|
+
[v.split('=')].to_h
|
53
|
+
rescue ::StandardError
|
54
|
+
{}
|
55
|
+
end
|
56
|
+
end
|
57
|
+
),
|
58
|
+
::CSVPlusPlus::CliFlag.new(
|
59
|
+
'-n SHEET_NAME',
|
60
|
+
'--sheet-name SHEET_NAME',
|
61
|
+
'The name of the sheet to apply the template to',
|
62
|
+
->(options, v) { options.sheet_name = v }
|
63
|
+
),
|
64
|
+
::CSVPlusPlus::CliFlag.new(
|
65
|
+
'-o OUTPUT_FILE',
|
66
|
+
'--output OUTPUT_FILE',
|
67
|
+
'The file to write to (must be .csv, .ods, .xls)',
|
68
|
+
->(options, v) { options.output_filename = v }
|
69
|
+
),
|
70
|
+
::CSVPlusPlus::CliFlag.new('-v', '--verbose', 'Enable verbose output', ->(options, _v) { options.verbose = true }),
|
71
|
+
::CSVPlusPlus::CliFlag.new(
|
72
|
+
'-x OFFSET',
|
73
|
+
'--offset-columns OFFSET',
|
74
|
+
'Apply the template offset by OFFSET cells',
|
75
|
+
->(options, v) { options.offset[0] = v }
|
76
|
+
),
|
77
|
+
::CSVPlusPlus::CliFlag.new(
|
78
|
+
'-y OFFSET',
|
79
|
+
'--offset-rows OFFSET',
|
80
|
+
'Apply the template offset by OFFSET rows',
|
81
|
+
->(options, v) { options.offset[1] = v }
|
82
|
+
)
|
83
|
+
].freeze
|
data/lib/csv_plus_plus/color.rb
CHANGED
@@ -3,20 +3,54 @@
|
|
3
3
|
module CSVPlusPlus
|
4
4
|
# A color value
|
5
5
|
class Color
|
6
|
-
attr_reader :
|
6
|
+
attr_reader :red_hex, :green_hex, :blue_hex
|
7
7
|
|
8
8
|
# create an instance from a string like "#FFF" or "#FFFFFF"
|
9
9
|
def initialize(hex_string)
|
10
|
-
@
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
10
|
+
@red_hex, @green_hex, @blue_hex = hex_string
|
11
|
+
.gsub(/^#?/, '')
|
12
|
+
.match(/([0-9a-f]{1,2})([0-9a-f]{1,2})([0-9a-f]{1,2})/i)
|
13
|
+
&.captures
|
14
|
+
&.map { |s| s.length == 1 ? s + s : s }
|
15
|
+
end
|
16
|
+
|
17
|
+
# The percent (decimal between 0-1) of red
|
18
|
+
def red_percent
|
19
|
+
hex_to_percent(@red_hex)
|
20
|
+
end
|
21
|
+
|
22
|
+
# The percent (decimal between 0-1) of green
|
23
|
+
def green_percent
|
24
|
+
hex_to_percent(@green_hex)
|
25
|
+
end
|
26
|
+
|
27
|
+
# The percent (decimal between 0-1) of blue
|
28
|
+
def blue_percent
|
29
|
+
hex_to_percent(@blue_hex)
|
30
|
+
end
|
31
|
+
|
32
|
+
# to_hex
|
33
|
+
def to_hex
|
34
|
+
[@red_hex, @green_hex, @blue_hex].join
|
35
|
+
end
|
36
|
+
|
37
|
+
# to_s
|
38
|
+
def to_s
|
39
|
+
"Color(r: #{@red_hex}, g: #{@green_hex}, b: #{@blue_hex})"
|
40
|
+
end
|
41
|
+
|
42
|
+
# ==
|
43
|
+
def ==(other)
|
44
|
+
other.is_a?(self.class) &&
|
45
|
+
other.red_hex == @red_hex &&
|
46
|
+
other.green_hex == @green_hex &&
|
47
|
+
other.blue_hex == @blue_hex
|
48
|
+
end
|
49
|
+
|
50
|
+
private
|
51
|
+
|
52
|
+
def hex_to_percent(hex)
|
53
|
+
hex.to_i(16) / 255
|
20
54
|
end
|
21
55
|
end
|
22
56
|
end
|
data/lib/csv_plus_plus/graph.rb
CHANGED
@@ -54,11 +54,6 @@ module CSVPlusPlus
|
|
54
54
|
include ::TSort
|
55
55
|
alias tsort_each_node each_key
|
56
56
|
|
57
|
-
# create a +DependencyGraph+ from a +Hash+
|
58
|
-
def self.from_hash(hash)
|
59
|
-
self[hash.map { |k, v| [k, v] }]
|
60
|
-
end
|
61
|
-
|
62
57
|
# sort each child
|
63
58
|
def tsort_each_child(node, &)
|
64
59
|
fetch(node).each(&)
|
@@ -127,8 +127,6 @@ module CSVPlusPlus
|
|
127
127
|
|
128
128
|
# this will throw a syntax error if it doesn't exist (which is what we want)
|
129
129
|
return ::BUILTIN_FUNCTIONS[id] if ::BUILTIN_FUNCTIONS.key?(id)
|
130
|
-
|
131
|
-
@runtime.raise_syntax_error('Unknown function', fn_id)
|
132
130
|
end
|
133
131
|
|
134
132
|
def apply_arguments(function, function_call)
|
@@ -4,7 +4,7 @@ module CSVPlusPlus
|
|
4
4
|
module Language
|
5
5
|
##
|
6
6
|
# An error that can be thrown for various syntax errors
|
7
|
-
class SyntaxError <
|
7
|
+
class SyntaxError < ::CSVPlusPlus::Error
|
8
8
|
# initialize
|
9
9
|
def initialize(message, bad_input, runtime, wrapped_error: nil)
|
10
10
|
@bad_input = bad_input.to_s
|
@@ -6,32 +6,20 @@ require_relative './expand'
|
|
6
6
|
require_relative './language/syntax_error'
|
7
7
|
|
8
8
|
module CSVPlusPlus
|
9
|
-
##
|
10
9
|
# A container representing the operations that can be applied to a cell or row
|
11
10
|
class Modifier
|
12
11
|
attr_reader :bordercolor, :borders, :color, :fontcolor, :formats
|
13
12
|
attr_writer :borderstyle
|
14
|
-
attr_accessor :expand, :fontfamily, :fontsize, :note, :numberformat, :row_level, :validation
|
13
|
+
attr_accessor :expand, :fontfamily, :fontsize, :halign, :valign, :note, :numberformat, :row_level, :validation
|
15
14
|
|
16
15
|
# initialize
|
17
16
|
def initialize(row_level: false)
|
18
17
|
@row_level = row_level
|
19
18
|
@freeze = false
|
20
|
-
@align = ::Set.new
|
21
19
|
@borders = ::Set.new
|
22
20
|
@formats = ::Set.new
|
23
21
|
end
|
24
22
|
|
25
|
-
# Set an align format. +direction+ must be 'center', 'left', 'right', 'bottom'
|
26
|
-
def align=(direction)
|
27
|
-
@align << direction
|
28
|
-
end
|
29
|
-
|
30
|
-
# Is it aligned to a given direction?
|
31
|
-
def aligned?(direction)
|
32
|
-
@align.include?(direction)
|
33
|
-
end
|
34
|
-
|
35
23
|
# Set the color. hex_value is a String
|
36
24
|
def color=(hex_value)
|
37
25
|
@color = ::CSVPlusPlus::Color.new(hex_value)
|
@@ -110,12 +98,13 @@ module CSVPlusPlus
|
|
110
98
|
# to_s
|
111
99
|
def to_s
|
112
100
|
# TODO... I dunno, not sure how to manage this
|
113
|
-
"Modifier(row_level: #{@row_level}
|
101
|
+
"Modifier(row_level: #{@row_level} halign: #{@halign} valign: #{@valign} format: #{@formats} " \
|
102
|
+
"font_size: #{@font_size})"
|
114
103
|
end
|
115
104
|
|
116
105
|
# Create a new modifier instance, with all values defaulted from +other+
|
117
106
|
def take_defaults_from!(other)
|
118
|
-
instance_variables.each do |property|
|
107
|
+
other.instance_variables.each do |property|
|
119
108
|
value = other.instance_variable_get(property)
|
120
109
|
instance_variable_set(property, value.clone)
|
121
110
|
end
|