class Buildr::ArtifactNamespace

An ArtifactNamespace is a hierarchical dictionary used to manage ArtifactRequirements. It can be used to have different artifact versions per project or to allow users to select a version for addons or modules.

Namespaces are opened using the Buildr.artifact_ns method, most important methods are:

need

Used to create a requirement on the namespace.

use

Set the artifact version to use for a requirement.

#values_at

Reference requirements by name.

each

Return each ArtifactRequirement in the namespace.

The #method_missing method for instances provides some syntactic sugar to these. See the following examples, and the methods for ArtifactRequirement.

Avoiding constant pollution on buildfile

Each project has its own ArtifactNamespace inheriting the one from the parent project up to the root namespace.

Consider the following snippet, as project grows, each subproject may need different artifact combinations and/or versions. Assigning artifact specifications to constants can make it painful to maintain their references even if using structs/hashes.

-- buildfile --
SPRING = 'org.springframework:spring:jar:2.5'
SPRING_OLD = 'org.springframework:spring:jar:1.0'
LOGGING = ['comons-logging:commons-logging:jar:1.1.1',
           'log4j:log4j:jar:1.2.15']
WL_LOGGING = artifact('bea:wlcommons-logging:jar:8.1').from('path/to/wlcommons-logging.jar')
LOGGING_WEBLOGIC = ['comons-logging:commons-logging:jar:1.1.1',
                    WL_LOGGING]
COMMONS = struct :collections => 'commons-collection:commons-collection:jar:3.1',
                 :net => 'commons-net:commons-net:jar:1.4.0'

define 'example1' do
  define 'one' do
    compile.with SPRING, LOGGING_WEBLOGIC, COMMONS
  end
  define 'two' do
    compile.with SPRING_OLD, LOGGING, COMMONS
  end
  define 'three' do
    compile.with "commons-collections:commons-collections:jar:2.2"
  end
end

With ArtifactNamespace you can do some more advanced stuff, the following annotated snipped could still be reduced if default artifact definitions were loaded from yaml file (see section below and ::load).

-- buildfile --
artifact_ns do |ns| # the current namespace (root if called outside a project)
  # default artifacts
  ns.spring = 'org.springframework:spring:jar:2.5'
  # default logger is log4j
  ns.logger = 'log4j:log4j:jar:1.2.15'

  # create a sub namespace by calling the #ns method,
  # artifacts defined on the sub-namespace can be referenced by
  # name :commons_net or by calling commons.net
  ns.ns :commons, :net => 'commons-net:commons-net:jar:1.4.0',
                  :logging => 'comons-logging:commons-logging:jar:1.1.1'

  # When a child namespace asks for the :log artifact,
  # these artifacts will be searched starting from the :current namespace.
  ns.virtual :log, :logger, :commons_logging
end

artifact_ns('example2:one') do |ns| # namespace for the one subproject
  ns.logger = artifact('bea:wlcommons-logging:jar:8.1').from('path/to/wlcommons-logging.jar')
end
artifact_ns('example2:two') do |ns|
  ns.spring = '1.0' # for project two use an older spring version (just for an example)
end
artifact_ns('example2:three').commons_collections = 2.2'
artifact_ns('example2:four') do |ns|
  ns.beanutils = 'commons-beanutils:commons-beanutils:jar:1.5'        # just for this project
  ns.ns(:compilation).use :commons_logging, :beanutils, :spring       # compile time dependencies
  ns.ns(:testing).use :log, :beanutils, 'cglib:cglib-nodep:jar:2.1.3' # run time dependencies
end

define 'example2' do
  define 'one' do
    compile.with :spring, :log, :commons # uses weblogic logging
  end
  define 'two' do
    compile.with :spring, :log, :commons # will take old spring
  end
  define 'three' do
    compile.with :commons_collections
    test.with artifact_ns('example2:two').spring # use spring from project two
  end
  define 'four' do
    compile.with artifact_ns.compilation
    test.with artifact_ns.testing
  end
  task(:down_them_all) do # again, just to fill this space with something ;)
    parent.projects.map(&method(:artifact_ns)).map(&:artifacts).map(&:invoke)
  end
end

Loading from a yaml file (e. profiles.yaml)

