Class: Jsus::Container

Inherits:
Object
  • Object
show all
Defined in:
lib/jsus/container.rb

Overview

Container is an array that contains source files. Main difference from an array is the fact that container maintains topological sort for the source files.

This class is mostly used internally.

Constant Summary collapse

CACHE_CLEAR_METHODS =

List of methods that clear cached state of container when called.

[
  "map!", "reject!", "inject!", "collect!", "delete", "delete_at"
]
DELEGATED_METHODS =

List of methods that are delegated to underlying array of sources.

[
  "==", "to_a", "map", "map!", "each", "inject", "inject!",
  "collect", "collect!", "reject", "reject!", "detect", "size",
  "length", "[]", "empty?", "index", "include?", "select",
  "delete_if", "delete", "-", "+", "|", "&"
]

Instance Method Summary collapse

Constructor Details

#initialize(*sources) ⇒ Container

Instantiates a container from given sources.

Parameters:



13
14
15
16
17
18
19
20
21
# File 'lib/jsus/container.rb', line 13

def initialize(*sources)
  @sources        = []
  @normal_sources = []
  @extensions     = []
  @replacements   = []
  sources.each do |source|
    push(source)
  end
end

Instance Method Details

#all_sourcesArray

Includes all sources, even those that would normally be replaced. Without any order.

Returns:

  • (Array)


71
72
73
# File 'lib/jsus/container.rb', line 71

def all_sources
  @normal_sources + @extensions + @replacements
end

#clear_cache!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Clears all caches for given container.



252
253
254
# File 'lib/jsus/container.rb', line 252

def clear_cache!
  @sorted = false
end

#dependency_cacheHash

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Cached map of dependencies pointing to source files.

Returns:

  • (Hash)


193
194
195
# File 'lib/jsus/container.rb', line 193

def dependency_cache
  @dependency_cache ||= {}
end

#flattenArray

Flattens the container items

Returns:

  • (Array)


52
53
54
# File 'lib/jsus/container.rb', line 52

def flatten
  map {|item| item.respond_to?(:flatten) ? item.flatten : item }.flatten
end

#insert_extensions!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



225
226
227
228
229
230
231
232
233
234
235
# File 'lib/jsus/container.rb', line 225

def insert_extensions!
  @extensions.each do |ext|
    ext_tag = ext.extends
    @sources.dup.each_with_index do |src, i|
      if src.provides.any? {|tag| tag == ext_tag }
        @sources.insert(i+1, ext)
        break
      end
    end
  end
end

#insert_replacements!Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



238
239
240
241
242
243
244
245
246
247
# File 'lib/jsus/container.rb', line 238

def insert_replacements!
  @replacements.each do |repl|
    @sources.each_with_index do |src, i|
      if src.provides.any? {|tag| tag == repl.replaces }
        @sources[i] = repl
        break
      end
    end
  end
end

#inspectObject

Shows inspection of the container.



120
121
122
# File 'lib/jsus/container.rb', line 120

def inspect
  "#<#{self.class.name}:#{self.object_id} #{self.sources.inspect}>"
end

#output_cycles(graph) ⇒ Object

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.



173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
# File 'lib/jsus/container.rb', line 173

def output_cycles(graph)
  cycles = graph.cycles
  error_msg = []
  unless cycles.empty?
    error_msg << "Jsus has discovered you have circular dependencies in your code."
    error_msg << "Please resolve them immediately!"
    error_msg << "List of circular dependencies:"
    cycles.each do |cycle|
      error_msg << "-" * 30
      error_msg << (cycle + [cycle.first]).map {|sf| sf.filename}.join(" => ")
    end
    error_msg << "-" * 30
    error_msg = error_msg.join("\n")
    Jsus.logger.fatal(error_msg)
  end
end

#providesArray

Returns all the tags provided by source files.

Returns:

  • (Array)


127
128
129
130
# File 'lib/jsus/container.rb', line 127

def provides
  sort!
  sources.map {|s| s.provides }.flatten
end

#provides_treeJsus::Util::Tree

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Cached tree of what source files provide.

Returns:



