Class: Cukedep::FeatureModel

Inherits:
Object
  • Object
show all
Defined in:
lib/cukedep/feature-model.rb

Overview

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

Defined Under Namespace

Classes: DepGraph, FeatureDependencies

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(theFeatureFiles) ⇒ FeatureModel

Returns a new instance of FeatureModel.



59
60
61
# File 'lib/cukedep/feature-model.rb', line 59

def initialize(theFeatureFiles)
  @feature_files = validated_model(theFeatureFiles)
end

Instance Attribute Details

#dependenciesObject (readonly)

An Array of FeatureDependencies



57
58
59
# File 'lib/cukedep/feature-model.rb', line 57

def dependencies
  @dependencies
end

#feature_filesObject (readonly)

Returns the value of attribute feature_files.



54
55
56
# File 'lib/cukedep/feature-model.rb', line 54

def feature_files
  @feature_files
end

Instance Method Details

#anonymous_featuresObject

The list of feature files without identifiers



79
80
81
# File 'lib/cukedep/feature-model.rb', line 79

def anonymous_features()
  feature_files.select { |ff| ff.feature.anonymous? }
end

Build an array of FileDependencies objects.



84
85
86
87
88
89
90
91
92
93
94
# File 'lib/cukedep/feature-model.rb', line 84

def dependency_links()
  if @dependencies.nil?
    # Build the mapping: feature identifier => feature
    features_by_id = id2features
    
    # Resolve the dependency tags
    resolve_dependencies(features_by_id)
  end
  
  return @dependencies
end

#draw_dependency_graph(theDOTfile, isVerbose = false) ⇒ Object

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.



136
137
138
139
140
141
142
# File 'lib/cukedep/feature-model.rb', line 136

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) ⇒ Object

Draw an edge between feature files having dependencies.



212
213
214
215
216
217
218
219
220
221
# File 'lib/cukedep/feature-model.rb', line 212

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) ⇒ Object

Draw a refinement node in DOT format



200
201
202
203
204
205
206
207
208
209
# File 'lib/cukedep/feature-model.rb', line 200

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) ⇒ Object

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



166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/cukedep/feature-model.rb', line 166

def emit_body(anIO)
anIO.puts  <<-EOS
subgraph island {
  node [shape = box, style=filled, color=lightgray];
EOS
  feature_files.each_with_index do |ff, i|
    draw_node(anIO, ff, i) if ff.feature.anonymous?
  end
  
  anIO.puts <<-EOS
  label = "Isolated features";
  }

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

// The edges represent dependencies
EOS
  dependencies.each { |a_dep| draw_edge(anIO, a_dep) }
end

#emit_heading(anIO) ⇒ Object



145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
# File 'lib/cukedep/feature-model.rb', line 145

def emit_heading(anIO)
  dir = File.dirname(File.absolute_path(feature_files[0].filepath))
  heading = <<-EOS
// 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
EOS
  anIO.write heading
end

#emit_trailing(anIO) ⇒ Object

Output the closing part of the graph drawing



195
196
197
# File 'lib/cukedep/feature-model.rb', line 195

def emit_trailing(anIO)
  anIO.puts '} // End of graph'
end

#generate_rake_tasks(rakefile, theProjDir) ⇒ Object



224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/cukedep/feature-model.rb', line 224

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 { |ff| ff.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) ⇒ Object

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



109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
# File 'lib/cukedep/feature-model.rb', line 109

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) ⇒ Object

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



65
66
67
68
69
70
71
72
73
74
75
76
# File 'lib/cukedep/feature-model.rb', line 65

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?
      fail(StandardError, "No feature file with identifier '#{an_id}'.")
    end
    sub_result << found_feature
  end
  
  return selection
end

#sort_features_by_depObject

Sort the feature files by dependency order.



98
99
100
101
102
103
104
105
# File 'lib/cukedep/feature-model.rb', line 98

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