If your projects use lots of jars (after all we are using java ;) you may prefer to have constant artifact definitions on an external file. Doing so would allow an external tool (or future Buildr feature) to maintain an artifacts.yaml for you. An example usage is documented on the ::load method.

For addon/plugin writers & Customizing artifact versions

Sometimes users would need to change the default artifact versions used by some module, for example, the XMLBeans compiler needs this, because of compatibility issues. Another example would be to select the groovy version to use on all our projects so that Buildr modules requiring groovy jars can use user prefered versions.

To meet this goal, an ArtifactNamespace allows to specify ArtifactRequirement objects. In fact the only difference with the examples you have already seen is that requirements have an associated VersionRequirement, so that each time a user tries to select a version, buildr checks if it satisfies the requirements.

Requirements are declared using the #need method, but again, syntactic sugar is provided by #method_missing.

The following example is taken from the XMLBeans compiler module. And illustrates how addon authors should specify their requirements, provide default versions, and document the namespace for users to customize.

module Buildr::XMLBeans

   # You need to document this constant, giving users some hints
   # about when are (maybe some of) these artifacts used. I mean,
   # some modules, add jars to the Buildr classpath when its file
   # is required, you would need to tell your users, so that they
   # can open the namespace and specify their defaults. Of course
   # when the requirements are defined, buildr checks if any compatible
   # version has been already defined, if so, uses it.
   #
   # Some things here have been changed to illustrate their meaning.
   REQUIRES = ArtifactNamespace.for(self).tap do |ns|

     # This jar requires a >2.0 version, default being 2.3.0
     ns.xmlbeans! 'org.apache.xmlbeans:xmlbeans:jar:2.3.0', '>2'

     # Users can customize with Buildr::XMLBeans::REQUIRES.stax_api = '1.2'
     # This is a non-flexible requirement, only satisfied by version 1.0.1
     ns.stax_api! 'stax:stax-api:jar:1.0.1'

     # This one is not part of XMLBeans, but is just another example
     # illustrating an `artifact requirement spec`.

     ns.need " some_name ->  ar:ti:fact:3.2.5 ->  ( >2 & <4)"

     # As you can see it's just an artifact spec, prefixed with
     # ' some_name -> ', this means users can use that name to
     # reference the requirement, also this string has a VersionRequirement
     # just after another ->.
   end

   # The REQUIRES constant is an ArtifactNamespace instance,
   # that means we can use it directly. Note that calling
   # Buildr.artifact_ns would lead to the currently executing context,
   # not the one for this module.
   def use
     # test if user specified his own version, if so, we could perform some
     # functionallity based on this.
     REQUIRES.some_name.selected? # => false

     REQUIRES.some_name.satisfied_by?('1.5') # => false
     puts REQUIRES.some_name.requirement     # => ( >2 & <4 )

     REQUIRES.artifacts # get the Artifact tasks
   end

end

A more advanced example using ArtifactRequirement listeners is included in the artifact_namespace_spec.rb description for ‘Extension using ArtifactNamespace’ That’s it for addon writers, now, users can select their prefered version with something like:

require 'buildr/xmlbeans'
Buildr::XMLBeans::REQUIRES.xmlbeans = '2.2.0'

More advanced stuff, if users really need to select an xmlbeans version per project, they can do so letting :current (that is, the currently running namespace) be parent of the REQUIRES namespace:

Buildr::XMLBeans::REQUIRES.parent = :current

Now, provided that the compiler does not caches its artifacts, it will select the correct version. (See the first section for how to select per project artifacts).

Attributes

name[R]

Public Class Methods

[](name = nil)
Alias for: instance
clear() click to toggle source

Forget all namespaces, create a new ROOT

# File lib/buildr/packaging/artifact_namespace.rb, line 217
def clear
  @instances = nil
  remove_const(:ROOT) rescue nil
  const_set(:ROOT, new('root'))
end
for(name = nil)
Alias for: instance
instance { |current_ns| ... } → current_ns click to toggle source
instance(name) { |ns| ... } → ns
instance(:current) { |current_ns| ... } → current_ns
instance(:root) { |root_ns| ... } → root_ns

Obtain an instance for the given name

