[DRE-commits] [ruby-hiera] 01/04: Imported Upstream version 1.3.0

Jonas Genannt jonas at brachium-system.net
Thu Nov 21 22:08:19 UTC 2013


This is an automated email from the git hooks/post-receive script.

hggh-guest pushed a commit to branch master
in repository ruby-hiera.

commit 14ce36e318b79bea4676014b783d2b28c472a987
Author: Jonas Genannt <jonas at brachium-system.net>
Date:   Thu Nov 21 22:56:39 2013 +0100

    Imported Upstream version 1.3.0
---
 .gitignore                                         |    3 -
 README.md                                          |    8 +-
 Rakefile                                           |   48 ---
 acceptance_tests/tests/yaml_backend/00-setup.rb    |   26 --
 .../yaml_backend/01-lookup_data_without_a_key.rb   |    9 -
 .../yaml_backend/02-lookup_data_with_no_options.rb |   48 ---
 .../yaml_backend/03-lookup_data_with_a_scope.rb    |   62 ----
 .../04-lookup_data_with_array_search.rb            |   46 ---
 .../05-lookup_data_with_hash_search.rb             |   49 ---
 bin/hiera                                          |    3 +-
 docs/images/hiera_hierarchy_resolution.png         |  Bin 15495 -> 0 bytes
 .../hiera_hierarchy_resolution_empty_scope.png     |  Bin 8172 -> 0 bytes
 docs/tutorials/getting_started.md                  |  113 ------
 docs/tutorials/hierarchies_sources_and_scope.md    |  123 -------
 ext/build_defaults.yaml                            |   23 --
 ext/debian/changelog.erb                           |   23 --
 ext/debian/compat                                  |    1 -
 ext/debian/control                                 |   13 -
 ext/debian/copyright                               |    1 -
 ext/debian/rules                                   |   35 --
 ext/debian/source/format                           |    1 -
 ext/hiera.yaml                                     |   15 -
 ext/ips/hiera.p5m.erb                              |   10 -
 ext/ips/rules                                      |   17 -
 ext/ips/transforms                                 |   20 --
 ext/osx/file_mapping.yaml                          |   32 --
 ext/osx/preflight.erb                              |   30 --
 ext/osx/prototype.plist.erb                        |   38 --
 ext/project_data.yaml                              |   17 -
 ext/redhat/hiera.spec.erb                          |   80 -----
 lib/hiera.rb                                       |   32 +-
 lib/hiera/backend.rb                               |   95 +++--
 lib/hiera/backend/json_backend.rb                  |   17 +-
 lib/hiera/backend/yaml_backend.rb                  |   40 +--
 lib/hiera/config.rb                                |   18 +-
 lib/hiera/error.rb                                 |    4 +
 lib/hiera/fallback_logger.rb                       |   41 +++
 lib/hiera/filecache.rb                             |   86 +++++
 lib/hiera/interpolate.rb                           |   48 +++
 lib/hiera/puppet_logger.rb                         |    4 +
 lib/hiera/recursive_guard.rb                       |   20 ++
 lib/hiera/version.rb                               |   89 +++++
 metadata.yml                                       |  104 ++++++
 spec/unit/backend/json_backend_spec.rb             |   32 +-
 spec/unit/backend/yaml_backend_spec.rb             |  109 ++----
 spec/unit/backend_spec.rb                          |  368 ++++++++++++++++----
 spec/unit/config_spec.rb                           |    7 +-
 spec/unit/fallback_logger_spec.rb                  |   80 +++++
 spec/unit/filecache_spec.rb                        |  142 ++++++++
 spec/unit/hiera_spec.rb                            |   41 ++-
 spec/unit/puppet_logger_spec.rb                    |   31 ++
 spec/unit/version_spec.rb                          |   44 +++
 52 files changed, 1205 insertions(+), 1141 deletions(-)

diff --git a/.gitignore b/.gitignore
deleted file mode 100644
index 55a711c..0000000
--- a/.gitignore
+++ /dev/null
@@ -1,3 +0,0 @@
-pkg
-test.rb
-ext/packaging
diff --git a/README.md b/README.md
index 0c18e9c..38f5db1 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
 # Hiera
 
+[![Build Status](https://travis-ci.org/puppetlabs/hiera.png?branch=master)](https://travis-ci.org/puppetlabs/hiera)
+
 A simple pluggable Hierarchical Database.
 
 -
@@ -115,7 +117,7 @@ A sample configuration file can be seen here:
 :logger: console
 
 :hierarchy:
-  - "%{location}"
+  - "sites/%{location}"
   - common
 
 :yaml:
@@ -128,14 +130,14 @@ A sample configuration file can be seen here:
 This configuration will require YAML files in  _/etc/puppet/hieradata_ these need to contain
 Hash data, sample files matching the hierarchy described in the _Why?_ section are below:
 
-_/etc/puppet/hieradata/dc1.yaml_:
+_/etc/puppet/hieradata/sites/dc1.yaml_:
 <pre>
 ---
 ntpserver: ntp1.dc1.example.com
 sysadmin: dc1noc at example.com
 </pre>
 
-_/etc/puppet/hieradata/dc2.yaml_:
+_/etc/puppet/hieradata/sites/dc2.yaml_:
 <pre>
 ---
 ntpserver: ntp1.dc2.example.com
diff --git a/Rakefile b/Rakefile
deleted file mode 100644
index 41ef3a4..0000000
--- a/Rakefile
+++ /dev/null
@@ -1,48 +0,0 @@
-begin
-  require 'rubygems'
-  require 'rspec/core/rake_task'
-rescue LoadError
-end
-
-Dir['tasks/**/*.rake'].each { |t| load t }
-Dir['ext/packaging/tasks/**/*'].sort.each { |t| load t }
-
-build_defs_file = 'ext/build_defaults.yaml'
-if File.exist?(build_defs_file)
-  begin
-    require 'yaml'
-    @build_defaults ||= YAML.load_file(build_defs_file)
-  rescue Exception => e
-    STDERR.puts "Unable to load yaml from #{build_defs_file}:"
-    STDERR.puts e
-  end
-  @packaging_url  = @build_defaults['packaging_url']
-  @packaging_repo = @build_defaults['packaging_repo']
-  raise "Could not find packaging url in #{build_defs_file}" if @packaging_url.nil?
-  raise "Could not find packaging repo in #{build_defs_file}" if @packaging_repo.nil?
-
-  namespace :package do
-    desc "Bootstrap packaging automation, e.g. clone into packaging repo"
-    task :bootstrap do
-      if File.exist?("ext/#{@packaging_repo}")
-        puts "It looks like you already have ext/#{@packaging_repo}. If you don't like it, blow it away with package:implode."
-      else
-        cd 'ext' do
-          %x{git clone #{@packaging_url}}
-        end
-      end
-    end
-    desc "Remove all cloned packaging automation"
-    task :implode do
-      rm_rf "ext/#{@packaging_repo}"
-    end
-  end
-end
-
-if defined?(RSpec::Core::RakeTask)
-  desc "Run all specs"
-  RSpec::Core::RakeTask.new(:test) do |t|
-    t.pattern = 'spec/**/*_spec.rb'
-  end
-end
-
diff --git a/acceptance_tests/tests/yaml_backend/00-setup.rb b/acceptance_tests/tests/yaml_backend/00-setup.rb
deleted file mode 100644
index 100e7e1..0000000
--- a/acceptance_tests/tests/yaml_backend/00-setup.rb
+++ /dev/null
@@ -1,26 +0,0 @@
-test_name "Hiera setup for YAML backend"
-
-apply_manifest_on master, <<-PP
-file { '/etc/hiera.yaml':
-  ensure  => present,
-  content => '---
-    :backends:
-      - "yaml"
-    :logger: "console"
-    :hierarchy:
-      - "%{fqdn}"
-      - "%{environment}"
-      - "global"
-
-    :yaml:
-      :datadir: "/etc/puppet/hieradata"
-  '
-}
-
-file { '/etc/puppet/hieradata':
-  ensure  => directory,
-  recurse => true,
-  purge   => true,
-  force   => true,
-}
-PP
diff --git a/acceptance_tests/tests/yaml_backend/01-lookup_data_without_a_key.rb b/acceptance_tests/tests/yaml_backend/01-lookup_data_without_a_key.rb
deleted file mode 100644
index 5a9c3e6..0000000
--- a/acceptance_tests/tests/yaml_backend/01-lookup_data_without_a_key.rb
+++ /dev/null
@@ -1,9 +0,0 @@
-test_name "Lookup data without a key"
-
-step "Try to lookup data without specifying a key"
-
-on master, hiera(""), :acceptable_exit_codes => [1] do
-  assert_output <<-OUTPUT
-    STDERR> Please supply a data item to look up
-  OUTPUT
-end
diff --git a/acceptance_tests/tests/yaml_backend/02-lookup_data_with_no_options.rb b/acceptance_tests/tests/yaml_backend/02-lookup_data_with_no_options.rb
deleted file mode 100644
index 6175d08..0000000
--- a/acceptance_tests/tests/yaml_backend/02-lookup_data_with_no_options.rb
+++ /dev/null
@@ -1,48 +0,0 @@
-begin test_name "Lookup data using the default options"
-
-step 'Setup'
-apply_manifest_on master, <<-PP
-file { '/etc/puppet/hieradata/global.yaml':
-  ensure  => present,
-  content => "---
-    http_port: 8080
-    ntp_servers: ['0.ntp.puppetlabs.com', '1.ntp.puppetlabs.com']
-    users:
-      pete:
-        uid: 2000
-      tom:
-        uid: 2001
-  "
-}
-PP
-
-step "Try to lookup string data"
-on master, hiera("http_port"), :acceptable_exit_codes => [0] do
-  assert_output <<-OUTPUT
-    STDOUT> 8080
-  OUTPUT
-end
-
-step "Try to lookup array data"
-on master, hiera("ntp_servers"), :acceptable_exit_codes => [0] do
-  assert_output <<-OUTPUT
-    STDOUT> ["0.ntp.puppetlabs.com", "1.ntp.puppetlabs.com"]
-  OUTPUT
-end
-
-step "Try to lookup hash data"
-on master, hiera("users"), :acceptable_exit_codes => [0] do
-  assert_match /tom[^}]+"uid"=>2001}/, result.output
-  assert_match /pete[^}]+"uid"=>2000}/, result.output
-end
-
-ensure step "Teardown"
-apply_manifest_on master, <<-PP
-file { '/etc/puppet/hieradata':
-  ensure  => directory,
-  recurse => true,
-  purge   => true,
-  force   => true,
-}
-PP
-end
diff --git a/acceptance_tests/tests/yaml_backend/03-lookup_data_with_a_scope.rb b/acceptance_tests/tests/yaml_backend/03-lookup_data_with_a_scope.rb
deleted file mode 100644
index 377c065..0000000
--- a/acceptance_tests/tests/yaml_backend/03-lookup_data_with_a_scope.rb
+++ /dev/null
@@ -1,62 +0,0 @@
-begin test_name "Lookup data with a scope"
-
-step 'Setup'
-apply_manifest_on master, <<-PP
-file { '/etc/puppet/hieradata/global.yaml':
-  ensure  => present,
-  content => "---
-    http_port: 8080
-    ntp_servers: ['0.ntp.puppetlabs.com', '1.ntp.puppetlabs.com']
-    users:
-      pete:
-        uid: 2000
-        gid: 2000
-        shell: '/bin/bash'
-      tom:
-        uid: 2001
-        gid: 2001
-        shell: '/bin/bash'
-  "
-}
-
-file { '/etc/puppet/hieradata/production.yaml':
-  ensure  => present,
-  content => "---
-    http_port: 9090
-    monitor: enable
-    ntp_servers: ['0.ntp.puppetlabs.com', '1.ntp.puppetlabs.com']
-  "
-}
-
-file { '/etc/puppet/scope.yaml':
-  ensure  => present,
-  content => "---
-    environment: production
-  "
-}
-PP
-
-step "Try to lookup string data using a scope from a yaml file"
-on master, hiera('monitor', '--yaml', '/etc/puppet/scope.yaml'),
-  :acceptable_exit_codes => [0] do
-  assert_output <<-OUTPUT
-    STDOUT> enable
-  OUTPUT
-end
-
-# TODO: Add a test for supplying scope from a json file.
-# We need to workout the requirement on the json gem.
-step "Try to lookup string data using a scope from a yaml file"
-
-ensure step "Teardown"
-apply_manifest_on master, <<-PP
-file { '/etc/puppet/hieradata':
-  ensure  => directory,
-  recurse => true,
-  purge   => true,
-  force   => true,
-}
-file { '/etc/puppet/scope.yaml': ensure => absent }
-file { '/etc/puppet/scope.json': ensure => absent }
-PP
-end
diff --git a/acceptance_tests/tests/yaml_backend/04-lookup_data_with_array_search.rb b/acceptance_tests/tests/yaml_backend/04-lookup_data_with_array_search.rb
deleted file mode 100644
index 65ec265..0000000
--- a/acceptance_tests/tests/yaml_backend/04-lookup_data_with_array_search.rb
+++ /dev/null
@@ -1,46 +0,0 @@
-begin test_name "Lookup data with Array search"
-
-step 'Setup'
-apply_manifest_on master, <<-PP
-file { '/etc/puppet/hieradata/production.yaml':
-  ensure  => present,
-  content => "---
-    ntpservers: ['production.ntp.puppetlabs.com']
-  "
-}
-
-file { '/etc/puppet/hieradata/global.yaml':
-  ensure  => present,
-  content => "---
-    ntpservers: ['global.ntp.puppetlabs.com']
-  "
-}
-
-file { '/etc/puppet/scope.yaml':
-  ensure  => present,
-  content => "---
-    environment: production
-  "
-}
-PP
-
-step "Try to lookup data using array search"
-on master, hiera('ntpservers', '--yaml', '/etc/puppet/scope.yaml', '--array'),
-  :acceptable_exit_codes => [0] do
-  assert_output <<-OUTPUT
-    STDOUT> ["production.ntp.puppetlabs.com", "global.ntp.puppetlabs.com"]
-  OUTPUT
-end
-
-ensure step "Teardown"
-apply_manifest_on master, <<-PP
-file { '/etc/puppet/hieradata':
-  ensure  => directory,
-  recurse => true,
-  purge   => true,
-  force   => true,
-}
-file { '/etc/puppet/scope.yaml': ensure => absent }
-file { '/etc/puppet/scope.json': ensure => absent }
-PP
-end
diff --git a/acceptance_tests/tests/yaml_backend/05-lookup_data_with_hash_search.rb b/acceptance_tests/tests/yaml_backend/05-lookup_data_with_hash_search.rb
deleted file mode 100644
index 8baffae..0000000
--- a/acceptance_tests/tests/yaml_backend/05-lookup_data_with_hash_search.rb
+++ /dev/null
@@ -1,49 +0,0 @@
-begin test_name "Lookup data with Hash search"
-
-step 'Setup'
-apply_manifest_on master, <<-PP
-file { '/etc/puppet/hieradata/production.yaml':
-  ensure  => present,
-  content => "---
-    users:
-      joe:
-        uid: 1000
-  "
-}
-
-file { '/etc/puppet/hieradata/global.yaml':
-  ensure  => present,
-  content => "---
-    users:
-      pete:
-        uid: 1001
-  "
-}
-
-file { '/etc/puppet/scope.yaml':
-  ensure  => present,
-  content => "---
-    environment: production
-  "
-}
-PP
-
-step "Try to lookup data using hash search"
-on master, hiera('users', '--yaml', '/etc/puppet/scope.yaml', '--hash'),
-  :acceptable_exit_codes => [0] do
-  assert_match /joe[^}]+"uid"=>1000}/, result.output
-  assert_match /pete[^}]+"uid"=>1001}/, result.output
-end
-
-ensure step "Teardown"
-apply_manifest_on master, <<-PP
-file { '/etc/puppet/hieradata':
-  ensure  => directory,
-  recurse => true,
-  purge   => true,
-  force   => true,
-}
-file { '/etc/puppet/scope.yaml': ensure => absent }
-file { '/etc/puppet/scope.json': ensure => absent }
-PP
-end
diff --git a/bin/hiera b/bin/hiera
index ddd4daa..ce2875e 100755
--- a/bin/hiera
+++ b/bin/hiera
@@ -24,6 +24,7 @@ end
 require 'hiera'
 require 'hiera/util'
 require 'optparse'
