[DRE-commits] [ruby-hiera] 02/10: Imported Upstream version 1.2.1

Jonas Genannt jonas at brachium-system.net
Fri Oct 18 18:25:16 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 43a742ae3fcd8022a2a63bcadbb8a1ea8bafb8f2
Author: Jonas Genannt <jonas at brachium-system.net>
Date:   Fri Oct 18 20:14:31 2013 +0200

    Imported Upstream version 1.2.1
---
 .gitignore                                         |    3 -
 README.md                                          |    2 +
 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 -----
 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                                       |   26 +--
 lib/hiera/backend.rb                               |   83 +++++----
 lib/hiera/backend/json_backend.rb                  |   17 +-
 lib/hiera/backend/yaml_backend.rb                  |   40 ++---
 lib/hiera/config.rb                                |   18 +-
 lib/hiera/fallback_logger.rb                       |   41 +++++
 lib/hiera/filecache.rb                             |   74 ++++++++
 lib/hiera/puppet_logger.rb                         |    4 +
 lib/hiera/recursive_lookup.rb                      |   31 ++++
 metadata.yml                                       |  114 ++++++++++++
 spec/unit/backend/json_backend_spec.rb             |   32 ++--
 spec/unit/backend/yaml_backend_spec.rb             |  109 ++++--------
 spec/unit/backend_spec.rb                          |  188 ++++++++++++++------
 spec/unit/config_spec.rb                           |    7 +-
 spec/unit/fallback_logger_spec.rb                  |   80 +++++++++
 spec/unit/filecache_spec.rb                        |   63 +++++++
 spec/unit/hiera_spec.rb                            |   41 +++--
 spec/unit/puppet_logger_spec.rb                    |   31 ++++
 47 files changed, 766 insertions(+), 1118 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..968290c 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.
 
 -
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/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..ec9e28b 100644
--- a/lib/hiera.rb
+++ b/lib/hiera.rb
@@ -1,14 +1,16 @@
 require 'yaml'
 
 class Hiera
-  VERSION = "1.1.2"
+  VERSION = "1.2.1"
 
-  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/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
@@ -23,13 +25,13 @@ class Hiera
     # 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"
 
-      require "hiera/#{logger}_logger" unless constants.include?(loggerclass)
-
-      @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..9b3af5e 100644
--- a/lib/hiera/backend.rb
+++ b/lib/hiera/backend.rb
@@ -1,7 +1,15 @@
 require 'hiera/util'
+require 'hiera/recursive_lookup'
+
+begin
+  require 'deep_merge'
+rescue LoadError
+end
 
 class Hiera
   module Backend
+    INTERPOLATION = /%\{([^\}]*)\}/
+
     class << self
       # Data lives in /var/lib/hiera by default.  If a backend
       # supplies a datadir in the config it will be used and
@@ -61,43 +69,38 @@ 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 !~ /::(.+)/
+        interpolate(data, Hiera::RecursiveLookup.new(scope, extra_data))
+      end
 
