Question

I have the following class:

require 'strscan'

class ConfParser
  include Enumerable

  class Error < StandardError; end
  VERSION = '0.0.1'
  SECTION_REGEX = /^\[       # Opening bracket
                   ([^\]]+)  # Section name
                   \]$       # Closing bracket
                 /x
  PARAMETER_REGEX = /^\s*([^:]+)  # Option
                      :
                      (.*?)$      # Value
                    /x

  attr_accessor :filename, :sections

  CONFIG_DIRECTORY = "./config"
  ENCODING = "UTF-8"

  def self.read(filename, opts = {})
    new(opts.merge(:filename => filename))
  end

  def initialize(opts = {})
    @filename = opts.fetch(:filename)
    @separator = opts.fetch(:separator, ":")
    @file = "#{CONFIG_DIRECTORY}/#{@filename}"
    @content = nil
    @config = Hash.new { |h,k| h[k] = Hash.new }

    load
  end

  def load
    raise_error("First line of config file contain be blank") if first_line_empty?

    f = File.open(@file, 'r')
    @content = f.read
    parse!

    ensure
      f.close if f && !f.closed?
  end

  def sections
    @config.keys
  end

  def [](section)
    return nil if section.nil?

    @config[section.to_s]
  end

  def []=( section, value )
    @config[section.to_s] = value
  end

  private

    def parse!
      @_section = nil
      @_current_line = nil
      property = ''
      string = ''

      @config.clear

      scanner = StringScanner.new(@content)

      until scanner.eos?
        @_current_line = scanner.check(%r/\A.*$/) if scanner.bol?

        if scanner.scan(SECTION_REGEX)
          @_section = @config[scanner[1]]
        else
          tmp = scanner.scan_until(%r/([\n"#{@param}#{@comment}] | \z | \\[\[\]#{@param}#{@comment}"])/mx)
          raise_error if tmp.nil?

          len = scanner[1].length
          tmp.slice!(tmp.length - len, len)

          scanner.pos = scanner.pos - len
          string << tmp
        end
      end

      process_property(property, string)

      logger @config
    end

    def process_property( property, value )
      value.chomp!
      return if property.empty? and value.empty?
      return if value.sub!(%r/\\\s*\z/, '')

      property.strip!
      value.strip!

      parse_error if property.empty?

      current_section[property.dup] = unescape_value(value.dup)

      property.slice!(0, property.length)
      value.slice!(0, value.length)

      nil
    end

    def logger log
      puts "*"*50
      puts log
      puts "*"*50
    end

    def first_line_empty?
      File.readlines(@file).first.chomp.empty?
    end

    def raise_error(msg = 'Error processing line')
      raise Error, "#{msg}: #{@_current_line}"
    end

    def current_section
      @_section ||= @config['header']
    end

end

The above class parses files that are setup like so:

[header]
project: Hello World
budget : 4.5
accessed :205

[meta data]
description : This is a tediously long description of the Hello World
  project that you are taking. Tedious isn't the right word, but
  it's the first word that comes to mind.

correction text: I meant 'moderately,' not 'tediously,' above.

[ trailer ]
budget:all out of budget.

You start running it like this:

require 'conf_parser'
cf = ConfParser.read "/path/to/conf/file"

For some reason when the parse! method runs, an infinite loop occurs and I can't figure out why. Any reason why this would be happening? I have never used StringScanner before, so it may be my lack of knowledge of the class

Was it helpful?

Solution

At the risk of stating the obvious, you are most likely never satisfying scanner.eos?, which in turn would mean that you're not advancing the scan pointer to the end of the string. Since the only change to scanner.pos in the else branch of parse! is to decrement it (i.e. by len), this would be understandable. If the if branch doesn't advance it to the end, you'll never terminate.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top