+require 'pp'
 
 options = {
   :default => nil,
@@ -222,5 +223,5 @@ ans = hiera.lookup(options[:key], options[:default], options[:scope], nil, optio
 if ans.is_a?(String)
   puts ans
 else
-  p ans
+  pp ans
 end
diff --git a/docs/images/hiera_hierarchy_resolution.png b/docs/images/hiera_hierarchy_resolution.png
deleted file mode 100644
index 1b37cf8..0000000
Binary files a/docs/images/hiera_hierarchy_resolution.png and /dev/null differ
diff --git a/docs/images/hiera_hierarchy_resolution_empty_scope.png b/docs/images/hiera_hierarchy_resolution_empty_scope.png
deleted file mode 100644
index 217839b..0000000
Binary files a/docs/images/hiera_hierarchy_resolution_empty_scope.png and /dev/null differ
diff --git a/docs/tutorials/getting_started.md b/docs/tutorials/getting_started.md
deleted file mode 100644
index 04b8912..0000000
--- a/docs/tutorials/getting_started.md
+++ /dev/null
@@ -1,113 +0,0 @@
-# Getting Started
-
-Hiera is a simple hierarchal database which provides an easy to use interface
-for looking up data using a key.
-
-    $ hiera puppetlabs_home_page
-    http://puppetlabs.com
-
-## Installation
-
-We are going to install Hiera using Rubygems, so now is a good time to make
-sure you meet the prerequisites.
-
-### Prerequisites
-
- * Ruby 1.8.5+
- * Rubygems 1.3.0+
-
-### Installing Hiera via Rubygems
-
-    $ gem install hiera
-    Successfully installed hiera-0.3.0
-    1 gem installed
-    Installing ri documentation for hiera-0.3.0...
-    Installing RDoc documentation for hiera-0.3.0...
-
-Make sure we can run the hiera command:
-
-    $ hiera -v
-    0.3.0
-
--
-
-**Note:** Some Linux distributions such as Debian squeeze do not put the gem bin
-directory (`/var/lib/gems/1.8/bin`) in your PATH by default. You may have
-to call hiera using the full path:
-
-    $ /var/lib/gems/1.8/bin/hiera -v
-    0.3.0
-
-## Configuration
-
-Before using Hiera we need to create a configuration file. By default Hiera
-attempts to load `hiera.yaml` from the `/etc/` directory. Lets create that
-file now:
-
-    $ vim /etc/hiera.yaml
-    ---
-    :backends:
-      - yaml
-
-    :hierarchy:
-      - global
-
-    :yaml:
-      :datadir: /var/lib/hiera/data
-
--
-
-**Note:** If Hiera cannot locate `/etc/hiera.yaml` you will receive the follow
-error when trying to lookup a value:
-
-    $ hiera key
-    Failed to start Hiera: RuntimeError: Config file /etc/hiera.yaml not found
-
-You can specify a different configuration file using the `--config` option:
-
-    $ hiera --config ~/hiera.yaml key
-
-## Adding data
-
-With configuration out of the way, lets add some data. The yaml backend
-expects to find data files under the `datadir` we configured earlier.
-
-Create the `/var/lib/hiera/data` data directory:
-
-    $ mkdir -p /var/lib/hiera/data
-
-For each source in the `hierarchy`, the yaml backend will search for a
-corresponding YAML file under the `datadir`.
-
-For example, our `hierarchy` consists of a single source named `global`. The
-yaml backend will look for `/var/lib/hiera/data/global.yaml`, and if missing
-skips it and move on to the next source in the hierarchy.
-
-Lets add some data to the `global` source:
-
-    $ vim /var/lib/hiera/data/global.yaml
-    ---
-    driftfile: '/etc/ntp/drift'
-    ntpservers:
-      - '0.north-america.pool.ntp.org'
-      - '1.north-america.pool.ntp.org'
-
-## Looking up data
-
-Now that we have our configuration setup and some data, lets lookup the
-'driftfile' key:
-
-    $ /var/lib/gems/1.8/bin/hiera driftfile
-    /etc/ntp/drift
-
-We get extacaly what we expected, '/etc/ntp/drift'.
-
-Running the lookup command with the `--debug` flag, we can see the details
-of how Hiera lookups data:
-
-    $ /var/lib/gems/1.8/bin/hiera driftfile --debug
-    DEBUG: Thu Jun 28 09:54:04 -0400 2012: Hiera YAML backend starting
-    DEBUG: Thu Jun 28 09:54:04 -0400 2012: Looking up driftfile in YAML backend
-    DEBUG: Thu Jun 28 09:54:04 -0400 2012: Looking for data source global
-    /etc/ntp/drift
-
diff --git a/docs/tutorials/hierarchies_sources_and_scope.md b/docs/tutorials/hierarchies_sources_and_scope.md
deleted file mode 100644
index 3b64290..0000000
--- a/docs/tutorials/hierarchies_sources_and_scope.md
+++ /dev/null
@@ -1,123 +0,0 @@
-# Hierarchies, Sources, and Scope
-
-The key to mastering Hiera is understanding the following concepts:
-
- * Hierarchies
- * Sources
- * Scope
-
-## Hierarchies
-
-At the very core of Hiera are the data hierarchies, which are made up of
-sources. Hierarchies are specified in Hiera configuration file `hiera.yaml` via the
-`:hierarchy:` array.
-
-    :hierarchy:
-      - "%{certname}"
-      - "%{environment}"
-      - default
-
-There are three sources in the above hierarchy, `%{certname}`, `%{environment}`,
-and `default`. The first two sources, `%{certname}` and `%{environment}`,
-represent dynamic sources which will be resolved at runtime. The third source
-`default` is static.
-
-
-When looking up a key Hiera iterates through each source in the hierarchy
-starting with the first one in the list. In our case `%{certname}`. There is
-no limit to the number of sources you can have. But lets not go crazy; try and
-keep your hierarchy below 5 - 6 levels deep. Any more than this you should
-start thinking about custom facts or how your data is organized.
-
-### Order is important
-
-Hiera uses the priority resolution type by default. This means Hiera stops at
-the first source in the hierarchy that provides a non `nil` answer. The
-behavior is a slightly different for the array and hash resolution types. Every
-scope in the hierarchy will be searched, but data is appended not overridden!
-
-## Sources
-
-Each level of the hierarchy is represented by a source which comes in two
-flavors, static and dynamic.
-
-### Static sources
-
-A source is considered static when it appears in the hierarchy as a simple
-string.
-
-    :hierarchy:
-      - default
-
--
-You should consider using a static source when you want a certain level in
-the hierarchy to apply to all nodes.
-
-### Dynamic sources
-
-A source is considered dynamic when it appears in the hierarchy as a string
-enclosed between `%{}` like this:
-
-    :hierarchy:
-      - %{certname}
-
-Dynamic sources are interpolated by Hiera at runtime.
-
--
-You should consider using a dynamic source when you want to provide different
-data based on Facter Facts.
-
-## Scope
-
-A scope is a collection of key/value pairs:
-
-    certname: agent.puppetlabs.com
-    environment: production
-    operatingsystem: Debian
-
-If you are thinking scopes look a lot like Facter Facts you are on to
-something. Hiera was designed around Facter Facts being the primary input
-for scope.
-
-### Source interpolation
-
-Hiera uses the scope when interpolating sources in the hierarchy.
-
-<img src='https://github.com/kelseyhightower/hiera/raw/maint/1.0rc/add_getting_started_tutorial/docs/images/hiera_hierarchy_resolution.png' />
-
-Scopes can be empty, and when they are, dynamic sources are excluded from the
-hierarchy at run time.
-
-<img src='https://github.com/kelseyhightower/hiera/raw/maint/1.0rc/add_getting_started_tutorial/docs/images/hiera_hierarchy_resolution_empty_scope.png' />
-
-### Feeding Hiera your scope
-
-You can provide Hiera a scope via the command line using the `--yaml` or
-`--json` options.
-
-For example:
-
-If we had the following scope:
-
-    $ cat /tmp/scope.yaml
-    ---
-    certname: agent.example.com
-    environment: production
-
-
-
-You can feed it to Hiera like this:
-
-    $ hiera --yaml /tmp/scope.yaml driftfile
-    /etc/ntp/drift
-
--
-**Note:** If you run into the follow error, you need to make sure Puppet is installed:
-
-    Could not load YAML scope: LoadError: no such file to load -- puppet
-
-The reason for this is that the scope yaml file could have been produced by
-Puppet, and contained serialized objects. Since it would be desirable to use 
-Hiera without Puppet, this restrict will be removed in the future.
-
-
diff --git a/ext/build_defaults.yaml b/ext/build_defaults.yaml
deleted file mode 100644
index 320704b..0000000
--- a/ext/build_defaults.yaml
+++ /dev/null
@@ -1,23 +0,0 @@
----
-packaging_url: 'git://github.com/puppetlabs/packaging.git --branch=master'
-packaging_repo: 'packaging'
-default_cow: 'base-squeeze-i386.cow'
-cows: 'base-lucid-i386.cow base-natty-i386.cow base-oneiric-i386.cow base-precise-i386.cow base-quantal-i386.cow base-sid-i386.cow base-squeeze-i386.cow base-stable-i386.cow base-testing-i386.cow base-unstable-i386.cow base-wheezy-i386.cow'
-pbuild_conf: '/etc/pbuilderrc'
-packager: 'puppetlabs'
-gpg_name: 'info at puppetlabs.com'
-gpg_key: '4BD6EC30'
-sign_tar: FALSE
-# a space separated list of mock configs
-final_mocks: 'pl-5-i386 pl-6-i386 fedora-16-i386 fedora-17-i386'
-rc_mocks: 'pl-5-i386-dev pl-6-i386-dev fedora-16-i386-dev fedora-17-i386-dev'
-build_gem: TRUE
-build_dmg: TRUE
-yum_host: 'burji.puppetlabs.com'
-yum_repo_path: '/opt/repository/yum/'
-apt_host: 'burji.puppetlabs.com'
-apt_repo_url: 'http://apt.puppetlabs.com'
-apt_repo_path: '/opt/repository/incoming'
-ips_repo: '/var/pkgrepo'
-ips_store: '/opt/repository'
-ips_host: 'solaris-11-ips-repo.acctest.dc1.puppetlabs.net'
diff --git a/ext/debian/changelog.erb b/ext/debian/changelog.erb
deleted file mode 100644
index b178c6e..0000000
--- a/ext/debian/changelog.erb
+++ /dev/null
@@ -1,23 +0,0 @@
-hiera (<%= @debversion %>) lucid maverick natty lenny squeeze precise wheezy sid unstable; urgency=low
-
-  * update to version <%= @debversion %>
-
- -- Puppet Labs Release  <info at puppetlabs.com>  <%= Time.now.strftime("%a, %d %b %Y %H:%M:%S %z")  %>
-
-hiera (0.99+1.0.0rc2-1puppet1) unstable; urgency=low
-
-  * Release of 1.0.0rc2
-
- -- Matthaus Litteken <matthaus at puppetlabs.com>  Tue, 15 May 2012 04:45:08 +0000
-
-hiera (0.99+1.0.0rc1-1puppet1) unstable; urgency=low
-
-  * Release of 1.0.0rc1
-
- -- Puppet Labs Release Key (Puppet Labs Release Key) <info at puppetlabs.com>  Mon, 14 May 2012 23:55:54 +0000
-
-hiera (0.3.0.24-1) unstable; urgency=low
-
-  * Initial release of upstream 0.3.0.24
-
- -- Puppet Labs Release Key (Puppet Labs Release Key) <info at puppetlabs.com>  Fri, 04 May 2012 20:43:22 +0000
diff --git a/ext/debian/compat b/ext/debian/compat
deleted file mode 100644
index 7f8f011..0000000
--- a/ext/debian/compat
+++ /dev/null
@@ -1 +0,0 @@
-7
diff --git a/ext/debian/control b/ext/debian/control
deleted file mode 100644
index 2f160aa..0000000
--- a/ext/debian/control
+++ /dev/null
@@ -1,13 +0,0 @@
-Source: hiera
-Section: utils
-Priority: extra
-Maintainer: Puppet Labs <info at puppetlabs.com>
-Build-Depends: debhelper (>= 7.0.0), cdbs, quilt, ruby | ruby-interpreter
-Standards-Version: 3.9.2
-Homepage: http://projects.puppetlabs.com/projects/hiera
-
-Package: hiera
-Architecture: all
-Depends: ${shlibs:Depends}, ${misc:Depends}, ruby | ruby-interpreter, libjson-ruby | ruby-json
-Description: A simple pluggable Hierarchical Database.
-	Hiera is a simple pluggable Hierarchical Database.
diff --git a/ext/debian/copyright b/ext/debian/copyright
deleted file mode 100644
index a27ad7b..0000000
--- a/ext/debian/copyright
+++ /dev/null
@@ -1 +0,0 @@
-/usr/share/common-licenses/Apache-2.0
diff --git a/ext/debian/rules b/ext/debian/rules
deleted file mode 100755
index 84d7e4e..0000000
--- a/ext/debian/rules
+++ /dev/null
@@ -1,35 +0,0 @@
-#!/usr/bin/make -f
-# -*- makefile -*-
-# Sample debian/rules that uses debhelper.
-# This file was originally written by Joey Hess and Craig Small.
-# As a special exception, when this file is copied by dh-make into a
-# dh-make output file, you may use that output file without restriction.
-# This special exception was added by Craig Small in version 0.37 of dh-make.
-
-# Uncomment this to turn on verbose mode.
-#export DH_VERBOSE=1
-
-include /usr/share/cdbs/1/rules/debhelper.mk
-include /usr/share/cdbs/1/rules/patchsys-quilt.mk
-
-BUILD_ROOT=$(DESTDIR)/$(CURDIR)/debian/$(cdbs_curpkg)
-LIBDIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendordir"]')
-BIN_DIR=$(shell /usr/bin/ruby -rrbconfig -e 'puts RbConfig::CONFIG["bindir"]')
-RUBYLIB=$(BUILD_ROOT)/$(LIBDIR)
-RUBYBIN=$(BUILD_ROOT)/$(BIN_DIR)
-DOC_DIR=$(BUILD_ROOT)/usr/share/doc/hiera/
-DATA_DIR=$(BUILD_ROOT)/var/lib/hiera
-
-install/hiera::
-	mkdir -p $(RUBYLIB)
-	mkdir -p $(RUBYBIN)
-	mkdir -p $(DOC_DIR)
-	mkdir -p $(DATA_DIR)
-	mkdir -p $(BUILD_ROOT)/etc
-	cp -pr  lib/hiera $(RUBYLIB)
-	cp -p lib/hiera.rb $(RUBYLIB)
-	cp -p  bin/* $(RUBYBIN)
-	cp -pr ext/hiera.yaml $(BUILD_ROOT)/etc
-	cp -p COPYING README.md $(DOC_DIR)
-
-clean::
diff --git a/ext/debian/source/format b/ext/debian/source/format
deleted file mode 100644
index 163aaf8..0000000
--- a/ext/debian/source/format
+++ /dev/null
@@ -1 +0,0 @@
-3.0 (quilt)
diff --git a/ext/hiera.yaml b/ext/hiera.yaml
deleted file mode 100644
index c005288..0000000
--- a/ext/hiera.yaml
+++ /dev/null
@@ -1,15 +0,0 @@
----
-:backends:
-  - yaml
-:hierarchy:
-  - defaults
-  - %{clientcert}
-  - %{environment}
-  - global
-
-:yaml:
-# datadir is empty here, so hiera uses its defaults:
-# - /var/lib/hiera on *nix
-# - %CommonAppData%\PuppetLabs\hiera\var on Windows
-# When specifying a datadir, make sure the directory exists.
-  :datadir:
diff --git a/ext/ips/hiera.p5m.erb b/ext/ips/hiera.p5m.erb
deleted file mode 100644
index 37c8a3d..0000000
--- a/ext/ips/hiera.p5m.erb
+++ /dev/null
@@ -1,10 +0,0 @@
-set name=pkg.fmri value=pkg://puppetlabs.com/application/<%=@name%>@<%=@ipsversion%>
-set name=pkg.summary value="<%=@summary%>"
-set name=pkg.human-version value="<%=@version%>"
-set name=pkg.description value="<%=@description%>"
-set name=info.classification value="org.opensolaris.category.2008:Applications/System Utilities"
-set name=org.opensolaris.consolidation value="puppet"
-set name=description value="<%=@description%>"
-set name=variant.opensolaris.zone value=global value=nonglobal
-set name=variant.arch value=sparc value=i386
-license hiera.license license="Apache v2.0"
diff --git a/ext/ips/rules b/ext/ips/rules
deleted file mode 100755
index cd87e8d..0000000
--- a/ext/ips/rules
+++ /dev/null
@@ -1,17 +0,0 @@
-#!/usr/bin/make -f
-
-LIBDIR=$(shell /usr/bin/ruby18 -rrbconfig -e 'puts RbConfig::CONFIG["rubylibdir"]')
-DESTDIR=$(CURDIR)/pkg/ips/proto
-
-install/hiera::
-	mkdir -p $(DESTDIR)/$(LIBDIR)
-	mkdir -p $(DESTDIR)/usr/bin
-	mkdir -p $(DESTDIR)/usr/share/doc/hiera
-	mkdir -p $(DESTDIR)/var/lib/hiera
-	mkdir -p $(DESTDIR)/etc
-	cp -pr lib/hiera $(DESTDIR)/$(LIBDIR)
-	cp -p lib/hiera.rb $(DESTDIR)/$(LIBDIR)
-	cp -p bin/* $(DESTDIR)/usr/bin
-	cp -pr ext/hiera.yaml $(DESTDIR)/etc
-	cp -p COPYING README.md $(DESTDIR)/usr/share/doc/hiera
-
diff --git a/ext/ips/transforms b/ext/ips/transforms
deleted file mode 100644
index e4a8e5b..0000000
--- a/ext/ips/transforms
+++ /dev/null
@@ -1,20 +0,0 @@
-<transform file dir link hardlink path=usr/share/man/.+(/.+)? -> default facet.doc.man true>
-<transform file path=usr/share/man/.+(/.+)? -> add restart_fmri svc:/application/man-index:default>
-
-# drop user
-<transform dir path=usr$->drop>
-<transform dir path=usr/bin$->drop>
-<transform dir path=etc$->drop>
-<transform dir path=var$->drop>
-<transform dir path=var/lib$->drop>
-<transform dir path=usr/share$->drop>
-<transform dir path=usr/share/doc$->drop>
-<transform dir path=usr/ruby$->drop>
-<transform dir path=usr/ruby/1.8$->drop>
-<transform dir path=usr/ruby/1.8/lib$->drop>
-<transform dir path=usr/ruby/1.8/lib/ruby$->drop>
-<transform dir path=usr/ruby/1.8/lib/ruby/1.8$->drop>
-
-
-# saner dependencies
-<transform depend -> edit fmri "@[^ \t\n\r\f\v]*" "">
diff --git a/ext/osx/file_mapping.yaml b/ext/osx/file_mapping.yaml
deleted file mode 100644
index 99fc090..0000000
--- a/ext/osx/file_mapping.yaml
+++ /dev/null
@@ -1,32 +0,0 @@
-directories:
-  lib:
-    path: 'usr/lib/ruby/site_ruby/1.8'
-    owner: 'root'
-    group: 'wheel'
-    perms: '0644'
-  bin:
-    path: 'usr/bin'
-    owner: 'root'
-    group: 'wheel'
-    perms: '0755'
-  hiera:
-    path: 'var/lib/hiera'
-    owner: 'root'
-    group: 'wheel'
-    perms: '0644'
-  etc:
-    path: 'etc'
-    owner: 'root'
-    group: 'wheel'
-    perms: '0644'
-files:
-  '[A-Z]*':
-    path: 'usr/share/doc/hiera'
-    owner: 'root'
-    group: 'wheel'
-    perms: '0644'
-  'ext/hiera.yaml':
-    path: 'etc'
-    owner: 'root'
-    group: 'wheel'
-    perms: '0644'
diff --git a/ext/osx/preflight.erb b/ext/osx/preflight.erb
deleted file mode 100644
index 93ecc00..0000000
--- a/ext/osx/preflight.erb
+++ /dev/null
@@ -1,30 +0,0 @@
-#!/bin/bash
-#
-# Make sure that old hiera cruft is removed
-# This also allows us to downgrade facter as
-# it's more likely that installing old versions
-# over new will cause issues.
-#
-# ${3} is the destination volume so that this works correctly
-# when being installed to volumes other than the current OS.
-
-<% begin %>
-<%  require 'rubygems' %>
-<% rescue LoadError %>
-<% end %>
-<% require 'rake' %>
-
-# remove libdir
-<% Dir.chdir("lib") %>
-<% FileList["**/*"].select {|i| File.file?(i)}.each do |file| %>
-/bin/rm -Rf "${3}<%= @apple_libdir %>/<%=file%>"
-<% end %>
-# remove bin files
-<% Dir.chdir("../bin") %>
-<% FileList["**/*"].select {|i| File.file?(i)}.each do |file| %>
-/bin/rm -Rf "${3}<%= @apple_bindir %>/<%=file%>"
-<% end %>
-<% Dir.chdir("..") %>
-
-# remove old doc files
-/bin/rm -Rf "${3}<%= @apple_docdir %>/<%=@package_name%>"
diff --git a/ext/osx/prototype.plist.erb b/ext/osx/prototype.plist.erb
deleted file mode 100644
index e3afe30..0000000
--- a/ext/osx/prototype.plist.erb
+++ /dev/null
@@ -1,38 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist>
-<dict>
-	<key>CFBundleIdentifier</key>
-	<string><%= @title %></string>
-	<key>CFBundleShortVersionString</key>
-	<string><%= @version %></string>
-	<key>IFMajorVersion</key>
-	<integer><%= @package_major_version %></integer>
-	<key>IFMinorVersion</key>
-	<integer><%= @package_minor_version %></integer>
-	<key>IFPkgBuildDate</key>
-	<date><%= @build_date %></date>
-	<key>IFPkgFlagAllowBackRev</key>
-	<false/>
-	<key>IFPkgFlagAuthorizationAction</key>
-	<string>RootAuthorization</string>
-	<key>IFPkgFlagDefaultLocation</key>
-	<string>/</string>
-	<key>IFPkgFlagFollowLinks</key>
-	<true/>
-	<key>IFPkgFlagInstallFat</key>
-	<false/>
-	<key>IFPkgFlagIsRequired</key>
-	<false/>
-	<key>IFPkgFlagOverwritePermissions</key>
-	<false/>
-	<key>IFPkgFlagRelocatable</key>
-	<false/>
-	<key>IFPkgFlagRestartAction</key>
-	<string><%= @pm_restart %></string>
-	<key>IFPkgFlagRootVolumeOnly</key>
-	<true/>
-	<key>IFPkgFlagUpdateInstalledLanguages</key>
-	<false/>
-</dict>
-</plist>
diff --git a/ext/project_data.yaml b/ext/project_data.yaml
deleted file mode 100644
index b034577..0000000
--- a/ext/project_data.yaml
+++ /dev/null
@@ -1,17 +0,0 @@
----
-project: 'hiera'
-author: 'Puppet Labs'
-email: 'info at puppetlabs.com'
-homepage: 'https://github.com/puppetlabs/hiera'
-summary: 'Light weight hierarchical data store'
-description: 'A pluggable data store for hierarcical data'
-version_file: 'lib/hiera.rb'
-# files and gem_files are space separated lists
-files: '[A-Z]* ext lib bin spec acceptance_tests'
-gem_files: '{bin,lib}/**/* COPYING README.md LICENSE'
-gem_require_path: 'lib'
-gem_test_files: 'spec/**/*'
-gem_executables: 'hiera'
-gem_runtime_dependencies:
-  json_pure:
-gem_default_executables: 'hiera'
diff --git a/ext/redhat/hiera.spec.erb b/ext/redhat/hiera.spec.erb
deleted file mode 100644
index 73c1b76..0000000
--- a/ext/redhat/hiera.spec.erb
+++ /dev/null
@@ -1,80 +0,0 @@
-# Fedora 17 ships with ruby 1.9, which uses vendorlibdir instead
-# of sitelibdir
-%if 0%{?fedora} >= 17
-%global hiera_libdir   %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["vendorlibdir"]')
-%else
-%global hiera_libdir   %(ruby -rrbconfig -e 'puts RbConfig::CONFIG["sitelibdir"]')
-%endif
-
-%if 0%{?rhel} == 5
-%global _sharedstatedir %{_prefix}/lib
-%endif
-
-# VERSION is subbed out during rake srpm process
-%global realversion <%= @version %>
-%global rpmversion <%= @rpmversion %>
-
-Name:           hiera
-Version:        %{rpmversion}
-Release:        <%= @rpmrelease -%>%{?dist}
-Summary:        A simple pluggable Hierarchical Database
-Vendor:         %{?_host_vendor}
-Group:          System Environment/Base
-License:        ASL 2.0
-URL:            http://projects.puppetlabs.com/projects/%{name}/
-Source0:        http://downloads.puppetlabs.com/%{name}/%{name}-%{realversion}.tar.gz
-BuildRoot:      %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n)
-BuildArch:      noarch
-BuildRequires:  ruby >= 1.8.5
-Requires:       ruby(abi) >= 1.8
-Requires:       ruby >= 1.8.5
-Requires:       rubygem-json
-
-%description
-A simple pluggable Hierarchical Database.
-
-%prep
-%setup -q  -n %{name}-%{realversion}
-
-
-%build
-
-
-%install
-rm -rf $RPM_BUILD_ROOT
-mkdir -p $RPM_BUILD_ROOT/%{hiera_libdir}
-mkdir -p $RPM_BUILD_ROOT/%{_bindir}
-mkdir -p $RPM_BUILD_ROOT/%{_sysconfdir}
-mkdir -p $RPM_BUILD_ROOT/%{_sharedstatedir}/hiera
-cp -pr lib/hiera $RPM_BUILD_ROOT/%{hiera_libdir}
-cp -pr lib/hiera.rb $RPM_BUILD_ROOT/%{hiera_libdir}
-install -p -m0755 bin/hiera $RPM_BUILD_ROOT/%{_bindir}
-install -p -m0644 ext/hiera.yaml $RPM_BUILD_ROOT/%{_sysconfdir}
-
-%clean
-rm -rf $RPM_BUILD_ROOT
-
-
-%files
-%defattr(-,root,root,-)
-%{_bindir}/hiera
-%{hiera_libdir}/hiera.rb
-%{hiera_libdir}/hiera
-%config(noreplace) %{_sysconfdir}/hiera.yaml
-%{_sharedstatedir}/hiera
-%doc COPYING README.md
-
-
-%changelog
-* <%= Time.now.strftime("%a %b %d %Y") %> Puppet Labs Release <info at puppetlabs.com> -  <%= @rpmversion %>-<%= @rpmrelease %>
-- Build for <%= @version %>
-
-* Mon May 14 2012 Matthaus Litteken <matthaus at puppetlabs.com> - 1.0.0-0.1rc2
-- 1.0.0rc2 release
-
-* Mon May 14 2012 Matthaus Litteken <matthaus at puppetlabs.com> - 1.0.0-0.1rc1
-- 1.0.0rc1 release
-
-* Thu May 03 2012 Matthaus Litteken <matthaus at puppetlabs.com> - 0.3.0.28-1
-- Initial Hiera Packaging. Upstream version 0.3.0.28
-
diff --git a/lib/hiera.rb b/lib/hiera.rb
index c1707f3..d1a8979 100644
--- a/lib/hiera.rb
+++ b/lib/hiera.rb
@@ -1,35 +1,33 @@
 require 'yaml'
 
 class Hiera
-  VERSION = "1.1.2"
-
-  autoload :Config,         "hiera/config"
-  autoload :Util,           "hiera/util"
-  autoload :Backend,        "hiera/backend"
-  autoload :Console_logger, "hiera/console_logger"
-  autoload :Puppet_logger,  "hiera/puppet_logger"
-  autoload :Noop_logger,    "hiera/noop_logger"
+  require "hiera/error"
+  require "hiera/version"
+  require "hiera/config"
+  require "hiera/util"
+  require "hiera/backend"
+  require "hiera/console_logger"
+  require "hiera/puppet_logger"
+  require "hiera/noop_logger"
+  require "hiera/fallback_logger"
+  require "hiera/filecache"
 
   class << self
     attr_reader :logger
 
-    def version
-      VERSION
-    end
-
     # Loggers are pluggable, just provide a class called
     # Hiera::Foo_logger and respond to :warn and :debug
     #
     # See hiera-puppet for an example that uses the Puppet
     # loging system instead of our own
     def logger=(logger)
-      loggerclass = "#{logger.capitalize}_logger"
-
-      require "hiera/#{logger}_logger" unless constants.include?(loggerclass)
+      require "hiera/#{logger}_logger"
 
-      @logger = const_get(loggerclass)
+      @logger = Hiera::FallbackLogger.new(
+        Hiera.const_get("#{logger.capitalize}_logger"),
+        Hiera::Console_logger)
     rescue Exception => e
-      @logger = Console_logger
+      @logger = Hiera::Console_logger
       warn("Failed to load #{logger} logger: #{e.class}: #{e}")
     end
 
diff --git a/lib/hiera/backend.rb b/lib/hiera/backend.rb
index 27cc1c6..c04bf24 100644
--- a/lib/hiera/backend.rb
+++ b/lib/hiera/backend.rb
@@ -1,4 +1,11 @@
 require 'hiera/util'
+require 'hiera/recursive_guard'
+require 'hiera/interpolate'
+
+begin
+  require 'deep_merge'
+rescue LoadError
+end
 
 class Hiera
   module Backend
@@ -8,13 +15,19 @@ class Hiera
       # subject to variable expansion based on scope
       def datadir(backend, scope)
         backend = backend.to_sym
-        default = Hiera::Util.var_dir
 
-        if Config.include?(backend)
-          parse_string(Config[backend][:datadir] || default, scope)
+        if Config[backend] && Config[backend][:datadir]
+          dir = Config[backend][:datadir]
         else
-          parse_string(default, scope)
+          dir = Hiera::Util.var_dir
+        end
+
+        if !dir.is_a?(String)
+          raise(Hiera::InvalidConfigurationError,
+                "datadir for #{backend} cannot be an array")
         end
+
+        parse_string(dir, scope)
       end
 
       # Finds the path to a datafile based on the Backend#datadir
@@ -61,42 +74,23 @@ class Hiera
         end
       end
 
-      # Parse a string like '%{foo}' against a supplied
+      # Parse a string like <code>'%{foo}'</code> against a supplied
       # scope and additional scope.  If either scope or
-      # extra_scope includes the varaible 'foo' it will
+      # extra_scope includes the variable 'foo', then it will
       # be replaced else an empty string will be placed.
       #
-      # If both scope and extra_data has "foo" scope
-      # will win.  See hiera-puppet for an example of
-      # this to make hiera aware of additional non scope
-      # variables
+      # If both scope and extra_data has "foo", then the value in scope
+      # will be used.
+      #
+      # @param data [String] The string to perform substitutions on.
+      #   This will not be modified, instead a new string will be returned.
+      # @param scope [#[]] The primary source of data for substitutions.
+      # @param extra_data [#[]] The secondary source of data for substitutions.
+      # @return [String] A copy of the data with all instances of <code>%{...}</code> replaced.
+      #
+      # @api public
       def parse_string(data, scope, extra_data={})
-        return nil unless data
-
-        tdata = data.clone
-
-        if tdata.is_a?(String)
-          while tdata =~ /%\{(.+?)\}/
-            begin
-              var = $1
-
-              val = ""
-
-              # Puppet can return :undefined for unknown scope vars,
-              # If it does then we still need to evaluate extra_data
-              # before returning an empty string.
-              if scope[var] && scope[var] != :undefined
-                  val = scope[var]
-              elsif extra_data[var]
-                  val = extra_data[var]
-              end
-            end until val != "" || var !~ /::(.+)/
-
-            tdata.gsub!(/%\{(::)?#{var}\}/, val)
-          end
-        end
-
-        return tdata
+        Hiera::Interpolate.interpolate(data, Hiera::RecursiveGuard.new, scope, extra_data)
       end
 
       # Parses a answer received from data files
@@ -111,7 +105,8 @@ class Hiera
         elsif data.is_a?(Hash)
           answer = {}
           data.each_pair do |key, val|
-            answer[key] = parse_answer(val, scope, extra_data)
+            interpolated_key = parse_string(key, scope, extra_data)
+            answer[interpolated_key] = parse_answer(val, scope, extra_data)
           end
 
           return answer
@@ -136,6 +131,26 @@ class Hiera
         end
       end
 
+      # Merges two hashes answers with the configured merge behavior.
+      #         :merge_behavior: {:native|:deep|:deeper}
+      #
+      # Deep merge options use the Hash utility function provided by [deep_merge](https://github.com/peritor/deep_merge)
+      #
+      #  :native => Native Hash.merge
+      #  :deep   => Use Hash.deep_merge
+      #  :deeper => Use Hash.deep_merge!
+      #
+      def merge_answer(left,right)
+        case Config[:merge_behavior]
+        when :deeper,'deeper'
+          left.deep_merge!(right)
+        when :deep,'deep'
+          left.deep_merge(right)
+        else # Native and undefined
+          left.merge(right)
+        end
+      end
+
       # Calls out to all configured backends in the order they
       # were specified.  The first one to answer will win.
       #
@@ -167,7 +182,7 @@ class Hiera
               when :hash
                 raise Exception, "Hiera type mismatch: expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
                 answer ||= {}
-                answer = new_answer.merge answer
+                answer = merge_answer(new_answer,answer)
               else
                 answer = new_answer
                 break
@@ -182,6 +197,10 @@ class Hiera
         return default if answer.nil?
         return answer
       end
+
+      def clear!
+        @backends = {}
+      end
     end
   end
 end
diff --git a/lib/hiera/backend/json_backend.rb b/lib/hiera/backend/json_backend.rb
index 3919511..da78b99 100644
--- a/lib/hiera/backend/json_backend.rb
+++ b/lib/hiera/backend/json_backend.rb
@@ -1,10 +1,12 @@
 class Hiera
   module Backend
     class Json_backend
-      def initialize
+      def initialize(cache=nil)
         require 'json'
 
         Hiera.debug("Hiera JSON backend starting")
+
+        @cache = cache || Filecache.new
       end
 
       def lookup(key, scope, order_override, resolution_type)
@@ -17,7 +19,11 @@ class Hiera
 
           jsonfile = Backend.datafile(:json, scope, source, "json") || next
 
-          data = JSON.parse(File.read(jsonfile))
+          next unless File.exist?(jsonfile)
+
+          data = @cache.read_file(jsonfile, Hash) do |data|
+            JSON.parse(data)
+          end
 
           next if data.empty?
           next unless data.include?(key)
@@ -30,10 +36,15 @@ class Hiera
           new_answer = Backend.parse_answer(data[key], scope)
           case resolution_type
           when :array
+            raise Exception, "Hiera type mismatch: expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
             answer ||= []
             answer << new_answer
+          when :hash
+            raise Exception, "Hiera type mismatch: expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
+            answer ||= {}
+            answer = Backend.merge_answer(new_answer,answer)
           else
-            answer = Backend.parse_answer(data[key], scope)
+            answer = new_answer
             break
           end
         end
diff --git a/lib/hiera/backend/yaml_backend.rb b/lib/hiera/backend/yaml_backend.rb
index 06a9d1e..0e100c1 100644
--- a/lib/hiera/backend/yaml_backend.rb
+++ b/lib/hiera/backend/yaml_backend.rb
@@ -1,11 +1,11 @@
 class Hiera
   module Backend
     class Yaml_backend
-      def initialize
+      def initialize(cache=nil)
         require 'yaml'
         Hiera.debug("Hiera YAML backend starting")
-        @data  = Hash.new
-        @cache = Hash.new
+
+        @cache = cache || Filecache.new
       end
 
       def lookup(key, scope, order_override, resolution_type)
@@ -17,20 +17,14 @@ class Hiera
           Hiera.debug("Looking for data source #{source}")
           yamlfile = Backend.datafile(:yaml, scope, source, "yaml") || next
 
-          # If you call stale? BEFORE you do encounter the YAML.load_file line
-          # it will populate the @cache variable and return true. The second
-          # time you call it, it will return false because @cache has been
-          # populated. Because of this there are two conditions to check:
-          # is @data[yamlfile] populated AND is the cache stale.
-          if @data[yamlfile]
-            @data[yamlfile] = YAML.load_file(yamlfile) if stale?(yamlfile)
-          else
-            @data[yamlfile] = YAML.load_file(yamlfile)
+          next unless File.exist?(yamlfile)
+
+          data = @cache.read_file(yamlfile, Hash) do |data|
+            YAML.load(data)
           end
 
-          next if ! @data[yamlfile]
-          next if @data[yamlfile].empty?
-          next unless @data[yamlfile].include?(key)
+          next if data.empty?
+          next unless data.include?(key)
 
           # Extra logging that we found the key. This can be outputted
           # multiple times if the resolution type is array or hash but that
@@ -43,7 +37,7 @@ class Hiera
           # the array
           #
           # for priority searches we break after the first found data item
-          new_answer = Backend.parse_answer(@data[yamlfile][key], scope)
+          new_answer = Backend.parse_answer(data[key], scope)
           case resolution_type
           when :array
             raise Exception, "Hiera type mismatch: expected Array and got #{new_answer.class}" unless new_answer.kind_of? Array or new_answer.kind_of? String
@@ -52,7 +46,7 @@ class Hiera
           when :hash
             raise Exception, "Hiera type mismatch: expected Hash and got #{new_answer.class}" unless new_answer.kind_of? Hash
             answer ||= {}
-            answer = new_answer.merge answer
+            answer = Backend.merge_answer(new_answer,answer)
           else
             answer = new_answer
             break
@@ -61,18 +55,6 @@ class Hiera
 
         return answer
       end
-
-      def stale?(yamlfile)
-        # NOTE: The mtime change in a file MUST be > 1 second before being
-        #       recognized as stale. File mtime changes within 1 second will
-        #       not be recognized.
-        stat    = File.stat(yamlfile)
-        current = { 'inode' => stat.ino, 'mtime' => stat.mtime, 'size' => stat.size }
-        return false if @cache[yamlfile] == current
-
-        @cache[yamlfile] = current
-        return true
-      end
     end
   end
 end
diff --git a/lib/hiera/config.rb b/lib/hiera/config.rb
index 807ed59..d29da7d 100644
--- a/lib/hiera/config.rb
+++ b/lib/hiera/config.rb
@@ -11,7 +11,8 @@ class Hiera::Config
     #   {:backends => "yaml", :hierarchy => "common"}
     def load(source)
       @config = {:backends => "yaml",
-                 :hierarchy => "common"}
+                 :hierarchy => "common",
+                 :merge_behavior => :native }
 
       if source.is_a?(String)
         if File.exist?(source)
@@ -41,10 +42,25 @@ class Hiera::Config
         @config[:logger] = "console"
         Hiera.logger = "console"
       end
+    
+      self.validate!
 
       @config
     end
 
+    def validate!
+      case @config[:merge_behavior]
+      when :deep,'deep',:deeper,'deeper'
+        begin
+          require "deep_merge"
+        rescue LoadError
+          Hiera.warn "Ignoring configured merge_behavior"
+          Hiera.warn "Must have 'deep_merge' gem installed."
+          @config[:merge_behavior] = :native
+        end
+      end
+    end
+
     ##
     # yaml_load_file directly delegates to YAML.load_file and is intended to be
     # a private, internal method suitable for stubbing and mocking.
diff --git a/lib/hiera/error.rb b/lib/hiera/error.rb
new file mode 100644
index 0000000..8ff4f3f
--- /dev/null
+++ b/lib/hiera/error.rb
@@ -0,0 +1,4 @@
+class Hiera
+  class Error < StandardError; end
+  class InvalidConfigurationError < Error; end
+end
diff --git a/lib/hiera/fallback_logger.rb b/lib/hiera/fallback_logger.rb
new file mode 100644
index 0000000..222bda1
--- /dev/null
+++ b/lib/hiera/fallback_logger.rb
@@ -0,0 +1,41 @@
+# Select from a given list of loggers the first one that
+# it suitable and use that as the actual logger
+#
+# @api private
+class Hiera::FallbackLogger
+  # Chooses the first suitable logger. For all of the loggers that are
+  # unsuitable it will issue a warning using the suitable logger stating that
+  # the unsuitable logger is not being used.
+  #
+  # @param implementations [Array<Hiera::Logger>] the implementations to choose from
+  # @raises when there are no suitable loggers
+  def initialize(*implementations)
+    warnings = []
+    @implementation = implementations.find do |impl|
+      if impl.respond_to?(:suitable?)
+        if impl.suitable?
+          true
+        else
+          warnings << "Not using #{impl.name}. It does not report itself to be suitable."
+          false
+        end
+      else
+        true
+      end
+    end
+
+    if @implementation.nil?
+      raise "No suitable logging implementation found."
+    end
+
+    warnings.each { |message| warn(message) }
+  end
+
+  def warn(message)
+    @implementation.warn(message)
+  end
+
+  def debug(message)
+    @implementation.debug(message)
+  end
+end
diff --git a/lib/hiera/filecache.rb b/lib/hiera/filecache.rb
new file mode 100644
index 0000000..f1a3d1f
--- /dev/null
+++ b/lib/hiera/filecache.rb
@@ -0,0 +1,86 @@
+class Hiera
+  class Filecache
+    def initialize
+      @cache = {}
+    end
+
+    # Reads a file, optionally parse it in some way check the
+    # output type and set a default
+    #
+    # Simply invoking it this way will return the file contents
+    #
+    #    data = read("/some/file")
+    #
+    # But as most cases of file reading in hiera involves some kind
+    # of parsing through a serializer there's some help for those
+    # cases:
+    #
+    #    data = read("/some/file", Hash, {}) do |data|
+    #       JSON.parse(data)
+    #    end
+    #
+    # In this case it will read the file, parse it using JSON then
+    # check that the end result is a Hash, if it's not a hash or if
+    # reading/parsing fails it will return {} instead
+    #
+    # Prior to calling this method you should be sure the file exist
+    def read(path, expected_type = Object, default=nil, &block)
+      read_file(path, expected_type, &block)
+    rescue TypeError => detail
+      Hiera.debug("#{detail.message}, setting defaults")
+      @cache[path][:data] = default
+    rescue => detail
+      error = "Reading data from #{path} failed: #{detail.class}: #{detail}"
+      if default.nil?
+        raise detail
+      else
+        Hiera.debug(error)
+        @cache[path][:data] = default
+      end
+    end
+
+    # Read a file when it changes. If a file is re-read and has not changed since the last time
+    # then the last, processed, contents will be returned.
+    #
+    # The processed data can also be checked against an expected type. If the
+    # type does not match a TypeError is raised.
+    #
+    # No error handling is done inside this method. Any failed reads or errors
+    # in processing will be propagated to the caller
+    def read_file(path, expected_type = Object)
+      if stale?(path)
+        data = File.read(path)
+        @cache[path][:data] = block_given? ? yield(data) : data
+
+        if !@cache[path][:data].is_a?(expected_type)
+          raise TypeError, "Data retrieved from #{path} is #{data.class} not #{expected_type}"
+        end
+      end
+
+      @cache[path][:data]
+    end
+
+    private
+
+    def stale?(path)
+      meta = path_metadata(path)
+
+      @cache[path] ||= {:data => nil, :meta => nil}
+
+      if @cache[path][:meta] == meta
+        return false
+      else
+        @cache[path][:meta] = meta
+        return true
+      end
+    end
+
+    # This is based on the old caching in the YAML backend and has a
+    # resolution of 1 second, changes made within the same second of
+    # a previous read will be ignored
+    def path_metadata(path)
+      stat = File.stat(path)
+      {:inode => stat.ino, :mtime => stat.mtime, :size => stat.size}
+    end
+  end
+end
diff --git a/lib/hiera/interpolate.rb b/lib/hiera/interpolate.rb
new file mode 100644
index 0000000..b5653f2
--- /dev/null
+++ b/lib/hiera/interpolate.rb
@@ -0,0 +1,48 @@
+require 'hiera/backend'
+
+class Hiera::Interpolate
+  class << self
+    INTERPOLATION = /%\{([^\}]*)\}/
+    METHOD_INTERPOLATION = /%\{(scope|hiera)\(['"]([^"']*)["']\)\}/
+
+    def interpolate(data, recurse_guard, scope, extra_data)
+      if data.is_a?(String) && (match = data.match(INTERPOLATION))
+        interpolation_variable = match[1]
+        recurse_guard.check(interpolation_variable) do
+          interpolate_method, key = get_interpolation_method_and_key(data)
+          interpolated_data = send(interpolate_method, data, key, scope, extra_data)
+          interpolate(interpolated_data, recurse_guard, scope, extra_data)
+        end
+      else
+        data
+      end
+    end
+
+    def get_interpolation_method_and_key(data)
+      if (match = data.match(METHOD_INTERPOLATION))
+        case match[1]
+        when 'hiera' then [:hiera_interpolate, match[2]]
+        when 'scope' then [:scope_interpolate, match[2]]
+        end
+      elsif (match = data.match(INTERPOLATION))
+        [:scope_interpolate, match[1]]
+      end
+    end
+    private :get_interpolation_method_and_key
+
+    def scope_interpolate(data, key, scope, extra_data)
+      value = scope[key]
+      if value.nil? || value == :undefined
+        value = extra_data[key]
+      end
+      data.sub(INTERPOLATION, value.to_s)
+    end
+    private :scope_interpolate
+
+    def hiera_interpolate(data, key, scope, extra_data)
+      value = Hiera::Backend.lookup(key, nil, scope, nil, :priority)
+      data.sub(METHOD_INTERPOLATION, value)
+    end
+    private :hiera_interpolate
+  end
+end
diff --git a/lib/hiera/puppet_logger.rb b/lib/hiera/puppet_logger.rb
index abe0b60..fb1185e 100644
--- a/lib/hiera/puppet_logger.rb
+++ b/lib/hiera/puppet_logger.rb
@@ -1,6 +1,10 @@
 class Hiera
   module Puppet_logger
     class << self
+      def suitable?
+        defined?(::Puppet) == "constant"
+      end
+
       def warn(msg)
         Puppet.notice("hiera(): #{msg}")
       end
diff --git a/lib/hiera/recursive_guard.rb b/lib/hiera/recursive_guard.rb
new file mode 100644
index 0000000..c68d21c
--- /dev/null
+++ b/lib/hiera/recursive_guard.rb
@@ -0,0 +1,20 @@
+# Allow for safe recursive lookup of values during variable interpolation.
+#
+# @api private
+class Hiera::InterpolationLoop < StandardError; end
+
+class Hiera::RecursiveGuard
+  def initialize
+    @seen = []
+  end
+
+  def check(value, &block)
+    if @seen.include?(value)
+      raise Hiera::InterpolationLoop, "Detected in [#{@seen.join(', ')}]"
+    end
+    @seen.push(value)
+    ret = yield
+    @seen.pop
+    ret
+  end
+end
diff --git a/lib/hiera/version.rb b/lib/hiera/version.rb
new file mode 100644
index 0000000..a5a5445
--- /dev/null
+++ b/lib/hiera/version.rb
@@ -0,0 +1,89 @@
+# The version method and constant are isolated in hiera/version.rb so that a
+# simple `require 'hiera/version'` allows a rubygems gemspec or bundler
+# Gemfile to get the hiera version of the gem install.
+#
+# The version is programatically settable because we want to allow the
+# Raketasks and such to set the version based on the output of `git describe`
+
+
+class Hiera
+  VERSION = "1.3.0"
+
+  ##
+  # version is a public API method intended to always provide a fast and
+  # lightweight way to determine the version of hiera.
+  #
+  # The intent is that software external to hiera be able to determine the
+  # hiera version with no side-effects.  The expected use is:
+  #
+  #     require 'hiera/version'
+  #     version = Hiera.version
+  #
+  # This function has the following ordering precedence.  This precedence list
+  # is designed to facilitate automated packaging tasks by simply writing to
+  # the VERSION file in the same directory as this source file.
+  #
+  #  1. If a version has been explicitly assigned using the Hiera.version=
+  #     method, return that version.
+  #  2. If there is a VERSION file, read the contents, trim any
+  #     trailing whitespace, and return that version string.
+  #  3. Return the value of the Hiera::VERSION constant hard-coded into
+  #     the source code.
+  #
+  # If there is no VERSION file, the method must return the version string of
+  # the nearest parent version that is an officially released version.  That is
+  # to say, if a branch named 3.1.x contains 25 patches on top of the most
+  # recent official release of 3.1.1, then the version method must return the
+  # string "3.1.1" if no "VERSION" file is present.
+  #
+  # By design the version identifier is _not_ intended to vary during the life
+  # a process.  There is no guarantee provided that writing to the VERSION file
+  # while a Hiera process is running will cause the version string to be
+  # updated.  On the contrary, the contents of the VERSION are cached to reduce
+  # filesystem accesses.
+  #
+  # The VERSION file is intended to be used by package maintainers who may be
+  # applying patches or otherwise changing the software version in a manner
+  # that warrants a different software version identifier.  The VERSION file is
+  # intended to be managed and owned by the release process and packaging
+  # related tasks, and as such should not reside in version control.  The
+  # VERSION constant is intended to be version controlled in history.
+  #
+  # Ideally, this behavior will allow package maintainers to precisely specify
+  # the version of the software they're packaging as in the following example:
+  #
+  #     $ git describe --match "1.2.*" > lib/hiera/VERSION
+  #     $ ruby -r hiera/version -e 'puts Hiera.version'
+  #     1.2.1-9-g9fda440
+  #
+  # @api public
+  #
+  # @return [String] containing the hiera version, e.g. "1.2.1"
+  def self.version
+    version_file = File.join(File.dirname(__FILE__), 'VERSION')
+    return @hiera_version if @hiera_version
+    if version = read_version_file(version_file)
+      @hiera_version = version
+    end
+    @hiera_version ||= VERSION
+  end
+
+  def self.version=(version)
+    @hiera_version = version
+  end
+
+  ##
+  # read_version_file reads the content of the "VERSION" file that lives in the
+  # same directory as this source code file.
+  #
+  # @api private
+  #
+  # @return [String] for example: "1.6.14-6-gea42046" or nil if the VERSION
+  #   file does not exist.
+  def self.read_version_file(path)
+    if File.exists?(path)
+      File.read(path).chomp
+    end
+  end
+  private_class_method :read_version_file
+end
diff --git a/metadata.yml b/metadata.yml
new file mode 100644
index 0000000..88aa39c
--- /dev/null
+++ b/metadata.yml
@@ -0,0 +1,104 @@
+--- !ruby/object:Gem::Specification
+name: hiera
+version: !ruby/object:Gem::Version
+  version: 1.3.0
+  prerelease: 
+platform: ruby
+authors:
+- Puppet Labs
+autorequire: 
+bindir: bin
+cert_chain: []
+date: 2013-11-21 00:00:00.000000000 Z
+dependencies:
+- !ruby/object:Gem::Dependency
+  name: json_pure
+  requirement: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+  type: :runtime
+  prerelease: false
+  version_requirements: !ruby/object:Gem::Requirement
+    none: false
+    requirements:
+    - - ! '>='
+      - !ruby/object:Gem::Version
+        version: '0'
+description: A pluggable data store for hierarcical data
+email: info at puppetlabs.com
+executables:
+- hiera
+extensions: []
+extra_rdoc_files: []
+files:
+- bin/hiera
+- lib/hiera/puppet_logger.rb
+- lib/hiera/console_logger.rb
+- lib/hiera/filecache.rb
+- lib/hiera/version.rb
+- lib/hiera/fallback_logger.rb
+- lib/hiera/error.rb
+- lib/hiera/interpolate.rb
+- lib/hiera/util.rb
+- lib/hiera/recursive_guard.rb
+- lib/hiera/backend.rb
+- lib/hiera/noop_logger.rb
+- lib/hiera/config.rb
+- lib/hiera/backend/yaml_backend.rb
+- lib/hiera/backend/json_backend.rb
+- lib/hiera.rb
+- COPYING
+- README.md
+- LICENSE
+- spec/unit/util_spec.rb
+- spec/unit/puppet_logger_spec.rb
+- spec/unit/version_spec.rb
+- spec/unit/config_spec.rb
+- spec/unit/backend_spec.rb
+- spec/unit/filecache_spec.rb
+- spec/unit/hiera_spec.rb
+- spec/unit/console_logger_spec.rb
+- spec/unit/backend/yaml_backend_spec.rb
+- spec/unit/backend/json_backend_spec.rb
+- spec/unit/fallback_logger_spec.rb
+- spec/spec_helper.rb
+homepage: https://github.com/puppetlabs/hiera
+licenses: []
+post_install_message: 
+rdoc_options: []
+require_paths:
+- lib
+required_ruby_version: !ruby/object:Gem::Requirement
+  none: false
+  requirements:
+  - - ! '>='
+    - !ruby/object:Gem::Version
+      version: '0'
+required_rubygems_version: !ruby/object:Gem::Requirement
+  none: false
+  requirements:
+  - - ! '>='
+    - !ruby/object:Gem::Version
+      version: '0'
+requirements: []
+rubyforge_project: 
+rubygems_version: 1.8.23
+signing_key: 
+specification_version: 3
+summary: Light weight hierarchical data store
+test_files:
+- spec/unit/util_spec.rb
+- spec/unit/puppet_logger_spec.rb
+- spec/unit/version_spec.rb
+- spec/unit/config_spec.rb
+- spec/unit/backend_spec.rb
+- spec/unit/filecache_spec.rb
+- spec/unit/hiera_spec.rb
+- spec/unit/console_logger_spec.rb
+- spec/unit/backend/yaml_backend_spec.rb
+- spec/unit/backend/json_backend_spec.rb
+- spec/unit/fallback_logger_spec.rb
+- spec/spec_helper.rb
diff --git a/spec/unit/backend/json_backend_spec.rb b/spec/unit/backend/json_backend_spec.rb
index 76e486c..fecc723 100644
--- a/spec/unit/backend/json_backend_spec.rb
+++ b/spec/unit/backend/json_backend_spec.rb
@@ -8,7 +8,8 @@ class Hiera
         Hiera.stubs(:debug)
         Hiera.stubs(:warn)
         Hiera::Backend.stubs(:empty_answer).returns(nil)
-        @backend = Json_backend.new
+        @cache = mock
+        @backend = Json_backend.new(@cache)
       end
 
       describe "#initialize" do
@@ -30,13 +31,9 @@ class Hiera
         it "should retain the data types found in data files" do
           Backend.expects(:datasources).yields("one").times(3)
           Backend.expects(:datafile).with(:json, {}, "one", "json").returns("/nonexisting/one.json").times(3)
-          File.expects(:read).with("/nonexisting/one.json").returns('{"stringval":"string",
-                                                                      "boolval":true,
-                                                                      "numericval":1}').times(3)
+          File.stubs(:exist?).with("/nonexisting/one.json").returns(true)
 
-          Backend.stubs(:parse_answer).with('string', {}).returns('string')
-          Backend.stubs(:parse_answer).with(true, {}).returns(true)
-          Backend.stubs(:parse_answer).with(1, {}).returns(1)
+          @cache.expects(:read_file).with("/nonexisting/one.json", Hash).returns({"stringval" => "string", "boolval" => true, "numericval" => 1}).times(3)
 
           @backend.lookup("stringval", {}, nil, :priority).should == "string"
           @backend.lookup("boolval", {}, nil, :priority).should == true
@@ -45,13 +42,12 @@ class Hiera
 
         it "should pick data earliest source that has it for priority searches" do
           scope = {"rspec" => "test"}
-          Backend.stubs(:parse_answer).with('answer', scope).returns("answer")
-          Backend.stubs(:parse_answer).with('test_%{rspec}', scope).returns("test_test")
           Backend.expects(:datasources).multiple_yields(["one"], ["two"])
           Backend.expects(:datafile).with(:json, scope, "one", "json").returns("/nonexisting/one.json")
-          Backend.expects(:datafile).with(:json, scope, "two", "json").returns(nil).never
-          File.expects(:read).with("/nonexisting/one.json").returns("one.json")
-          JSON.expects(:parse).with("one.json").returns({"key" => "test_%{rspec}"})
+          Backend.expects(:datafile).with(:json, scope, "two", "json").never
+
+          File.stubs(:exist?).with("/nonexisting/one.json").returns(true)
+          @cache.expects(:read_file).with("/nonexisting/one.json", Hash).returns({"key" => "test_%{rspec}"})
 
           @backend.lookup("key", scope, nil, :priority).should == "test_test"
         end
@@ -64,11 +60,11 @@ class Hiera
 
           Backend.expects(:datasources).multiple_yields(["one"], ["two"])
 
-          File.expects(:read).with("/nonexisting/one.json").returns("one.json")
-          File.expects(:read).with("/nonexisting/two.json").returns("two.json")
+          File.expects(:exist?).with("/nonexisting/one.json").returns(true)
+          File.expects(:exist?).with("/nonexisting/two.json").returns(true)
 
-          JSON.expects(:parse).with("one.json").returns({"key" => "answer"})
-          JSON.expects(:parse).with("two.json").returns({"key" => "answer"})
+          @cache.expects(:read_file).with("/nonexisting/one.json", Hash).returns({"key" => "answer"})
+          @cache.expects(:read_file).with("/nonexisting/two.json", Hash).returns({"key" => "answer"})
 
           @backend.lookup("key", {}, nil, :array).should == ["answer", "answer"]
         end
@@ -78,8 +74,8 @@ class Hiera
           Backend.expects(:datasources).yields("one")
           Backend.expects(:datafile).with(:json, {"rspec" => "test"}, "one", "json").returns("/nonexisting/one.json")
 
-          File.expects(:read).with("/nonexisting/one.json").returns("one.json")
-          JSON.expects(:parse).with("one.json").returns({"key" => "test_%{rspec}"})
+          File.expects(:exist?).with("/nonexisting/one.json").returns(true)
+          @cache.expects(:read_file).with("/nonexisting/one.json", Hash).returns({"key" => "test_%{rspec}"})
 
           @backend.lookup("key", {"rspec" => "test"}, nil, :priority).should == "test_test"
         end
diff --git a/spec/unit/backend/yaml_backend_spec.rb b/spec/unit/backend/yaml_backend_spec.rb
index 2f0608d..2dee576 100644
--- a/spec/unit/backend/yaml_backend_spec.rb
+++ b/spec/unit/backend/yaml_backend_spec.rb
@@ -5,10 +5,11 @@ class Hiera
   module Backend
     describe Yaml_backend do
       before do
+        Config.load({})
         Hiera.stubs(:debug)
         Hiera.stubs(:warn)
-        @backend = Yaml_backend.new
-        @backend.stubs(:stale?).returns(true)
+        @cache = mock
+        @backend = Yaml_backend.new(@cache)
       end
 
       describe "#initialize" do
@@ -31,7 +32,8 @@ class Hiera
           Backend.expects(:datasources).multiple_yields(["one"], ["two"])
           Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns("/nonexisting/one.yaml")
           Backend.expects(:datafile).with(:yaml, {}, "two", "yaml").returns(nil).never
-          YAML.expects(:load_file).with("/nonexisting/one.yaml").returns(YAML.load("---\nkey: answer"))
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>"answer"})
+          File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
 
           @backend.lookup("key", {}, nil, :priority).should == "answer"
         end
@@ -47,7 +49,8 @@ class Hiera
         it "should return nil for empty data files" do
           Backend.expects(:datasources).multiple_yields(["one"])
           Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns("/nonexisting/one.yaml")
-          YAML.expects(:load_file).with("/nonexisting/one.yaml").returns(YAML.load(""))
+          File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({})
 
           @backend.lookup("key", {}, nil, :priority).should be_nil
         end
@@ -56,9 +59,11 @@ class Hiera
           Backend.expects(:datasources).multiple_yields(["one"], ["two"])
           Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns("/nonexisting/one.yaml")
           Backend.expects(:datafile).with(:yaml, {}, "two", "yaml").returns("/nonexisting/two.yaml")
+          File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
+          File.stubs(:exist?).with("/nonexisting/two.yaml").returns(true)
 
-          YAML.expects(:load_file).with("/nonexisting/one.yaml").returns(YAML.load("---\nkey: answer"))
-          YAML.expects(:load_file).with("/nonexisting/two.yaml").returns(YAML.load("---\nkey: answer"))
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>"answer"})
+          @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>"answer"})
 
           @backend.lookup("key", {}, nil, :array).should == ["answer", "answer"]
         end