-            tdata.gsub!(/%\{(::)?#{var}\}/, val)
+      def interpolate(data, values)
+        if data.is_a?(String)
+          data.gsub(INTERPOLATION) do
+            name = $1
+            values.lookup(name) do |value|
+              interpolate(value, values)
+            end
           end
+        else
+          data
         end
-
-        return tdata
       end
+      private :interpolate
 
       # Parses a answer received from data files
       #
@@ -136,6 +139,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 +190,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
diff --git a/lib/hiera/backend/json_backend.rb b/lib/hiera/backend/json_backend.rb
index 3919511..4ca9481 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(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..1ce6bd1 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(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/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..ac46e02
--- /dev/null
+++ b/lib/hiera/filecache.rb
@@ -0,0 +1,74 @@
+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=nil, default=nil)
+      @cache[path] ||= {:data => nil, :meta => path_metadata(path)}
+
+      if File.exist?(path) && !@cache[path][:data] || stale?(path)
+        if block_given?
+          begin
+            @cache[path][:data] = yield(File.read(path))
+          rescue => e
+            Hiera.debug("Reading data from %s failed: %s: %S" % [path, e.class, e.to_s])
+            @cache[path][:data] = default
+          end
+        else
+          @cache[path][:data] = File.read(path)
+        end
+      end
+
+      if block_given? && !expected_type.nil?
+        unless @cache[path][:data].is_a?(expected_type)
+          Hiera.debug("Data retrieved from %s is not a %s, setting defaults" % [path, expected_type])
+          @cache[path][:data] = default
+        end
+      end
+
+      @cache[path][:data]
+    end
+
+    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/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_lookup.rb b/lib/hiera/recursive_lookup.rb
new file mode 100644
index 0000000..5dfc53b
--- /dev/null
+++ b/lib/hiera/recursive_lookup.rb
@@ -0,0 +1,31 @@
+# Allow for safe recursive lookup of values during variable interpolation.
+#
+# @api private
+class Hiera::RecursiveLookup
+  def initialize(scope, extra_data)
+    @seen = []
+    @scope = scope
+    @extra_data = extra_data
+  end
+
+  def lookup(name, &block)
+    if @seen.include?(name)
+      raise Exception, "Interpolation loop detected in [#{@seen.join(', ')}]"
+    end
+    @seen.push(name)
+    ret = yield(current_value)
+    @seen.pop
+    ret
+  end
+
+  def current_value
+    name = @seen.last
+
+    scope_val = @scope[name]
+    if scope_val.nil? || scope_val == :undefined
+      @extra_data[name]
+    else
+      scope_val
+    end
+  end
+end
diff --git a/metadata.yml b/metadata.yml
new file mode 100644
index 0000000..fb04e08
--- /dev/null
+++ b/metadata.yml
@@ -0,0 +1,114 @@
+--- !ruby/object:Gem::Specification 
+name: hiera
+version: !ruby/object:Gem::Version 
+  hash: 29
+  prerelease: 
+  segments: 
+  - 1
+  - 2
+  - 1
+  version: 1.2.1
+platform: ruby
+authors: 
+- Puppet Labs
+autorequire: 
+bindir: bin
+cert_chain: []
+
+date: 2013-04-18 00:00:00 Z
+dependencies: 
+- !ruby/object:Gem::Dependency 
+  name: json_pure
+  prerelease: false
+  requirement: &id001 !ruby/object:Gem::Requirement 
+    none: false
+    requirements: 
+    - - ">="
+      - !ruby/object:Gem::Version 
+        hash: 3
+        segments: 
+        - 0
+        version: "0"
+  type: :runtime
+  version_requirements: *id001
+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/recursive_lookup.rb
+- lib/hiera/console_logger.rb
+- lib/hiera/filecache.rb
+- lib/hiera/fallback_logger.rb
+- lib/hiera/util.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/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 
+      hash: 3
+      segments: 
+      - 0
+      version: "0"
+required_rubygems_version: !ruby/object:Gem::Requirement 
+  none: false
+  requirements: 
+  - - ">="
+    - !ruby/object:Gem::Version 
+      hash: 3
+      segments: 
+      - 0
+      version: "0"
+requirements: []
+
+rubyforge_project: 
+rubygems_version: 1.8.24
+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/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..57497de 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).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).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).with("/nonexisting/one.json", Hash, {}).returns({"key" => "answer"})
+          @cache.expects(:read).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).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..72048c2 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).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).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).with("/nonexisting/one.yaml", Hash, {}).returns({"key"=>"answer"})
+          @cache.expects(:read).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).with("/nonexisting/one.yaml", Hash, {}).returns({})
+          @cache.expects(:read).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).with("/nonexisting/one.yaml", Hash, {}).returns({"key"=>{"a"=>"answer"}})
+          @cache.expects(:read).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).with("/nonexisting/one.yaml", Hash, {}).returns({"key"=>["a", "answer"]})
+          @cache.expects(:read).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).with("/nonexisting/one.yaml", Hash, {}).returns({"key"=>{"a"=>"answer"}})
+          @cache.expects(:read).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).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).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..d5147c2 100644
--- a/spec/unit/backend_spec.rb
+++ b/spec/unit/backend_spec.rb
@@ -4,13 +4,13 @@ require 'hiera/util'
 class Hiera
   describe Backend do
     describe "#datadir" do
-      it "should use the backend configured dir" do
+      it "interpolates any values in the configured value" do
         Config.load({:rspec => {:datadir => "/tmp"}})
         Backend.expects(:parse_string).with("/tmp", {})
         Backend.datadir(:rspec, {})
       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, {})
@@ -18,13 +18,13 @@ class Hiera
     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 +32,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 +41,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 +49,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 +57,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 +68,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 +86,153 @@ 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"
+      it "replaces interpolations with data looked up in the scope" do
+        input = "replace %{part1} and %{part2}"
+        scope = {"part1" => "value of part1", "part2" => "value of part2"}
+
+        Backend.parse_string(input, scope).should == "replace value of part1 and value of part2"
       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
