Skip to content

Commit

Permalink
Merge pull request #859 from barraq/feature/support-environments
Browse files Browse the repository at this point in the history
Support nanoc environments
  • Loading branch information
denisdefreyne committed Aug 22, 2016
2 parents 43fd291 + 15b0075 commit d529a87
Show file tree
Hide file tree
Showing 15 changed files with 139 additions and 32 deletions.
32 changes: 27 additions & 5 deletions lib/nanoc/base/entities/configuration.rb
Expand Up @@ -33,11 +33,21 @@ class Configuration
string_pattern_type: 'glob',
}.freeze

contract Hash => C::Any
# @return [String, nil] The active environment for the configuration
attr_reader :env_name

# Configuration environments property key
ENVIRONMENTS_CONFIG_KEY = :environments
NANOC_ENV = 'NANOC_ENV'.freeze
NANOC_ENV_DEFAULT = 'default'.freeze

contract C::KeywordArgs[hash: C::Optional[Hash], env_name: C::Maybe[String]] => C::Any
# Creates a new configuration with the given hash.
#
# @param [Hash] hash The actual configuration hash
def initialize(hash = {})
# @param [String, nil] env_name The active environment for this configuration
def initialize(hash: {}, env_name: nil)
@env_name = env_name
@wrapped = hash.__nanoc_symbolize_keys_recursively
end

Expand All @@ -48,7 +58,19 @@ def with_defaults
DEFAULT_DATA_SOURCE_CONFIG.merge(ds)
end

self.class.new(new_wrapped)
self.class.new(hash: new_wrapped)
end

def with_environment
return self unless @wrapped.key?(ENVIRONMENTS_CONFIG_KEY)

# Set active environment
env_name = @env_name || ENV.fetch(NANOC_ENV, NANOC_ENV_DEFAULT)

# Load given environment configuration
env_config = @wrapped[ENVIRONMENTS_CONFIG_KEY].fetch(env_name.to_sym, {})

self.class.new(hash: @wrapped, env_name: env_name).merge(env_config)
end

contract C::None => Hash
Expand Down Expand Up @@ -86,12 +108,12 @@ def []=(key, value)

contract C::Or[Hash, self] => self
def merge(hash)
self.class.new(@wrapped.merge(hash.to_h))
self.class.new(hash: @wrapped.merge(hash.to_h), env_name: @env_name)
end

contract C::Any => self
def without(key)
self.class.new(@wrapped.reject { |k, _v| k == key })
self.class.new(hash: @wrapped.reject { |k, _v| k == key }, env_name: @env_name)
end

contract C::Any => self
Expand Down
2 changes: 1 addition & 1 deletion lib/nanoc/base/repos/checksum_store.rb
Expand Up @@ -6,7 +6,7 @@ module Nanoc::Int
class ChecksumStore < ::Nanoc::Int::Store
# @param [Nanoc::Int::Site] site
def initialize(site: nil)
super('tmp/checksums', 1)
super(Nanoc::Int::Store.tmp_path_for(env_name: (site.config.env_name if site), store_name: 'checksums'), 1)

@site = site

Expand Down
4 changes: 2 additions & 2 deletions lib/nanoc/base/repos/compiled_content_cache.rb
Expand Up @@ -4,8 +4,8 @@ module Nanoc::Int
#
# @api private
class CompiledContentCache < ::Nanoc::Int::Store
def initialize
super('tmp/compiled_content', 2)
def initialize(env_name: nil)
super(Nanoc::Int::Store.tmp_path_for(env_name: env_name, store_name: 'compiled_content'), 2)

@cache = {}
end
Expand Down
6 changes: 3 additions & 3 deletions lib/nanoc/base/repos/config_loader.rb
Expand Up @@ -38,9 +38,9 @@ def new_from_cwd

# Read
apply_parent_config(
Nanoc::Int::Configuration.new(YAML.load_file(filename)),
Nanoc::Int::Configuration.new(hash: YAML.load_file(filename)),
[filename],
).with_defaults
).with_defaults.with_environment
end

