csv_plus_plus 0.0.2
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 +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
|