+      it "interprets nil in scope as a non-value" do
+        input = "test_%{rspec}_test"
+        Backend.parse_string(input, {"rspec" => nil}).should == "test__test"
+      end
+
+      it "interprets false in scope as a real value" do
+        input = "test_%{rspec}_test"
+        Backend.parse_string(input, {"rspec" => false}).should == "test_false_test"
+      end
+
+      it "interprets false in extra_data as a real value" do
+        input = "test_%{rspec}_test"
+        Backend.parse_string(input, {}, {"rspec" => false}).should == "test_false_test"
+      end
+
+      it "interprets nil in extra_data as a non-value" do
+        input = "test_%{rspec}_test"
+        Backend.parse_string(input, {}, {"rspec" => nil}).should == "test__test"
+      end
+
+      it "interprets :undefined in scope as a non-value" do
         input = "test_%{rspec}_test"
         Backend.parse_string(input, {"rspec" => :undefined}).should == "test__test"
       end
 
-      it "should match data from extra_data when scope contains :undefined" do
+      it "uses the value from extra_data when scope is :undefined" do
         input = "test_%{rspec}_test"
-        Backend.parse_string(input, {"rspec" => :undefined}, {"rspec" => "test"}).should == "test_test_test"
+        Backend.parse_string(input, {"rspec" => :undefined}, { "rspec" => "extra" }).should == "test_extra_test"
+      end
+
+      it "looks up the interpolated value exactly as it appears in the input" do
+        input = "test_%{::rspec::data}_test"
+        Backend.parse_string(input, {"::rspec::data" => "value"}).should == "test_value_test"
+      end
+
+      it "does not remove any surrounding whitespace when parsing the key to lookup" do
+        input = "test_%{\trspec::data }_test"
+        Backend.parse_string(input, {"\trspec::data " => "value"}).should == "test_value_test"
       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 "does not try removing leading :: when a full lookup fails (#17434)" do
+        input = "test_%{::rspec::data}_test"
+        Backend.parse_string(input, {"rspec::data" => "value"}).should == "test__test"
+      end
+
+      it "does not try removing leading sections separated by :: when a full lookup fails (#17434)" do
+        input = "test_%{::rspec::data}_test"
+        Backend.parse_string(input, {"data" => "value"}).should == "test__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 Exception, "Interpolation loop detected in [first, second]"
       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 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 "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
     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 +243,7 @@ class Hiera
         Hiera.stubs(:warn)
       end
 
-      it "should cache backends" do
+      it "caches loaded backends" do
         Hiera.expects(:debug).with(regexp_matches(/Hiera YAML backend starting/)).once
 
         Config.load({:yaml => {:datadir => "/tmp"}})
@@ -201,7 +253,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 +262,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 +275,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 +287,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 +298,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 +310,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 +321,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 +332,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 +345,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 +355,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 +364,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..2a7dd72
--- /dev/null
+++ b/spec/unit/filecache_spec.rb
@@ -0,0 +1,63 @@
+require 'spec_helper'
+
+class Hiera
+  describe Filecache do
+    before do
+      File.stubs(:exist?).returns(true)
+      @cache = Filecache.new
+    end
+
+    describe "#read" do
+      it "should cache and read data" do
+        File.expects(:read).with("/nonexisting").returns("text")
+        @cache.expects(:path_metadata).returns(File.stat(__FILE__)).once
+        @cache.expects(:stale?).once.returns(false)
+
+        @cache.read("/nonexisting").should == "text"
+        @cache.read("/nonexisting").should == "text"
+      end
+
+      it "should support validating return types and setting defaults" do
+        File.expects(:read).with("/nonexisting").returns('{"rspec":1}')
+
+        @cache.expects(:path_metadata).returns(File.stat(__FILE__))
+
+        Hiera.expects(:debug).with(regexp_matches(/is not a Hash, setting defaults/))
+
+        # return bogus data on purpose, triggers setting defaults
+        data = @cache.read("/nonexisting", Hash, {"rspec" => 1}) do |data|
+          nil
+        end
+
+        data.should == {"rspec" => 1}
+      end
+    end
+
+    describe "#stale?" do
+      it "should return false when the file has not changed" do
+        stat = File.stat(__FILE__)
+
+        @cache.stubs(:path_metadata).returns(stat)
+        @cache.stale?("/nonexisting").should == true
+        @cache.stale?("/nonexisting").should == false
+      end
+
+      it "should update and return true when the file changed" do
+        @cache.expects(:path_metadata).returns({:inode => 1, :mtime => Time.now, :size => 1})
+        @cache.stale?("/nonexisting").should == true
+        @cache.expects(:path_metadata).returns({:inode => 2, :mtime => Time.now, :size => 1})
+        @cache.stale?("/nonexisting").should == true
+      end
+    end
+
+    describe "#path_metadata" do
+      it "should return the right data" do
+        stat = File.stat(__FILE__)
+
+        File.expects(:stat).with("/nonexisting").returns(stat)
+
+        @cache.path_metadata("/nonexisting").should == {:inode => stat.ino, :mtime => stat.mtime, :size => stat.size}
+      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

-- 
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