# File lib/buildr/packaging/artifact_namespace.rb, line 283
def instance(name = nil)
  case name
  when :root, 'root' then return ROOT
  when ArtifactNamespace then return name
  when Array then name = name.join(':')
  when Module, Project then name = name.name
  when :current, 'current', nil then
    task = Thread.current[:rake_chain]
    task = task.instance_variable_get(:@value) if task
    name = case task
           when Project then task.name
           when Rake::Task then task.scope.join(':')
           when nil then Buildr.application.current_scope.join(':')
           end
  end
  name = name.to_s
  if name.size == 0
    instance = ROOT
  else
    name = name.to_s
    @instances ||= Hash.new { |h, k| h[k] = new(k) }
    instance = @instances[name]
  end
  yield(instance) if block_given?
  instance
end
Also aliased as: [], for
load(namespaces = {}) click to toggle source

Populate namespaces from a hash of hashes. The following example uses the profiles yaml to achieve this.

-- profiles.yaml --
development:
  artifacts:
    root:        # root namespace
      spring:     org.springframework:spring:jar:2.5
      groovy:     org.codehaus.groovy:groovy:jar:1.5.4
      logging:    # define a named group
        - log4j:log4j:jar:1.2.15
        - commons-logging:commons-logging:jar:1.1.1

    # open Buildr::XMLBeans namespace
    Buildr::XMLBeans:
      xmlbeans: 2.2

    # for subproject one:oldie
    one:oldie:
      spring:  org.springframework:spring:jar:1.0

-- buildfile --
ArtifactNamespace.load(Buildr.settings.profile['artifacts'])
# File lib/buildr/packaging/artifact_namespace.rb, line 272
def load(namespaces = {})
  namespaces.each_pair { |name, uses| instance(name).use(uses) }
end
root { |ns| ... } → ns click to toggle source

Obtain the root namespace, returns the ROOT constant

# File lib/buildr/packaging/artifact_namespace.rb, line 317
def root
  yield ROOT if block_given?
  ROOT
end
to_hash(spec) click to toggle source

Differs from Buildr::Artifact.to_hash in that 1) it does not choke when version isn’t present and 2) it assumes that if an artifact spec ends with a colon, e.g. “org.example:library:jdk5:” it indicates the last segment (“jdk5”) is a classifier.

# File lib/buildr/packaging/artifact_namespace.rb, line 226
def to_hash(spec)
  if spec.respond_to?(:to_spec)
    to_hash spec.to_spec
  elsif Hash === spec
    return spec
  elsif String === spec || Symbol === spec
    spec = spec.to_s
    if spec[-1,1] == ':'
      group, id, type, classifier, *rest = spec.split(':').map { |part| part.empty? ? nil : part }
    else
      group, id, type, version, *rest = spec.split(':').map { |part| part.empty? ? nil : part }
      unless rest.empty?
        # Optional classifier comes before version.
        classifier, version = version, rest.shift
      end
    end
    fail "Expecting <group:id:type:version> or <group:id:type:classifier:version>, found <#{spec}>" unless rest.empty?
    { :group => group, :id => id, :type => type, :version => version, :classifier => classifier }.reject { |k,v| v == nil }
  else
    fail "Unexpected artifact spec: #{spec.inspect}"
  end
end

Public Instance Methods

artifact_ns[:name] → ArtifactRequirement click to toggle source
artifact_ns[:many, :names] → [ArtifactRequirement]
# File lib/buildr/packaging/artifact_namespace.rb, line 778
def [](*names)
  ary = values_at(*names)
  names.size == 1 ? ary.first : ary
end
artifact_ns[:name] = 'some:cool:jar:1.0.2' click to toggle source
artifact_ns[:name] = '1.0.2'

Just like the use method

# File lib/buildr/packaging/artifact_namespace.rb, line 788
def []=(*names)
  values = names.pop
  values = [values] unless Array === values
  names.each_with_index do |name, i|
    use name => (values[i] || values.last)
  end
end
accessor(*names) click to toggle source

Return an anonymous module

# first create a requirement
artifact_ns.cool_aid! 'cool:aid:jar:>=1.0'

# extend an object as a cool_aid delegator
jars = Object.new.extend(artifact_ns.accessor(:cool_aid))
jars.cool_aid = '2.0'

artifact_ns.cool_aid.version # -> '2.0'
# File lib/buildr/packaging/artifact_namespace.rb, line 951
def accessor(*names)
  ns = self
  Module.new do
    names.each do |name|
      define_method("#{name}") { ns.send("#{name}") }
      define_method("#{name}?") { ns.send("#{name}?") }
      define_method("#{name}=") { |vers| ns.send("#{name}=", vers) }
    end
  end