@@ -67,9 +72,11 @@ class Hiera
           Backend.expects(:datasources).multiple_yields(["one"], ["two"])
           Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns("/nonexisting/one.yaml")
           Backend.expects(:datafile).with(:yaml, {}, "two", "yaml").returns("/nonexisting/two.yaml")
+          File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
+          File.stubs(:exist?).with("/nonexisting/two.yaml").returns(true)
 
-          YAML.expects(:load_file).with("/nonexisting/one.yaml").returns(YAML.load(""))
-          YAML.expects(:load_file).with("/nonexisting/two.yaml").returns(YAML.load("---\nkey:\n a: answer"))
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({})
+          @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>{"a"=>"answer"}})
 
           @backend.lookup("key", {}, nil, :hash).should == {"a" => "answer"}
         end
@@ -78,9 +85,11 @@ class Hiera
           Backend.expects(:datasources).multiple_yields(["one"], ["two"])
           Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns("/nonexisting/one.yaml")
           Backend.expects(:datafile).with(:yaml, {}, "two", "yaml").returns("/nonexisting/two.yaml")
+          File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
+          File.stubs(:exist?).with("/nonexisting/two.yaml").returns(true)
 
-          YAML.expects(:load_file).with("/nonexisting/one.yaml").returns(YAML.load("---\nkey:\n a: answer"))
-          YAML.expects(:load_file).with("/nonexisting/two.yaml").returns(YAML.load("---\nkey:\n a: wrong\n b: answer"))
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>{"a"=>"answer"}})
+          @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>{"b"=>"answer", "a"=>"wrong"}})
 
           @backend.lookup("key", {}, nil, :hash).should == {"a" => "answer", "b" => "answer"}
         end
