jsus 0.1.4 → 0.1.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,63 +1,121 @@
1
+ #
2
+ # Pool class is designed for three purposes:
3
+ # * Maintain connections between SourceFiles and/or Packages
4
+ # * Resolve dependencies
5
+ # * Look up extensions
6
+ #
7
+ #
8
+
9
+
1
10
  module Jsus
2
11
  class Pool
3
12
 
13
+ # Constructors
14
+
15
+ #
16
+ # Basic constructor.
17
+ #
18
+ # Accepts an optional directory argument, when it is set, it looks up for
19
+ # packages from the given directory and if it finds any, adds them to the pool.
20
+ #
21
+ # Directory is considered a Package directory if it contains +package.yml+ file.
22
+ #
4
23
  def initialize(dir = nil)
5
24
  if dir
6
25
  Dir[File.join(dir, '**', 'package.yml')].each do |package_path|
7
- Package.new(Pathname.new(package_path).parent.to_s, :pool => self)
26
+ Package.new(File.dirname(package_path), :pool => self)
8
27
  end
9
28
  end
10
29
  end
11
-
30
+
31
+
32
+ #
33
+ # An array containing all the packages in the pool. Unordered.
34
+ #
12
35
  def packages
13
36
  @packages ||= []
14
- @packages.uniq!
15
- @packages
16
37
  end
17
38
 
39
+ #
40
+ # Container with all the sources in the pool. It is actually ordered in case you
41
+ # want to include ALL the source files from the pool.
42
+ #
18
43
  def sources
19
44
  @sources ||= Container.new
20
45
  end
21
46
 
47
+ #
48
+ # Looks up for a file providing given tag or tag key.
49
+ #
50
+ # If given a source file, returns the input.
51
+ #
22
52
  def lookup(source_or_key)
23
53
  case source_or_key
24
54
  when String
55
+ provides_map[Tag[source_or_key]]
56
+ when Tag
25
57
  provides_map[source_or_key]
26
- when Jsus::SourceFile
58
+ when SourceFile
27
59
  source_or_key
28
60
  else
29
- raise "Illegal lookup query. Expected String or SourceFile, " <<
61
+ raise "Illegal lookup query. Expected String, Tag or SourceFile, " <<
30
62
  "given #{source_or_key.inspect}, an instance of #{source_or_key.class.name}."
31
63
  end
32
64
  end
65
+
33
66
 
34
- def lookup_direct_dependencies(source_or_source_key)
35
- source = lookup(source_or_source_key)
36
- result = source.external_dependencies.map {|d| lookup(d)}
37
- Container.new(*result)
38
- end
39
-
40
- def lookup_dependencies(source_or_source_key)
67
+ #
68
+ # Looks up for dependencies for given file recursively.
69
+ #
70
+ # Returns an instance of Container which contains needed files sorted.
71
+ #
72
+ def lookup_dependencies(source_or_source_key)
41
73
  source = lookup(source_or_source_key)
42
74
  result = Container.new
43
- dependencies = lookup_direct_dependencies(source)
44
- while !dependencies.empty?
45
- dependencies.each { |d| result.push(d) }
46
- dependencies = dependencies.map {|d| lookup_direct_dependencies(d).to_a }.flatten.uniq
75
+ if source
76
+ dependencies = lookup_direct_dependencies(source)
77
+ while !dependencies.empty?
78
+ dependencies.each { |d| result.push(d) }
79
+ dependencies = dependencies.map {|d| lookup_direct_dependencies(d).to_a }.flatten.uniq
80
+ end
81
+ result.sort!
47
82
  end
48
- result.sort!
83
+ result
49
84
  end
50
85
 
86
+ #
87
+ # Returns an array with SourceFile-s with extensions for given tag.
88
+ #
89
+ def lookup_extensions(tag_or_tag_key)
90
+ tag = Tag[tag_or_tag_key]
91
+ extensions_map[tag]
92
+ end
93
+
94
+ #
95
+ # Pushes an item into a pool.
96
+ #
97
+ # Can be given:
98
+ # * SourceFile
99
+ # * Package (pushing all the child source files into the pool)
100
+ # * Array or Container (pushing all the contained source files into the pool)
101
+ #
102
+ # returns self.
51
103
  def <<(source_or_sources_or_package)
