class Cukedep::FeatureModel

The internal representation of a set of feature files. Dependencies: use topological sort TSort module ruby-doc.org/stdlib-2.6/libdoc/tsort/rdoc/index.html See also: Is this topological sort in Ruby flawed?

Constants

FeatureDependencies

Attributes

dependencies[R]

An Array of FeatureDependencies

feature_files[R]

Public Class Methods

new(theFeatureFiles) click to toggle source
# File lib/cukedep/feature-model.rb, line 55
def initialize(theFeatureFiles)
  @feature_files = validated_model(theFeatureFiles)
end

Public Instance Methods

anonymous_features() click to toggle source

The list of feature files without identifiers

# File lib/cukedep/feature-model.rb, line 76
def anonymous_features
  feature_files.select { |ff| ff.feature.anonymous? }
end
draw_dependency_graph(theDOTfile, isVerbose = false) click to toggle source

Create a graphical representation of the dependencies. The result is a DOT file that can be rendered via the DOT application from the GraphViz distribution.

# File lib/cukedep/feature-model.rb, line 132
def draw_dependency_graph(theDOTfile, isVerbose = false)
  puts "  #{theDOTfile}" if isVerbose
  dot_file = File.open(theDOTfile, 'w')
  emit_heading(dot_file)
  emit_body(dot_file)
  emit_trailing(dot_file)
end
draw_edge(anIO, aDependency) click to toggle source

Draw an edge between feature files having dependencies.

# File lib/cukedep/feature-model.rb, line 207
def draw_edge(anIO, aDependency)
  source_id = feature_files.find_index(aDependency.dependee)
  target_ids = aDependency.dependents.map do |a_target|
    feature_files.find_index(a_target)
  end

  target_ids.each do |t_id|
    anIO.puts "\tnode_#{source_id} -> node_#{t_id};"
  end
end
draw_node(anIO, aFeatureFile, anIndex) click to toggle source

Draw a refinement node in DOT format