end
alias(new_name, old_name) click to toggle source

Create an alias for a named requirement.

# File lib/buildr/packaging/artifact_namespace.rb, line 885
def alias(new_name, old_name)
  registry.alias(new_name, old_name) or
    raise NameError.new("Undefined artifact name: #{old_name}")
end
artifacts(*names) click to toggle source

return Artifact objects for each requirement

# File lib/buildr/packaging/artifact_namespace.rb, line 802
def artifacts(*names)
  (names.empty? && values || values_at(*names)).map(&:artifact)
end
clear() click to toggle source
# File lib/buildr/packaging/artifact_namespace.rb, line 859
def clear
  keys.each { |k| delete(k) }
end
delete(name, include_parents = false) click to toggle source
# File lib/buildr/packaging/artifact_namespace.rb, line 854
def delete(name, include_parents = false)
  registry.delete(name, include_parents)
  self
end
each(&block) click to toggle source

yield each ArtifactRequirement

# File lib/buildr/packaging/artifact_namespace.rb, line 797
def each(&block)
  values.each(&block)
end
fetch(name, default = nil, &block) click to toggle source

Like Hash#fetch

# File lib/buildr/packaging/artifact_namespace.rb, line 769
def fetch(name, default = nil, &block)
  block ||= proc { raise IndexError.new("No artifact found by name #{name.inspect} in namespace #{self}") }
  real_name = name.to_s[/^[\w\-\.]+$/] ? name : ArtifactRequirement.unversioned_spec(name)
  get(real_name.to_sym) || default || block.call(name)
end
group :who, :me, :you click to toggle source
group :them, :me, :you, :namespace → ns

Create a virtual group on this namespace. When the namespace is asked for the who artifact, it’s value is an array made from the remaining names. These names are searched by default from the current namespace. Second form specified the starting namespace to search from.

# File lib/buildr/packaging/artifact_namespace.rb, line 872
def group(group_name, *members)
  namespace = (Hash === members.last && members.pop[:namespace]) || :current
  registry[group_name] = lambda do
    artifacts = self.class[namespace].values_at(*members)
    artifacts = artifacts.first if members.size == 1
    artifacts
  end
  self
end
Also aliased as: virtual
key?(name, include_parents = false) click to toggle source
# File lib/buildr/packaging/artifact_namespace.rb, line 845
def key?(name, include_parents = false)
  name = ArtifactRequirement.unversioned_spec(name) unless name.to_s[/^[\w\-\.]+$/]
  registry.key?(name, include_parents)
end
keys() click to toggle source
# File lib/buildr/packaging/artifact_namespace.rb, line 850
def keys
  values.map(&:name)
end
cool_aid!('cool:aid:jar:2.3.4', '~>2.3') → artifact_requirement click to toggle source
cool_aid = '2.3.5'
cool_aid → artifact_requirement
cool_aid? → true | false

First form creates an ArtifactRequirement on the namespace. It is equivalent to providing a requirement_spec to the need method:

artifact_ns.need "cool_aid -> cool:aid:jar:2.3.4 -> ~>2.3"

the second argument is optional.

Second form can be used to select an artifact version and is equivalent to:

artifact_ns.use :cool_aid => '1.0'

Third form obtains the named ArtifactRequirement, can be used to test if a named requirement has been defined. It is equivalent to:

artifact_ns.fetch(:cool_aid) { nil }

Last form tests if the ArtifactRequirement has been defined and a version has been selected for use. It is equivalent to:

