Buildr C0 Coverage Information - RCov

lib/buildr/packaging/artifact_namespace.rb

Name Total Lines Lines of Code Total Coverage Code Coverage
lib/buildr/packaging/artifact_namespace.rb 984 537
23.88%
36.69%

Key

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.

Coverage Details

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 pollution 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 different artifact combinations and/or versions. Assigning
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 below 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 ArtifactRequirement objects.
140   # In fact the only difference 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:jar: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.settings.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         if name.size == 0
278           instance = ROOT
279         else
280           name = name.to_s
281           @instances ||= Hash.new { |h, k| h[k] = new(k) }
282           instance = @instances[name]
283         end
284         yield(instance) if block_given?
285         instance
286       end
287 
288       alias_method :[], :instance
289       alias_method :for, :instance
290 
291       # :call-seq:
292       #   ArtifactNamespace.root { |ns| ... } -> ns
293       #
294       # Obtain the root namespace, returns the ROOT constant
295       def root
296         yield ROOT if block_given?
297         ROOT
298       end
299     end
300 
301     module DClone #:nodoc:
302       def dclone
303         clone = self.clone
304         clone.instance_variables.each do |i|
305           value = clone.instance_variable_get(i)
306           value = value.dclone rescue
307           clone.instance_variable_set(i, value)
308         end
309         clone
310       end
311     end
312 
313     class Registry < Hash #:nodoc:
314       include DClone
315 
316       attr_accessor :parent
317       def alias(new_name, old_name)
318         new_name = new_name.to_sym
319         old_name = old_name.to_sym
320         if obj = get(old_name, true)
321           self[new_name] = obj
322           @aliases ||= []
323           group = @aliases.find { |a| a.include?(new_name) }
324           group.delete(new_name) if group
325           group = @aliases.find { |a| a.include?(old_name) }
326           @aliases << (group = [old_name]) unless group
327           group << new_name unless group.include?(new_name)
328         end
329         obj
330       end
331 
332       def aliases(name)
333         return [] unless name
334         name = name.to_sym
335         ((@aliases ||= []).find { |a| a.include?(name) } || [name]).dup
336       end
337 
338       def []=(key, value)
339         return unless key
340         super(key.to_sym, value)
341       end
342 
343       def get(key, include_parent = nil)
344         [].tap { |a| aliases(key).select { |n| a[0] = self[n] } }.first ||
345           (include_parent && parent && parent.get(key, include_parent))
346       end
347 
348       def keys(include_parent = nil)
349         (super() | (include_parent && parent && parent.keys(include_parent) || [])).uniq
350       end
351 
352       def values(include_parent = nil)
353         (super() | (include_parent && parent && parent.values(include_parent) || [])).uniq
354       end
355 
356       def key?(key, include_parent = nil)
357         return false unless key
358         super(key.to_sym) || (include_parent && parent && parent.key?(key, include_parent))
359       end
360 
361       def delete(key, include_parent = nil)
362         aliases(key).map {|n| super(n) } && include_parent && parent && parent.delete(key, include_parent)
363       end
364     end
365 
366     # An artifact requirement is an object that ActsAsArtifact and has
367     # an associated VersionRequirement. It also knows the name (some times equal to the
368     # artifact id) that is used to store it in an ArtifactNamespace.
369     class ArtifactRequirement
370       attr_accessor :version
371       attr_reader :name, :requirement
372 
373       include DClone
374 
375       # Create a requirement from an `artifact requirement spec`.
376       # This spec has three parts, separated by  ->
377       #
378       #     some_name ->  ar:ti:fact:3.2.5 ->  ( >2 & <4)
379       #
380       # As you can see it's just an artifact spec, prefixed with
381       #     some_name ->
382       # the :some_name symbol becomes this object's name and
383       # is used to store it on an ArtifactNamespace.
384       #
385       #                   ar:ti:fact:3.2.5
386       #
387       # The second part is an artifact spec by itself, and specifies
388       # all remaining attributes, the version of this spec becomes
389       # the default version of this requirement.
390       #
391       # The last part consist of a VersionRequirement.
392       #                                     ->  ( >2 & <4)
393       #
394       # VersionRequirement supports RubyGem's comparision operators
395       # in adition to parens, logical and, logical or and negation.
396       # See the docs for VersionRequirement for more info on operators.
397       def initialize(spec)
398         self.class.send :include, ActsAsArtifact unless ActsAsArtifact === self
399         if ArtifactRequirement === spec
400           copy_attrs(spec)
401         else
402           spec = requirement_hash(spec)
403           apply_spec(spec[:spec])
404           self.name = spec[:name]
405           @requirement = spec[:requirement]
406           @version = @requirement.default if VersionRequirement.requirement?(@version)
407         end
408       end
409 
410       # Copy attributes from other to this object
411       def copy_attrs(other)
412         (ActsAsArtifact::ARTIFACT_ATTRIBUTES + [:name, :requirement]).each do |attr|
413           value = other.instance_variable_get("@#{attr}")
414           value = value.dup if value && !value.kind_of?(Numeric) && !value.kind_of?(Symbol)
415           instance_variable_set("@#{attr}", value)
416         end
417       end
418 
419       def name=(name)
420         @name = name.to_s
421       end
422 
423       # Set a the requirement, this must be an string formatted for
424       # VersionRequirement#create to parse.
425       def requirement=(version_requirement)
426         @requirement = VersionRequirement.create(version_requirement.to_s)
427       end
428 
429       # Return a hash consisting of :name, :spec, :requirement
430       def requirement_hash(spec = self)
431         result = {}
432         if String === spec
433           parts = spec.split(/\s*->\s*/, 3).map(&:strip)
434           case parts.size
435           when 1
436             result[:spec] = Artifact.to_hash(parts.first)
437           when 2
438             if /^\w+$/ === parts.first
439               result[:name] = parts.first
440               result[:spec] = Artifact.to_hash(parts.last)
441             else
442               result[:spec] = Artifact.to_hash(parts.first)
443               result[:requirement] = VersionRequirement.create(parts.last)
444             end
445           when 3
446             result[:name] = parts.first
447             result[:spec] = Artifact.to_hash(parts[1])
448             result[:requirement] = VersionRequirement.create(parts.last)
449           end
450         else
451           result[:spec] = Artifact.to_hash(spec)
452         end
453         result[:name] ||= result[:spec][:id].to_s.to_sym
454         result[:requirement] ||= VersionRequirement.create(result[:spec][:version])
455         result
456       end
457 
458       # Test if this requirement is satisfied by an artifact spec.
459       def satisfied_by?(spec)
460         return false unless requirement
461         spec = Artifact.to_hash(spec)
462         hash = to_spec_hash
463         hash.delete(:version)
464         version = spec.delete(:version)
465         hash == spec && requirement.satisfied_by?(version)
466       end
467 
468       # Has user selected a version for this requirement?
469       def selected?
470         @selected
471       end
472 
473       def selected! #:nodoc:
474         @selected = true
475         @listeners.each { |l| l.call(self) } if @listeners
476         self
477       end
478 
479       def add_listener(&callback)
480         (@listeners ||= []) << callback
481       end
482 
483       # Return the Artifact object for the currently selected version
484       def artifact
485         ::Buildr.artifact(self)
486       end
487 
488       # Format this requirement as an `artifact requirement spec`
489       def to_requirement_spec
490         result = to_spec
491         result = "#{name} -> #{result}" if name
492         result = "#{result} -> #{requirement}" if requirement
493         result
494       end
495 
496       def to_s #:nodoc:
497         id ? to_requirement_spec : version
498       end
499 
500       # Return an artifact spec without the version part.
501       def unversioned_spec
502         str = to_spec
503         return nil if str =~ /^:+/
504         ary = str.split(':')
505         ary = ary[0...-1] if ary.size > 3
506         ary.join(':')
507       end
508 
509       class << self
510         # Return an artifact spec without the version part.
511         def unversioned_spec(spec)
512           str = spec.to_s
513           return nil if str =~ /^:+/
514           ary = str.split(':')
515           ary = ary[0...-1] if ary.size > 3
516           if ary.size > 2
517             ary.join(':')
518           else
519             new(spec).unversioned_spec
520           end
521         end
522       end
523     end
524 
525     include DClone
526     include Enumerable
527     attr_reader :name
528 
529     def initialize(name = nil) #:nodoc:
530       @name = name.to_s if name
531     end
532     clear
533 
534     def root
535       yield ROOT if block_given?
536       ROOT
537     end
538 
539     # ROOT namespace has no parent
540     def parent
541       if root?
542         nil
543       elsif @parent.kind_of?(ArtifactNamespace)
544         @parent
545       elsif @parent
546         ArtifactNamespace.instance(@parent)
547       elsif name
548         parent_name = name.gsub(/::?[^:]+$/, '')
549         parent_name == name ? root : ArtifactNamespace.instance(parent_name)
550       else
551         root
552       end
553     end
554 
555     # Set the parent for the current namespace, except if it is ROOT
556     def parent=(other)
557       raise 'Cannot set parent of root namespace' if root?
558       @parent = other
559       @registry = nil
560     end
561 
562     # Is this the ROOT namespace?
563     def root?
564       ROOT == self
565     end
566 
567     # Create a named sub-namespace, sub-namespaces are themselves
568     # ArtifactNamespace instances but cannot be referenced by
569     # the Buildr.artifact_ns, ArtifactNamespace.instance methods.
570     # Reference needs to be through this object using the given +name+
571     #
572     #   artifact_ns('foo').ns(:bar).need :thing => 'some:thing:jar:1.0'
573     #   artifact_ns('foo').bar # => the sub-namespace 'foo.bar'
574     #   artifact_ns('foo').bar.thing # => the some thing artifact
575     #
576     # See the top level ArtifactNamespace documentation for examples
577     def ns(name, *uses, &block)
578       name = name.to_sym
579       sub = registry[name]
580       if sub
581         raise TypeError.new("#{name} is not a sub namespace of #{self}") unless sub.kind_of?(ArtifactNamespace)
582       else
583         sub = ArtifactNamespace.new("#{self.name}.#{name}")
584         sub.parent = self
585         registry[name] = sub
586       end
587       sub.use(*uses)
588       yield sub if block_given?
589       sub
590     end
591 
592     # Test if a sub-namespace by the given name exists
593     def ns?(name)
594       sub = registry[name.to_sym]
595       ArtifactNamespace === sub
596     end
597 
598     # :call-seq:
599     #   artifact_ns.need 'name -> org:foo:bar:jar:~>1.2.3 -> 1.2.5'
600     #   artifact_ns.need :name => 'org.foo:bar:jar:1.0'
601     #
602     # Create a new ArtifactRequirement on this namespace.
603     # ArtifactNamespace#method_missing provides syntactic sugar for this.
604     def need(*specs)
605       named = specs.flatten.inject({}) do |seen, spec|
606         if Hash === spec && (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty?
607           spec.each_pair do |name, spec|
608             if Array === spec # a group
609               seen[name] ||= spec.map { |s| ArtifactRequirement.new(s) }
610             else
611               artifact = ArtifactRequirement.new(spec)
612               artifact.name = name
613               seen[artifact.name] ||= artifact
614             end
615           end
616         else
617           artifact = ArtifactRequirement.new(spec)
618           seen[artifact.name] ||= artifact
619         end
620         seen
621       end
622       named.each_pair do |name, artifact|
623         if Array === artifact # a group
624           artifact.each do |a|
625             unvers = a.unversioned_spec
626             previous = registry[unvers]
627             if previous && previous.selected? && a.satisfied_by?(previous)
628               a.version = previous.version
629             end
630             registry[unvers] = a
631           end
632           group(name, *(artifact.map { |a| a.unversioned_spec } + [{:namespace => self}]))
633         else
634           unvers = artifact.unversioned_spec
635           previous = registry.get(unvers, true)
636           if previous && previous.selected? && artifact.satisfied_by?(previous)
637             artifact.version = previous.version
638             artifact.selected!
639           end
640           registry[unvers] = artifact
641           registry.alias name, unvers unless name.to_s[/^\s*$/]
642         end
643       end
644       self
645     end
646 
647     # :call-seq:
648     #   artifact_ns.use 'name -> org:foo:bar:jar:1.2.3'
649     #   artifact_ns.use :name => 'org:foo:bar:jar:1.2.3'
650     #   artifact_ns.use :name => '2.5.6'
651     #
652     # First and second form are equivalent, the third is used when an
653     # ArtifactRequirement has been previously defined with :name, so it
654     # just selects the version.
655     #
656     # ArtifactNamespace#method_missing provides syntactic sugar for this.
657     def use(*specs)
658       named = specs.flatten.inject({}) do |seen, spec|
659         if Hash === spec && (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty?
660           spec.each_pair do |name, spec|
661             if ArtifactNamespace === spec # create as subnamespace
662               raise ArgumentError.new("Circular reference") if self == spec
663               registry[name.to_sym] = spec
664             elsif Numeric === spec || (String === spec && VersionRequirement.version?(spec))
665               artifact = ArtifactRequirement.allocate
666               artifact.name = name
667               artifact.version = spec.to_s
668               seen[artifact.name] ||= artifact
669             elsif Symbol === spec
670               self.alias name, spec
671             elsif Array === spec # a group
672               seen[name] ||= spec.map { |s| ArtifactRequirement.new(s) }
673             else
674               artifact = ArtifactRequirement.new(spec)
675               artifact.name = name
676               seen[artifact.name] ||= artifact
677             end
678           end
679         else
680           if Symbol === spec
681             artifact = get(spec).dclone
682           else
683             artifact = ArtifactRequirement.new(spec)
684           end
685           seen[artifact.name] ||= artifact
686         end
687         seen
688       end
689       named.each_pair do |name, artifact|
690         is_group = Array === artifact
691         artifact = [artifact].flatten.map do |artifact|
692           unvers = artifact.unversioned_spec
693           previous = get(unvers, false) || get(name, false)
694           if previous # have previous on current namespace
695             if previous.requirement # we must satisfy the requirement
696               unless unvers # we only have the version
697                 satisfied = previous.requirement.satisfied_by?(artifact.version)
698               else
699                 satisfied = previous.satisfied_by?(artifact)
700               end
701               raise "Unsatisfied dependency #{previous} " +
702                 "not satisfied by #{artifact}" unless satisfied
703               previous.version = artifact.version # OK, set new version
704               artifact = previous # use the same object for aliases
705             else # not a requirement, set the new values
706               unless artifact.id == previous.id && name != previous.name
707                 previous.copy_attrs(artifact)
708                 artifact = previous
709               end
710             end
711           else
712             if unvers.nil? && # we only have the version
713                 (previous = get(unvers, true, false, false))
714               version = artifact.version
715               artifact.copy_attrs(previous)
716               artifact.version = version
717             end
718             artifact.requirement = nil
719           end
720           artifact.selected!
721         end
722         artifact = artifact.first unless is_group
723         if is_group
724           names = artifact.map do |art|
725             unv = art.unversioned_spec
726             registry[unv] = art
727             unv
728           end
729           group(name, *(names + [{:namespace => self}]))
730         elsif artifact.id
731           unvers = artifact.unversioned_spec
732           registry[name] = artifact
733           registry.alias unvers, name
734         else
735           registry[name] = artifact
736         end
737       end
738       self
739     end
740 
741     # Like Hash#fetch
742     def fetch(name, default = nil, &block)
743       block ||= proc { raise IndexError.new("No artifact found by name #{name.inspect} in namespace #{self}") }
744       real_name = name.to_s[/^[\w\-\.]+$/] ? name : ArtifactRequirement.unversioned_spec(name)
745       get(real_name.to_sym) || default || block.call(name)
746     end
747 
748     # :call-seq:
749     #   artifact_ns[:name] -> ArtifactRequirement
750     #   artifact_ns[:many, :names] -> [ArtifactRequirement]
751     def [](*names)
752       ary = values_at(*names)
753       names.size == 1 ? ary.first : ary
754     end
755 
756     # :call-seq:
757     #   artifact_ns[:name] = 'some:cool:jar:1.0.2'
758     #   artifact_ns[:name] = '1.0.2'
759     #
760     # Just like the use method
761     def []=(*names)
762       values = names.pop
763       values = [values] unless Array === values
764       names.each_with_index do |name, i|
765         use name => (values[i] || values.last)
766       end
767     end
768 
769     # yield each ArtifactRequirement
770     def each(&block)
771       values.each(&block)
772     end
773 
774     # return Artifact objects for each requirement
775     def artifacts(*names)
776       (names.empty? && values || values_at(*names)).map(&:artifact)
777     end
778 
779     # Return all requirements for this namespace
780     def values(include_parents = false, include_groups = true)
781       seen, dict = {}, registry
782       while dict
783         dict.each do |k, v|
784           v = v.call if v.respond_to?(:call)
785           v = v.values if v.kind_of?(ArtifactNamespace)
786           if Array === v && include_groups
787             v.compact.each { |v| seen[v.name] = v unless seen.key?(v.name) }
788           else
789             seen[v.name] = v unless seen.key?(v.name)
790           end
791         end
792         dict = include_parents ? dict.parent : nil
793       end
794       seen.values
795     end
796 
797     # Return only the named requirements
798     def values_at(*names)
799       names.map do |name|
800         catch :artifact do
801           unless name.to_s[/^[\w\-\.]+$/]
802             unvers = ArtifactRequirement.unversioned_spec(name)
803             unless unvers.to_s == name.to_s
804               req = ArtifactRequirement.new(name)
805               reg = self
806               while reg
807                 candidate = reg.send(:get, unvers, false, false, true)
808                 throw :artifact, candidate if req.satisfied_by?(candidate)
809                 reg = reg.parent
810               end
811             end
812           end
813           get(name.to_sym)
814         end
815       end
816     end
817 
818     def key?(name, include_parents = false)
819       name = ArtifactRequirement.unversioned_spec(name) unless name.to_s[/^[\w\-\.]+$/]
820       registry.key?(name, include_parents)
821     end
822 
823     def keys
824       values.map(&:name)
825     end
826 
827     def delete(name, include_parents = false)
828       registry.delete(name, include_parents)
829       self
830     end
831 
832     def clear
833       keys.each { |k| delete(k) }
834     end
835 
836     # :call-seq:
837     #   group :who, :me, :you
838     #   group :them, :me, :you, :namespace => ns
839     #
840     # Create a virtual group on this namespace. When the namespace
841     # is asked for the +who+ artifact, it's value is an array made from
842     # the remaining names. These names are searched by default from the current
843     # namespace.
844     # Second form specified the starting namespace to search from.
845     def group(group_name, *members)
846       namespace = (Hash === members.last && members.pop[:namespace]) || :current
847       registry[group_name] = lambda do
848         artifacts = self.class[namespace].values_at(*members)
849         artifacts = artifacts.first if members.size == 1
850         artifacts
851       end
852       self
853     end
854 
855     alias_method :virtual, :group
856 
857     # Create an alias for a named requirement.
858     def alias(new_name, old_name)
859       registry.alias(new_name, old_name) or
860         raise NameError.new("Undefined artifact name: #{old_name}")
861     end
862 
863     def to_s #:nodoc:
864       name.to_s
865     end
866 
867     # :call-seq:
868     #   artifact_ns.cool_aid!('cool:aid:jar:2.3.4', '~>2.3') -> artifact_requirement
869     #   artifact_ns.cool_aid = '2.3.5'
870     #   artifact_ns.cool_aid  -> artifact_requirement
871     #   artifact_ns.cool_aid? -> true | false
872     #
873     # First form creates an ArtifactRequirement on the namespace.
874     # It is equivalent to providing a requirement_spec to the #need method:
875     #   artifact_ns.need "cool_aid -> cool:aid:jar:2.3.4 -> ~>2.3"
876     # the second argument is optional.
877     #
878     # Second form can be used to select an artifact version
879     # and is equivalent to:
880     #   artifact_ns.use :cool_aid => '1.0'
881     #
882     # Third form obtains the named ArtifactRequirement, can be
883     # used to test if a named requirement has been defined.
884     # It is equivalent to:
885     #   artifact_ns.fetch(:cool_aid) { nil }
886     #
887     # Last form tests if the ArtifactRequirement has been defined
888     # and a version has been selected for use.
889     # It is equivalent to:
890     #
891     #   artifact_ns.has_cool_aid?
892     #   artifact_ns.values_at(:cool_aid).flatten.all? { |a| a && a.selected? }
893     #
894     def method_missing(name, *args, &block)
895       case name.to_s
896       when /!$/ then
897         name = $`.intern
898         if args.size < 1 || args.size > 2
899           raise ArgumentError.new("wrong number of arguments for #{name}!(spec, version_requirement?)")
900         end
901         need name => args.first
902         get(name).tap { |r| r.requirement = args.last if args.size == 2 }
903       when /=$/ then use $` => args.first
904       when /\?$/ then
905         name = $`.gsub(/^(has|have)_/, '').intern
906         [get(name)].flatten.all? { |a| a && a.selected? }
907       else
908         if block || args.size > 0
909           raise ArgumentError.new("wrong number of arguments #{args.size} for 0 or block given")
910         end
911         get(name)
912       end
913     end
914 
915     # Return an anonymous module
916     #   # first create a requirement
917     #   artifact_ns.cool_aid! 'cool:aid:jar:>=1.0'
918     #
919     #   # extend an object as a cool_aid delegator
920     #   jars = Object.new.extend(artifact_ns.accessor(:cool_aid))
921     #   jars.cool_aid = '2.0'
922     #
923     #   artifact_ns.cool_aid.version # -> '2.0'
924     def accessor(*names)
925       ns = self
926       Module.new do
927         names.each do |name|
928           define_method("#{name}") { ns.send("#{name}") }
929           define_method("#{name}?") { ns.send("#{name}?") }
930           define_method("#{name}=") { |vers| ns.send("#{name}=", vers) }
931         end
932       end
933     end
934 
935    private
936     def get(name, include_parents = true, include_subs = true, include_self = true) #:nodoc:
937       if include_subs && name.to_s[/_/] # try sub namespaces first
938         sub, parts = self, name.to_s.split('_')
939         sub_name = parts.shift.to_sym
940         until sub != self || parts.empty?
941           if registry[sub_name].kind_of?(ArtifactNamespace)
942             sub = registry[sub_name]
943             artifact = sub[parts.join('_')]
944           else
945             sub_name = [sub_name, parts.shift].join('_').to_sym
946           end
947         end
948       end
949       unless artifact
950         if include_self
951           artifact = registry.get(name, include_parents)
952         elsif include_parents && registry.parent
953           artifact = registry.parent.get(name, true)
954         end
955       end
956       artifact = artifact.call if artifact.respond_to?(:call)
957       artifact
958     end
959 
960     def registry
961       @registry ||= Registry.new.tap do |m|
962         m.parent = parent.send(:registry) unless root?
963       end
964     end
965 
966   end # ArtifactNamespace
967 
968   # :call-seq:
969   #   project.artifact_ns -> ArtifactNamespace
970   #   Buildr.artifact_ns(name) -> ArtifactNamespace
971   #   Buildr.artifact_ns -> ArtifactNamespace for the currently running Project
972   #
973   # Open an ArtifactNamespace.
974   # If a block is provided, the namespace is yielded to it.
975   #
976   # See also ArtifactNamespace.instance
977   def artifact_ns(name = nil, &block)
978     name = self if name.nil? && self.kind_of?(Project)
979     ArtifactNamespace.instance(name, &block)
980   end
981 
982 end
983 
984 

Generated on 2011-07-06 23:35:38 -0700 with rcov 0.9.8