@@ -89,28 +98,34 @@ class Hiera
           Backend.expects(:datasources).multiple_yields(["one"], ["two"])
           Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns("/nonexisting/one.yaml")
           Backend.expects(:datafile).with(:yaml, {}, "two", "yaml").returns("/nonexisting/two.yaml")
+          File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
+          File.stubs(:exist?).with("/nonexisting/two.yaml").returns(true)
 
-          YAML.expects(:load_file).with("/nonexisting/one.yaml").returns(YAML.load("---\nkey:\n- a\n- answer"))
-          YAML.expects(:load_file).with("/nonexisting/two.yaml").returns(YAML.load("---\nkey:\n a: answer"))
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>["a", "answer"]})
+          @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>{"a"=>"answer"}})
 
-          lambda {@backend.lookup("key", {}, nil, :array)}.should raise_error(Exception, "Hiera type mismatch: expected Array and got Hash")
+          expect {@backend.lookup("key", {}, nil, :array)}.to raise_error(Exception, "Hiera type mismatch: expected Array and got Hash")
         end
 
         it "should fail when trying to merge an Array" do
           Backend.expects(:datasources).multiple_yields(["one"], ["two"])
           Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns("/nonexisting/one.yaml")
           Backend.expects(:datafile).with(:yaml, {}, "two", "yaml").returns("/nonexisting/two.yaml")