# @api private
Expand All @@ -60,7 +60,7 @@ def apply_parent_config(config, processed_paths = [])
end

# Load
parent_config = Nanoc::Int::Configuration.new(YAML.load_file(parent_path))
parent_config = Nanoc::Int::Configuration.new(hash: YAML.load_file(parent_path))
full_parent_config = apply_parent_config(parent_config, processed_paths + [parent_path])
full_parent_config.merge(config.without(:parent_config_file))
end
Expand Down
4 changes: 2 additions & 2 deletions lib/nanoc/base/repos/dependency_store.rb
Expand Up @@ -5,8 +5,8 @@ class DependencyStore < ::Nanoc::Int::Store
attr_accessor :objects

# @param [Array<Nanoc::Int::Item, Nanoc::Int::Layout>] objects
def initialize(objects)
super('tmp/dependencies', 4)
def initialize(objects, env_name: nil)
super(Nanoc::Int::Store.tmp_path_for(env_name: env_name, store_name: 'dependencies'), 4)

@objects = objects
@graph = Nanoc::Int::DirectedGraph.new([nil] + @objects)
Expand Down
4 changes: 2 additions & 2 deletions lib/nanoc/base/repos/rule_memory_store.rb
Expand Up @@ -4,8 +4,8 @@ module Nanoc::Int
#
# @api private
class RuleMemoryStore < ::Nanoc::Int::Store
def initialize
super('tmp/rule_memory', 1)
def initialize(env_name: nil)
super(Nanoc::Int::Store.tmp_path_for(env_name: env_name, store_name: 'rule_memory'), 1)

@rule_memories = {}
end
Expand Down
2 changes: 1 addition & 1 deletion lib/nanoc/base/repos/site_loader.rb
Expand Up @@ -5,7 +5,7 @@ def new_empty
end

def new_with_config(hash)
site_from_config(Nanoc::Int::Configuration.new(hash).with_defaults)
site_from_config(Nanoc::Int::Configuration.new(hash: hash).with_defaults)
end

def new_from_cwd
Expand Down
9 changes: 9 additions & 0 deletions lib/nanoc/base/repos/store.rb
Expand Up @@ -12,6 +12,8 @@ module Nanoc::Int
#
# @api private
class Store
include Nanoc::Int::ContractsSupport

# @return [String] The name of the file where data will be loaded from and
# stored to.
attr_reader :filename
Expand All @@ -34,6 +36,13 @@ def initialize(filename, version)
@version = version
end

# Logic for building tmp path from active environment and store name
# @api private
contract C::KeywordArgs[env_name: C::Maybe[String], store_name: String] => String
def self.tmp_path_for(env_name:, store_name:)
File.join('tmp', env_name.to_s, store_name)
end

# @group Loading and storing data

# @return The data that should be written to the disk
Expand Down
6 changes: 3 additions & 3 deletions lib/nanoc/base/services/compiler_loader.rb
Expand Up @@ -2,10 +2,10 @@ module Nanoc::Int
# @api private
class CompilerLoader
def load(site)
rule_memory_store = Nanoc::Int::RuleMemoryStore.new
rule_memory_store = Nanoc::Int::RuleMemoryStore.new(env_name: site.config.env_name)

dependency_store =
Nanoc::Int::DependencyStore.new(site.items.to_a + site.layouts.to_a)
Nanoc::Int::DependencyStore.new(site.items.to_a + site.layouts.to_a, env_name: site.config.env_name)

checksum_store =
Nanoc::Int::ChecksumStore.new(site: site)
Expand All @@ -25,7 +25,7 @@ def load(site)
)