201
202
203
# File 'lib/jsus/container.rb', line 201

def provides_tree
  @provides_tree ||= provides_tree!
end

#provides_tree!Jsus::Util::Tree

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Returns tree of what source files provide.

Returns:



209
210
211
212
213
214
215
216
217
218
219
220
221
222
# File 'lib/jsus/container.rb', line 209

def provides_tree!
  tree = Util::Tree.new
  # Provisions
  @normal_sources.each do |file|
    provisions = file.provides
    if replacement = @replacements.detect {|r| provisions.any? {|tag| tag == r.replaces } }
      file = replacement
    end
    provisions.each do |tag|
      tree[tag] = file
    end
  end
  tree
end

#push(source) ⇒ Object Also known as: <<

Pushes an item to the container

Parameters:



28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/jsus/container.rb', line 28

def push(source)
  if source
    if source.kind_of?(Array)
      source.each {|s| self.push(s) }
    elsif source.kind_of?(Container)
      source.all_sources.each {|s| self.push(s) }
    else
      if source.extension?
        @extensions << source unless @extensions.include?(source)
      elsif source.replacement?
        @replacements << source unless @replacements.include?(source)
      else
        @normal_sources << source unless @normal_sources.include?(source)
      end
    end
  end
  clear_cache!
  self
end

#required_files(root = nil) ⇒ Array

Lists all the required files (dependencies and extensions) for the sources in the container. Consider it a projection from source files space onto filesystem space.

Optionally accepts a filesystem point to calculate relative paths from.

Parameters:

  • root (String) (defaults to: nil)

    root point from which the relative paths are calculated. When omitted, full paths are returned.

Returns:

  • (Array)

    ordered list of required files



108
109
110
111
112
113
114
115
116
# File 'lib/jsus/container.rb', line 108

def required_files(root = nil)
  sort!
  files = sources.map {|s| s.required_files }.flatten
  if root
    root = Pathname.new(File.expand_path(root))
    files = files.map {|f| Pathname.new(File.expand_path(f)).relative_path_from(root).to_s }
  end
  files
end

#requiresArray

Returns all the tags required by source files, except for those which are provided by other files in the container (i.e. unresolved dependencies)

Returns:

  • (Array)


136
137
138
139
# File 'lib/jsus/container.rb', line 136

def requires
  sort!
  sources.map {|s| s.requires }.flatten - provides
end

#sort!self

Topologically sorts items in container if required.

Returns:

  • (self)


79
80
81
82
83
84
85
86
87
88
# File 'lib/jsus/container.rb', line 79

def sort!
  unless sorted?
    @sources = topsort
    insert_extensions!
    insert_replacements!
    @sources.uniq!
    @sorted = true
  end
  self
end

#sorted?Boolean

Returns whether container requires sorting.

Returns:

  • (Boolean)


94
95
96
# File 'lib/jsus/container.rb', line 94

def sorted?
  !!@sorted
end

#sourcesArray Also known as: to_a

Contains the source files.

Returns:

  • (Array)


60
61
62
63
# File 'lib/jsus/container.rb', line 60

def sources
  sort!
  @sources
end

#topsortObject

This method is part of a private API. You should avoid using this method if possible, as it may be removed or be changed in the future.

Performs topological sort inside current container.



146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
# File 'lib/jsus/container.rb', line 146

def topsort
  graph = RGL::DirectedAdjacencyGraph.new
  # init vertices
  items = @normal_sources
  items.each {|item| graph.add_vertex(item) }
  # init edges
  items.each do |item|
    item.dependencies.each do |dependency|
      # If we can find items that provide the required dependency...
      # (dependency could be a wildcard as well, hence items)
      dependency_cache[dependency] ||= provides_tree.glob(dependency)
      # ... we draw an edge from every required item to the dependant item
      dependency_cache[dependency].each do |required_item|
        graph.add_edge(required_item, item)
      end
    end
  end

  begin
    graph.topsorted_vertices
  rescue RGL::TopsortedGraphHasCycles => e
    output_cycles(graph)
    raise e # fail fast
  end
end