figaro 0.7.0 → 1.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. checksums.yaml +6 -14
  2. data/.gitignore +1 -1
  3. data/.travis.yml +23 -18
  4. data/CHANGELOG.md +98 -0
  5. data/CONTRIBUTING.md +48 -0
  6. data/Gemfile +4 -6
  7. data/{LICENSE → LICENSE.txt} +0 -0
  8. data/README.md +184 -61
  9. data/Rakefile +1 -12
  10. data/bin/figaro +5 -0
  11. data/doc/title.png +0 -0
  12. data/figaro.gemspec +9 -5
  13. data/gemfiles/rails30.gemfile +3 -4
  14. data/gemfiles/rails31.gemfile +3 -4
  15. data/gemfiles/rails32.gemfile +3 -4
  16. data/gemfiles/rails40.gemfile +3 -7
  17. data/gemfiles/rails41.gemfile +11 -0
  18. data/lib/figaro.rb +16 -29
  19. data/lib/figaro/application.rb +91 -0
  20. data/lib/figaro/cli.rb +24 -0
  21. data/lib/figaro/cli/heroku_set.rb +29 -0
  22. data/lib/figaro/cli/task.rb +27 -0
  23. data/lib/figaro/env.rb +36 -7
  24. data/lib/figaro/error.rb +12 -0
  25. data/lib/figaro/rails.rb +9 -0
  26. data/lib/figaro/rails/application.rb +21 -0
  27. data/lib/figaro/rails/railtie.rb +9 -0
  28. data/lib/figaro/{tasks.rake → rails/tasks.rake} +0 -0
  29. data/lib/generators/figaro/install/install_generator.rb +1 -1
  30. data/lib/generators/figaro/install/templates/application.yml +10 -6
  31. data/spec/figaro/application_spec.rb +258 -0
  32. data/spec/figaro/cli/heroku_set_spec.rb +62 -0
  33. data/spec/figaro/env_spec.rb +167 -35
  34. data/spec/figaro/rails/application_spec.rb +43 -0
  35. data/spec/figaro_spec.rb +74 -36
  36. data/spec/rails_spec.rb +66 -0
  37. data/spec/spec_helper.rb +6 -3
  38. data/spec/support/aruba.rb +19 -0
  39. data/spec/support/bin/heroku +5 -0
  40. data/spec/support/command_helpers.rb +17 -0
  41. data/spec/support/command_interceptor.rb +33 -0
  42. data/spec/support/random.rb +3 -0
  43. data/spec/support/reset.rb +13 -0
  44. metadata +88 -44
  45. data/features/rails.feature +0 -97
  46. data/features/step_definitions/bundler_steps.rb +0 -7
  47. data/features/step_definitions/common_steps.rb +0 -19
  48. data/features/step_definitions/rails_steps.rb +0 -12
  49. data/features/support/aruba.rb +0 -12
  50. data/features/support/env.rb +0 -8
  51. data/lib/figaro/railtie.rb +0 -16
  52. data/lib/figaro/tasks.rb +0 -28
  53. data/spec/figaro/tasks_spec.rb +0 -71
  54. data/spec/support/rake.rb +0 -11
data/Rakefile CHANGED
@@ -1,17 +1,6 @@
1
1
  require "bundler/gem_tasks"
2
2
  require "rspec/core/rake_task"
3
- require "cucumber/rake/task"
4
3
 
5
4
  RSpec::Core::RakeTask.new(:spec)
6
- Cucumber::Rake::Task.new(:cucumber)
7
5
 
8
- task :default => [:spec, :cucumber]
9
-
10
- if ENV["COVERAGE"]
11
- Rake::Task[:default].enhance do
12
- require "simplecov"
13
- require "coveralls"
14
-
15
- Coveralls::SimpleCov::Formatter.new.format(SimpleCov.result)
16
- end
17
- end
6
+ task default: :spec
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "figaro/cli"
4
+
5
+ Figaro::CLI.start
Binary file
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |gem|
4
4
  gem.name = "figaro"
5
- gem.version = "0.7.0"
5
+ gem.version = "1.0.0.rc1"
6
6
 
7
7
  gem.author = "Steve Richert"
8
8
  gem.email = "steve.richert@gmail.com"
@@ -11,10 +11,14 @@ Gem::Specification.new do |gem|
11
11
  gem.homepage = "https://github.com/laserlemon/figaro"