params = {
compiled_content_cache: Nanoc::Int::CompiledContentCache.new,
compiled_content_cache: Nanoc::Int::CompiledContentCache.new(env_name: site.config.env_name),
checksum_store: checksum_store,
rule_memory_store: rule_memory_store,
dependency_store: dependency_store,
Expand Down
4 changes: 4 additions & 0 deletions lib/nanoc/cli/commands/nanoc.rb
Expand Up @@ -10,6 +10,10 @@
Nanoc::CLI.debug = true
end

opt :e, :env, 'set environment', argument: :required do |value|
ENV.store('NANOC_ENV', value)
end

opt :h, :help, 'show the help message and quit' do |_value, cmd|
puts cmd.help
exit 0
Expand Down
8 changes: 4 additions & 4 deletions spec/nanoc/base/checksummer_spec.rb
Expand Up @@ -158,7 +158,7 @@
end

context 'Nanoc::Int::Configuration' do
let(:obj) { Nanoc::Int::Configuration.new({ 'foo' => 'bar' }) }
let(:obj) { Nanoc::Int::Configuration.new(hash: { 'foo' => 'bar' }) }
it { is_expected.to eql('Nanoc::Int::Configuration<Symbol<foo>=String<bar>,>') }
end

Expand Down Expand Up @@ -241,15 +241,15 @@

context 'Nanoc::ConfigView' do
let(:obj) { Nanoc::ConfigView.new(config, nil) }
let(:config) { Nanoc::Int::Configuration.new({ 'foo' => 'bar' }) }
let(:config) { Nanoc::Int::Configuration.new(hash: { 'foo' => 'bar' }) }

it { is_expected.to eql('Nanoc::ConfigView<Nanoc::Int::Configuration<Symbol<foo>=String<bar>,>>') }
end

context 'Nanoc::ItemCollectionWithRepsView' do
let(:obj) { Nanoc::ItemCollectionWithRepsView.new(wrapped, nil) }

let(:config) { Nanoc::Int::Configuration.new({ 'foo' => 'bar' }) }
let(:config) { Nanoc::Int::Configuration.new(hash: { 'foo' => 'bar' }) }

let(:wrapped) do
Nanoc::Int::IdentifiableCollection.new(config).tap do |arr|
Expand All @@ -264,7 +264,7 @@
context 'Nanoc::ItemCollectionWithoutRepsView' do
let(:obj) { Nanoc::ItemCollectionWithoutRepsView.new(wrapped, nil) }

let(:config) { Nanoc::Int::Configuration.new({ 'foo' => 'bar' }) }
let(:config) { Nanoc::Int::Configuration.new(hash: { 'foo' => 'bar' }) }

let(:wrapped) do
Nanoc::Int::IdentifiableCollection.new(config).tap do |arr|
Expand Down
36 changes: 33 additions & 3 deletions spec/nanoc/base/entities/configuration_spec.rb
@@ -1,10 +1,9 @@
describe Nanoc::Int::Configuration do
let(:configuration) { described_class.new(hash) }

let(:hash) { { foo: 'bar' } }
let(:config) { described_class.new(hash: hash) }

describe '#key?' do
subject { configuration.key?(key) }
subject { config.key?(key) }

context 'non-existent key' do
let(:key) { :donkey }
Expand All @@ -16,4 +15,35 @@
it { is_expected.to be }
end
end

context 'with environments defined' do
let(:hash) { { foo: 'bar', environments: { test: { foo: 'test-bar' }, default: { foo: 'default-bar' } } } }
let(:config) { described_class.new(hash: hash, env_name: env_name).with_environment }

subject { config }

context 'with existing environment' do
let(:env_name) { 'test' }

it 'inherits options from given environment' do
expect(subject[:foo]).to eq('test-bar')
end
end

context 'with unknown environment' do
let(:env_name) { 'wtf' }

it 'does not inherits options from any environment' do
expect(subject[:foo]).to eq('bar')
end
end

context 'without given environment' do
let(:env_name) { nil }

it 'inherits options from default environment' do
expect(subject[:foo]).to eq('default-bar')
end
end
end
end
30 changes: 25 additions & 5 deletions spec/nanoc/base/repos/config_loader_spec.rb
Expand Up @@ -52,6 +52,29 @@
expect(subject[:parent_config_file]).to be_nil
end
end

context 'config file present and environment defined' do
before do
File.write('nanoc.yaml', YAML.dump({ foo: 'bar', tofoo: 'bar', environments: { test: { foo: 'test-bar' }, default: { foo: 'default-bar' } } }))
end

it 'returns the configuration' do
expect(subject).to be_a(Nanoc::Int::Configuration)
end

it 'has option defined not within environments' do
expect(subject[:tofoo]).to eq('bar')
end

it 'has the test environment custom option' do
allow(ENV).to receive(:fetch).with(Nanoc::Int::Configuration::NANOC_ENV, Nanoc::Int::Configuration::NANOC_ENV_DEFAULT).and_return('test')
expect(subject[:foo]).to eq('test-bar')
end

it 'has the default environment custom option' do
expect(subject[:foo]).to eq('default-bar')
end
end
end

describe '.cwd_is_nanoc_site? + .config_filename_for_cwd' do
Expand Down Expand Up @@ -88,7 +111,7 @@
describe '#apply_parent_config' do
subject { loader.apply_parent_config(config, processed_paths) }

let(:config) { Nanoc::Int::Configuration.new(foo: 'bar') }
let(:config) { Nanoc::Int::Configuration.new(hash: { foo: 'bar' }) }

let(:processed_paths) { ['nanoc.yaml'] }

Expand All @@ -100,10 +123,7 @@

context 'parent config file is set' do
let(:config) do
Nanoc::Int::Configuration.new(
parent_config_file: 'foo.yaml',
foo: 'bar',
)
Nanoc::Int::Configuration.new(hash: { parent_config_file: 'foo.yaml', foo: 'bar' })
end

context 'parent config file is not present' do
Expand Down
2 changes: 1 addition & 1 deletion spec/nanoc/base/views/config_view_spec.rb
@@ -1,6 +1,6 @@
describe Nanoc::ConfigView do
let(:config) do
Nanoc::Int::Configuration.new(hash)
Nanoc::Int::Configuration.new(hash: hash)
end

let(:hash) { { amount: 9000, animal: 'donkey' } }
Expand Down
22 changes: 22 additions & 0 deletions test/base/test_store.rb
Expand Up @@ -32,4 +32,26 @@ def test_delete_and_reload_on_error
store.load
assert_equal(nil, store.data)
end

def test_tmp_path_with_nil_env
tmp_path_for_checksum = Nanoc::Int::Store.tmp_path_for(env_name: nil, store_name: 'checksum')
tmp_path_for_rule_memory = Nanoc::Int::Store.tmp_path_for(env_name: nil, store_name: 'rule_memory')
tmp_path_for_dependencies = Nanoc::Int::Store.tmp_path_for(env_name: nil, store_name: 'dependencies')
tmp_path_for_compiled_content = Nanoc::Int::Store.tmp_path_for(env_name: nil, store_name: 'compiled_content')
assert_equal('tmp/checksum', tmp_path_for_checksum)
assert_equal('tmp/rule_memory', tmp_path_for_rule_memory)
assert_equal('tmp/dependencies', tmp_path_for_dependencies)
assert_equal('tmp/compiled_content', tmp_path_for_compiled_content)
end

def test_tmp_path_with_test_env
tmp_path_for_checksum = Nanoc::Int::Store.tmp_path_for(env_name: 'test', store_name: 'checksum')
tmp_path_for_rule_memory = Nanoc::Int::Store.tmp_path_for(env_name: 'test', store_name: 'rule_memory')
tmp_path_for_dependencies = Nanoc::Int::Store.tmp_path_for(env_name: 'test', store_name: 'dependencies')
tmp_path_for_compiled_content = Nanoc::Int::Store.tmp_path_for(env_name: 'test', store_name: 'compiled_content')
assert_equal('tmp/test/checksum', tmp_path_for_checksum)
assert_equal('tmp/test/rule_memory', tmp_path_for_rule_memory)
assert_equal('tmp/test/dependencies', tmp_path_for_dependencies)
assert_equal('tmp/test/compiled_content', tmp_path_for_compiled_content)
end
end

0 comments on commit d529a87

Please sign in to comment.