class Shallot

shallot exposes only one class; Shallot. A Shallot instance encompasses the state of the parsing operation, which can be advanced by feeding it lines of gherkin. That sounded weird. The Shallot class also exposes methods to wrap the instance, allowing you to quickly serve parsed feature files.

Attributes

background[R]

A list of strings; the lines comprising the Background steps.

feature[R]

The name of the feature, as specified on the “Feature:” line.

scenarios[R]

A list of hashes for each Scenario or Scenario Outline; the hash contains:

  • :name: the name as given on the “Scenario:” or “Scenario Outline:” line

  • :outline: true if this is a Scenario Outline, false if Scenario

  • :tags: the list of tags for this scenario (sans “@”), including feature-wide tags

  • :contents: the list of steps (one string per line), including all whitespace, tables, etc.

Public Class Methods

new() click to toggle source

Creates a fresh Shallot instance.

# File lib/shallot.rb, line 44
def initialize
  @state = :opening
  @file_tags = []
  @scenario_tags = []
  @qqq = nil
  @scenario = nil
  @line = 0

  @background = []
  @scenarios = []
  @feature = nil
end
parse(file) click to toggle source

Parses file in its entirety (by creating a new Shallot instance), and returns a hash with :feature, :background and :scenarios keys as for the Shallot instance object. file only needs to implement each_line. If an error occurs, Shallot::Error will be thrown.

# File lib/shallot.rb, line 31
def self.parse file
  parser = new
  file.each_line {|line| parser.parse line}
  parser.eof!

  {
    feature:    parser.feature,
    background: parser.background,
    scenarios:  parser.scenarios,
  }
end

Public Instance Methods

eof!() click to toggle source

Signals to the parser that the end of the file has been reached. This may throw Shallot::Error if EOF wasn’t expected in its current state.

# File lib/shallot.rb, line 101
def eof!
  method = :"eof_#@state"
  raise Error, "no eof handler for #@state" unless respond_to? method
  send method
end
parse(line) click to toggle source

Parses the next line, line. Parse or internal errors may cause Shallot::Error to be thrown.

# File lib/shallot.rb, line 74
def parse line
  # Check some conditions that should be (more or less) invariant across
  # a feature file.
  @line += 1
  
  if @qqq.nil? and %w{''' """}.include? line.strip
    # Start triple-quote.
    @qqq = line.strip

  elsif @qqq == line.strip
    # End triple-quote.
    @qqq = nil

  elsif @qqq.nil? and [nil, ?#].include? line.strip[0]
    # Blank or comment; outside triple-quote.  Process as an empty line.
    line = '' unless [:background, :scenario].include? @state
  end

  # Use state.
  
  method = :"parse_#@state"
  raise Error, "no parser for #@state" unless respond_to? method
  send method, line
end

Protected Instance Methods

eof_scenario() click to toggle source

Handles EOF after having seen “Scenario:” or “Scenario Outline:”.

# File lib/shallot.rb, line 181
def eof_scenario
  @scenarios << @scenario
end
parse_background(line) click to toggle source

Parses line after we’ve seen “Background:”, but before any scenario.

# File lib/shallot.rb, line 149
def parse_background line
  if @qqq.nil? and tags = parse_tag_line(line)
    # Tags; presumably before a scenario.
    @scenario_tags.concat tags

  elsif @qqq.nil? and scenario = parse_scenario_start(line)
    # Start scenario (outline).
    start_scenario scenario
  
  else
    # Any other step; part of the background.
    @background << line.gsub(/\n$/, "")
  end
end
parse_feature(line) click to toggle source

Parses line after we’ve seen “Feature:”, but before the Background or any scenario (outline).

# File lib/shallot.rb, line 128
def parse_feature line
  if is_background_start? line
    # Start background.
    @state = :background

    raise Error, "tags before background" if @scenario_tags.length > 0
  
  elsif scenario = parse_scenario_start(line)
    # Start scenario (outline).
    start_scenario scenario

  elsif tags = parse_tag_line(line)
    # Tags; presumably before a scenario.
    @scenario_tags.concat tags

  else
    # Most likely part of the "As a .." prelude. Ignore.
  end
end
parse_opening(line) click to toggle source

Parses line before we’ve seen the opening “Feature:” line.

# File lib/shallot.rb, line 110
def parse_opening line
  if tags = parse_tag_line(line)
    # Tags on beginning of file.
    @file_tags.concat tags
  
  elsif feature = parse_feature_start(line)
    # Start feature.
    @state = :feature
    @feature = feature
  
  elsif line != ''
    # There shouldn't be anything else in here.
    raise Error, "unexpected line before feature start"
  end
end
parse_scenario(line) click to toggle source

Parses line after we’ve seen “Scenario:” or “Scenario Outline:”.

# File lib/shallot.rb, line 165
def parse_scenario line
  if @qqq.nil? and tags = parse_tag_line(line)
    # Tags; presumably before a scenario.
    @scenario_tags.concat tags

  elsif @qqq.nil? and scenario = parse_scenario_start(line)
    # Start scenario (outline).
    start_scenario scenario

  else
    # Any other step; part of the scenario (outline).
    @scenario[:contents] << line.gsub(/\n$/, "")
  end
end
start_scenario(scenario) click to toggle source

Moves to the scenario parsing state with the ad-hoc information in scenario from parse_scenario_start.

# File lib/shallot.rb, line 187
def start_scenario scenario
  @state = :scenario
  @scenarios << @scenario if @scenario

  @scenario = {
    name:     scenario[:name],
    outline:  scenario[:outline],
    tags:     (@file_tags + @scenario_tags).uniq,
    contents: [],
    line:     @line,
  }

  @scenario_tags = []
end

Private Instance Methods

is_background_start?(line) click to toggle source

Returns true if line is a “Background:” line.

# File lib/shallot.rb, line 219
def is_background_start? line
  line.strip.downcase == "background:"
end
parse_feature_start(line) click to toggle source

Parses a “Feature:” line, line, returning the title on the line, or nil if it wasn’t a feature line at all.

# File lib/shallot.rb, line 214
def parse_feature_start line
  $1.strip if line.strip =~ /^feature:(.*)$/i
end
parse_scenario_start(line) click to toggle source

Parses a scenario (outline) starting line, line, returning a hash with :name containing the title given on the line, and :outline with true if it was “Scenario Outline:”, and false if “Scenario:”. If it wasn’t a scenario starting line, returns nil.

# File lib/shallot.rb, line 227
def parse_scenario_start line
  if line.strip =~ /^scenario( outline)?:(.*)$/i
    {name: $2.strip, outline: !!$1} 
  end
end
parse_tag_line(line) click to toggle source

Parses a line of tags, line, returning a list of downcased tags sans “@”. Returns nil if the line didn’t contain only tags (and at least one).

# File lib/shallot.rb, line 206
def parse_tag_line line
  if (tags = line.strip.split).all? {|w| w[0] == ?@} and tags.length > 0
    tags.map {|t| t[1..-1].downcase}
  end
end