12
12
  gem.license = "MIT"
13
13
 
14
- gem.add_dependency "bundler", "~> 1.0"
15
14
  gem.add_dependency "rails", ">= 3", "< 5"
15
+ gem.add_dependency "thor", "~> 0.14"
16
16
 
17
- gem.files = `git ls-files`.split($\)
18
- gem.test_files = gem.files.grep(/^(features|spec)/)
19
- gem.require_paths = ["lib"]
17
+ gem.add_development_dependency "bundler", "~> 1.5"
18
+ gem.add_development_dependency "rake", "~> 10.1"
19
+
20
+ gem.files = `git ls-files`.split($\)
21
+ gem.test_files = gem.files.grep(/^spec/)
22
+
23
+ gem.executables << "figaro"
20
24
  end
@@ -1,12 +1,11 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gemspec :path => "../"
3
+ gemspec path: "../"
4
4
 
5
5
  gem "rails", "~> 3.0.2"
6
6
 
7
7
  group :test do
8
8
  gem "aruba", "~> 0.5"
9
- gem "cucumber", "~> 1.3"
10
- gem "rake", "~> 10.0"
11
- gem "rspec", "~> 2.13"
9
+ gem "rspec", "~> 2.14"
10
+ gem "sqlite3", "~> 1.3"
12
11
  end
@@ -1,12 +1,11 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gemspec :path => "../"
3
+ gemspec path: "../"
4
4
 
5
5
  gem "rails", "~> 3.1.0"
6
6
 
7
7
  group :test do
8
8
  gem "aruba", "~> 0.5"
9
- gem "cucumber", "~> 1.3"
10
- gem "rake", "~> 10.0"
11
- gem "rspec", "~> 2.13"
9
+ gem "rspec", "~> 2.14"
10
+ gem "sqlite3", "~> 1.3"
12
11
  end
@@ -1,12 +1,11 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gemspec :path => "../"
3
+ gemspec path: "../"
4
4
 
5
5
  gem "rails", "~> 3.2.0"
6
6
 
7
7
  group :test do
8
8
  gem "aruba", "~> 0.5"
9
- gem "cucumber", "~> 1.3"
10
- gem "rake", "~> 10.0"
11
- gem "rspec", "~> 2.13"
9
+ gem "rspec", "~> 2.14"
10
+ gem "sqlite3", "~> 1.3"
12
11
  end
@@ -1,15 +1,11 @@
1
1
  source "https://rubygems.org"
2
2
 
3
- gemspec :path => "../"
3
+ gemspec path: "../"
4
4
 
5
5
  gem "rails", "~> 4.0.0"
6
6
 
7
7
  group :test do
8
8
  gem "aruba", "~> 0.5"
9
- gem "coveralls", "~> 0.6", :require => false
10
- gem "cucumber", "~> 1.3"
11
- gem "json", "~> 1.7.7"
12
- gem "rake", "~> 10.0"
13
- gem "rspec", "~> 2.13"
14
- gem "simplecov", "~> 0.7", :require => false
9
+ gem "rspec", "~> 2.14"
10
+ gem "sqlite3", "~> 1.3"
15
11
  end
@@ -0,0 +1,11 @@
1
+ source "https://rubygems.org"
2
+
3
+ gemspec path: "../"
4
+
5
+ gem "rails", "~> 4.1.0"
6
+
7
+ group :test do
8
+ gem "aruba", "~> 0.5"
9
+ gem "rspec", "~> 2.14"
10
+ gem "sqlite3", "~> 1.3"
11
+ end
@@ -1,45 +1,32 @@
1
- require "shellwords"
1
+ require "figaro/error"
2
2
  require "figaro/env"
3
- require "figaro/railtie"
4
- require "figaro/tasks"
3
+ require "figaro/application"
5
4
 
6
5
  module Figaro
7
6
  extend self
8
7
 
9
- def vars(custom_environment = nil)
10
- env(custom_environment).map { |key, value|
11
- "#{key}=#{Shellwords.escape(value)}"
12
- }.sort.join(" ")
13
- end
14
-
15
- def env(custom_environment = nil)
16
- environment = (custom_environment || self.environment).to_s
17
- Figaro::Env.from(stringify(flatten(raw).merge(raw.fetch(environment, {}))))
18
- end
8
+ attr_writer :adapter, :application
19
9
 
