csv_plus_plus 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/lib/csv_plus_plus/cell.rb +51 -0
  3. data/lib/csv_plus_plus/code_section.rb +49 -0
  4. data/lib/csv_plus_plus/color.rb +22 -0
  5. data/lib/csv_plus_plus/expand.rb +18 -0
  6. data/lib/csv_plus_plus/google_options.rb +23 -0
  7. data/lib/csv_plus_plus/graph.rb +68 -0
  8. data/lib/csv_plus_plus/language/cell_value.tab.rb +333 -0
  9. data/lib/csv_plus_plus/language/code_section.tab.rb +443 -0
  10. data/lib/csv_plus_plus/language/compiler.rb +170 -0
  11. data/lib/csv_plus_plus/language/entities/boolean.rb +32 -0
  12. data/lib/csv_plus_plus/language/entities/cell_reference.rb +26 -0
  13. data/lib/csv_plus_plus/language/entities/entity.rb +70 -0
  14. data/lib/csv_plus_plus/language/entities/function.rb +33 -0
  15. data/lib/csv_plus_plus/language/entities/function_call.rb +25 -0
  16. data/lib/csv_plus_plus/language/entities/number.rb +34 -0
  17. data/lib/csv_plus_plus/language/entities/runtime_value.rb +27 -0
  18. data/lib/csv_plus_plus/language/entities/string.rb +29 -0
  19. data/lib/csv_plus_plus/language/entities/variable.rb +25 -0
  20. data/lib/csv_plus_plus/language/entities.rb +28 -0
  21. data/lib/csv_plus_plus/language/references.rb +53 -0
  22. data/lib/csv_plus_plus/language/runtime.rb +147 -0
  23. data/lib/csv_plus_plus/language/scope.rb +199 -0
  24. data/lib/csv_plus_plus/language/syntax_error.rb +61 -0
  25. data/lib/csv_plus_plus/lexer/lexer.rb +64 -0
  26. data/lib/csv_plus_plus/lexer/tokenizer.rb +65 -0
  27. data/lib/csv_plus_plus/lexer.rb +14 -0
  28. data/lib/csv_plus_plus/modifier.rb +124 -0
  29. data/lib/csv_plus_plus/modifier.tab.rb +921 -0
  30. data/lib/csv_plus_plus/options.rb +70 -0
  31. data/lib/csv_plus_plus/row.rb +42 -0
  32. data/lib/csv_plus_plus/template.rb +61 -0
  33. data/lib/csv_plus_plus/version.rb +6 -0
  34. data/lib/csv_plus_plus/writer/base_writer.rb +21 -0
  35. data/lib/csv_plus_plus/writer/csv.rb +31 -0
  36. data/lib/csv_plus_plus/writer/excel.rb +13 -0
  37. data/lib/csv_plus_plus/writer/google_sheet_builder.rb +173 -0
  38. data/lib/csv_plus_plus/writer/google_sheets.rb +139 -0
  39. data/lib/csv_plus_plus/writer/open_document.rb +14 -0
  40. data/lib/csv_plus_plus/writer.rb +25 -0
  41. data/lib/csv_plus_plus.rb +20 -0
  42. 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