csv_plus_plus 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/csv_plus_plus/cell.rb +51 -0
- data/lib/csv_plus_plus/code_section.rb +49 -0
- data/lib/csv_plus_plus/color.rb +22 -0
- data/lib/csv_plus_plus/expand.rb +18 -0
- data/lib/csv_plus_plus/google_options.rb +23 -0
- data/lib/csv_plus_plus/graph.rb +68 -0
- data/lib/csv_plus_plus/language/cell_value.tab.rb +333 -0
- data/lib/csv_plus_plus/language/code_section.tab.rb +443 -0
- data/lib/csv_plus_plus/language/compiler.rb +170 -0
- data/lib/csv_plus_plus/language/entities/boolean.rb +32 -0
- data/lib/csv_plus_plus/language/entities/cell_reference.rb +26 -0
- data/lib/csv_plus_plus/language/entities/entity.rb +70 -0
- data/lib/csv_plus_plus/language/entities/function.rb +33 -0
- data/lib/csv_plus_plus/language/entities/function_call.rb +25 -0
- data/lib/csv_plus_plus/language/entities/number.rb +34 -0
- data/lib/csv_plus_plus/language/entities/runtime_value.rb +27 -0
- data/lib/csv_plus_plus/language/entities/string.rb +29 -0
- data/lib/csv_plus_plus/language/entities/variable.rb +25 -0
- data/lib/csv_plus_plus/language/entities.rb +28 -0
- data/lib/csv_plus_plus/language/references.rb +53 -0
- data/lib/csv_plus_plus/language/runtime.rb +147 -0
- data/lib/csv_plus_plus/language/scope.rb +199 -0
- data/lib/csv_plus_plus/language/syntax_error.rb +61 -0
- data/lib/csv_plus_plus/lexer/lexer.rb +64 -0
- data/lib/csv_plus_plus/lexer/tokenizer.rb +65 -0
- data/lib/csv_plus_plus/lexer.rb +14 -0
- data/lib/csv_plus_plus/modifier.rb +124 -0
- data/lib/csv_plus_plus/modifier.tab.rb +921 -0
- data/lib/csv_plus_plus/options.rb +70 -0
- data/lib/csv_plus_plus/row.rb +42 -0
- data/lib/csv_plus_plus/template.rb +61 -0
- data/lib/csv_plus_plus/version.rb +6 -0
- data/lib/csv_plus_plus/writer/base_writer.rb +21 -0
- data/lib/csv_plus_plus/writer/csv.rb +31 -0
- data/lib/csv_plus_plus/writer/excel.rb +13 -0
- data/lib/csv_plus_plus/writer/google_sheet_builder.rb +173 -0
- data/lib/csv_plus_plus/writer/google_sheets.rb +139 -0
- data/lib/csv_plus_plus/writer/open_document.rb +14 -0
- data/lib/csv_plus_plus/writer.rb +25 -0
- data/lib/csv_plus_plus.rb +20 -0
- metadata +83 -0
@@ -0,0 +1,61 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSVPlusPlus
|
4
|
+
module Language
|
5
|
+
##
|
6
|
+
# An error that can be thrown for various syntax errors
|
7
|
+
class SyntaxError < StandardError
|
8
|
+
# initialize
|
9
|
+
def initialize(message, bad_input, runtime, wrapped_error: nil)
|
10
|
+
@bad_input = bad_input.to_s
|
11
|
+
@runtime = runtime
|
12
|
+
@wrapped_error = wrapped_error
|
13
|
+
@message = message
|
14
|
+
|
15
|
+
super(message)
|
16
|
+
end
|
17
|
+
|
18
|
+
# to_s
|
19
|
+
def to_s
|
20
|
+
to_trace
|
21
|
+
end
|
22
|
+
|
23
|
+
# Output a verbose user-helpful string that references the current runtime
|
24
|
+
def to_verbose_trace
|
25
|
+
warn(@wrapped_error.full_message)
|
26
|
+
warn(@wrapped_error.backtrace)
|
27
|
+
to_trace
|
28
|
+
end
|
29
|
+
|
30
|
+
# Output a user-helpful string that references the runtime state
|
31
|
+
def to_trace
|
32
|
+
"#{message_prefix}#{cell_index} #{message_postfix}"
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
def cell_index
|
38
|
+
row_index = @runtime.row_index
|
39
|
+
if @runtime.cell_index
|
40
|
+
"[#{row_index},#{@runtime.cell_index}]"
|
41
|
+
elsif row_index
|
42
|
+
"[#{row_index}]"
|
43
|
+
else
|
44
|
+
''
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def message_prefix
|
49
|
+
line_number = @runtime.line_number
|
50
|
+
filename = @runtime.filename
|
51
|
+
|
52
|
+
line_str = line_number ? ":#{line_number}" : ''
|
53
|
+
"csv++ #{filename}#{line_str}"
|
54
|
+
end
|
55
|
+
|
56
|
+
def message_postfix
|
57
|
+
"#{@message}: \"#{@bad_input}\""
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module CSVPlusPlus
|
4
|
+
# Common methods to be mixed into our Racc parsers
|
5
|
+
module Lexer
|
6
|
+
# initialize
|
7
|
+
def initialize
|
8
|
+
@tokens = []
|
9
|
+
end
|
10
|
+
|
11
|
+
# Used by racc to iterate each token
|
12
|
+
def next_token
|
13
|
+
@tokens.shift
|
14
|
+
end
|
15
|
+
|
16
|
+
# parse
|
17
|
+
def parse(input, runtime)
|
18
|
+
return if input.nil?
|
19
|
+
|
20
|
+
return return_value unless anything_to_parse?(input)
|
21
|
+
|
22
|
+
tokenize(input, runtime)
|
23
|
+
do_parse
|
24
|
+
return_value
|
25
|
+
rescue ::Racc::ParseError => e
|
26
|
+
runtime.raise_syntax_error("Error parsing #{parse_subject}", e.message, wrapped_error: e)
|
27
|
+
end
|
28
|
+
|
29
|
+
protected
|
30
|
+
|
31
|
+
def tokenize(input, runtime)
|
32
|
+
return if input.nil?
|
33
|
+
|
34
|
+
t = tokenizer(input)
|
35
|
+
|
36
|
+
until t.scanner.empty?
|
37
|
+
next if t.matches_ignore?
|
38
|
+
|
39
|
+
return if t.stop?
|
40
|
+
|
41
|
+
t.scan_tokens!
|
42
|
+
consume_token(t, runtime)
|
43
|
+
end
|
44
|
+
|
45
|
+
@tokens << %i[EOL EOL]
|
46
|
+
end
|
47
|
+
|
48
|
+
def e(type, *entity_args)
|
49
|
+
::CSVPlusPlus::Language::TYPES[type].new(*entity_args)
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def consume_token(tokenizer, runtime)
|
55
|
+
if tokenizer.last_token
|
56
|
+
@tokens << [tokenizer.last_token, tokenizer.last_match]
|
57
|
+
elsif tokenizer.scan_catchall
|
58
|
+
@tokens << [tokenizer.last_match, tokenizer.last_match]
|
59
|
+
else
|
60
|
+
runtime.raise_syntax_error("Unable to parse #{parse_subject} starting at", tokenizer.peek)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'strscan'
|
4
|
+
|
5
|
+
module CSVPlusPlus
|
6
|
+
module Lexer
|
7
|
+
# A class that contains the use-case-specific regexes for parsing
|
8
|
+
class Tokenizer
|
9
|
+
attr_reader :last_token, :scanner
|
10
|
+
|
11
|
+
# initialize
|
12
|
+
# rubocop:disable Metrics/ParameterLists
|
13
|
+
def initialize(input:, tokens:, catchall: nil, ignore: nil, alter_matches: {}, stop_fn: nil)
|
14
|
+
@scanner = ::StringScanner.new(input.strip)
|
15
|
+
@last_token = nil
|
16
|
+
|
17
|
+
@catchall = catchall
|
18
|
+
@ignore = ignore
|
19
|
+
@tokens = tokens
|
20
|
+
@stop_fn = stop_fn
|
21
|
+
@alter_matches = alter_matches
|
22
|
+
end
|
23
|
+
# rubocop:enable Metrics/ParameterLists
|
24
|
+
|
25
|
+
# Scan tokens and see if any match
|
26
|
+
def scan_tokens!
|
27
|
+
m = @tokens.find { |t| @scanner.scan(t.first) }
|
28
|
+
@last_token = m ? m[1] : nil
|
29
|
+
end
|
30
|
+
|
31
|
+
# Scan input against the catchall pattern
|
32
|
+
def scan_catchall
|
33
|
+
@scanner.scan(@catchall) if @catchall
|
34
|
+
end
|
35
|
+
|
36
|
+
# Scan input against the ignore pattern
|
37
|
+
def matches_ignore?
|
38
|
+
@scanner.scan(@ignore) if @ignore
|
39
|
+
end
|
40
|
+
|
41
|
+
# The value of the last token matched
|
42
|
+
def last_match
|
43
|
+
return @alter_matches[@last_token].call(@scanner.matched) if @alter_matches.key?(@last_token)
|
44
|
+
|
45
|
+
@scanner.matched
|
46
|
+
end
|
47
|
+
|
48
|
+
# Peek the input
|
49
|
+
def peek
|
50
|
+
@scanner.peek(100)
|
51
|
+
end
|
52
|
+
|
53
|
+
# Scan for our stop token (if there is one - some parsers stop early and some don't)
|
54
|
+
def stop?
|
55
|
+
@stop_fn ? @stop_fn.call(@scanner) : false
|
56
|
+
end
|
57
|
+
|
58
|
+
# The rest of the un-parsed input. The tokenizer might not need to
|
59
|
+
# parse the entire input
|
60
|
+
def rest
|
61
|
+
@scanner.rest
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative './lexer/lexer'
|
4
|
+
require_relative './lexer/tokenizer'
|
5
|
+
|
6
|
+
module CSVPlusPlus
|
7
|
+
module Lexer
|
8
|
+
END_OF_CODE_SECTION = '---'
|
9
|
+
public_constant :END_OF_CODE_SECTION
|
10
|
+
|
11
|
+
VARIABLE_REF = '$$'
|
12
|
+
public_constant :VARIABLE_REF
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,124 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
require_relative './color'
|
5
|
+
require_relative './expand'
|
6
|
+
require_relative './language/syntax_error'
|
7
|
+
|
8
|
+
module CSVPlusPlus
|
9
|
+
##
|
10
|
+
# A container representing the operations that can be applied to a cell or row
|
11
|
+
class Modifier
|
12
|
+
attr_reader :bordercolor, :borders, :color, :fontcolor, :formats
|
13
|
+
attr_writer :borderstyle
|
14
|
+
attr_accessor :expand, :fontfamily, :fontsize, :note, :numberformat, :row_level, :validation
|
15
|
+
|
16
|
+
# initialize
|
17
|
+
def initialize(row_level: false)
|
18
|
+
@row_level = row_level
|
19
|
+
@freeze = false
|
20
|
+
@align = ::Set.new
|
21
|
+
@borders = ::Set.new
|
22
|
+
@formats = ::Set.new
|
23
|
+
end
|
24
|
+
|
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
|
+
# Set the color. hex_value is a String
|
36
|
+
def color=(hex_value)
|
37
|
+
@color = ::CSVPlusPlus::Color.new(hex_value)
|
38
|
+
end
|
39
|
+
|
40
|
+
# Assign a border. +side+ must be 'top', 'left', 'bottom', 'right' or 'all'
|
41
|
+
def border=(side)
|
42
|
+
@borders << side
|
43
|
+
end
|
44
|
+
|
45
|
+
# Does this have a border along +side+?
|
46
|
+
def border_along?(side)
|
47
|
+
border_all? || @borders.include?(side)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Does this have a border along all sides?
|
51
|
+
def border_all?
|
52
|
+
@borders.include?('all')
|
53
|
+
end
|
54
|
+
|
55
|
+
# Set the bordercolor
|
56
|
+
def bordercolor=(hex_value)
|
57
|
+
@bordercolor = ::CSVPlusPlus::Color.new(hex_value)
|
58
|
+
end
|
59
|
+
|
60
|
+
# Are there any borders set?
|
61
|
+
def any_border?
|
62
|
+
!@borders.empty?
|
63
|
+
end
|
64
|
+
|
65
|
+
# Set the fontcolor
|
66
|
+
def fontcolor=(hex_value)
|
67
|
+
@fontcolor = ::CSVPlusPlus::Color.new(hex_value)
|
68
|
+
end
|
69
|
+
|
70
|
+
# Set a format. +type+ must be 'bold', 'italic', 'underline' or 'strikethrough'
|
71
|
+
def format=(value)
|
72
|
+
@formats << value
|
73
|
+
end
|
74
|
+
|
75
|
+
# Is the given format set?
|
76
|
+
def formatted?(type)
|
77
|
+
@formats.include?(type)
|
78
|
+
end
|
79
|
+
|
80
|
+
# Freeze the row from edits
|
81
|
+
def freeze!
|
82
|
+
@frozen = true
|
83
|
+
end
|
84
|
+
|
85
|
+
# Is the row forzen?
|
86
|
+
def frozen?
|
87
|
+
@frozen
|
88
|
+
end
|
89
|
+
|
90
|
+
# Mark this modifer as row-level
|
91
|
+
def row_level!
|
92
|
+
@row_level = true
|
93
|
+
end
|
94
|
+
|
95
|
+
# Is this a row-level modifier?
|
96
|
+
def row_level?
|
97
|
+
@row_level
|
98
|
+
end
|
99
|
+
|
100
|
+
# Is this a cell-level modifier?
|
101
|
+
def cell_level?
|
102
|
+
!@row_level
|
103
|
+
end
|
104
|
+
|
105
|
+
# Style of border
|
106
|
+
def borderstyle
|
107
|
+
@borderstyle || 'solid'
|
108
|
+
end
|
109
|
+
|
110
|
+
# to_s
|
111
|
+
def to_s
|
112
|
+
# TODO... I dunno, not sure how to manage this
|
113
|
+
"Modifier(row_level: #{@row_level} align: #{@align} format: #{@formats} font_size: #{@font_size})"
|
114
|
+
end
|
115
|
+
|
116
|
+
# Create a new modifier instance, with all values defaulted from +other+
|
117
|
+
def take_defaults_from!(other)
|
118
|
+
instance_variables.each do |property|
|
119
|
+
value = other.instance_variable_get(property)
|
120
|
+
instance_variable_set(property, value.clone)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|