52
- case
104
+ case
53
105
  when source_or_sources_or_package.kind_of?(SourceFile)
54
- source = source_or_sources_or_package
106
+ source = source_or_sources_or_package
55
107
  sources << source
56
- source.provides.each {|p| provides_map[p] = source }
108
+ if source.extends
109
+ extensions_map[source.extends] ||= []
110
+ extensions_map[source.extends] << source
111
+ else
112
+ source.provides.each {|p| provides_map[p] = source }
113
+ end
57
114
  when source_or_sources_or_package.kind_of?(Package)
58
115
  package = source_or_sources_or_package
59
116
  packages << package
60
- package.source_files.each {|s| self << s}
117
+ package.source_files.each {|s| s.pool = self }
118
+ package.extensions.each {|e| e.pool = self }
61
119
  when source_or_sources_or_package.kind_of?(Array) || source_or_sources_or_package.kind_of?(Container)
62
120
  sources = source_or_sources_or_package
63
121
  sources.each {|s| self << s}
@@ -66,11 +124,26 @@ module Jsus
66
124
  end
67
125
 
68
126
  (Array.instance_methods - self.instance_methods).each {|m| delegate m, :to => :sources }
127
+ # Private API
128
+
129
+ #
130
+ # Looks up direct dependencies for the given source_file or provides tag.
131
+ # You probably will find yourself using #include_dependencies instead.
132
+ #
133
+ def lookup_direct_dependencies(source_or_source_key)
134
+ source = lookup(source_or_source_key)
135
+ result = source ? source.external_dependencies.map {|d| lookup(d)} : []
136
+ Container.new(*result)
137
+ end
69
138
 
70
139
  protected
71
140
 
72
- def provides_map
141
+ def provides_map # :nodoc:
73
142
  @provides_map ||= {}
74
143
  end
144
+
145
+ def extensions_map # :nodoc:
146
+ @extensions_map ||= Hash.new{|hash, key| hash[key] = [] }
147
+ end
75
148
  end
76
149
  end
@@ -1,18 +1,39 @@
1
+ #
2
+ # SourceFile is a base for any Jsus operation.
3
+ #
4
+ # It contains basic info about source as well as file content.
5
+ #
6
+ #
1
7
  module Jsus
2
8
  class SourceFile
3
- attr_accessor :relative_filename
4
- attr_accessor :filename
5
- attr_accessor :content
6
- attr_accessor :package
7
- # constructors
9
+ attr_accessor :relative_filename, :filename, :package # :nodoc:
10
+ # Constructors
8
11
 
12
+ # Basic constructor.
13
+ #
14
+ # You probably should use SourceFile.from_file instead.
15
+ #
16
+ # But if you know what you are doing, it accepts the following values:
17
+ # * +package+ — an instance of Package, normally passed by a parent
18
+ # * +relative_filename+ — used in Package, for generating tree structure of the source files
19
+ # * +filename+ — full filename for the given package
20
+ # * +content+ — file content of the source file
21
+ # * +pool+ — an instance of Pool
9
22
  def initialize(options = {})
10
- [:header, :relative_filename, :filename, :content, :package].each do |field|
23
+ [:package, :header, :relative_filename, :filename, :content, :pool].each do |field|
11
24
  send("#{field}=", options[field]) if options[field]
12
25
  end
13
- options[:pool] << self if options[:pool]
14
26
  end
15
27
 
28
+ #
29
+ # Initializes a SourceFile given the filename and options
30
+ #
31
+ # options:
32
+ # * <tt>:pool:</tt> — an instance of Pool
33
+ # * <tt>:package:</tt> — an instance of Package
34
+ #
35
+ # returns either an instance of SourceFile or nil when it's not possible to parse the input
36
+ #
16
37
  def self.from_file(filename, options = {})
17
38
  if File.exists?(filename)
18
39
  source = File.read(filename)