# File lib/cukedep/feature-model.rb, line 195
def draw_node(anIO, aFeatureFile, anIndex)
  basename = File.basename(aFeatureFile.filepath, '.feature')
  its_feature = aFeatureFile.feature
  if its_feature.anonymous?
    id_suffix = ''
  else
    id_suffix = " -- #{its_feature.identifier}"
  end
  anIO.puts %Q(    node_#{anIndex} [label = "#{basename}#{id_suffix}"];)
end
emit_body(anIO) click to toggle source

Output the nodes as graph vertices + their edges with parent node

# File lib/cukedep/feature-model.rb, line 161
  def emit_body(anIO)
    anIO.puts <<-DOT
  subgraph island {
    node [shape = box, style=filled, color=lightgray];
DOT
    feature_files.each_with_index do |ff, i|
      draw_node(anIO, ff, i) if ff.feature.anonymous?
    end

    anIO.puts <<-DOT
    label = "Isolated features";
    }

  subgraph dependencies {
    node [shape = box, fillcolor = none];
DOT
    feature_files.each_with_index do |ff, i|
      draw_node(anIO, ff, i) unless ff.feature.anonymous?
    end
    anIO.puts <<-DOT
    label = "Dependencies";
  }

  // The edges represent dependencies
DOT
    dependencies.each { |a_dep| draw_edge(anIO, a_dep) }
  end
emit_heading(anIO) click to toggle source
# File lib/cukedep/feature-model.rb, line 140
  def emit_heading(anIO)
    dir = File.dirname(File.absolute_path(feature_files[0].filepath))
    heading = <<-DOT
// Graph of dependencies of feature files in directory:
// '#{dir}'
// This file uses the DOT syntax, a free utility from the Graphviz toolset.
// Graphviz is available at: www.graphviz.org
// File generated on #{Time.now.asctime}.

digraph g {
  size = "7, 11"; // Dimensions in inches...
  center = true;
  rankdir = BT; // Draw from bottom to top
  label = "\\nDependency graph of '#{dir}'";

  // Nodes represent feature files
DOT
    anIO.write heading
  end
emit_trailing(anIO) click to toggle source

Output the closing part of the graph drawing

# File lib/cukedep/feature-model.rb, line 190
def emit_trailing(anIO)
  anIO.puts '} // End of graph'
end
generate_rake_tasks(rakefile, theProjDir) click to toggle source
# File lib/cukedep/feature-model.rb, line 218
def generate_rake_tasks(rakefile, theProjDir)
  puts "  #{rakefile}"
  grandparent_path = Pathname.new(File.dirname(__FILE__)).parent.parent
  template_source = File.read(grandparent_path + './templates/rake.erb')

  # Create one template engine instance
  engine = ERB.new(template_source)

  source_dir = File.absolute_path(Dir.getwd)
  proj_dir = File.absolute_path(theProjDir)
  anonymous = anonymous_features.map(&:basename)
  feature_ids = feature_files.map { |ff| ff.feature.identifier }
  feature_ids.compact!
  deps = dependencies.reject { |dep| dep.dependee.feature.anonymous? }

  # Generate the text representation with given context
  file_source = engine.result(binding)
  File.open(rakefile, 'w') { |f| f.write(file_source) }
end
mapping_reports(fileFeature2id, fileId2Feature, isVerbose = false) click to toggle source

Generate CSV files detailing the feature to identifier mapping and vise versa

# File lib/cukedep/feature-model.rb, line 105
def mapping_reports(fileFeature2id, fileId2Feature, isVerbose = false)
  puts "  #{fileFeature2id}" if isVerbose
  # Generate the feature file name => feature identifier report
  CSV.open(fileFeature2id, 'wb') do |f|
    f << ['Feature file', 'Identifier']
    feature_files.each do |ff|
      identifier = ff.feature.identifier
      filename = File.basename(ff.filepath)
      f << [filename, identifier.nil? ? 'nil' : identifier]
    end
  end

  # Generate the feature file name => feature identifier report
  puts "  #{fileId2Feature}" if isVerbose
  CSV.open(fileId2Feature, 'wb') do |f|
    f << ['identifier', 'feature file']
    feature_files.each do |ff|
      identifier = ff.feature.identifier
      filename = File.basename(ff.filepath)
      f << [identifier, filename] unless identifier.nil?
    end
  end
end
select_by_ids(*theIds) click to toggle source

Retrieve the feature file matching the given feature identifiers theIds one or more Strings, each being one feature identifier

# File lib/cukedep/feature-model.rb, line 61
def select_by_ids(*theIds)
  features_by_ids = id2features
  selection = theIds.each_with_object([]) do |an_id, sub_result|
    found_feature = features_by_ids[an_id]
    if found_feature.nil?
      raise StandardError, "No feature file with identifier '#{an_id}'."
    end

    sub_result << found_feature
  end

  return selection
end
sort_features_by_dep() click to toggle source

Sort the feature files by dependency order.

# File lib/cukedep/feature-model.rb, line 94
def sort_features_by_dep
  dep_links = dependency_links
  graph = DepGraph.new(dep_links)
  sorted_deps = graph.tsort

  all_sorted = sorted_deps.map(&:dependee)
  @sorted_features = all_sorted.reject { |f| f.feature.anonymous? }
end

Protected Instance Methods

id2features() click to toggle source

Build the mapping: feature identifier => feature

# File lib/cukedep/feature-model.rb, line 246
def id2features
  mapping = feature_files.each_with_object({}) do |file, mp|
    feature_id = file.feature.identifier
    mp[feature_id] = file unless feature_id.nil?
  end

  return mapping
end
resolve_dependencies(aMapping) click to toggle source

Given a feature identifier => feature mapping, resolve the dependency tags; that is, Establish links between a feature file object and its dependent feature file objects.

# File lib/cukedep/feature-model.rb, line 259
def resolve_dependencies(aMapping)
  @dependencies = []

  feature_files.each do |feature_file|
    feature = feature_file.feature
    its_id = feature.identifier
    dep_tags = feature.dependency_tags
    # Complain when self dependency detected
    if dep_tags.include?(its_id)
      msg = "Feature with identifier #{its_id} depends on itself!"
      raise StandardError, msg
    end

    # Complain when dependency tag refers to an unknown feature
    dependents = dep_tags.map do |a_tag|
      unless aMapping.include?(a_tag)
        msg_p1 = "Feature with identifier '#{its_id}'"
        msg_p2 = " depends on unknown feature '#{a_tag}'"
        raise StandardError, msg_p1 + msg_p2
      end
      aMapping[a_tag]
    end

    @dependencies << FeatureDependencies.new(feature_file, dependents)
  end

  return @dependencies
end
validated_model(theFeatureFiles) click to toggle source
# File lib/cukedep/feature-model.rb, line 241
def validated_model(theFeatureFiles)
  return theFeatureFiles
end