class CukeTagger::Tagger

Constants

USAGE

Public Class Methods

execute(args) click to toggle source
# File lib/cuketagger/tagger.rb, line 5
def self.execute(args)
  new.execute(args)
end

Public Instance Methods

execute(args) click to toggle source
# File lib/cuketagger/tagger.rb, line 9
def execute(args)
  abort(USAGE) if args.empty? || args.first =~ /^(-h|--help)$/

  force = false

  args.each do |arg|
    case arg
    when /^-v|--version$/
      puts CukeTagger::Version
    when /^(.+?\.feature)((:\d+)*)$/
     add_feature $1, $2.to_s
    when /^(add|remove):(.+?)$/
      alterations << [$1.to_sym, $2]
    when /^(replace):(.+?):(.+)$/
      alterations << [$1.to_sym, [$2, $3]]
    when /^(-f|--force)$/
      force = true
    else
      abort(USAGE)
    end
  end

  alterations.uniq!

  files = features_to_change.map { |file, line| file }.uniq
  files.each { |file| parse file, force }
end

Private Instance Methods

add_feature(path, lines) click to toggle source
# File lib/cuketagger/tagger.rb, line 79
def add_feature(path, lines)
  lines = lines.split(":")
  lines.delete ""

  if lines.empty?
    features_to_change << [path, nil]
  else
    lines.each do |line|
      features_to_change << [path, Integer(line)]
    end
  end
end
add_tag(thing, tag, content) click to toggle source
# File lib/cuketagger/tagger.rb, line 152
def add_tag(thing, tag, content)
  @file_offset ||= Hash.new(0)
  @line_removed ||= {}

  insertion_index = thing.source_line + @file_offset[thing.get_ancestor(:feature_file).path] - 2

  if new_line_needed?(thing, content, insertion_index)
    insertion_index += 1
    content.insert(insertion_index, '')

    @line_removed[thing] = false
    @file_offset[thing.get_ancestor(:feature_file).path] += 1
  end

  empty_line = content[insertion_index].chomp =~ /^\s*$/
  trim_line(content, insertion_index, !empty_line)

  content[insertion_index] = content[insertion_index].chomp + "#{tag_spacing(content, insertion_index)}@#{tag}\n"
end
alter_thing(thing, alteration, content) click to toggle source
# File lib/cuketagger/tagger.rb, line 124
def alter_thing(thing, alteration, content)

  case alteration.first
    when :add
      add_tag(thing, alteration.last, content)
    when :remove
      remove_tag(thing, alteration.last, content)
    when :replace
      replace_tag(thing, alteration.last.first, alteration.last.last, content)
    else
      raise "Unknown alteration type: #{alteration.first}"
  end
end
alterations() click to toggle source
# File lib/cuketagger/tagger.rb, line 92
def alterations
  @alterations ||= []
end
collect_taggable_models(feature_model) click to toggle source
# File lib/cuketagger/tagger.rb, line 116
def collect_taggable_models(feature_model)
  results = feature_model.query do
    select :model
    from scenarios, outlines, examples
  end
  [feature_model] + results.collect { |result| result[:model] }
end
feature_to_change?(file_name) click to toggle source
# File lib/cuketagger/tagger.rb, line 101
def feature_to_change?(file_name)
  features_to_change.any? { |name, line_number| name == file_name }
end
features_to_change() click to toggle source

todo - add warning if there are features that do not get changed (e.g. the user provided an incorrect file/line number or replaces a non-existant tag)

# File lib/cuketagger/tagger.rb, line 97
def features_to_change
  @features_to_change ||= Set.new
end
line_was_removed?(thing) click to toggle source
# File lib/cuketagger/tagger.rb, line 218
def line_was_removed?(thing)
  @line_removed[thing]
end
new_line_needed?(thing, content, insertion_index) click to toggle source
# File lib/cuketagger/tagger.rb, line 214
def new_line_needed?(thing, content, insertion_index)
  line_was_removed?(thing) || (non_tag_line?(content, insertion_index) && non_empty_line?(content, insertion_index))
end
non_empty_line?(content, insertion_index) click to toggle source
# File lib/cuketagger/tagger.rb, line 226
def non_empty_line?(content, insertion_index)
  content[insertion_index] !~ /^\s*$/