20
- def raw
21
- @raw ||= yaml && YAML.load(yaml) || {}
10
+ def env
11
+ Figaro::ENV
22
12
  end
23
13
 
24
- def yaml
25
- @yaml ||= File.exist?(path) ? ERB.new(File.read(path)).result : nil
14
+ def adapter
15
+ @adapter ||= Figaro::Application
26
16
  end
27
17
 
28
- def path
29
- @path ||= Rails.root.join("config", "application.yml")
18
+ def application
19
+ @application ||= adapter.new
30
20
  end
31
21
 
32
- def environment
33
- Rails.env
22
+ def load
23
+ application.load
34
24
  end
35
25
 
36
- private
37
-
38
- def flatten(hash)
39
- hash.reject { |_, v| Hash === v }
40
- end
41
-
42
- def stringify(hash)
43
- hash.inject({}) { |h, (k, v)| h[k.to_s] = v.nil? ? nil : v.to_s; h }
26
+ def require(*keys)
27
+ missing_keys = keys.flatten - ::ENV.keys
28
+ raise MissingKeys.new(missing_keys) if missing_keys.any?
44
29
  end
45
30
  end
31
+
32
+ require "figaro/rails"
@@ -0,0 +1,91 @@
1
+ require "erb"
2
+ require "yaml"
3
+
4
+ module Figaro
5
+ class Application
6
+ FIGARO_ENV_PREFIX = "_FIGARO_"
7
+
8
+ include Enumerable
9
+
10
+ def initialize(options = {})
11
+ @options = options.inject({}) { |m, (k, v)| m[k.to_sym] = v; m }
12
+ end
13
+
14
+ def path
15
+ @options.fetch(:path) { default_path }.to_s
16
+ end
17
+
18
+ def path=(path)
19
+ @options[:path] = path
20
+ end
21
+
22
+ def environment
23
+ environment = @options.fetch(:environment) { default_environment }
24
+ environment.nil? ? nil : environment.to_s
25
+ end
26
+
27
+ def environment=(environment)
28
+ @options[:environment] = environment
29
+ end
30
+
31
+ def configuration
32
+ global_configuration.merge(environment_configuration)
33
+ end
34
+
35
+ def load
36
+ each do |key, value|
37
+ skip?(key) ? key_skipped!(key) : set(key, value)
38
+ end
39
+ end
40
+
41
+ def each(&block)
42
+ configuration.each(&block)
43
+ end
44
+
45
+ private
46
+
47
+ def default_path
48
+ raise NotImplementedError
49
+ end
50
+
51
+ def default_environment
52
+ nil
53
+ end
54
+
55
+ def raw_configuration
56
+ (@parsed ||= Hash.new { |hash, path| hash[path] = parse(path) })[path]
57
+ end
58
+
59
+ def parse(path)
60
+ File.exist?(path) && YAML.load(ERB.new(File.read(path)).result) || {}
61
+ end
62
+
63
+ def global_configuration
64
+ raw_configuration.reject { |_, value| value.is_a?(Hash) }
65
+ end
66
+
67
+ def environment_configuration
68
+ raw_configuration.fetch(environment) { {} }
69
+ end
70
+
71
+ def set(key, value)
72
+ non_string_configuration!(key) unless key.is_a?(String)
73
+ non_string_configuration!(value) unless value.is_a?(String) || value.nil?
74
+
75
+ ::ENV[key.to_s] = value.nil? ? nil : value.to_s
76
+ ::ENV[FIGARO_ENV_PREFIX + key.to_s] = value.nil? ? nil: value.to_s
77
+ end
78
+
79
+ def skip?(key)
80
+ ::ENV.key?(key.to_s) && !::ENV.key?(FIGARO_ENV_PREFIX + key.to_s)
81
+ end
82
+
83
+ def non_string_configuration!(value)
84
+ warn "WARNING: Use strings for Figaro configuration. #{value.inspect} was converted to #{value.to_s.inspect}."
85
+ end
86
+
87
+ def key_skipped!(key)
88
+ warn "WARNING: Skipping key #{key.inspect}. Already set in ENV."
89
+ end
90
+ end
91
+ end
@@ -0,0 +1,24 @@
1
+ require "thor"
2
+
3
+ require "figaro/cli/heroku_set"
4
+
5
+ module Figaro
6
+ class CLI < Thor
7
+ desc "heroku:set", "Send Figaro configuration to Heroku"
8
+
9
+ method_option "app",
10
+ aliases: ["-a"],
11
+ desc: "Specify a Heroku app"
12
+ method_option "environment",
13
+ aliases: ["-e"],
14
+ desc: "Specify an application environment"
15
+ method_option "path",
16
+ aliases: ["-p"],
17
+ default: "config/application.yml",
18
+ desc: "Specify a configuration file path"
19
+
20
+ define_method "heroku:set" do
21
+ HerokuSet.run(options)
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,29 @@
1
+ require "figaro/cli/task"
2
+
3
+ module Figaro
4
+ class CLI < Thor
5
+ class HerokuSet < Task
6
+ def run
7
+ system(configuration, command)
8
+ end
9
+
10
+ private
11
+
12
+ def command
13
+ "heroku config:set #{vars} #{for_app}"
14
+ end
15
+
16
+ def for_app
17
+ options[:app] ? "--app=#{options[:app]}" : nil
18
+ end
19
+
20
+ def vars
21
+ configuration.keys.map { |k| var(k) }.join(" ")
22
+ end
23
+
24
+ def var(key)
25
+ Gem.win_platform? ? %(#{key}="%#{key}%") : %(#{key}="$#{key}")
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,27 @@
1
+ require "figaro/application"
2
+
3
+ module Figaro
4
+ class CLI < Thor
5
+ class Task
6
+ attr_reader :options
7
+
8
+ def self.run(options = {})
9
+ new(options).run
10
+ end
11
+
12
+ def initialize(options = {})
13
+ @options = options
14
+ end
15
+
16
+ private
17
+
18
+ def configuration
19
+ application.configuration
20
+ end
21
+
22
+ def application
23
+ @application ||= Figaro::Application.new(options)
24
+ end
25
+ end
26
+ end
27
+ end
@@ -1,16 +1,45 @@
1
1
  module Figaro
2
- class Env < Hash
3
- def self.from(hash)
4
- new.replace(hash)
2
+ module ENV
3
+ extend self
4
+
5
+ def respond_to?(method, *)
6
+ key, punctuation = extract_key_from_method(method)
7
+
8
+ case punctuation
9
+ when "!" then has_key?(key) || super
10
+ when "?", nil then true
11
+ else super
12
+ end
5
13
  end
6
14
 
15
+ private
16
+
7
17
  def method_missing(method, *)
8
- pair = ENV.detect { |k, _| k.upcase == method.to_s.upcase }
9
- pair ? pair[1] : super
18
+ key, punctuation = extract_key_from_method(method)
19
+
20
+ case punctuation
21
+ when "!" then send(key) || missing_key!(key)
22
+ when "?" then !!send(key)
23
+ when nil then get_value(key)
24
+ else super
25
+ end
10
26
  end
11
27
 
12
- def respond_to?(method, *)
13
- ENV.keys.any? { |k| k.upcase == method.to_s.upcase } || super
28
+ def extract_key_from_method(method)
29
+ method.to_s.downcase.match(/^(.+?)([!?=])?$/).captures
30
+ end
31
+
32
+ def has_key?(key)
33
+ ::ENV.any? { |k, _| k.downcase == key }
34
+ end
35
+
36
+ def missing_key!(key)
37
+ raise MissingKey.new("Missing required Figaro configuration key #{key.inspect}.")
38
+ end
39
+
40
+ def get_value(key)
41
+ _, value = ::ENV.detect { |k, _| k.downcase == key }
42
+ value
14
43
  end
15
44
  end
16
45
  end
@@ -0,0 +1,12 @@
1
+ module Figaro
2
+ class Error < StandardError; end
3
+
4
+ class RailsNotInitialized < Error; end
5
+ class MissingKey < Error; end
6
+
7
+ class MissingKeys < Error
8
+ def initialize(keys)
9
+ super("Missing required configuration keys: #{keys.inspect}")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ begin
2
+ require "rails"
3
+ rescue LoadError
4
+ else
5
+ require "figaro/rails/application"
6
+ require "figaro/rails/railtie"
7
+
8
+ Figaro.adapter = Figaro::Rails::Application
9
+ end