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.
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