C0 code coverage information

Generated on Wed Oct 07 08:34:05 -0700 2009 with rcov 0.8.2.1


Code reported as executed by Ruby looks like this...
and this: this line is also marked as covered.
Lines considered as run by rcov, but not reported by Ruby, look like this,
and this: these lines were inferred by rcov (using simple heuristics).
Finally, here's a line marked as not executed.
Name Total lines Lines of code Total coverage Code coverage
lib/buildr/packaging/artifact_namespace.rb 972 527
95.6%  
91.8%  
  1 # Licensed to the Apache Software Foundation (ASF) under one or more
  2 # contributor license agreements.  See the NOTICE file distributed with this
  3 # work for additional information regarding copyright ownership.  The ASF
  4 # licenses this file to you under the Apache License, Version 2.0 (the
  5 # "License"); you may not use this file except in compliance with the License.
  6 # You may obtain a copy of the License at
  7 #
  8 #    http://www.apache.org/licenses/LICENSE-2.0
  9 #
 10 # Unless required by applicable law or agreed to in writing, software
 11 # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 12 # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 13 # License for the specific language governing permissions and limitations under
 14 # the License.
 15 
 16 
 17 require 'buildr/packaging/version_requirement'
 18 
 19 
 20 module Buildr
 21   
 22   # An ArtifactNamespace is a hierarchical dictionary used to manage ArtifactRequirements.
 23   # It can be used to have different artifact versions per project
 24   # or to allow users to select a version for addons or modules.
 25   #
 26   # Namespaces are opened using the Buildr.artifact_ns method, most important methods are:
 27   # [need]      Used to create a requirement on the namespace.
 28   # [use]       Set the artifact version to use for a requirement.
 29   # [values_at] Reference requirements by name.
 30   # [each]      Return each ArtifactRequirement in the namespace.
 31   # The method_missing method for instances provides some syntactic sugar to these.
 32   # See the following examples, and the methods for ArtifactRequirement.
 33   #
 34   # = Avoiding constant polution on buildfile
 35   #
 36   # Each project has its own ArtifactNamespace inheriting the one from the 
 37   # parent project up to the root namespace.
 38   # 
 39   # Consider the following snippet, as project grows, each subproject 
 40   # may need diferent artifact combinations and/or versions. Asigning 
 41   # artifact specifications to constants can make it painful to maintain
 42   # their references even if using structs/hashes.
 43   #
 44   #   -- buildfile --
 45   #   SPRING = 'org.springframework:spring:jar:2.5'
 46   #   SPRING_OLD = 'org.springframework:spring:jar:1.0'
 47   #   LOGGING = ['comons-logging:commons-logging:jar:1.1.1', 
 48   #              'log4j:log4j:jar:1.2.15']
 49   #   WL_LOGGING = artifact('bea:wlcommons-logging:jar:8.1').from('path/to/wlcommons-logging.jar')
 50   #   LOGGING_WEBLOGIC = ['comons-logging:commons-logging:jar:1.1.1', 
 51   #                       WL_LOGGING]
 52   #   COMMONS = struct :collections => 'commons-collection:commons-collection:jar:3.1',
 53   #                    :net => 'commons-net:commons-net:jar:1.4.0'
 54   #   
 55   #   define 'example1' do
 56   #     define 'one' do
 57   #       compile.with SPRING, LOGGING_WEBLOGIC, COMMONS
 58   #     end
 59   #     define 'two' do
 60   #       compile.with SPRING_OLD, LOGGING, COMMONS
 61   #     end
 62   #     define 'three' do
 63   #       compile.with "commons-collections:commons-collections:jar:2.2"
 64   #     end
 65   #   end
 66   #
 67   # 
 68   # With ArtifactNamespace you can do some more advanced stuff, the following
 69   # annotated snipped could still be reduced if default artifact definitions were
 70   # loaded from yaml file (see section bellow and ArtifactNamespace.load).
 71   # 
 72   #   -- buildfile --
 73   #   artifact_ns do |ns| # the current namespace (root if called outside a project)
 74   #     # default artifacts
 75   #     ns.spring = 'org.springframework:spring:jar:2.5'
 76   #     # default logger is log4j
 77   #     ns.logger = 'log4j:log4j:jar:1.2.15'
 78   #
 79   #     # create a sub namespace by calling the #ns method,
 80   #     # artifacts defined on the sub-namespace can be referenced by
 81   #     # name :commons_net or by calling commons.net
 82   #     ns.ns :commons, :net => 'commons-net:commons-net:jar:1.4.0',
 83   #                     :logging => 'comons-logging:commons-logging:jar:1.1.1'
 84   #
 85   #
 86   #     # When a child namespace asks for the :log artifact, 
 87   #     # these artifacts will be searched starting from the :current namespace.
 88   #     ns.virtual :log, :logger, :commons_logging
 89   #   end
 90   #   
 91   #   artifact_ns('example2:one') do |ns| # namespace for the one subproject
 92   #     ns.logger = artifact('bea:wlcommons-logging:jar:8.1').from('path/to/wlcommons-logging.jar')
 93   #   end
 94   #   artifact_ns('example2:two') do |ns| 
 95   #     ns.spring = '1.0' # for project two use an older spring version (just for an example)
 96   #   end
 97   #   artifact_ns('example2:three').commons_collections = 2.2'
 98   #   artifact_ns('example2:four') do |ns|
 99   #     ns.beanutils = 'commons-beanutils:commons-beanutils:jar:1.5'        # just for this project