+          File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
+          File.stubs(:exist?).with("/nonexisting/two.yaml").returns(true)
 
-          YAML.expects(:load_file).with("/nonexisting/one.yaml").returns(YAML.load("---\nkey:\n a: answer"))
-          YAML.expects(:load_file).with("/nonexisting/two.yaml").returns(YAML.load("---\nkey:\n- a\n- wrong"))
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>{"a"=>"answer"}})
+          @cache.expects(:read_file).with("/nonexisting/two.yaml", Hash).returns({"key"=>["a", "wrong"]})
 
-          lambda {@backend.lookup("key", {}, nil, :hash)}.should raise_error(Exception, "Hiera type mismatch: expected Hash and got Array")
+          expect { @backend.lookup("key", {}, nil, :hash) }.to raise_error(Exception, "Hiera type mismatch: expected Hash and got Array")
         end
 
         it "should parse the answer for scope variables" do
           Backend.expects(:datasources).yields("one")
           Backend.expects(:datafile).with(:yaml, {"rspec" => "test"}, "one", "yaml").returns("/nonexisting/one.yaml")
-          YAML.expects(:load_file).with("/nonexisting/one.yaml").returns(YAML.load("---\nkey: 'test_%{rspec}'"))
+          File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
+
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).returns({"key"=>"test_%{rspec}"})
 
           @backend.lookup("key", {"rspec" => "test"}, nil, :priority).should == "test_test"
         end