@@ -24,70 +45,183 @@ module Jsus
24
45
  options[:content] = source
25
46
  new(options)
26
47
  else
27
- #puts "WARNING: file #{filename} has invalid format (should be YAML)"
48
+ # puts "WARNING: file #{filename} has invalid format (should be YAML)"
28
49
  nil
29
50
  end
30
51
  else
31
- #puts "WARNING: file #{filename} does not exist"
52
+ # puts "WARNING: file #{filename} does not exist"
32
53
  nil
33
54
  end
34
55
  end
35
56
 
36
57
  # Public API
37
- def header=(new_header)
38
- @header = new_header
39
- # prepare defaults
40
- @header["description"] ||= ""
41
- @header["requires"] = [@header["requires"] || []].flatten
42
- @header["requires"].map! {|r| r.gsub(/^(\.)?\//, "") }
43
- @header["provides"] = [@header["provides"] || []].flatten
44
- end
45
58
 
59
+ #
60
+ # Returns a header parsed from YAML-formatted source file first comment.
61
+ # Contains information about authorship, naming, source files, etc.
62
+ #
46
63
  def header
47
64
  self.header = {} unless @header
48
65
  @header
49
66
  end
50
67
 
51
- def dependencies(options = {})
52
- if !options[:short] && package
53
- header["requires"].map {|r| r.index("/") ? r : "#{package.name}/#{r}"}
54
- else
55
- header["requires"]
56
- end
68
+ #
69
+ # A string containing the description of the source file.
70
+ #
71
+ def description
72
+ header["description"]
73
+ end
74
+
75
+ #
76
+ # Returns an array of dependencies tags. Unordered.
77
+ #
78
+ def dependencies
79
+ @dependencies
57
80
  end
58
81
  alias_method :requires, :dependencies
59
82
 
83
+ #
84
+ # Returns an array with names of dependencies. Unordered.
85
+ # Accepts options:
86
+ # * <tt>:short:</tt> — whether inner dependencies should not prepend package name
87
+ # e.g. 'Class' instead of 'Core/Class' when in package 'Core').
88
+ # Doesn't change anything for external dependencies
89
+ #
90
+ def dependencies_names(options = {})
91
+ dependencies.map {|d| d.name(options) }
92
+ end
93
+ alias_method :requires_names, :dependencies_names
94
+
95
+ #
96
+ # Returns an array of external dependencies tags. Unordered.
97
+ #
60
98
  def external_dependencies
61
- dependencies(:short => true).select {|d| d.index("/") }
99
+ dependencies.select {|d| d.external? }
62
100
  end
63
101
 
64
- def internal_dependencies
65
- dependencies(:short => true) - external_dependencies
102
+ #
103
+ # Returns an array with names for external dependencies. Unordered.
104
+ #
105
+ def external_dependencies_names
106
+ external_dependencies.map {|d| d.name }
66
107
  end
67
108
 
68
- def provides(options = {})
69
- if !options[:short] && package
70
- header["provides"].map {|p| "#{package.name}/#{p}"}
71
- else
72
- header["provides"]
109
+ #
110
+ # Returns an array with provides tags.
111
+ #
112
+ def provides
113
+ @provides
114
+ end
115
+
116
+ #
117
+ # Returns an array with provides names.
118
+ # Accepts options:
119
+ # * <tt>:short:</tt> — whether provides should not prepend package name
120
+ # e.g. 'Class' instead of 'Core/Class' when in package 'Core')
121
+ def provides_names(options = {})
122
+ provides.map {|p| p.name(options)}
123
+ end
124
+
125
+
126
+ #
127
+ # Returns a tag for source file, which this one is an extension for.
128
+ #
129
+ # E.g.: file Foo.js in package Core provides ['Class', 'Hash']. File Bar.js in package Bar
130
+ # extends 'Core/Class'. That means its contents would be appended to the Foo.js when compiling
131
+ # the result.
132
+ #
133
+ def extends
134
+ @extends
135
+ end
136
+
137
+ #
138
+ # Returns whether the source file is an extension.
139
+ #
140
+ def extension?
141
+ extends && !extends.empty?
142
+ end
143
+
144
+ #
145
+ # Returns an array of included extensions for given source.
146
+ #
147
+ def extensions
148
+ @extensions ||= []
149
+ @extensions = @extensions.flatten.compact.uniq
150
+ @extensions
151
+ end
152
+
153
+ def extensions=(new_value) # :nodoc:
154
+ @extensions = new_value
155
+ end
156
+
157
+ #
158
+ # Looks up for extensions in the #pool and then includes
159
+ # extensions for all the provides tag this source file has.
160
+ #
161
+ def include_extensions!
162
+ if pool
163
+ provides.each do |p|
164
+ extensions << pool.lookup_extensions(p)
165
+ end
73
166
  end
74
167
  end
75
168
 
76
- def description
77
- header["description"]
169
+ #
170
+ # Returns an array of files required by this files including all the filenames for extensions.
171
+ # SourceFile filename always goes first, all the extensions are unordered.
172
+ #
173
+ def required_files
174
+ [filename, extensions.map {|e| e.filename}].flatten
78
175
  end
79
176
 
177
+ #
178
+ # Returns a hash containing basic info with dependencies/provides tags' names
179
+ # and description for source file.
180
+ #
80
181
  def to_hash
81
182
  {
82
183
  "desc" => description,
83
- "requires" => dependencies(:short => true),
84
- "provides" => provides(:short => true)
184
+ "requires" => dependencies_names(:short => true),
185
+ "provides" => provides_names(:short => true)
85
186
  }
86
187
  end
87
188
 
88
- def inspect
189
+ def inspect # :nodoc:
89
190
  self.to_hash.inspect
90
191
  end
192
+ # Private API
193
+
194
+ def header=(new_header) # :nodoc:
195
+ @header = new_header
196
+ # prepare defaults
197
+ @header["description"] ||= ""
198
+ # handle tags
199
+ @dependencies = [@header["requires"] || []].flatten
200
+ @dependencies.map! {|tag_name| Tag.new(tag_name, :package => package) }
201
+ @provides = [@header["provides"] || []].flatten
202
+ @provides.map! {|tag_name| Tag.new(tag_name, :package => package) }
203
+ @extends = (@header["extends"] && !@header["extends"].empty?) ? Tag.new(@header["extends"]) : nil
204
+ end
205
+
206
+ def content=(new_value) # :nodoc:
207
+ @content = new_value
208
+ end
209
+
210
+ def content # :nodoc:
211
+ [@content, extensions.map {|e| e.content}].flatten.compact.join("\n")
212
+ end
213
+
214
+ # Assigns an instance of Jsus::Pool to the source file.
215
+ # Also performs push to that pool.
216
+ def pool=(new_value)
217
+ @pool = new_value
218
+ @pool << self if @pool
219
+ end
91
220
 
221
+ # A pool which the source file is assigned to. Used in #include_extensions!
222
+ def pool
223
+ @pool
224
+ end
225
+
92
226
  end
93
227
  end
@@ -0,0 +1,142 @@
1
+ #
2
+ # Tag is basically just a string that contains a package name and a name for class
3
+ # (or not necessarily a class) which the given SourceFile provides/requires/extends.
4
+ #
5
+ module Jsus
6
+ class Tag
7
+ attr_accessor :package, :external # :nodoc:
8
+
9
+ # Constructors
10
+
11
+ #
12
+ # Creates a tag from given name/options.
13
+ #
14
+ # The way it works may seem a bit tricky but actually it parses name/options
15
+ # combinations in different ways and may be best described by examples:
16
+ #
17
+ # a = Tag.new("Class") # :package_name => "", :name => "Class", :external => false
18
+ # b = Tag.new("Core/Class") # :package_name => "Core", :name => "Class", :external => true
19
+ # core = Package.new(...) # let's consider its name is 'Core'
20
+ # c = Tag.new("Class", :package => core) # :package_name => "Core", :name => "Class", :external => false
21
+ # d = Tag.new("Core/Class", :package => core) # :package_name => "Core", :name => "Class", :external => false
22
+ # mash = Package.new(...) # let's consider its name is 'Mash'
23
+ # e = Tag.new("Core/Class", :package => mash) # :package_name => "Core", :name => "Class", :external => true
24
+ #
25
+ # Between all those, tags b,c,d and e are equal, meaning they all use
26
+ # the same spot in Hash or whatever else.
27
+ #
28
+ def initialize(name, options = {})
29
+ normalized_options = Tag.normalize_name_and_options(name, options)
30
+ [:name, :package, :package_name, :external].each do |field|
31
+ self.send("#{field}=", normalized_options[field])
32
+ end
33
+ end
34
+
35
+ def self.new(tag_or_name, *args, &block) # :nodoc:
36
+ if tag_or_name.kind_of?(Tag)
37
+ tag_or_name
38
+ else
39
+ super
40
+ end
41
+ end
42
+
43
+ # alias for Tag.new
44
+ def self.[](*args)
45
+ new(*args)
46
+ end
47
+
48
+ # Public API
49
+
50
+ #
51
+ # Returns true if tag is external. See initialization for more info on cases.
52
+ #
53
+ def external?
54
+ !!external
55
+ end
56
+
57
+ #
58
+ # Returns a well-formed name for the tag.
59
+ # Options:
60
+ # * +:short:+ — whether the tag should try using short form
61
+ #
62
+ # Important note: only non-external tags support short forms.
63
+ #
64
+ # Tag.new('Core/Class').name(:short => true) # => 'Core/Class'
65
+ # core = Package.new(...) # let's consider its name is 'Core'
66
+ # Tag.new('Core/Class', :package => core).name(:short => true) # => 'Class'
67
+ def name(options = {})
68
+ if !package_name || package_name.empty? || (options[:short] && !external?)
69
+ @name
70
+ else
71
+ "#{package_name}/#{@name}"
72
+ end
73
+ end
74
+ alias_method :to_s, :name
75
+
76
+ # Returns its package name or an empty string
77
+ def package_name
78
+ @package_name ||= (@package ? @package.name : "")
79
+ end
80
+
81
+ # Returns true if its name is empty
82
+ def empty?
83
+ @name.empty?
84
+ end
85
+
86
+ # Returns true if it has the same name as other tag
87
+ def ==(other)
88
+ if other.kind_of?(Tag)
89
+ self.name == other.name
90
+ else
91
+ super
92
+ end
93
+ end
94
+
95
+ # Private API
96
+
97
+ def self.normalize_name_and_options(name, options = {}) # :nodoc:
98
+ result = {}
99
+ name.gsub!(%r(^(\.)?/), "")
100
+ if name.index("/")
101
+ parsed_name = name.split("/")
102
+ result[:package_name], result[:name] = parsed_name[0..-2].join("/"), parsed_name[-1]
103
+ result[:external] = options[:package] ? (result[:package_name] != options[:package].name) : true
104
+ else
105
+ if options[:package]
106
+ result[:package] = options[:package]
107
+ result[:package_name] = options[:package].name
108
+ end
109
+ result[:name] = name
110
+ end
111
+ result
112
+ end
113
+
114
+ def self.normalized_options_to_full_name(options) # :nodoc:
115
+ [options[:package_name], options[:name]].compact.join("/")
116
+ end
117
+
118
+ def self.name_and_options_to_full_name(name, options = {}) # :nodoc:
119
+ normalized_options_to_full_name(normalize_name_and_options(name, options))
120
+ end
121
+
122
+ def package_name=(new_value) # :nodoc:
123
+ @package_name = new_value
124
+ end
125
+
126
+ def name=(new_value) # :nodoc:
127
+ @name = new_value
128
+ end
129
+
130
+ def eql?(other) # :nodoc:
131
+ self.==(other)
132
+ end
133
+
134
+ def hash # :nodoc:
135
+ self.name.hash
136
+ end
137
+
138
+ def inspect # :nodoc
139
+ "<Jsus::Tag: #{name}>"
140
+ end
141
+ end
142
+ end