100   #     ns.ns(:compilation).use :commons_logging, :beanutils, :spring       # compile time dependencies
101   #     ns.ns(:testing).use :log, :beanutils, 'cglib:cglib-nodep:jar:2.1.3' # run time dependencies
102   #   end
103   #   
104   #   define 'example2' do 
105   #     define 'one' do
106   #       compile.with :spring, :log, :commons # uses weblogic logging
107   #     end
108   #     define 'two' do
109   #       compile.with :spring, :log, :commons # will take old spring
110   #     end
111   #     define 'three' do
112   #       compile.with :commons_collections
113   #       test.with artifact_ns('example2:two').spring # use spring from project two
114   #     end
115   #     define 'four' do
116   #       compile.with artifact_ns.compilation
117   #       test.with artifact_ns.testing
118   #     end
119   #     task(:down_them_all) do # again, just to fill this space with something ;)
120   #       parent.projects.map(&method(:artifact_ns)).map(&:artifacts).map(&:invoke)
121   #     end
122   #   end
123   #
124   # = Loading from a yaml file (e. profiles.yaml)
125   #
126   # If your projects use lots of jars (after all we are using java ;) you may prefer
127   # to have constant artifact definitions on an external file.
128   # Doing so would allow an external tool (or future Buildr feature) to maintain
129   # an artifacts.yaml for you.
130   # An example usage is documented on the ArtifactNamespace.load method.
131   #
132   # = For addon/plugin writers & Customizing artifact versions
133   # 
134   # Sometimes users would need to change the default artifact versions used by some
135   # module, for example, the XMLBeans compiler needs this, because of compatibility
136   # issues. Another example would be to select the groovy version to use on all our
137   # projects so that Buildr modules requiring groovy jars can use user prefered versions.
138   #
139   # To meet this goal, an ArtifactNamespace allows to specify ArgumentRequirement objects.
140   # In fact the only diference with the examples you have already seen is that requirements
141   # have an associated VersionRequirement, so that each time a user tries to select a version,
142   # buildr checks if it satisfies the requirements.
143   #
144   # Requirements are declared using the ArtifactNamespace#need method, but again,
145   # syntactic sugar is provided by ArtifactNamespace#method_missing.
146   #
147   # The following example is taken from the XMLBeans compiler module.
148   # And illustrates how addon authors should specify their requirements, 
149   # provide default versions, and document the namespace for users to customize.
150   # 
151   #    module Buildr::XMLBeans
152   #
153   #       # You need to document this constant, giving users some hints
154   #       # about when are (maybe some of) these artifacts used. I mean, 
155   #       # some modules, add jars to the Buildr classpath when its file 
156   #       # is required, you would need to tell your users, so that they
157   #       # can open the namespace and specify their defaults. Of course
158   #       # when the requirements are defined, buildr checks if any compatible
159   #       # version has been already defined, if so, uses it.
160   #       #
161   #       # Some things here have been changed to illustrate their meaning.
162   #       REQUIRES = ArtifactNamespace.for(self).tap do |ns|
163   #
164   #         # This jar requires a >2.0 version, default being 2.3.0
165   #         ns.xmlbeans! 'org.apache.xmlbeans:xmlbeans:jar:2.3.0', '>2'
166   #
167   #         # Users can customize with Buildr::XMLBeans::REQUIRES.stax_api = '1.2'
168   #         # This is a non-flexible requirement, only satisfied by version 1.0.1
169   #         ns.stax_api! 'stax:stax-api:jar:1.0.1'
170   #
171   #         # This one is not part of XMLBeans, but is just another example
172   #         # illustrating an `artifact requirement spec`.
173   #
174   #         ns.need " some_name ->  ar:ti:fact:3.2.5 ->  ( >2 & <4)"
175   #
176   #         # As you can see it's just an artifact spec, prefixed with
177   #         # ' some_name -> ', this means users can use that name to 
178   #         # reference the requirement, also this string has a VersionRequirement
179   #         # just after another ->.
180   #       end
181   #
182   #       # The REQUIRES constant is an ArtifactNamespace instance, 
183   #       # that means we can use it directly. Note that calling 
184   #       # Buildr.artifact_ns would lead to the currently executing context,
185   #       # not the one for this module.
186   #       def use
187   #         # test if user specified his own version, if so, we could perform some
188   #         # functionallity based on this.
189   #         REQUIRES.some_name.selected? # => false 
190   #         
191   #         REQUIRES.some_name.satisfied_by?('1.5') # => false
192   #         puts REQUIRES.some_name.requirement     # => ( >2 & <4 )
193   #
194   #         REQUIRES.artifacts # get the Artifact tasks
195   #       end
196   #
197   #    end 
198   #
199   # A more advanced example using ArtifactRequirement listeners is included 
200   # in the artifact_namespace_spec.rb description for 'Extension using ArtifactNamespace'
201   # That's it for addon writers, now, users can select their prefered version with
202   # something like:
203   #
204   #    require 'buildr/xmlbeans'
205   #    Buildr::XMLBeans::REQUIRES.xmlbeans = '2.2.0'
206   #
207   # More advanced stuff, if users really need to select an xmlbeans version
208   # per project, they can do so letting :current (that is, the currently running
209   # namespace) be parent of the REQUIRES namespace:
210   #
211   #    Buildr::XMLBeans::REQUIRES.parent = :current
212   #
213   # Now, provided that the compiler does not caches its artifacts, it will 
214   # select the correct version. (See the first section for how to select per project 
215   # artifacts).
216   #
217   #
218   class ArtifactNamespace
219     class << self
220       # Forget all namespaces, create a new ROOT
221       def clear
222         @instances = nil
223         remove_const(:ROOT) rescue nil
224         const_set(:ROOT, new('root'))
225       end
226 
227       # Populate namespaces from a hash of hashes. 
228       # The following example uses the profiles yaml to achieve this.
229       #
230       #   -- profiles.yaml --
231       #   development:
232       #     artifacts:
233       #       root:        # root namespace
234       #         spring:     org.springframework:spring:jar:2.5
235       #         groovy:     org.codehaus.groovy:groovy:jar:1.5.4
236       #         logging:    # define a named group
237       #           - log4j:log4j:jar:1.2.15
238       #           - commons-logging:commons-logging:1.1.1
239       #       
240       #       # open Buildr::XMLBeans namespace
241       #       Buildr::XMLBeans:
242       #         xmlbeans: 2.2
243       #
244       #       # for subproject one:oldie
245       #       one:oldie:
246       #         spring:  org.springframework:spring:jar:1.0
247       #
248       #   -- buildfile --
249       #   ArtifactNamespace.load(Buildr.profile['artifacts'])
250       def load(namespaces = {})
251         namespaces.each_pair { |name, uses| instance(name).use(uses) }
252       end
253       
254       # :call-seq:
255       #   ArtifactNamespace.instance { |current_ns| ... } -> current_ns
256       #   ArtifactNamespace.instance(name) { |ns| ... } -> ns
257       #   ArtifactNamespace.instance(:current) { |current_ns| ... } -> current_ns
258       #   ArtifactNamespace.instance(:root) { |root_ns| ... } -> root_ns
259       #
260       # Obtain an instance for the given name
261       def instance(name = nil)
262         case name
263         when :root, 'root' then return ROOT
264         when ArtifactNamespace then return name
265         when Array then name = name.join(':')
266         when Module, Project then name = name.name
267         when :current, 'current', nil then
268           task = Thread.current[:rake_chain]
269           task = task.instance_variable_get(:@value) if task
270           name = case task
271                  when Project then task.name
272                  when Rake::Task then task.scope.join(':')
273                  when nil then Buildr.application.current_scope.join(':')
274                  end
275         end
276         name = name.to_s
277         return ROOT if name.size == 0
278         name = name.to_s
279         @instances ||= Hash.new { |h, k| h[k] = new(k) }
280         instance = @instances[name]
281         yield(instance) if block_given?
282         instance
283       end
284 
285       alias_method :[], :instance
286       alias_method :for, :instance
287       
288       # :call-seq:
289       #   ArtifactNamespace.root { |ns| ... } -> ns
290       #
291       # Obtain the root namespace, returns the ROOT constant
292       def root
293         yield ROOT if block_given?
294         ROOT
295       end
296     end
297 
298     module DClone #:nodoc:
299       def dclone 
300         clone = self.clone
301         clone.instance_variables.each do |i| 
302           value = clone.instance_variable_get(i)
303           value = value.dclone rescue
304           clone.instance_variable_set(i, value)
305         end
306         clone
307       end
308     end
309 
310     class Registry < Hash #:nodoc:
311       include DClone
312 
313       attr_accessor :parent
314       def alias(new_name, old_name)
315         new_name = new_name.to_sym
316         old_name = old_name.to_sym
317         if obj = get(old_name, true)
318           self[new_name] = obj
319           @aliases ||= []
320           group = @aliases.find { |a| a.include?(new_name) }
321           group.delete(new_name) if group
322           group = @aliases.find { |a| a.include?(old_name) }
323           @aliases << (group = [old_name]) unless group
324           group << new_name unless group.include?(new_name)
325         end
326         obj
327       end
328       
329       def aliases(name)
330         return [] unless name
331         name = name.to_sym
332         ((@aliases ||= []).find { |a| a.include?(name) } || [name]).dup
333       end
334 
335       def []=(key, value)
336         return unless key
337         super(key.to_sym, value)
338       end
339 
340       def get(key, include_parent = nil)
341         [].tap { |a| aliases(key).select { |n| a[0] = self[n] } }.first || 
342           (include_parent && parent && parent.get(key, include_parent))
343       end
344 
345       def keys(include_parent = nil)
346         (super() | (include_parent && parent && parent.keys(include_parent) || [])).uniq
347       end
348 
349       def values(include_parent = nil)
350         (super() | (include_parent && parent && parent.values(include_parent) || [])).uniq
351       end
352 
353       def key?(key, include_parent = nil)
354         return false unless key
355         super(key.to_sym) || (include_parent && parent && parent.key?(key, include_parent))
356       end
357 
358       def delete(key, include_parent = nil)
359         aliases(key).map {|n| super(n) } && include_parent && parent && parent.delete(key, include_parent)
360       end
361     end
362 
363     # An artifact requirement is an object that ActsAsArtifact and has
364     # an associated VersionRequirement. It also knows the name (some times equal to the 
365     # artifact id) that is used to store it in an ArtifactNamespace.
366     class ArtifactRequirement
367       attr_accessor :version
368       attr_reader :name, :requirement
369 
370       include DClone
371   
372       # Create a requirement from an `artifact requirement spec`.
373       # This spec has three parts, separated by  -> 
374       # 
375       #     some_name ->  ar:ti:fact:3.2.5 ->  ( >2 & <4)
376       #
377       # As you can see it's just an artifact spec, prefixed with
378       #     some_name ->
379       # the :some_name symbol becomes this object's name and
380       # is used to store it on an ArtifactNamespace.
381       #
382       #                   ar:ti:fact:3.2.5
383       #
384       # The second part is an artifact spec by itself, and specifies
385       # all remaining attributes, the version of this spec becomes
386       # the default version of this requirement.
387       #
388       # The last part consist of a VersionRequirement.
389       #                                     ->  ( >2 & <4)
390       #                        
391       # VersionRequirement supports RubyGem's comparision operators
392       # in adition to parens, logical and, logical or and negation.
393       # See the docs for VersionRequirement for more info on operators.
394       def initialize(spec)
395         self.class.send :include, ActsAsArtifact unless ActsAsArtifact === self
396         if ArtifactRequirement === spec
397           copy_attrs(spec)
398         else
399           spec = requirement_hash(spec)
400           apply_spec(spec[:spec])
401           self.name = spec[:name]
402           @requirement = spec[:requirement]
403           @version = @requirement.default if VersionRequirement.requirement?(@version)
404         end
405       end
406 
407       # Copy attributes from other to this object
408       def copy_attrs(other)
409         (ActsAsArtifact::ARTIFACT_ATTRIBUTES + [:name, :requirement]).each do |attr|
410           value = other.instance_variable_get("@#{attr}")
411           value = value.dup if value && !value.kind_of?(Numeric) && !value.kind_of?(Symbol)
412           instance_variable_set("@#{attr}", value)
413         end
414       end
415 
416       def name=(name)
417         @name = name.to_s
418       end
419       
420       # Set a the requirement, this must be an string formatted for
421       # VersionRequirement#create to parse.
422       def requirement=(version_requirement)
423         @requirement = VersionRequirement.create(version_requirement.to_s)
424       end
425 
426       # Return a hash consisting of :name, :spec, :requirement
427       def requirement_hash(spec = self)
428         result = {}
429         if String === spec 
430           parts = spec.split(/\s*->\s*/, 3).map(&:strip)
431           case parts.size
432           when 1
433             result[:spec] = Artifact.to_hash(parts.first)
434           when 2
435             if /^\w+$/ === parts.first
436               result[:name] = parts.first
437               result[:spec] = Artifact.to_hash(parts.last)
438             else
439               result[:spec] = Artifact.to_hash(parts.first)
440               result[:requirement] = VersionRequirement.create(parts.last)
441             end
442           when 3
443             result[:name] = parts.first
444             result[:spec] = Artifact.to_hash(parts[1])
445             result[:requirement] = VersionRequirement.create(parts.last)
446           end
447         else
448           result[:spec] = Artifact.to_hash(spec)
449         end
450         result[:name] ||= result[:spec][:id].to_s.to_sym
451         result[:requirement] ||= VersionRequirement.create(result[:spec][:version])
452         result
453       end
454       
455       # Test if this requirement is satisfied by an artifact spec.
456       def satisfied_by?(spec)
457         return false unless requirement
458         spec = Artifact.to_hash(spec)
459         hash = to_spec_hash
460         hash.delete(:version)
461         version = spec.delete(:version)
462         hash == spec && requirement.satisfied_by?(version)
463       end
464 
465       # Has user selected a version for this requirement?
466       def selected?
467         @selected
468       end
469 
470       def selected! #:nodoc:
471         @selected = true
472         @listeners.each { |l| l.call(self) } if @listeners
473         self
474       end
475 
476       def add_listener(&callback)
477         (@listeners ||= []) << callback
478       end
479 
480       # Return the Artifact object for the currently selected version
481       def artifact
482         ::Buildr.artifact(self)
483       end
484 
485       # Format this requirement as an `artifact requirement spec`
486       def to_requirement_spec
487         result = to_spec
488         result = "#{name} -> #{result}" if name
489         result = "#{result} -> #{requirement}" if requirement
490         result
491       end
492 
493       def to_s #:nodoc:
494         id ? to_requirement_spec : version
495       end
496 
497       # Return an artifact spec without the version part.
498       def unversioned_spec
499         str = to_spec
500         return nil if str =~ /^:+/
501         ary = str.split(':')
502         ary = ary[0...-1] if ary.size > 3
503         ary.join(':')
504       end
505     
506       class << self
507         # Return an artifact spec without the version part.
508         def unversioned_spec(spec)
509           str = spec.to_s
510           return nil if str =~ /^:+/
511           ary = str.split(':')
512           ary = ary[0...-1] if ary.size > 3
513           if ary.size > 2
514             ary.join(':')
515           else
516             new(spec).unversioned_spec
517           end
518         end
519       end
520     end
521 
522     include DClone
523     include Enumerable
524     attr_reader :name
525     
526     def initialize(name = nil) #:nodoc:
527       @name = name.to_s if name
528     end
529     clear
530     
531     def root
532       ROOT
533     end
534     
535     # ROOT namespace has no parent
536     def parent
537       if root?
538         nil
539       elsif @parent.kind_of?(ArtifactNamespace)
540         @parent
541       elsif @parent
542         ArtifactNamespace.instance(@parent)
543       elsif name
544         parent_name = name.gsub(/::?[^:]+$/, '')
545         parent_name == name ? root : ArtifactNamespace.instance(parent_name)
546       else
547         root
548       end
549     end
550     
551     # Set the parent for the current namespace, except if it is ROOT
552     def parent=(other)
553       raise 'Cannot set parent of root namespace' if root?
554       @parent = other
555       @registry = nil
556     end
557     
558     # Is this the ROOT namespace?
559     def root?
560       ROOT == self
561     end
562 
563     # Create a named sub-namespace, sub-namespaces are themselves 
564     # ArtifactNamespace instances but cannot be referenced by
565     # the Buildr.artifact_ns, ArtifactNamespace.instance methods. 
566     # Reference needs to be through this object using the given +name+
567     #
568     #   artifact_ns('foo').ns(:bar).need :thing => 'some:thing:jar:1.0'
569     #   artifact_ns('foo').bar # => the sub-namespace 'foo.bar'
570     #   artifact_ns('foo').bar.thing # => the some thing artifact
571     #
572     # See the top level ArtifactNamespace documentation for examples
573     def ns(name, *uses, &block)
574       name = name.to_sym
575       sub = registry[name]
576       if sub
577         raise TypeError.new("#{name} is not a sub namespace of #{self}") unless sub.kind_of?(ArtifactNamespace)
578       else
579         sub = ArtifactNamespace.new("#{self.name}.#{name}")
580         sub.parent = self
581         registry[name] = sub
582       end
583       sub.use(*uses)
584       yield sub if block_given?
585       sub
586     end
587 
588     # Test if a sub-namespace by the given name exists
589     def ns?(name)
590       sub = registry[name.to_sym]
591       ArtifactNamespace === sub
592     end
593 
594     # :call-seq:
595     #   artifact_ns.need 'name -> org:foo:bar:jar:~>1.2.3 -> 1.2.5'
596     #   artifact_ns.need :name => 'org.foo:bar:jar:1.0'
597     #
598     # Create a new ArtifactRequirement on this namespace.
599     # ArtifactNamespace#method_missing provides syntactic sugar for this.
600     def need(*specs)
601       named = specs.flatten.inject({}) do |seen, spec|
602         if Hash === spec && (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty?
603           spec.each_pair do |name, spec|
604             if Array === spec # a group
605               seen[name] ||= spec.map { |s| ArtifactRequirement.new(s) }
606             else
607               artifact = ArtifactRequirement.new(spec)
608               artifact.name = name
609               seen[artifact.name] ||= artifact
610             end
611           end
612         else
613           artifact = ArtifactRequirement.new(spec)
614           seen[artifact.name] ||= artifact
615         end
616         seen
617       end
618       named.each_pair do |name, artifact|
619         if Array === artifact # a group
620           artifact.each do |a| 
621             unvers = a.unversioned_spec
622             previous = registry[unvers]
623             if previous && previous.selected? && a.satisfied_by?(previous)
624               a.version = previous.version
625             end
626             registry[unvers] = a
627           end
628           group(name, *(artifact.map { |a| a.unversioned_spec } + [{:namespace => self}]))
629         else
630           unvers = artifact.unversioned_spec
631           previous = registry.get(unvers, true)
632           if previous && previous.selected? && artifact.satisfied_by?(previous)
633             artifact.version = previous.version
634             artifact.selected!
635           end
636           registry[unvers] = artifact
637           registry.alias name, unvers unless name.to_s[/^\s*$/]
638         end
639       end
640       self
641     end
642 
643     # :call-seq:
644     #   artifact_ns.use 'name -> org:foo:bar:jar:1.2.3'
645     #   artifact_ns.use :name => 'org:foo:bar:jar:1.2.3'
646     #   artifact_ns.use :name => '2.5.6'
647     #
648     # First and second form are equivalent, the third is used when an 
649     # ArtifactRequirement has been previously defined with :name, so it
650     # just selects the version.
651     #
652     # ArtifactNamespace#method_missing provides syntactic sugar for this.
653     def use(*specs)
654       named = specs.flatten.inject({}) do |seen, spec|
655         if Hash === spec && (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty?
656           spec.each_pair do |name, spec|
657             if ArtifactNamespace === spec # create as subnamespace
658               raise ArgumentError.new("Circular reference") if self == spec
659               registry[name.to_sym] = spec
660             elsif Numeric === spec || (String === spec && VersionRequirement.version?(spec))
661               artifact = ArtifactRequirement.allocate
662               artifact.name = name
663               artifact.version = spec.to_s
664               seen[artifact.name] ||= artifact
665             elsif Symbol === spec
666               self.alias name, spec
667             elsif Array === spec # a group
668               seen[name] ||= spec.map { |s| ArtifactRequirement.new(s) }
669             else
670               artifact = ArtifactRequirement.new(spec)
671               artifact.name = name
672               seen[artifact.name] ||= artifact
673             end
674           end
675         else
676           if Symbol === spec
677             artifact = get(spec).dclone
678           else
679             artifact = ArtifactRequirement.new(spec)
680           end
681           seen[artifact.name] ||= artifact
682         end
683         seen
684       end
685       named.each_pair do |name, artifact|
686         is_group = Array === artifact
687         artifact = [artifact].flatten.map do |artifact|
688           unvers = artifact.unversioned_spec
689           previous = get(unvers, false) || get(name, false) 
690           if previous # have previous on current namespace
691             if previous.requirement # we must satisfy the requirement
692               unless unvers # we only have the version
693                 satisfied = previous.requirement.satisfied_by?(artifact.version)
694               else
695                 satisfied = previous.satisfied_by?(artifact)
696               end
697               raise "Unsatisfied dependency #{previous} " +
698                 "not satisfied by #{artifact}" unless satisfied
699               previous.version = artifact.version # OK, set new version
700               artifact = previous # use the same object for aliases  
701             else # not a requirement, set the new values
702               unless artifact.id == previous.id && name != previous.name
703                 previous.copy_attrs(artifact)
704                 artifact = previous
705               end
706             end
707           else
708             if unvers.nil? && # we only have the version
709                 (previous = get(unvers, true, false, false))
710               version = artifact.version
711               artifact.copy_attrs(previous)
712               artifact.version = version
713             end
714             artifact.requirement = nil  
715           end
716           artifact.selected!
717         end
718         artifact = artifact.first unless is_group
719         if is_group
720           names = artifact.map do |art| 
721             unv = art.unversioned_spec
722             registry[unv] = art
723             unv
724           end
725           group(name, *(names + [{:namespace => self}]))
726         elsif artifact.id
727           unvers = artifact.unversioned_spec
728           registry[name] = artifact
729           registry.alias unvers, name
730         else
731           registry[name] = artifact
732         end
733       end
734       self
735     end
736 
737     # Like Hash#fetch
738     def fetch(name, default = nil, &block)
739       block ||= proc { raise IndexError.new("No artifact found by name #{name.inspect} in namespace #{self}") }
740       real_name = name.to_s[/^[\w\-\.]+$/] ? name : ArtifactRequirement.unversioned_spec(name)
741       get(real_name.to_sym) || default || block.call(name)
742     end
743 
744     # :call-seq:
745     #   artifact_ns[:name] -> ArtifactRequirement
746     #   artifact_ns[:many, :names] -> [ArtifactRequirement]
747     def [](*names)
748       ary = values_at(*names)
749       names.size == 1 ? ary.first : ary
750     end
751 
752     # :call-seq:
753     #   artifact_ns[:name] = 'some:cool:jar:1.0.2'
754     #   artifact_ns[:name] = '1.0.2'
755     #
756     # Just like the use method
757     def []=(*names)
758       values = names.pop
759       values = [values] unless Array === values
760       names.each_with_index do |name, i|
761         use name => (values[i] || values.last)
762       end
763     end
764 
765     # yield each ArtifactRequirement
766     def each(&block)
767       values.each(&block)
768     end
769 
770     # return Artifact objects for each requirement
771     def artifacts(*names)
772       (names.empty? && values || values_at(*names)).map(&:artifact)
773     end
774 
775     # Return all requirements for this namespace
776     def values(include_parents = false, include_groups = true)
777       seen, dict = {}, registry
778       while dict
779         dict.each do |k, v|
780           v = v.call if v.respond_to?(:call)
781           v = v.values if v.kind_of?(ArtifactNamespace)
782           if Array === v && include_groups
783             v.compact.each { |v| seen[v.name] = v unless seen.key?(v.name) }
784           else
785             seen[v.name] = v unless seen.key?(v.name)
786           end
787         end
788         dict = include_parents ? dict.parent : nil
789       end
790       seen.values
791     end
792 
793     # Return only the named requirements
794     def values_at(*names)
795       names.map do |name| 
796         catch :artifact do
797           unless name.to_s[/^[\w\-\.]+$/]
798             unvers = ArtifactRequirement.unversioned_spec(name)
799             unless unvers.to_s == name.to_s
800               req = ArtifactRequirement.new(name)
801               reg = self
802               while reg
803                 candidate = reg.send(:get, unvers, false, false, true)
804                 throw :artifact, candidate if req.satisfied_by?(candidate)
805                 reg = reg.parent
806               end
807             end
808           end
809           get(name.to_sym)
810         end
811       end
812     end
813 
814     def key?(name, include_parents = false)
815       name = ArtifactRequirement.unversioned_spec(name) unless name.to_s[/^[\w\-\.]+$/]
816       registry.key?(name, include_parents)
817     end
818 
819     def delete(name, include_parents = false)
820       registry.delete(name, include_parents)
821       self
822     end
823     
824     # :call-seq:
825     #   group :who, :me, :you
826     #   group :them, :me, :you, :namespace => ns
827     #
828     # Create a virtual group on this namespace. When the namespace
829     # is asked for the +who+ artifact, it's value is an array made from
830     # the remaining names. These names are searched by default from the current
831     # namespace. 
832     # Second form specified the starting namespace to search from.
833     def group(group_name, *members)
834       namespace = (Hash === members.last && members.pop[:namespace]) || :current
835       registry[group_name] = lambda do
836         artifacts = self.class[namespace].values_at(*members)
837         artifacts = artifacts.first if members.size == 1
838         artifacts
839       end
840       self
841     end
842 
843     alias_method :virtual, :group
844 
845     # Create an alias for a named requirement.
846     def alias(new_name, old_name)
847       registry.alias(new_name, old_name) or
848         raise NameError.new("Undefined artifact name: #{old_name}")
849     end
850 
851     def to_s #:nodoc:
852       name.to_s
853     end
854 
855     # :call-seq:
856     #   artifact_ns.cool_aid!('cool:aid:jar:2.3.4', '~>2.3') -> artifact_requirement
857     #   artifact_ns.cool_aid = '2.3.5'
858     #   artifact_ns.cool_aid  -> artifact_requirement
859     #   artifact_ns.cool_aid? -> true | false
860     #
861     # First form creates an ArtifactRequirement on the namespace.
862     # It is equivalent to providing a requirement_spec to the #need method:
863     #   artifact_ns.need "cool_aid -> cool:aid:jar:2.3.4 -> ~>2.3"
864     # the second argument is optional.
865     #
866     # Second form can be used to select an artifact version
867     # and is equivalent to:
868     #   artifact_ns.use :cool_aid => '1.0'
869     #
870     # Third form obtains the named ArtifactRequirement, can be 
871     # used to test if a named requirement has been defined.
872     # It is equivalent to:
873     #   artifact_ns.fetch(:cool_aid) { nil }
874     #
875     # Last form tests if the ArtifactRequirement has been defined
876     # and a version has been selected for use.
877     # It is equivalent to:
878     #
879     #   artifact_ns.has_cool_aid?
880     #   artifact_ns.values_at(:cool_aid).flatten.all? { |a| a && a.selected? }
881     #
882     def method_missing(name, *args, &block)
883       case name.to_s
884       when /!$/ then
885         name = $`.intern
886         if args.size < 1 || args.size > 2
887           raise ArgumentError.new("wrong number of arguments for #{name}!(spec, version_requirement?)")
888         end
889         need name => args.first
890         get(name).tap { |r| r.requirement = args.last if args.size == 2 }
891       when /=$/ then use $` => args.first
892       when /\?$/ then
893         name = $`.gsub(/^(has|have)_/, '').intern
894         [get(name)].flatten.all? { |a| a && a.selected? }
895       else 
896         if block || args.size > 0
897           raise ArgumentError.new("wrong number of arguments #{args.size} for 0 or block given")
898         end
899         get(name)
900       end
901     end
902 
903     # Return an anonymous module 
904     #   # first create a requirement
905     #   artifact_ns.cool_aid! 'cool:aid:jar:>=1.0'
906     #
907     #   # extend an object as a cool_aid delegator
908     #   jars = Object.new.extend(artifact_ns.accessor(:cool_aid))
909     #   jars.cool_aid = '2.0'
910     #
911     #   artifact_ns.cool_aid.version # -> '2.0'
912     def accessor(*names)
913       ns = self
914       Module.new do 
915         names.each do |name|
916           define_method("#{name}") { ns.send("#{name}") }
917           define_method("#{name}?") { ns.send("#{name}?") }
918           define_method("#{name}=") { |vers| ns.send("#{name}=", vers) }
919         end
920       end
921     end
922 
923    private
924     def get(name, include_parents = true, include_subs = true, include_self = true) #:nodoc:
925       if include_subs && name.to_s[/_/] # try sub namespaces first
926         sub, parts = self, name.to_s.split('_')
927         sub_name = parts.shift.to_sym
928         until sub != self || parts.empty?
929           if registry[sub_name].kind_of?(ArtifactNamespace)
930             sub = registry[sub_name]
931             artifact = sub[parts.join('_')]
932           else
933             sub_name = [sub_name, parts.shift].join('_').to_sym
934           end
935         end
936       end
937       unless artifact
938         if include_self
939           artifact = registry.get(name, include_parents)
940         elsif include_parents && registry.parent
941           artifact = registry.parent.get(name, true)
942         end
943       end
944       artifact = artifact.call if artifact.respond_to?(:call)
945       artifact
946     end
947 
948     def registry
949       @registry ||= Registry.new.tap do |m|
950         m.parent = parent.send(:registry) unless root?
951       end
952     end
953      
954   end # ArtifactNamespace
955 
956   # :call-seq:
957   #   project.artifact_ns -> ArtifactNamespace
958   #   Buildr.artifact_ns(name) -> ArtifactNamespace
959   #   Buildr.artifact_ns -> ArtifactNamespace for the currently running Project
960   #
961   # Open an ArtifactNamespace.
962   # If a block is provided, the namespace is yielded to it.
963   #
964   # See also ArtifactNamespace.instance
965   def artifact_ns(name = nil, &block)
966     name = self if name.nil? && self.kind_of?(Project)
967     ArtifactNamespace.instance(name, &block)
968   end
969   
970 end
971 
972 

Generated using the rcov code coverage analysis tool for Ruby version 0.8.2.1.

Valid XHTML 1.0! Valid CSS!