@@ -118,8 +133,11 @@ class Hiera
         it "should retain datatypes found in yaml files" do
           Backend.expects(:datasources).yields("one").times(3)
           Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns("/nonexisting/one.yaml").times(3)
+          File.stubs(:exist?).with("/nonexisting/one.yaml").returns(true)
+
+          yaml = "---\nstringval: 'string'\nboolval: true\nnumericval: 1"
 
-          YAML.expects(:load_file).with("/nonexisting/one.yaml").returns(YAML.load("---\nstringval: 'string'\nboolval: true\nnumericval: 1")).times(3)
+          @cache.expects(:read_file).with("/nonexisting/one.yaml", Hash).times(3).returns({"boolval"=>true, "numericval"=>1, "stringval"=>"string"})
 
           @backend.lookup("stringval", {}, nil, :priority).should == "string"
           @backend.lookup("boolval", {}, nil, :priority).should == true
@@ -127,60 +145,5 @@ class Hiera
         end
       end
     end
-
-    describe '#stale?' do
-      before do
-        Hiera.stubs(:debug)
-        Hiera.stubs(:warn)
-        @backend = Yaml_backend.new
-        @fakestat = Struct.new(:ino, :mtime, :size)
-      end
-
-      def create_yaml_file(data, path)
-        File.open(path, 'w') do |f|
-          f.write(data)
-        end
-      end
-
-      def update_file(data, path)
-        File.open(path, 'a') do |f|
-          f.write(data)
-        end
-      end
-
-      it 'should report a stale cache if a data lookup has not been performed' do
-        tmp_yamlfile = Pathname(Dir.mktmpdir('yaml')) + 'yamlfile'
-        create_yaml_file({'foo' => 'bar'}.to_yaml, tmp_yamlfile)
-        @backend.stale?(tmp_yamlfile).should == true
-      end
-
-      describe 'lookup tests' do
-        before(:each) do
-          @tmp_yamlfile = Pathname(Dir.mktmpdir('yaml')) + 'yamlfile'
-          create_yaml_file({'foo' => 'bar'}.to_yaml, @tmp_yamlfile)
-          Backend.expects(:datasources).yields("one")
-          Backend.expects(:datafile).with(:yaml, {}, "one", "yaml").returns(@tmp_yamlfile)
-        end
-
-        it 'should not report a stale cache after a data lookup' do
-          @backend.stale?(@tmp_yamlfile).should == true
-          @backend.lookup('foo', {}, nil, :priority).should == 'bar'
-          @backend.stale?(@tmp_yamlfile).should == false
-        end
-
-        [:ino, :mtime, :size].each do |attribute|
-          it "should report a stale cache if a backend file's #{attribute} has changed" do
-            @stat_instance = @fakestat.new(1234, 1234, 1234)
-            File.expects(:stat).with(@tmp_yamlfile).returns(@stat_instance).twice
-            @backend.stale?(@tmp_yamlfile).should == true
-            @backend.lookup('foo', {}, nil, :priority).should == 'bar'
-            @backend.stale?(@tmp_yamlfile).should == false
-            @stat_instance[attribute] += 1
-            File.unstub && File.expects(:stat).with(@tmp_yamlfile).returns(@stat_instance)
-            @backend.stale?(@tmp_yamlfile).should == true
-          end
-        end
-      end
-    end
   end
 end
diff --git a/spec/unit/backend_spec.rb b/spec/unit/backend_spec.rb
index 2a55497..b70f87a 100644
--- a/spec/unit/backend_spec.rb
+++ b/spec/unit/backend_spec.rb
@@ -4,27 +4,42 @@ require 'hiera/util'
 class Hiera
   describe Backend do
     describe "#datadir" do
-      it "should use the backend configured dir" do
-        Config.load({:rspec => {:datadir => "/tmp"}})
-        Backend.expects(:parse_string).with("/tmp", {})
-        Backend.datadir(:rspec, {})
+      it "interpolates any values in the configured value" do
+        Config.load({:rspec => {:datadir => "/tmp/%{interpolate}"}})
+
+        dir = Backend.datadir(:rspec, { "interpolate" => "my_data" })
+
+        dir.should == "/tmp/my_data"
       end
 
-      it "should use a default var directory" do
+      it "defaults to a directory in var" do
         Config.load({})
-        Backend.expects(:parse_string).with(Hiera::Util.var_dir, {})
-        Backend.datadir(:rspec, {})
+        Backend.datadir(:rspec, {}).should == Hiera::Util.var_dir
+
+        Config.load({:rspec => nil})
+        Backend.datadir(:rspec, {}).should == Hiera::Util.var_dir
+
+        Config.load({:rspec => {}})
+        Backend.datadir(:rspec, {}).should == Hiera::Util.var_dir
+      end
+
+      it "fails when the datadir is an array" do
+        Config.load({:rspec => {:datadir => []}})
+
+        expect do
+          Backend.datadir(:rspec, {})
+        end.to raise_error(Hiera::InvalidConfigurationError, /datadir for rspec cannot be an array/)
       end
     end
 
     describe "#datafile" do
-      it "should check if the file exist and return nil if not" do
+      it "translates a non-existant datafile into nil" do
         Hiera.expects(:debug).with("Cannot find datafile /nonexisting/test.yaml, skipping")
         Backend.expects(:datadir).returns("/nonexisting")
         Backend.datafile(:yaml, {}, "test", "yaml").should == nil
       end
 
-      it "should return the correct file name" do
+      it "concatenates the datadir and datafile and format to produce the full datafile filename" do
         Backend.expects(:datadir).returns("/nonexisting")
         File.expects(:exist?).with("/nonexisting/test.yaml").returns(true)
         Backend.datafile(:yaml, {}, "test", "yaml").should == "/nonexisting/test.yaml"
@@ -32,7 +47,7 @@ class Hiera
     end
 
     describe "#datasources" do
-      it "should use the supplied hierarchy" do
+      it "iterates over the datasources in the order of the given hierarchy" do
         expected = ["one", "two"]
         Backend.datasources({}, nil, ["one", "two"]) do |backend|
           backend.should == expected.delete_at(0)
@@ -41,7 +56,7 @@ class Hiera
         expected.empty?.should == true
       end
 
-      it "should use the configured hierarchy if none is supplied" do
+      it "uses the configured hierarchy no specific hierarchy is given" do
         Config.load(:hierarchy => "test")
 
         Backend.datasources({}) do |backend|
@@ -49,7 +64,7 @@ class Hiera
         end
       end
 
-      it "should default to common if not configured or supplied" do
+      it "defaults to a hierarchy of only 'common' if not configured or given" do
         Config.load({})
 
         Backend.datasources({}) do |backend|
@@ -57,7 +72,7 @@ class Hiera
         end
       end
 
-      it "should insert the override if provided" do
+      it "prefixes the hierarchy with the override if an override is provided" do
         Config.load({})
 
         expected = ["override", "common"]
@@ -68,12 +83,12 @@ class Hiera
         expected.empty?.should == true
       end
 
-      it "should parse the sources based on scope" do
+      it "parses the names of the hierarchy levels using the given scope" do
         Backend.expects(:parse_string).with("common", {:rspec => :tests})
         Backend.datasources({:rspec => :tests}) { }
       end
 
-      it "should not return empty sources" do
+      it "defaults to 'common' if the hierarchy contains no hierarchies with non-empty names" do
         Config.load({})
 
         expected = ["common"]
@@ -86,101 +101,305 @@ class Hiera
     end
 
     describe "#parse_string" do
-      it "should not try to parse invalid data" do
+      it "passes nil through untouched" do
         Backend.parse_string(nil, {}).should == nil
       end
 
-      it "should clone the supplied data" do
-        data = ""
-        data.expects(:clone).returns("")
-        Backend.parse_string(data, {})
+      it "does not modify the input data" do
+        data = "%{value}"
+        Backend.parse_string(data, { "value" => "replacement" })
+
+        data.should == "%{value}"
       end
 
-      it "should only parse string data" do
-        data = ""
-        data.expects(:is_a?).with(String)
-        Backend.parse_string(data, {})
+      it "passes non-string data through untouched" do
+        input = { "not a" => "string" }
+
+        Backend.parse_string(input, {}).should == input
       end
 
-      it "should match data from scope" do
-        input = "test_%{rspec}_test"
-        Backend.parse_string(input, {"rspec" => "test"}).should == "test_test_test"
+      @scope_interpolation_tests = {
+        "replace %{part1} and %{part2}" =>
+          "replace value of part1 and value of part2",
+        "replace %{scope('part1')} and %{scope('part2')}" =>
+          "replace value of part1 and value of part2"
+      }
+
+      @scope_interpolation_tests.each do |input, expected|
+        it "replaces interpolations with data looked up in the scope" do
+          scope = {"part1" => "value of part1", "part2" => "value of part2"}
+
+          Backend.parse_string(input, scope).should == expected
+        end
       end
 
-      it "should match data from extra_data" do
+      it "replaces interpolations with data looked up in extra_data when scope does not contain the value" do
         input = "test_%{rspec}_test"