artifact_ns.has_cool_aid?
artifact_ns.values_at(:cool_aid).flatten.all? { |a| a && a.selected? }
# File lib/buildr/packaging/artifact_namespace.rb, line 921
def method_missing(name, *args, &block)
  case name.to_s
  when /!$/ then
    name = $`.intern
    if args.size < 1 || args.size > 2
      raise ArgumentError.new("wrong number of arguments for #{name}!(spec, version_requirement?)")
    end
    need name => args.first
    get(name).tap { |r| r.requirement = args.last if args.size == 2 }
  when /=$/ then use $` => args.first
  when /\?$/ then
    name = $`.gsub(/^(has|have)_/, '').intern
    [get(name)].flatten.all? { |a| a && a.selected? }
  else
    if block || args.size > 0
      raise ArgumentError.new("wrong number of arguments #{args.size} for 0 or block given")
    end
    get(name)
  end
end
need 'name -> org:foo:bar:jar:~>1.2.3 → 1.2.5' click to toggle source
need :name → 'org.foo:bar:jar:1.0'

Create a new ArtifactRequirement on this namespace. #method_missing provides syntactic sugar for this.

# File lib/buildr/packaging/artifact_namespace.rb, line 631
def need(*specs)
  named = specs.flatten.inject({}) do |seen, spec|
    if Hash === spec && (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty?
      spec.each_pair do |name, spec|
        if Array === spec # a group
          seen[name] ||= spec.map { |s| ArtifactRequirement.new(s) }
        else
          artifact = ArtifactRequirement.new(spec)
          artifact.name = name
          seen[artifact.name] ||= artifact
        end
      end
    else
      artifact = ArtifactRequirement.new(spec)
      seen[artifact.name] ||= artifact
    end
    seen
  end
  named.each_pair do |name, artifact|
    if Array === artifact # a group
      artifact.each do |a|
        unvers = a.unversioned_spec
        previous = registry[unvers]
        if previous && previous.selected? && a.satisfied_by?(previous)
          a.version = previous.version
        end
        registry[unvers] = a
      end
      group(name, *(artifact.map { |a| a.unversioned_spec } + [{:namespace => self}]))
    else
      unvers = artifact.unversioned_spec
      previous = registry.get(unvers, true)
      if previous && previous.selected? && artifact.satisfied_by?(previous)
        artifact.version = previous.version
        artifact.selected!
      end
      registry[unvers] = artifact
      registry.alias name, unvers unless name.to_s[/^\s*$/]
    end
  end
  self
end
ns(name, *uses) { |sub| ... } click to toggle source

Create a named sub-namespace, sub-namespaces are themselves ArtifactNamespace instances but cannot be referenced by the Buildr.artifact_ns, ::instance methods. Reference needs to be through this object using the given name

artifact_ns('foo').ns(:bar).need :thing => 'some:thing:jar:1.0'
artifact_ns('foo').bar # => the sub-namespace 'foo.bar'
artifact_ns('foo').bar.thing # => the some thing artifact

See the top level ArtifactNamespace documentation for examples

# File lib/buildr/packaging/artifact_namespace.rb, line 604
def ns(name, *uses, &block)
  name = name.to_sym
  sub = registry[name]
  if sub
    raise TypeError.new("#{name} is not a sub namespace of #{self}") unless sub.kind_of?(ArtifactNamespace)
  else
    sub = ArtifactNamespace.new("#{self.name}.#{name}")
    sub.parent = self
    registry[name] = sub
  end
  sub.use(*uses)
  yield sub if block_given?
  sub
end
ns?(name) click to toggle source

Test if a sub-namespace by the given name exists

# File lib/buildr/packaging/artifact_namespace.rb, line 620
def ns?(name)
  sub = registry[name.to_sym]
  ArtifactNamespace === sub
end
parent() click to toggle source

ROOT namespace has no parent

# File lib/buildr/packaging/artifact_namespace.rb, line 567
def parent
  if root?
    nil
  elsif @parent.kind_of?(ArtifactNamespace)
    @parent
  elsif @parent
    ArtifactNamespace.instance(@parent)
  elsif name
    parent_name = name.gsub(/::?[^:]+$/, '')
    parent_name == name ? root : ArtifactNamespace.instance(parent_name)
  else
    root
  end
end
parent=(other) click to toggle source

Set the parent for the current namespace, except if it is ROOT

# File lib/buildr/packaging/artifact_namespace.rb, line 583
def parent=(other)
  raise 'Cannot set parent of root namespace' if root?
  @parent = other
  @registry = nil
end
root() { |ROOT| ... } click to toggle source
# File lib/buildr/packaging/artifact_namespace.rb, line 561
def root
  yield ROOT if block_given?
  ROOT
end
root?() click to toggle source

Is this the ROOT namespace?

# File lib/buildr/packaging/artifact_namespace.rb, line 590
def root?
  ROOT == self
end
use 'name → org:foo:bar:jar:1.2.3' click to toggle source
use :name → 'org:foo:bar:jar:1.2.3'
use :name → '2.5.6'

First and second form are equivalent, the third is used when an ArtifactRequirement has been previously defined with :name, so it just selects the version.

#method_missing provides syntactic sugar for this.

# File lib/buildr/packaging/artifact_namespace.rb, line 684
def use(*specs)
  named = specs.flatten.inject({}) do |seen, spec|
    if Hash === spec && (spec.keys & ActsAsArtifact::ARTIFACT_ATTRIBUTES).empty?
      spec.each_pair do |name, spec|
        if ArtifactNamespace === spec # create as subnamespace
          raise ArgumentError.new("Circular reference") if self == spec
          registry[name.to_sym] = spec
        elsif Numeric === spec || (String === spec && VersionRequirement.version?(spec))
          artifact = ArtifactRequirement.allocate
          artifact.name = name
          artifact.version = spec.to_s
          seen[artifact.name] ||= artifact
        elsif Symbol === spec
          self.alias name, spec
        elsif Array === spec # a group
          seen[name] ||= spec.map { |s| ArtifactRequirement.new(s) }
        else
          artifact = ArtifactRequirement.new(spec)
          artifact.name = name
          seen[artifact.name] ||= artifact
        end
      end
    else
      if Symbol === spec
        artifact = get(spec).dclone
      else
        artifact = ArtifactRequirement.new(spec)
      end
      seen[artifact.name] ||= artifact
    end
    seen
  end
  named.each_pair do |name, artifact|
    is_group = Array === artifact
    artifact = [artifact].flatten.map do |artifact|
      unvers = artifact.unversioned_spec
      previous = get(unvers, false) || get(name, false)
      if previous # have previous on current namespace
        if previous.requirement # we must satisfy the requirement
          unless unvers # we only have the version
            satisfied = previous.requirement.satisfied_by?(artifact.version)
          else
            satisfied = previous.satisfied_by?(artifact)
          end
          raise "Unsatisfied dependency #{previous} " +
            "not satisfied by #{artifact}" unless satisfied
          previous.version = artifact.version # OK, set new version
          artifact = previous # use the same object for aliases
        else # not a requirement, set the new values
          unless artifact.id == previous.id && name != previous.name
            previous.copy_attrs(artifact)
            artifact = previous
          end
        end
      else
        if unvers.nil? && # we only have the version
            (previous = get(unvers, true, false, false))
          version = artifact.version
          artifact.copy_attrs(previous)
          artifact.version = version
        end
        artifact.requirement = nil
      end
      artifact.selected!
    end
    artifact = artifact.first unless is_group
    if is_group
      names = artifact.map do |art|
        unv = art.unversioned_spec
        registry[unv] = art
        unv
      end
      group(name, *(names + [{:namespace => self}]))
    elsif artifact.id
      unvers = artifact.unversioned_spec
      registry[name] = artifact
      registry.alias unvers, name
    else
      registry[name] = artifact
    end
  end
  self
end
values(include_parents = false, include_groups = true) click to toggle source

Return all requirements for this namespace

# File lib/buildr/packaging/artifact_namespace.rb, line 807
def values(include_parents = false, include_groups = true)
  seen, dict = {}, registry
  while dict
    dict.each do |k, v|
      v = v.call if v.respond_to?(:call)
      v = v.values if v.kind_of?(ArtifactNamespace)
      if Array === v && include_groups
        v.compact.each { |v| seen[v.name] = v unless seen.key?(v.name) }
      else
        seen[v.name] = v unless seen.key?(v.name)
      end
    end
    dict = include_parents ? dict.parent : nil
  end
  seen.values
end
values_at(*names) click to toggle source

Return only the named requirements

# File lib/buildr/packaging/artifact_namespace.rb, line 825
def values_at(*names)
  names.map do |name|
    catch :artifact do
      unless name.to_s[/^[\w\-\.]+$/]
        unvers = ArtifactRequirement.unversioned_spec(name)
        unless unvers.to_s == name.to_s
          req = ArtifactRequirement.new(name)
          reg = self
          while reg
            candidate = reg.send(:get, unvers, false, false, true)
            throw :artifact, candidate if req.satisfied_by?(candidate)
            reg = reg.parent
          end
        end
      end
      get(name.to_sym)
    end
  end
end
virtual(group_name, *members)
Alias for: group