end
non_tag_line?(content, insertion_index) click to toggle source
# File lib/cuketagger/tagger.rb, line 222
def non_tag_line?(content, insertion_index)
  content[insertion_index] !~ /^\s*@/
end
parse(file_path, write) click to toggle source
# File lib/cuketagger/tagger.rb, line 41
def parse(file_path, write)
  return unless feature_to_change?(file_path)

  content = File.open(file_path) { |file| file.readlines }

  feature_model = CukeModeler::FeatureFile.new(file_path).feature

  io = write ? File.open(file_path, "w") : $stdout

  begin
    taggable_things = collect_taggable_models(feature_model)

    # Elements must be altered in the order that they appear in the file in order to
    # guarantee that any line adjustments are applied appropriately.
    taggable_things.sort!{|a,b| a.source_line <=> b.source_line}

    taggable_things.each do |thing|
      if thing_to_tag?(thing)
        alterations.each do |alteration|
          alter_thing(thing, alteration, content)
        end
      end
    end

    content = content.join

    io.write(content)
  ensure
    io.close unless io == $stdout
  end
end
remove_tag(thing, tag, content) click to toggle source
# File lib/cuketagger/tagger.rb, line 172
def remove_tag(thing, tag, content)
  @file_offset ||= Hash.new(0)
  @line_removed ||= {}

  relevant_tag = thing.tags.select { |tag_model| tag_model.name == "@#{tag}" }.first

  return unless relevant_tag

  insertion_index = relevant_tag.source_line + @file_offset[thing.get_ancestor(:feature_file).path] - 1
  content[insertion_index] = content[insertion_index].sub(/@#{Regexp.escape(tag)} ?/, '')

  trim_line(content, insertion_index, true)

  if content[insertion_index] =~ /^\s*$/
    content[insertion_index] = nil
    content.compact!
    @file_offset[thing.get_ancestor(:feature_file).path] -= 1
    @line_removed[thing] = true
  end
end
replace_tag(thing, old_tag, new_tag, content) click to toggle source
# File lib/cuketagger/tagger.rb, line 138
def replace_tag(thing, old_tag, new_tag, content)
  @file_offset ||= Hash.new(0)
  @line_removed ||= {}

  relevant_tag = thing.tags.select { |tag_model| tag_model.name == "@#{old_tag}" }.first

  if relevant_tag
    insertion_index = relevant_tag.source_line + @file_offset[thing.get_ancestor(:feature_file).path] - 1
    content[insertion_index] = content[insertion_index].sub("@#{old_tag}", "@#{new_tag}")
  else
    $stderr.puts "expected \"@#{old_tag}\" at #{thing.get_ancestor(:feature_file).name}:#{thing.source_line}, skipping"
  end
end
should_alter?(uri, element) click to toggle source
# File lib/cuketagger/tagger.rb, line 73
def should_alter?(uri, element)
  features_to_change.any? do |file, line|
    file == uri && (element.line == line || (line.nil? && element.kind_of?(Gherkin::Formatter::Model::Feature)))
  end
end
tag_spacing(content, insertion_index) click to toggle source
# File lib/cuketagger/tagger.rb, line 205
def tag_spacing(content, insertion_index)
  if content[insertion_index] =~ /\S/
    ' '
  else
    next_line_leading_spaces = content[insertion_index + 1].match(/^(\s*)/)[1]
    ' ' * next_line_leading_spaces.length
  end
end
thing_to_tag?(thing) click to toggle source
# File lib/cuketagger/tagger.rb, line 105
def thing_to_tag?(thing)
  #todo - pass in file name as well for performance?
  features_to_change.any? { |name, line_number|
    name_match = (name == thing.get_ancestor(:feature_file).path)
    number_match = (thing.source_line == line_number)

    (name_match && number_match) || (name_match && thing.is_a?(CukeModeler::Feature) && line_number.nil?
    )
  }
end
trim_line(content, insertion_index, keep_indentation) click to toggle source
# File lib/cuketagger/tagger.rb, line 193
def trim_line(content, insertion_index, keep_indentation)
  line_match = content[insertion_index].match(/^(\s*)(\S.*)?/)
  indentation = line_match[1]
  line_content = line_match[2]

  trimmed_line = keep_indentation ? indentation : ''
  trimmed_line += line_content.squeeze(' ').strip if line_content
  trimmed_line = "#{trimmed_line.chomp}\n"

  content[insertion_index] = trimmed_line
end