-        Backend.parse_string(input, {}, {"rspec" => "test"}).should == "test_test_test"
+        Backend.parse_string(input, {}, {"rspec" => "extra"}).should == "test_extra_test"
       end
 
-      it "should prefer scope over extra_data" do
+      it "prefers data from scope over data from extra_data" do
         input = "test_%{rspec}_test"
         Backend.parse_string(input, {"rspec" => "test"}, {"rspec" => "fail"}).should == "test_test_test"
       end
 
-      it "should treat :undefined in scope as empty" do
+      @interprets_nil_in_scope_tests = {
+        "test_%{rspec}_test" => "test__test",
+        "test_%{scope('rspec')}_test" => "test__test"
+      }
+
+      @interprets_nil_in_scope_tests.each do |input, expected|
+        it "interprets nil in scope as a non-value" do
+          Backend.parse_string(input, {"rspec" => nil}).should == expected
+        end
+      end
+
+      @interprets_false_in_scope_tests = {
+        "test_%{rspec}_test" => "test_false_test",
+        "test_%{scope('rspec')}_test" => "test_false_test"
+      }
+
+      @interprets_false_in_scope_tests.each do |input, expected|
+        it "interprets false in scope as a real value" do
+          input = "test_%{scope('rspec')}_test"
+          Backend.parse_string(input, {"rspec" => false}).should == expected
+        end
+      end
+
+      it "interprets false in extra_data as a real value" do
         input = "test_%{rspec}_test"
-        Backend.parse_string(input, {"rspec" => :undefined}).should == "test__test"
+        Backend.parse_string(input, {}, {"rspec" => false}).should == "test_false_test"
       end
 
-      it "should match data from extra_data when scope contains :undefined" do
+      it "interprets nil in extra_data as a non-value" do
         input = "test_%{rspec}_test"
-        Backend.parse_string(input, {"rspec" => :undefined}, {"rspec" => "test"}).should == "test_test_test"
+        Backend.parse_string(input, {}, {"rspec" => nil}).should == "test__test"
+      end
+
+      @interprets_undefined_in_scope_tests = {
+        "test_%{rspec}_test" => "test__test",
+        "test_%{scope('rspec')}_test" => "test__test"
+      }
+
+      @interprets_undefined_in_scope_tests.each do |input, expected|
+        it "interprets :undefined in scope as a non-value" do
+          Backend.parse_string(input, {"rspec" => :undefined}).should == expected
+        end
       end
 
-      it "should match data in puppet ${::fact} style" do
-        input = "test_%{::rspec}_test"
-        Backend.parse_string(input, {"rspec" => "test"}).should == "test_test_test"
+      it "uses the value from extra_data when scope is :undefined" do
+        input = "test_%{rspec}_test"
+        Backend.parse_string(input, {"rspec" => :undefined}, { "rspec" => "extra" }).should == "test_extra_test"
+      end
+
+      @exact_lookup_tests = {
+        "test_%{::rspec::data}_test" => "test_value_test",
+        "test_%{scope('::rspec::data')}_test" => "test_value_test"
+      }
+
+      @exact_lookup_tests.each do |input, expected|
+        it "looks up the interpolated value exactly as it appears in the input" do
+          Backend.parse_string(input, {"::rspec::data" => "value"}).should == expected
+        end
+      end
+
+      @surrounding_whitespace_tests = {
+        "test_%{\trspec::data }_test" => "test_value_test",
+        "test_%{scope('\trspec::data ')}_test" => "test_value_test"
+      }
+      @surrounding_whitespace_tests.each do |input, expected|
+        it "does not remove any surrounding whitespace when parsing the key to lookup" do
+          Backend.parse_string(input, {"\trspec::data " => "value"}).should == expected
+        end
+      end
+
+      @leading_double_colon_tests = {
+        "test_%{::rspec::data}_test" => "test__test",
+        "test_%{scope('::rspec::data')}_test" => "test__test"
+      }
+
+      @leading_double_colon_tests.each do |input, expected|
+        it "does not try removing leading :: when a full lookup fails (#17434)" do
+          Backend.parse_string(input, {"rspec::data" => "value"}).should == expected
+        end
+      end
+
+      @double_colon_key_tests = {
+        "test_%{::rspec::data}_test" => "test__test",
+        "test_%{scope('::rspec::data')}_test" => "test__test"
+      }
+      @double_colon_key_tests.each do |input, expected|
+        it "does not try removing leading sections separated by :: when a full lookup fails (#17434)" do
+          Backend.parse_string(input, {"data" => "value"}).should == expected
+        end
+      end
+
+      it "does not try removing unknown, preceeding characters when looking up values" do
+        input = "test_%{$var}_test"
+        Backend.parse_string(input, {"$var" => "value"}).should == "test_value_test"
+      end
+
+      it "looks up recursively" do
+        scope = {"rspec" => "%{first}", "first" => "%{last}", "last" => "final"}
+        input = "test_%{rspec}_test"
+        Backend.parse_string(input, scope).should == "test_final_test"
+      end
+
+      it "raises an error if the recursive lookup results in an infinite loop" do
+        scope = {"first" => "%{second}", "second" => "%{first}"}
+        input = "test_%{first}_test"
+        expect do
+          Backend.parse_string(input, scope)
+        end.to raise_error Hiera::InterpolationLoop, "Detected in [first, second]"
+      end
+
+      it "replaces hiera interpolations with data looked up in hiera" do
+        input = "%{hiera('key1')}"
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("key1", scope, nil, :priority).returns("answer")
+
+        Backend.parse_string(input, scope).should == "answer"
       end
     end
 
     describe "#parse_answer" do
-      it "should parse strings correctly" do
+      it "interpolates values in strings" do
         input = "test_%{rspec}_test"
         Backend.parse_answer(input, {"rspec" => "test"}).should == "test_test_test"
       end
 
-      it "should parse each string in an array" do
+      it "interpolates each string in an array" do
         input = ["test_%{rspec}_test", "test_%{rspec}_test", ["test_%{rspec}_test"]]
         Backend.parse_answer(input, {"rspec" => "test"}).should == ["test_test_test", "test_test_test", ["test_test_test"]]
       end
 
-      it "should parse each string in a hash" do
+      it "interpolates each string in a hash" do
         input = {"foo" => "test_%{rspec}_test", "bar" => "test_%{rspec}_test"}
         Backend.parse_answer(input, {"rspec" => "test"}).should == {"foo"=>"test_test_test", "bar"=>"test_test_test"}
       end
 
-      it "should parse mixed arrays and hashes" do
+      it "interpolates string in hash keys" do
+        input = {"%{rspec}" => "test"}
+        Backend.parse_answer(input, {"rspec" => "foo"}).should == {"foo"=>"test"}
+      end
+
+      it "interpolates strings in nested hash keys" do
+        input = {"topkey" => {"%{rspec}" => "test"}}
+        Backend.parse_answer(input, {"rspec" => "foo"}).should == {"topkey"=>{"foo" => "test"}}
+      end
+
+      it "interpolates strings in a mixed structure of arrays and hashes" do
         input = {"foo" => "test_%{rspec}_test", "bar" => ["test_%{rspec}_test", "test_%{rspec}_test"]}
         Backend.parse_answer(input, {"rspec" => "test"}).should == {"foo"=>"test_test_test", "bar"=>["test_test_test", "test_test_test"]}
       end
 
-      it "should parse integers correctly" do
+      it "interpolates hiera lookups values in strings" do
+        input = "test_%{hiera('rspec')}_test"
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("test")
+        Backend.parse_answer(input, scope).should == "test_test_test"
+      end
+
+      it "interpolates hiera lookups in each string in an array" do
+        input = ["test_%{hiera('rspec')}_test", "test_%{hiera('rspec')}_test", ["test_%{hiera('rspec')}_test"]]
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("test")
+        Backend.parse_answer(input, scope).should == ["test_test_test", "test_test_test", ["test_test_test"]]
+      end
+
+      it "interpolates hiera lookups in each string in a hash" do
+        input = {"foo" => "test_%{hiera('rspec')}_test", "bar" => "test_%{hiera('rspec')}_test"}
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("test")
+        Backend.parse_answer(input, scope).should == {"foo"=>"test_test_test", "bar"=>"test_test_test"}
+      end
+
+      it "interpolates hiera lookups in string in hash keys" do
+        input = {"%{hiera('rspec')}" => "test"}
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("foo")
+        Backend.parse_answer(input, scope).should == {"foo"=>"test"}
+      end
+
+      it "interpolates hiera lookups in strings in nested hash keys" do
+        input = {"topkey" => {"%{hiera('rspec')}" => "test"}}
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("foo")
+        Backend.parse_answer(input, scope).should == {"topkey"=>{"foo" => "test"}}
+      end
+
+      it "interpolates hiera lookups in strings in a mixed structure of arrays and hashes" do
+        input = {"foo" => "test_%{hiera('rspec')}_test", "bar" => ["test_%{hiera('rspec')}_test", "test_%{hiera('rspec')}_test"]}
+        scope = {}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("test")
+        Backend.parse_answer(input, scope).should == {"foo"=>"test_test_test", "bar"=>["test_test_test", "test_test_test"]}
+      end
+
+      it "interpolates hiera lookups and scope lookups in the same string" do
+        input = {"foo" => "test_%{hiera('rspec')}_test", "bar" => "test_%{rspec2}_test"}
+        scope = {"rspec2" => "scope_rspec"}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("hiera_rspec")
+        Backend.parse_answer(input, scope).should == {"foo"=>"test_hiera_rspec_test", "bar"=>"test_scope_rspec_test"}
+      end
+
+      it "interpolates hiera and scope lookups with the same lookup query in a single string" do
+        input =  "test_%{hiera('rspec')}_test_%{rspec}"
+        scope = {"rspec" => "scope_rspec"}
+        Config.load({:yaml => {:datadir => "/tmp"}})
+        Config.load_backends
+        Backend::Yaml_backend.any_instance.stubs(:lookup).with("rspec", scope, nil, :priority).returns("hiera_rspec")
+        Backend.parse_answer(input, scope).should == "test_hiera_rspec_test_scope_rspec"
+      end
+
+      it "passes integers unchanged" do
         input = 1
         Backend.parse_answer(input, {"rspec" => "test"}).should == 1
       end
 
-      it "should parse floats correctly" do
+      it "passes floats unchanged" do
         input = 0.233
         Backend.parse_answer(input, {"rspec" => "test"}).should == 0.233
       end
 
-      it "should parse true boolean values correctly" do
+      it "passes the boolean true unchanged" do
         input = true
         Backend.parse_answer(input, {"rspec" => "test"}).should == true
       end
 
-      it "should parse false boolean values correctly" do
+      it "passes the boolean false unchanged" do
         input = false
         Backend.parse_answer(input, {"rspec" => "test"}).should == false
       end
+
+      it "interpolates lookups using single or double quotes" do
+        input =  "test_%{scope(\"rspec\")}_test_%{scope('rspec')}"
+        scope = {"rspec" => "scope_rspec"}
+        Backend.parse_answer(input, scope).should == "test_scope_rspec_test_scope_rspec"
+      end
     end
 
     describe "#resolve_answer" do
-      it "should correctly parse array data" do
+      it "flattens and removes duplicate values from arrays during an array lookup" do
         Backend.resolve_answer(["foo", ["foo", "foo"], "bar"], :array).should == ["foo", "bar"]
       end
 
-      it "should just return the answer for non array data" do
+      it "returns the data unchanged during a priority lookup" do
         Backend.resolve_answer(["foo", ["foo", "foo"], "bar"], :priority).should == ["foo", ["foo", "foo"], "bar"]
       end
     end
@@ -191,7 +410,8 @@ class Hiera
         Hiera.stubs(:warn)
       end
 
-      it "should cache backends" do
+      it "caches loaded backends" do
+        Backend.clear!
         Hiera.expects(:debug).with(regexp_matches(/Hiera YAML backend starting/)).once
 
         Config.load({:yaml => {:datadir => "/tmp"}})
@@ -201,7 +421,7 @@ class Hiera
         Backend.lookup("key", "default", {}, nil, nil)
       end
 
-      it "should return the answer from the backend" do
+      it "returns the answer from the backend" do
         Config.load({:yaml => {:datadir => "/tmp"}})
         Config.load_backends
 
@@ -210,7 +430,7 @@ class Hiera
         Backend.lookup("key", "default", {}, nil, nil).should == "answer"
       end
 
-      it "should retain the datatypes as returned by the backend" do
+      it "retains the datatypes as returned by the backend" do
         Config.load({:yaml => {:datadir => "/tmp"}})
         Config.load_backends
 
@@ -223,7 +443,7 @@ class Hiera
         Backend.lookup("numericval", "default", {}, nil, nil).should == 1
       end
 
-      it "should call to all backends till an answer is found" do
+      it "calls to all backends till an answer is found" do
         backend = mock
         backend.expects(:lookup).returns("answer")
         Config.load({})
@@ -235,7 +455,7 @@ class Hiera
         Backend.lookup("key", "test_%{rspec}", {"rspec" => "test"}, nil, nil).should == "answer"
       end
 
-      it "should call to all backends till an answer is found when doing array lookups" do
+      it "calls to all backends till an answer is found when doing array lookups" do
         backend = mock
         backend.expects(:lookup).returns(["answer"])
         Config.load({})
@@ -246,7 +466,7 @@ class Hiera
         Backend.lookup("key", "notfound", {"rspec" => "test"}, nil, :array).should == ["answer"]
       end
 
-      it "should call to all backends till an answer is found when doing hash lookups" do
+      it "calls to all backends till an answer is found when doing hash lookups" do
         thehash = {:answer => "value"}
         backend = mock
         backend.expects(:lookup).returns(thehash)
@@ -258,7 +478,7 @@ class Hiera
         Backend.lookup("key", "notfound", {"rspec" => "test"}, nil, :hash).should == thehash
       end
 
-      it "should build a merged hash from all backends for hash searches" do
+      it "builds a merged hash from all backends for hash searches" do
         backend1 = mock :lookup => {"a" => "answer"}
         backend2 = mock :lookup => {"b" => "bnswer"}
         Config.load({})
@@ -269,7 +489,7 @@ class Hiera
         Backend.lookup("key", {}, {"rspec" => "test"}, nil, :hash).should == {"a" => "answer", "b" => "bnswer"}
       end
 
-      it "should build an array from all backends for array searches" do
+      it "builds an array from all backends for array searches" do
         backend1 = mock :lookup => ["a", "b"]
         backend2 = mock :lookup => ["c", "d"]
         Config.load({})
@@ -280,7 +500,7 @@ class Hiera
         Backend.lookup("key", {}, {"rspec" => "test"}, nil, :array).should == ["a", "b", "c", "d"]
       end
 
-      it "should use the earliest backend result for priority searches" do
+      it "uses the earliest backend result for priority searches" do
         backend1 = mock
         backend1.stubs(:lookup).returns(["a", "b"])
         backend2 = mock
@@ -293,7 +513,7 @@ class Hiera
         Backend.lookup("key", {}, {"rspec" => "test"}, nil, :priority).should == ["a", "b"]
       end
 
-      it "should parse the answers based on resolution_type" do
+      it "parses the answers based on resolution_type" do
         Config.load({:yaml => {:datadir => "/tmp"}})
         Config.load_backends
 
