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
         |