tty-config 0.2.0 → 0.3.0

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.
@@ -1,5 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module TTY
2
4
  class Config
3
- VERSION = "0.2.0".freeze
5
+ VERSION = "0.3.0"
4
6
  end
5
7
  end
@@ -0,0 +1,54 @@
1
+ if ENV['COVERAGE'] || ENV['TRAVIS']
2
+ require 'simplecov'
3
+ require 'coveralls'
4
+
5
+ SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[
6
+ SimpleCov::Formatter::HTMLFormatter,
7
+ Coveralls::SimpleCov::Formatter
8
+ ]
9
+
10
+ SimpleCov.start do
11
+ command_name 'spec'
12
+ add_filter 'spec'
13
+ end
14
+ end
15
+
16
+ require "bundler/setup"
17
+ require "tty/config"
18
+
19
+ module TestHelpers
20
+ module Paths
21
+ def gem_root
22
+ File.expand_path(File.join(File.dirname(__FILE__), ".."))
23
+ end
24
+
25
+ def dir_path(*args)
26
+ path = File.join(gem_root, *args)
27
+ FileUtils.mkdir_p(path) unless ::File.exist?(path)
28
+ File.realpath(path)
29
+ end
30
+
31
+ def tmp_path(*args)
32
+ File.join(dir_path('tmp'), *args)
33
+ end
34
+
35
+ def fixtures_path(*args)
36
+ File.join(dir_path('spec/fixtures'), *args)
37
+ end
38
+
39
+ def within_dir(target, &block)
40
+ ::Dir.chdir(target, &block)
41
+ end
42
+ end
43
+ end
44
+
45
+ RSpec.configure do |config|
46
+ config.include(TestHelpers::Paths)
47
+ config.disable_monkey_patching!
48
+ config.after(:example, type: :cli) do
49
+ FileUtils.rm_rf(tmp_path)
50
+ end
51
+ config.expect_with :rspec do |c|
52
+ c.syntax = :expect
53
+ end
54
+ end
@@ -0,0 +1,72 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe TTY::Config, '#alias_setting' do
4
+ it "aliases setting key" do
5
+ config = TTY::Config.new
6
+ config.set :foo, value: :baz
7
+
8
+ config.alias_setting :foo, to: :bar
9
+
10
+ expect(config.fetch(:foo)).to eq(:baz)
11
+ expect(config.fetch(:bar)).to eq(:baz)
12
+ end
13
+
14
+ it "aliases nested key to flat key" do
15
+ config = TTY::Config.new
16
+ config.set(:foo, :bar, :baz) { 12 }
17
+
18
+ config.alias_setting(:foo, :bar, :baz, to: :flat_foo)
19
+
20
+ expect(config.fetch(:foo, :bar, :baz)).to eq(12)
21
+ expect(config.fetch(:flat_foo)).to eq(12)
22
+ end
23
+
24
+ it "aliases nested key as a string to flat key" do
25
+ config = TTY::Config.new
26
+ config.set('foo.bar.baz') { 12 }
27
+
28
+ config.alias_setting(:foo, :bar, :baz, to: :flat_foo)
29
+
30
+ expect(config.fetch(:foo, :bar, :baz)).to eq(12)
31
+ expect(config.fetch(:flat_foo)).to eq(12)
32
+ end
33
+
34
+ it "aliases nested key to nested key" do
35
+ config = TTY::Config.new
36
+ config.set(:foo, :bar, :baz) { 12 }
37
+
38
+ config.alias_setting(:foo, :bar, :baz, to: [:bee, :bop])
39
+
40
+ expect(config.fetch(:foo, :bar, :baz)).to eq(12)
41
+ expect(config.fetch(:bee, :bop)).to eq(12)
42
+ end
43
+
44
+ it "fails to alias to already existing key" do
45
+ config = TTY::Config.new
46
+ config.set(:foo, value: 1)
47
+ config.set(:bar, value: 2)
48
+
49
+ expect {
50
+ config.alias_setting(:foo, to: :bar)
51
+ }.to raise_error(ArgumentError, "Setting already exists with an alias ':bar'")
52
+ end
53
+
54
+ it "fails to alias to already existing key" do
55
+ config = TTY::Config.new
56
+ config.set(:foo, :bar, value: 1)
57
+ config.set(:baz, :woo, value: 2)
58
+
59
+ expect {
60
+ config.alias_setting(:foo, :bar, to: [:baz, :woo])
61
+ }.to raise_error(ArgumentError, "Setting already exists with an alias ':baz, :woo'")
62
+ end
63
+
64
+ it "fails to alias to matching key" do
65
+ config = TTY::Config.new
66
+ config.set(:foo, :bar, value: 1)
67
+
68
+ expect {
69
+ config.alias_setting(:foo, :bar, to: [:foo, :bar])
70
+ }.to raise_error(ArgumentError, "Alias matches setting key")
71
+ end
72
+ end
@@ -0,0 +1,26 @@
1
+ RSpec.describe TTY::Config, '#append' do
2
+ it "returns appended values" do
3
+ config = TTY::Config.new
4
+ values = config.append(:foo, :bar, to: :values)
5
+
6
+ expect(values).to eq([:foo, :bar])
7
+ expect(config.fetch(:values)).to eq([:foo, :bar])
8
+ end
9
+
10
+ it "appends values to already existing key" do
11
+ config = TTY::Config.new
12
+ config.set(:values) { :foo }
13
+ values = config.append(:bar, :baz, to: :values)
14
+
15
+ expect(config.fetch(:values)).to eq([:foo, :bar, :baz])
16
+ expect(values).to eq([:foo, :bar, :baz])
17
+ end
18
+
19
+ it "appends values to nested key" do
20
+ config = TTY::Config.new
21
+ config.set(:foo, :bar) { 1 }
22
+ values = config.append(2,3, to: [:foo, :bar])
23
+ expect(values).to eq([1,2,3])
24
+ expect(config.fetch(:foo, :bar)).to eq([1,2,3])
25
+ end
26
+ end
@@ -0,0 +1,62 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe TTY::Config, '#autoload_env' do
4
+ it "autoloads env variables" do
5
+ allow(ENV).to receive(:[]).with('HOST').and_return('localhost')
6
+ config = TTY::Config.new
7
+ expect(config.fetch(:host)).to eq(nil)
8
+
9
+ config.autoload_env
10
+
11
+ expect(config.fetch(:host)).to eq('localhost')
12
+ end
13
+
14
+ it "autoloads env variables with prefix" do
15
+ allow(ENV).to receive(:[]).with('MYTOOL_HOST').and_return('localhost')
16
+ config = TTY::Config.new
17
+ config.env_prefix = 'mytool'
18
+
19
+ expect(config.fetch(:host)).to eq(nil)
20
+
21
+ config.autoload_env
22
+
23
+ expect(config.fetch(:host)).to eq('localhost')
24
+ end
25
+
26
+ it "prioritises set over env vars" do
27
+ allow(ENV).to receive(:[]).with('HOST').and_return('localhost')
28
+ config = TTY::Config.new
29
+ config.autoload_env
30
+
31
+ config.set(:host, value: 'myhost')
32
+
33
+ expect(config.fetch(:host)).to eq('myhost')
34
+ end
35
+
36
+ it "prioritises env vars over defaults when a keyword" do
37
+ allow(ENV).to receive(:[]).with('PORT').and_return('7727')
38
+ config = TTY::Config.new
39
+
40
+ config.autoload_env
41
+
42
+ expect(config.fetch(:port, default: '3000')).to eq('7727')
43
+ end
44
+
45
+ it "prioritises env vars over defaults when block" do
46
+ allow(ENV).to receive(:[]).with('PORT').and_return('7727')
47
+ config = TTY::Config.new
48
+
49
+ config.autoload_env
50
+
51
+ expect(config.fetch(:port) {'3000' }).to eq('7727')
52
+ end
53
+
54
+ it "prioritises present configuration over env vars" do
55
+ allow(ENV).to receive(:[]).with('PORT').and_return('7727')
56
+ config = TTY::Config.new(port: '3000')
57
+
58
+ config.autoload_env
59
+
60
+ expect(config.fetch(:port)).to eq('3000')
61
+ end
62
+ end
@@ -0,0 +1,22 @@
1
+ RSpec.describe TTY::Config, '#delete' do
2
+ it "deletes the value" do
3
+ config = TTY::Config.new
4
+ config.set(:foo, value: 2)
5
+ expect(config.delete(:foo)).to eq(2)
6
+ expect(config.fetch(:foo)).to eq(nil)
7
+ end
8
+
9
+ it "deletes the value under deeply nested key" do
10
+ config = TTY::Config.new
11
+ config.set(:foo, :bar, :baz) { 2 }
12
+ expect(config.delete(:foo, :bar, :baz).call).to eq(2)
13
+ expect(config.fetch(:foo, :bar, :baz)).to eq(nil)
14
+ end
15
+
16
+ it "deletes innermost key with array value" do
17
+ config = TTY::Config.new
18
+ config.set(:foo, :bar, value: [1,2,3])
19
+ expect(config.delete(:foo, :bar)).to eq([1,2,3])
20
+ expect(config.fetch(:foo, :bar)).to eq(nil)
21
+ end
22
+ end
@@ -0,0 +1,24 @@
1
+ RSpec.describe TTY::Config, '#exist?', type: :cli do
2
+ it "checks if configuration file exists" do
3
+ config = TTY::Config.new
4
+ config.append_path(tmp_path)
5
+
6
+ expect(config.exist?).to eq(false)
7
+
8
+ config.write(tmp_path('investments.yml'))
9
+
10
+ expect(config.exist?).to eq(true)
11
+ end
12
+
13
+ it "checks if a file without extension is present" do
14
+ config = TTY::Config.new
15
+ config.append_path tmp_path
16
+
17
+ expect(config.exist?).to eq(false)
18
+
19
+ config.write(tmp_path('investments'), format: :yml)
20
+
21
+ expect(config.exist?).to eq(true)
22
+ expect(config.persisted?).to eq(true)
23
+ end
24
+ end
@@ -0,0 +1,45 @@
1
+ RSpec.describe TTY::Config do
2
+ it "fetches default if no value" do
3
+ config = TTY::Config.new
4
+ expect(config.fetch(:foo, default: :bar)).to eq(:bar)
5
+ end
6
+
7
+ it "fetches default proc value" do
8
+ config = TTY::Config.new
9
+ expect(config.fetch(:foo, default: -> { :bar })).to eq(:bar)
10
+ end
11
+
12
+ it "fetches deeply nested proc value" do
13
+ config = TTY::Config.new
14
+ expect(config.fetch(:foo, default: -> { -> { :bar }})).to eq(:bar)
15
+ end
16
+
17
+ it "fetches default as block" do
18
+ config = TTY::Config.new
19
+ expect(config.fetch(:foo) { :bar }).to eq(:bar)
20
+ end
21
+
22
+ it "fetches default as block for deeply nested missing key" do
23
+ config = TTY::Config.new
24
+ expect(config.fetch(:foo, :bar, :baz) { 2 }).to eq(2)
25
+ end
26
+
27
+ it "fetches value for deeply nested key" do
28
+ config = TTY::Config.new
29
+ config.set(:foo, :bar, :baz, value: 2)
30
+ expect(config.fetch(:foo, :bar, :baz)).to eq(2)
31
+ end
32
+
33
+ it "fetches value as string delimited by . for deeply nested key" do
34
+ config = TTY::Config.new
35
+ config.set('foo', 'bar', 'baz') { 2 }
36
+ expect(config.fetch("foo.bar.baz")).to eq(2)
37
+ end
38
+
39
+ it "fetches key with indifferent access" do
40
+ config = TTY::Config.new
41
+ config.set(:foo, :bar, :baz, value: 2)
42
+
43
+ expect(config.fetch('foo', :bar, 'baz')).to eq(2)
44
+ end
45
+ end
@@ -0,0 +1,70 @@
1
+ # frozen_string_literal: true
2
+
3
+ RSpec.describe TTY::Config, '#generate' do
4
+ it "generate config content" do
5
+ conf = {
6
+ 'int' => 1,
7
+ 'false' => false,
8
+ 'str' => 'hello',
9
+ 'array' => [1,2,3],
10
+ 'deep_array' => [
11
+ {foo: 1},
12
+ {bar: 2}
13
+ ],
14
+ 'section' => {
15
+ 'value' => 1,
16
+ 'empty' => nil,
17
+ 'array' => [1,2,3]
18
+ },
19
+ "empty" => { },
20
+ 'nil' => nil,
21
+ }
22
+
23
+ content = TTY::Config.generate(conf)
24
+
25
+ expect(content).to eq <<-EOS
26
+ array = 1,2,3
27
+ false = false
28
+ int = 1
29
+ str = hello
30
+
31
+ [deep_array]
32
+ foo = 1
33
+ bar = 2
34
+
35
+ [section]
36
+ value = 1
37
+ array = 1,2,3
38
+ EOS
39
+ end
40
+
41
+ it "generate config content with custom separator" do
42
+ conf = {
43
+ 'str' => 'hello',
44
+ 'array' => [1,2,3],
45
+ 'deep_array' => [
46
+ {foo: 1},
47
+ {bar: 2}
48
+ ],
49
+ 'section' => {
50
+ 'value' => 1,
51
+ 'array' => [1,2,3]
52
+ }
53
+ }
54
+
55
+ content = TTY::Config.generate(conf, separator: ':')
56
+
57
+ expect(content).to eq <<-EOS
58
+ array : 1,2,3
59
+ str : hello
60
+
61
+ [deep_array]
62
+ foo : 1
63
+ bar : 2
64
+
65
+ [section]
66
+ value : 1
67
+ array : 1,2,3
68
+ EOS
69
+ end
70
+ end
@@ -0,0 +1,13 @@
1
+ RSpec.describe TTY::Config, '#merge' do
2
+ it "merges nested hash" do
3
+ config = TTY::Config.new
4
+ config.set(:a, :b, value: 1)
5
+ config.set(:a, :c, value: 2)
6
+
7
+ config.merge({'a' => {'c' => 3, 'd' => 4}})
8
+
9
+ expect(config.fetch(:a, :b)).to eq(1)
10
+ expect(config.fetch(:a, :c)).to eq(3)
11
+ expect(config.fetch(:a, :d)).to eq(4)
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ RSpec.describe TTY::Config, '#new' do
2
+ it "sets settings through initialization" do
3
+ config = TTY::Config.new(foo: "bar")
4
+ expect(config.fetch(:foo)).to eq("bar")
5
+ end
6
+ end
@@ -0,0 +1,21 @@
1
+ RSpec.describe TTY::Config, '#normalize_hash' do
2
+ it "normalizes keys via method to symbols" do
3
+ hash = {
4
+ "settings" => {
5
+ "base" => "USD",
6
+ "color" => true,
7
+ "exchange" => "CCCAGG"
8
+ },
9
+ "coins" => ["BTC", "ETH", "TRX", "DASH"]
10
+ }
11
+
12
+ expect(TTY::Config.normalize_hash(hash)).to eq({
13
+ settings: {
14
+ base: "USD",
15
+ color: true,
16
+ exchange: "CCCAGG"
17
+ },
18
+ coins: ["BTC", "ETH", "TRX", "DASH"]
19
+ })
20
+ end
21
+ end
@@ -0,0 +1,109 @@
1
+ RSpec.describe TTY::Config, '#read' do
2
+ it "reads from a specified file" do
3
+ file = fixtures_path('investments.yml')
4
+ config = TTY::Config.new
5
+
6
+ config.read(file)
7
+
8
+ expect(config.fetch(:settings, :base)).to eq('USD')
9
+ end
10
+
11
+ it "searched for a file to read from" do
12
+ config = TTY::Config.new
13
+ config.filename = 'investments'
14
+ config.append_path(fixtures_path)
15
+
16
+ config.read
17
+
18
+ expect(config.fetch(:settings, :base)).to eq('USD')
19
+ end
20
+
21
+ it "reads from a specified file and merges with defaults" do
22
+ file = fixtures_path('investments.yml')
23
+ config = TTY::Config.new
24
+ config.set(:settings, :base, value: 'EUR')
25
+ config.set(:settings, :top, value: 50)
26
+
27
+ config.read(file)
28
+
29
+ expect(config.fetch(:settings, :base)).to eq('USD')
30
+ expect(config.fetch(:settings, :exchange)).to eq('CCCAGG')
31
+ expect(config.fetch(:settings, :top)).to eq(50)
32
+ end
33
+
34
+ it "reads a json format" do
35
+ file = fixtures_path('investments.json')
36
+ config = TTY::Config.new
37
+
38
+ config.read(file)
39
+
40
+ expect(config.filename).to eq('investments')
41
+ expect(config.extname).to eq('.json')
42
+ expect(config.fetch(:settings, :base)).to eq('USD')
43
+ expect(config.fetch(:coins)).to eq(["BTC", "ETH", "TRX", "DASH"])
44
+ end
45
+
46
+ it "reads a toml format" do
47
+ file = fixtures_path('investments.toml')
48
+ config = TTY::Config.new
49
+
50
+ config.read(file)
51
+
52
+ expect(config.filename).to eq('investments')
53
+ expect(config.extname).to eq('.toml')
54
+ expect(config.fetch(:settings, :base)).to eq('USD')
55
+ expect(config.fetch(:coins)).to eq(["BTC", "ETH", "TRX", "DASH"])
56
+ end
57
+
58
+ it "reads an ini format" do
59
+ file = fixtures_path('investments.ini')
60
+ config = TTY::Config.new
61
+
62
+ config.read(file)
63
+
64
+ expect(config.filename).to eq('investments')
65
+ expect(config.extname).to eq('.ini')
66
+ expect(config.fetch(:settings, :base)).to eq('USD')
67
+ expect(config.fetch(:coins).split(',')).to eq(["BTC", "ETH", "TRX", "DASH"])
68
+ end
69
+
70
+ it "reads custom format" do
71
+ file = fixtures_path('.env')
72
+ config = TTY::Config.new
73
+
74
+ config.read(file, format: :ini)
75
+
76
+ expect(config.filename).to eq('.env')
77
+ expect(config.extname).to eq('')
78
+ expect(config.fetch(:settings, :base)).to eq('USD')
79
+ expect(config.fetch(:coins).split(',')).to eq(["BTC", "ETH", "TRX", "DASH"])
80
+ end
81
+
82
+ it "fails to find a file to read" do
83
+ config = TTY::Config.new
84
+ expect {
85
+ config.read
86
+ }.to raise_error(TTY::Config::ReadError,
87
+ "No file found to read configuration from!")
88
+ end
89
+
90
+ it "fails to read a file" do
91
+ config = TTY::Config.new
92
+ file = fixtures_path('unknown.yml')
93
+
94
+ expect {
95
+ config.read(file)
96
+ }.to raise_error(TTY::Config::ReadError,
97
+ "Configuration file `#{file}` does not exist!")
98
+ end
99
+
100
+ it "fails to load dependency for reading file format" do
101
+ config = TTY::Config.new
102
+ file = fixtures_path('investments.yml')
103
+ allow(config).to receive(:require).with('yaml').and_raise(LoadError)
104
+
105
+ expect {
106
+ config.read(file)
107
+ }.to raise_error(TTY::Config::ReadError, "Gem `yaml` is missing. Please install it to read .yml configuration format.")
108
+ end
109
+ end