@@ -303,7 +523,7 @@ class Hiera
         Backend.lookup("key", "test_%{rspec}", {"rspec" => "test"}, nil, :priority).should == "parsed"
       end
 
-      it "should return the default with variables parsed if nothing is found" do
+      it "returns the default with variables parsed if nothing is found" do
         Config.load({:yaml => {:datadir => "/tmp"}})
         Config.load_backends
 
@@ -312,26 +532,52 @@ class Hiera
         Backend.lookup("key", "test_%{rspec}", {"rspec" => "test"}, nil, nil).should == "test_test"
       end
 
-      it "should correctly handle string default data" do
+      it "keeps string default data as a string" do
         Config.load({:yaml => {:datadir => "/tmp"}})
         Config.load_backends
         Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, nil)
         Backend.lookup("key", "test", {}, nil, nil).should == "test"
       end
 
-      it "should correctly handle array default data" do
+      it "keeps array default data as an array" do
         Config.load({:yaml => {:datadir => "/tmp"}})
         Config.load_backends
         Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, :array)
         Backend.lookup("key", ["test"], {}, nil, :array).should == ["test"]
       end
 
-      it "should correctly handle hash default data" do
+      it "keeps hash default data as a hash" do
         Config.load({:yaml => {:datadir => "/tmp"}})
         Config.load_backends
         Backend::Yaml_backend.any_instance.expects(:lookup).with("key", {}, nil, :hash)
         Backend.lookup("key", {"test" => "value"}, {}, nil, :hash).should == {"test" => "value"}
       end
     end
+
+    describe '#merge_answer' do
+      before do
+        Hiera.stubs(:debug)
+        Hiera.stubs(:warn)
+        Config.stubs(:validate!)
+      end
+
+      it "uses Hash.merge when configured with :merge_behavior => :native" do
+        Config.load({:merge_behavior => :native})
+        Hash.any_instance.expects(:merge).with({"b" => "bnswer"}).returns({"a" => "answer", "b" => "bnswer"})
+        Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"}).should == {"a" => "answer", "b" => "bnswer"}
+      end
+
+      it "uses deep_merge! when configured with :merge_behavior => :deeper" do
+        Config.load({:merge_behavior => :deeper})
+        Hash.any_instance.expects('deep_merge!').with({"b" => "bnswer"}).returns({"a" => "answer", "b" => "bnswer"})
+        Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"}).should == {"a" => "answer", "b" => "bnswer"}
+      end
+
+      it "uses deep_merge when configured with :merge_behavior => :deep" do
+        Config.load({:merge_behavior => :deep})
+        Hash.any_instance.expects('deep_merge').with({"b" => "bnswer"}).returns({"a" => "answer", "b" => "bnswer"})
+        Backend.merge_answer({"a" => "answer"},{"b" => "bnswer"}).should == {"a" => "answer", "b" => "bnswer"}
+      end
+    end
   end
 end
diff --git a/spec/unit/config_spec.rb b/spec/unit/config_spec.rb
index 60a1627..44f3064 100644
--- a/spec/unit/config_spec.rb
+++ b/spec/unit/config_spec.rb
@@ -7,7 +7,8 @@ class Hiera
         {
           :backends  => ["yaml"],
           :hierarchy => "common",
-          :logger    => "console"
+          :logger    => "console",
+          :merge_behavior=>:native
         }
       end
 
@@ -43,11 +44,11 @@ class Hiera
 
       it "should merge defaults with the loaded or supplied config" do
         config = Config.load({})
-        config.should == {:backends => ["yaml"], :hierarchy => "common", :logger => "console"}
+        config.should == {:backends => ["yaml"], :hierarchy => "common", :logger => "console", :merge_behavior=>:native}
       end
 
       it "should force :backends to be a flattened array" do
-        Config.load({:backends => [["foo", ["bar"]]]}).should == {:backends => ["foo", "bar"], :hierarchy => "common", :logger => "console"}
+        Config.load({:backends => [["foo", ["bar"]]]}).should == {:backends => ["foo", "bar"], :hierarchy => "common", :logger => "console", :merge_behavior=>:native}
       end
 
       it "should load the supplied logger" do
diff --git a/spec/unit/fallback_logger_spec.rb b/spec/unit/fallback_logger_spec.rb
new file mode 100644
index 0000000..bf864d0
--- /dev/null
+++ b/spec/unit/fallback_logger_spec.rb
@@ -0,0 +1,80 @@
+require 'hiera/fallback_logger'
+
+describe Hiera::FallbackLogger do
+  before :each do
+    InMemoryLogger.reset
+    SuitableLogger.reset
+  end
+
+  it "delegates #warn to the logger implemenation" do
+    logger = Hiera::FallbackLogger.new(InMemoryLogger)
+
+    logger.warn("the message")
+
+    InMemoryLogger.warnings.should == ["the message"]
+  end
+
+  it "delegates #debug to the logger implemenation" do
+    logger = Hiera::FallbackLogger.new(InMemoryLogger)
+
+    logger.debug("the message")
+
+    InMemoryLogger.debugs.should == ["the message"]
+  end
+
+  it "chooses the first logger that is suitable" do
+    logger = Hiera::FallbackLogger.new(UnsuitableLogger, SuitableLogger)
+
+    logger.warn("for the suitable logger")
+
+    SuitableLogger.warnings.should include("for the suitable logger")
+  end
+
+  it "raises an error if no implementation is suitable" do
+    expect do
+      Hiera::FallbackLogger.new(UnsuitableLogger)
+    end.to raise_error "No suitable logging implementation found."
+  end
+
+  it "issues a warning for each implementation that is not suitable" do
+    Hiera::FallbackLogger.new(UnsuitableLogger, UnsuitableLogger, SuitableLogger)
+
+    SuitableLogger.warnings.should == [
+      "Not using UnsuitableLogger. It does not report itself to be suitable.",
+      "Not using UnsuitableLogger. It does not report itself to be suitable."]
+  end
+
+  # Preserves log messages in memory
+  # and also serves as a "legacy" logger that has no
+  # suitable? method
+  class InMemoryLogger
+    class << self
+      attr_accessor :warnings, :debugs
+    end
+
+    def self.reset
+      self.warnings = []
+      self.debugs = []
+    end
+
+    def self.warn(message)
+      self.warnings << message
+    end
+
+    def self.debug(message)
+      self.debugs << message
+    end
+  end
+
+  class UnsuitableLogger
+    def self.suitable?
+      false
+    end
+  end
+
+  class SuitableLogger < InMemoryLogger
+    def self.suitable?
+      true
+    end
+  end
+end
diff --git a/spec/unit/filecache_spec.rb b/spec/unit/filecache_spec.rb
new file mode 100644
index 0000000..ae9a6a7
--- /dev/null
+++ b/spec/unit/filecache_spec.rb
@@ -0,0 +1,142 @@
+require 'spec_helper'
+require 'tmpdir'
+
+class Hiera
+  describe Filecache do
+    before do
+      @cache = Filecache.new
+    end
+
+    def write_file(file, contents)
+      File.open(file, 'w') do |f|
+        f.write(contents)
+      end
+    end
+
+    describe "#read" do
+      it "reads data from a file" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+
+          @cache.read(file).should == "my data"
+        end
+      end
+
+      it "rereads data when the file changes" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+          @cache.read(file).should == "my data"
+
+          write_file(file, "changed data")
+          @cache.read(file).should == "changed data"
+        end
+      end
+
+      it "uses the provided default when the type does not match the expected type" do
+        Hiera.expects(:debug).with(regexp_matches(/String.*not.*Hash, setting defaults/))
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+          data = @cache.read(file, Hash, { :testing => "hash" }) do |data|
+            "a string"
+          end
+
+          data.should == { :testing => "hash" }
+        end
+      end
+
+      it "traps any errors from the block and uses the default value" do
+        Hiera.expects(:debug).with(regexp_matches(/Reading data.*failed:.*testing error/))
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+          data = @cache.read(file, Hash, { :testing => "hash" }) do |data|
+            raise ArgumentError, "testing error"
+          end
+
+          data.should == { :testing => "hash" }
+        end
+      end
+
+      it "raises an error when there is no default given and there is a problem" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+
+          expect do
+            @cache.read(file, Hash) do |data|
+              raise ArgumentError, "testing error"
+            end
+          end.to raise_error(ArgumentError, "testing error")
+        end
+      end
+    end
+
+    describe "#read_file" do
+      it "reads data from a file" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+
+          @cache.read_file(file).should == "my data"
+        end
+      end
+
+      it "rereads data when the file changes" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+          @cache.read_file(file).should == "my data"
+
+          write_file(file, "changed data")
+          @cache.read_file(file).should == "changed data"
+        end
+      end
+
+      it "errors when the type does not match the expected type" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+
+          expect do
+            @cache.read_file(file, Hash) do |data|
+              "a string"
+            end
+          end.to raise_error(TypeError)
+        end
+      end
+
+      it "converts the read data using the block" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+
+          @cache.read_file(file, Hash) do |data|
+            { :data => data }
+          end.should == { :data => "my data" }
+        end
+      end
+
+      it "errors when the file does not exist" do
+        expect do
+          @cache.read_file("/notexist")
+        end.to raise_error(Errno::ENOENT)
+      end
+
+      it "propogates any errors from the block" do
+        Dir.mktmpdir do |dir|
+          file = File.join(dir, "testing")
+          write_file(file, "my data")
+
+          expect do
+            @cache.read_file(file) do |data|
+              raise ArgumentError, "testing error"
+            end
+          end.to raise_error(ArgumentError, "testing error")
+        end
+      end
+    end
+  end
+end
diff --git a/spec/unit/hiera_spec.rb b/spec/unit/hiera_spec.rb
index e9edaef..99ec89a 100644
--- a/spec/unit/hiera_spec.rb
+++ b/spec/unit/hiera_spec.rb
@@ -1,50 +1,69 @@
 require 'spec_helper'
 require 'hiera/util'
 
+# This is only around for the logger setup tests
+module Hiera::Foo_logger
+end
+
 describe "Hiera" do
   describe "#logger=" do
-    it "should attempt to load the supplied logger" do
-      Hiera.stubs(:warn)
-      Hiera.expects(:require).with("hiera/foo_logger").raises("fail")
+    it "loads the given logger" do
+      Hiera.expects(:require).with("hiera/foo_logger")
+
       Hiera.logger = "foo"
     end
 
-    it "should fall back to the Console logger on failure" do
+    it "falls back to the Console logger when the logger could not be loaded" do
       Hiera.expects(:warn)
-      Hiera.logger = "foo"
+
+      Hiera.logger = "no_such_logger"
+
+      Hiera.logger.should be Hiera::Console_logger
+    end
+
+    it "falls back to the Console logger when the logger class could not be found" do
+      Hiera.expects(:warn)
+      Hiera.expects(:require).with("hiera/no_constant_logger")
+
+      Hiera.logger = "no_constant"
+
       Hiera.logger.should be Hiera::Console_logger
     end
   end
 
   describe "#warn" do
-    it "should call the supplied logger" do
+    it "delegates to the configured logger" do
+      Hiera.logger = 'console'
       Hiera::Console_logger.expects(:warn).with("rspec")
+
       Hiera.warn("rspec")
     end
   end
 
   describe "#debug" do
-    it "should call the supplied logger" do
+    it "delegates to the configured logger" do
+      Hiera.logger = 'console'
       Hiera::Console_logger.expects(:debug).with("rspec")
+
       Hiera.debug("rspec")
     end
   end
 
   describe "#initialize" do
-    it "should use a default config" do
+    it "uses a default config file when none is provided" do
       config_file = File.join(Hiera::Util.config_dir, 'hiera.yaml')
       Hiera::Config.expects(:load).with(config_file)
       Hiera::Config.stubs(:load_backends)
       Hiera.new
     end
 
-    it "should pass the supplied config to the config class" do
+    it "passes the supplied config to the config class" do
       Hiera::Config.expects(:load).with({"test" => "rspec"})
       Hiera::Config.stubs(:load_backends)
       Hiera.new(:config => {"test" => "rspec"})
     end
 
-    it "should load all backends on start" do
+    it "loads all backends on start" do
       Hiera::Config.stubs(:load)
       Hiera::Config.expects(:load_backends)
       Hiera.new
@@ -52,7 +71,7 @@ describe "Hiera" do
   end
 
   describe "#lookup" do
-    it "should proxy to the Backend#lookup method" do
+    it "delegates to the Backend#lookup method" do
       Hiera::Config.stubs(:load)
       Hiera::Config.stubs(:load_backends)
       Hiera::Backend.expects(:lookup).with(:key, :default, :scope, :order_override, :resolution_type)
diff --git a/spec/unit/puppet_logger_spec.rb b/spec/unit/puppet_logger_spec.rb
new file mode 100644
index 0000000..c3e23ad
--- /dev/null
+++ b/spec/unit/puppet_logger_spec.rb
@@ -0,0 +1,31 @@
+require 'hiera/puppet_logger'
+
+describe Hiera::Puppet_logger do
+  it "is not suitable when Puppet is not defined" do
+    ensure_puppet_not_defined
+
+    Hiera::Puppet_logger.suitable?.should == false
+  end
+
+  it "is suitable when Puppet is defined" do
+    ensure_puppet_defined
+
+    Hiera::Puppet_logger.suitable?.should == true
+  end
+
+  after :each do
+    ensure_puppet_not_defined
+  end
+
+  def ensure_puppet_defined
+    if !Kernel.const_defined? :Puppet
+      Kernel.const_set(:Puppet, "Fake Puppet")
+    end
+  end
+
+  def ensure_puppet_not_defined
+    if Kernel.const_defined? :Puppet
+      Kernel.send(:remove_const, :Puppet)
+    end
+  end
+end
diff --git a/spec/unit/version_spec.rb b/spec/unit/version_spec.rb
new file mode 100644
index 0000000..bc6e19a
--- /dev/null
+++ b/spec/unit/version_spec.rb
@@ -0,0 +1,44 @@
+require "spec_helper"
+require "hiera/version"
+require 'pathname'
+
+describe "Hiera.version Public API" do
+  subject() { Hiera }
+
+  before :each do
+    Hiera.instance_eval do
+      if @hiera_version
+        @hiera_version = nil
+      end
+    end
+  end
+
+  context "without a VERSION file" do
+    before :each do
+      subject.stubs(:read_version_file).returns(nil)
+    end
+
+    it "is Hiera::VERSION" do
+      subject.version.should == Hiera::VERSION
+    end
+    it "respects the version= setter" do
+      subject.version = '1.2.3'
+      subject.version.should == '1.2.3'
+    end
+  end
+
+  context "with a VERSION file" do
+    it "is the content of the file" do
+      subject.expects(:read_version_file).with() do |path|
+        pathname = Pathname.new(path)
+        pathname.basename.to_s == "VERSION"
+      end.returns('1.2.1-9-g9fda440')
+
+      subject.version.should == '1.2.1-9-g9fda440'
+    end
+    it "respects the version= setter" do
+      subject.version = '1.2.3'
+      subject.version.should == '1.2.3'
+    end
+  end
+end

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-ruby-extras/ruby-hiera.git



More information about the Pkg-ruby-extras-commits mailing list