levels 0.1.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.
- data/.gitignore +17 -0
- data/.rbenv-version +1 -0
- data/CHANGELOG.md +4 -0
- data/Gemfile +12 -0
- data/Guardfile +14 -0
- data/LICENSE +22 -0
- data/README.md +315 -0
- data/Rakefile +28 -0
- data/bin/levels +130 -0
- data/examples/01_base.rb +6 -0
- data/examples/01_merge_to_json.sh +27 -0
- data/examples/01_prod.json +8 -0
- data/examples/02_base.rb +4 -0
- data/examples/02_merge_with_file.sh +20 -0
- data/examples/02_value +1 -0
- data/levels.gemspec +20 -0
- data/lib/levels.rb +77 -0
- data/lib/levels/audit.rb +24 -0
- data/lib/levels/audit/group_observer.rb +26 -0
- data/lib/levels/audit/nested_group_observer.rb +37 -0
- data/lib/levels/audit/root_observer.rb +63 -0
- data/lib/levels/audit/value.rb +64 -0
- data/lib/levels/audit/value_observer.rb +46 -0
- data/lib/levels/audit/values.rb +66 -0
- data/lib/levels/configuration.rb +98 -0
- data/lib/levels/configured_group.rb +62 -0
- data/lib/levels/event_handler.rb +127 -0
- data/lib/levels/group.rb +61 -0
- data/lib/levels/input/json.rb +17 -0
- data/lib/levels/input/ruby.rb +120 -0
- data/lib/levels/input/system.rb +63 -0
- data/lib/levels/input/yaml.rb +17 -0
- data/lib/levels/key.rb +28 -0
- data/lib/levels/key_values.rb +54 -0
- data/lib/levels/lazy_evaluator.rb +54 -0
- data/lib/levels/level.rb +80 -0
- data/lib/levels/method_missing.rb +14 -0
- data/lib/levels/output/json.rb +33 -0
- data/lib/levels/output/system.rb +29 -0
- data/lib/levels/output/yaml.rb +19 -0
- data/lib/levels/runtime.rb +30 -0
- data/lib/levels/setup.rb +132 -0
- data/lib/levels/system/constants.rb +8 -0
- data/lib/levels/system/key_formatter.rb +15 -0
- data/lib/levels/system/key_generator.rb +50 -0
- data/lib/levels/system/key_parser.rb +67 -0
- data/lib/levels/version.rb +3 -0
- data/test/acceptance/audit_test.rb +105 -0
- data/test/acceptance/event_handler_test.rb +43 -0
- data/test/acceptance/read_json_test.rb +35 -0
- data/test/acceptance/read_ruby_test.rb +117 -0
- data/test/acceptance/read_system_test.rb +121 -0
- data/test/acceptance/read_yaml_test.rb +38 -0
- data/test/acceptance/setup_test.rb +115 -0
- data/test/acceptance/write_json_test.rb +39 -0
- data/test/acceptance/write_system_test.rb +68 -0
- data/test/acceptance/write_yaml_test.rb +33 -0
- data/test/bin/merge_test.rb +194 -0
- data/test/bin/options_test.rb +41 -0
- data/test/helper.rb +12 -0
- data/test/support/acceptance_spec.rb +58 -0
- data/test/support/base_spec.rb +14 -0
- data/test/support/bin_spec.rb +65 -0
- data/test/support/tempfile_helper.rb +35 -0
- data/test/unit/audit/group_observer_test.rb +24 -0
- data/test/unit/audit/nested_group_observer_test.rb +28 -0
- data/test/unit/audit/root_observer_test.rb +54 -0
- data/test/unit/audit/value_observer_test.rb +63 -0
- data/test/unit/audit/value_test.rb +41 -0
- data/test/unit/audit/values_test.rb +86 -0
- data/test/unit/configuration_test.rb +72 -0
- data/test/unit/configured_group_test.rb +75 -0
- data/test/unit/group_test.rb +105 -0
- data/test/unit/input/json_test.rb +32 -0
- data/test/unit/input/ruby_test.rb +140 -0
- data/test/unit/input/system_test.rb +59 -0
- data/test/unit/input/yaml_test.rb +33 -0
- data/test/unit/key_test.rb +45 -0
- data/test/unit/key_values_test.rb +106 -0
- data/test/unit/lazy_evaluator_test.rb +38 -0
- data/test/unit/level_test.rb +89 -0
- data/test/unit/levels_test.rb +23 -0
- data/test/unit/output/json_test.rb +55 -0
- data/test/unit/output/system_test.rb +32 -0
- data/test/unit/output/yaml_test.rb +38 -0
- data/test/unit/runtime_test.rb +40 -0
- data/test/unit/system/key_formatter_test.rb +43 -0
- data/test/unit/system/key_generator_test.rb +21 -0
- data/test/unit/system/key_parser_test.rb +207 -0
- metadata +215 -0
@@ -0,0 +1,63 @@
|
|
1
|
+
module Levels
|
2
|
+
module Input
|
3
|
+
# This input creates an env level from the system environment
|
4
|
+
# (ENV in ruby). It does so by using an existing level as a
|
5
|
+
# template for the group names and values. For each value, it
|
6
|
+
# attempts to typecast a String value into the same type found in
|
7
|
+
# the template.
|
8
|
+
#
|
9
|
+
# Examples
|
10
|
+
#
|
11
|
+
# # Given a template level with a single group:
|
12
|
+
# settings: {
|
13
|
+
# hostname: "example.com",
|
14
|
+
# workers: 5,
|
15
|
+
# queues: ["low", "high", "other"]
|
16
|
+
# }
|
17
|
+
#
|
18
|
+
# # These environment variables will set new values.
|
19
|
+
# SETTINGS_HOSTNAME=foo.com
|
20
|
+
# SETTINGS_WORKERS=1
|
21
|
+
# SETTINGS_QUEUES=high:low:other
|
22
|
+
#
|
23
|
+
class System
|
24
|
+
|
25
|
+
# Initialize a new system reader.
|
26
|
+
#
|
27
|
+
# template - Enumerator that defines the possible keys.
|
28
|
+
# prefix - String prefix for the keys (default: no
|
29
|
+
# prefix).
|
30
|
+
#
|
31
|
+
def initialize(template, key_formatter = nil, env_hash = ENV)
|
32
|
+
@template = template
|
33
|
+
@env_hash = env_hash
|
34
|
+
@key_formatter = key_formatter || Levels::System::KeyFormatter.new
|
35
|
+
@key_parser = Levels::System::KeyParser.new(@key_formatter)
|
36
|
+
end
|
37
|
+
|
38
|
+
def read(level)
|
39
|
+
@template.each do |group_name, group|
|
40
|
+
env_data = {}
|
41
|
+
group_data = {}
|
42
|
+
group.each do |key, value|
|
43
|
+
group_data[key.to_sym] = value
|
44
|
+
end
|
45
|
+
@env_hash.each do |key, value|
|
46
|
+
match_key = @key_formatter.create(group_name, "(.+)")
|
47
|
+
matcher = Regexp.new("^#{match_key}$")
|
48
|
+
if key =~ matcher
|
49
|
+
attr_name = $1.downcase.to_sym
|
50
|
+
if group_data.key?(attr_name)
|
51
|
+
cast_value = @key_parser.parse(@env_hash, key, group_data[attr_name])
|
52
|
+
env_data[attr_name] = cast_value
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
if env_data.any?
|
57
|
+
level.set_group(group_name, env_data)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Levels
|
2
|
+
module Input
|
3
|
+
class YAML
|
4
|
+
|
5
|
+
def initialize(yaml_string)
|
6
|
+
@yaml = ::YAML.load(yaml_string)
|
7
|
+
end
|
8
|
+
|
9
|
+
def read(level)
|
10
|
+
@yaml.each do |group_name, group|
|
11
|
+
level.set_group(group_name, group)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
data/lib/levels/key.rb
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
module Levels
|
2
|
+
class Key
|
3
|
+
|
4
|
+
def initialize(key)
|
5
|
+
@key = key
|
6
|
+
end
|
7
|
+
|
8
|
+
def to_sym
|
9
|
+
@key.to_sym
|
10
|
+
end
|
11
|
+
|
12
|
+
def eql?(other)
|
13
|
+
other.class == self.class && to_sym == other.to_sym
|
14
|
+
end
|
15
|
+
|
16
|
+
alias == eql?
|
17
|
+
|
18
|
+
def hash
|
19
|
+
self.class.hash ^ to_sym.hash
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"<Levels::Key #{to_sym.inspect}>"
|
24
|
+
end
|
25
|
+
|
26
|
+
alias to_s inspect
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Levels
|
2
|
+
class KeyValues
|
3
|
+
|
4
|
+
def Key(key)
|
5
|
+
case key
|
6
|
+
when Levels::Key then key
|
7
|
+
else Levels::Key.new(key)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
def initialize(data = nil)
|
14
|
+
@hash = {}
|
15
|
+
data.each { |k, v| self[k] = v } if data
|
16
|
+
end
|
17
|
+
|
18
|
+
def [](key)
|
19
|
+
@hash[Key(key)]
|
20
|
+
end
|
21
|
+
|
22
|
+
def pair(key)
|
23
|
+
return Key(key), self[key]
|
24
|
+
end
|
25
|
+
|
26
|
+
def []=(key, value)
|
27
|
+
@hash[Key(key)] = value
|
28
|
+
end
|
29
|
+
|
30
|
+
def key?(key)
|
31
|
+
@hash.key?(Key(key))
|
32
|
+
end
|
33
|
+
|
34
|
+
def each(&block)
|
35
|
+
@hash.each(&block)
|
36
|
+
end
|
37
|
+
|
38
|
+
def eql?(other)
|
39
|
+
self.class == other.class && @hash.eql?(other.instance_variable_get(:@hash))
|
40
|
+
end
|
41
|
+
|
42
|
+
alias == eql?
|
43
|
+
|
44
|
+
def hash
|
45
|
+
self.class.hash ^ @hash.hash
|
46
|
+
end
|
47
|
+
|
48
|
+
def inspect
|
49
|
+
"<Levels::KeyValues>"
|
50
|
+
end
|
51
|
+
|
52
|
+
alias to_s inspect
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Levels
|
2
|
+
# Whenever a value is read, it's interpreted by the LazyEvaluator. This class
|
3
|
+
# implements all of the interpolation rules.
|
4
|
+
class LazyEvaluator
|
5
|
+
|
6
|
+
# Internal: Initialize a new LazyEvaluator.
|
7
|
+
#
|
8
|
+
# level - Levels::Level or equivalent, used to find referenced
|
9
|
+
# groups and keys.
|
10
|
+
# key_formatter - Levels::System::KeyFormatter.
|
11
|
+
#
|
12
|
+
def initialize(level, key_formatter = nil)
|
13
|
+
@level = level
|
14
|
+
@key_formatter = key_formatter || Levels::System::KeyFormatter.new
|
15
|
+
end
|
16
|
+
|
17
|
+
# Internal: Interpret the value.
|
18
|
+
def call(value)
|
19
|
+
loop do
|
20
|
+
case value
|
21
|
+
#when /\$\{[A-Z_]+\}/
|
22
|
+
when Proc
|
23
|
+
dsl = DSL.new(@level)
|
24
|
+
value = dsl.instance_exec(&value)
|
25
|
+
when Array
|
26
|
+
return value.map { |v| call(v) }
|
27
|
+
else
|
28
|
+
return value
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# This is the class that evaluations Proc values. When you define a value
|
34
|
+
# as a Proc, it's evaluation in the context of an instance of this class.
|
35
|
+
class DSL
|
36
|
+
include Levels::MethodMissing
|
37
|
+
include Levels::Runtime
|
38
|
+
|
39
|
+
def initialize(level)
|
40
|
+
@level = level
|
41
|
+
end
|
42
|
+
|
43
|
+
# Public: Determine if a group exists.
|
44
|
+
def defined?(group_key)
|
45
|
+
@level.defined?(group_key)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Public: Retrieve a group.
|
49
|
+
def [](group_key)
|
50
|
+
@level[group_key]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/levels/level.rb
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
module Levels
|
2
|
+
# A Level is a named set of groups. A Configuration is made up of multiple
|
3
|
+
# levels with clear semantics on how those levels are merged. You generally won't
|
4
|
+
# instantiate a Level directly, but instead load one from an external source.
|
5
|
+
#
|
6
|
+
# Examples
|
7
|
+
#
|
8
|
+
# level = Levels::Level.new("My Level")
|
9
|
+
#
|
10
|
+
# level.set_group(:group1, a: 1, b: 2)
|
11
|
+
# level.set_group(:group2, c: 3, d: 4)
|
12
|
+
#
|
13
|
+
# level.group1 # => { a: 1, b: 2 }
|
14
|
+
# level.group2 # => { c: 3, d: 4 }
|
15
|
+
#
|
16
|
+
class Level
|
17
|
+
include Levels::MethodMissing
|
18
|
+
|
19
|
+
# Internal: Initialize a new level.
|
20
|
+
#
|
21
|
+
# name - String name of the level.
|
22
|
+
#
|
23
|
+
def initialize(name)
|
24
|
+
@name = name
|
25
|
+
@groups = Levels::KeyValues.new
|
26
|
+
end
|
27
|
+
|
28
|
+
# Public: Get a group by name.
|
29
|
+
#
|
30
|
+
# group_name - Symbol name of the group.
|
31
|
+
#
|
32
|
+
# Returns a Levels::Group.
|
33
|
+
# Raises Levels::UnknownGroup if the group is not defined.
|
34
|
+
def [](group_name)
|
35
|
+
@groups[group_name] or raise UnknownGroup, "#{group_name.inspect} group is not defined"
|
36
|
+
end
|
37
|
+
|
38
|
+
# Public: Determine if a group has been defined.
|
39
|
+
#
|
40
|
+
# Returns a Boolean.
|
41
|
+
def defined?(group_name)
|
42
|
+
@groups.key?(group_name)
|
43
|
+
end
|
44
|
+
|
45
|
+
# Internal: Define a group.
|
46
|
+
#
|
47
|
+
# group_name - Symbol name of the group.
|
48
|
+
# hash - Hash of values.
|
49
|
+
#
|
50
|
+
# Returns nothing.
|
51
|
+
def set_group(group_name, hash)
|
52
|
+
if @groups.key?(group_name)
|
53
|
+
raise DuplicateGroup, "#{group_name} has already been defined"
|
54
|
+
end
|
55
|
+
@groups[group_name] = Group.new(hash)
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_s
|
59
|
+
"<Levels::Level #{@name.inspect}>"
|
60
|
+
end
|
61
|
+
|
62
|
+
# Returns an Enumerator which yields [group_name, Group#to_enum].
|
63
|
+
def to_enum
|
64
|
+
Enumerator.new do |y|
|
65
|
+
@groups.each do |name, group|
|
66
|
+
y << [name.to_sym, group.to_enum]
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
def _level_name
|
72
|
+
@name
|
73
|
+
end
|
74
|
+
|
75
|
+
def eql_hash?(hash)
|
76
|
+
key_values = Levels::KeyValues.new(hash)
|
77
|
+
@groups.all? { |name, group| group.eql_hash?(key_values[name]) }
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Levels
|
2
|
+
# Enables dot syntax for levels and groups.
|
3
|
+
module MethodMissing
|
4
|
+
|
5
|
+
def method_missing(message, *args, &block)
|
6
|
+
raise ArgumentError, "arguments are not allowed: #{message}(#{args.inspect})" if args.any?
|
7
|
+
if message =~ /^(.*)\?$/
|
8
|
+
self.defined?($1.to_sym)
|
9
|
+
else
|
10
|
+
self[message]
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Levels
|
2
|
+
module Output
|
3
|
+
class JSON
|
4
|
+
|
5
|
+
SPACE = " ".freeze
|
6
|
+
JSON_OPTS = {
|
7
|
+
indent: SPACE * 2,
|
8
|
+
space: SPACE * 1,
|
9
|
+
space_before: SPACE * 0,
|
10
|
+
object_nl: "\n",
|
11
|
+
array_nl: "\n",
|
12
|
+
allow_nan: false,
|
13
|
+
max_nesting: 10
|
14
|
+
}
|
15
|
+
|
16
|
+
def initialize(json_opts = nil)
|
17
|
+
@json_opts = json_opts || JSON_OPTS
|
18
|
+
end
|
19
|
+
|
20
|
+
def generate(enumerator)
|
21
|
+
hash = {}
|
22
|
+
enumerator.each do |group_name, group|
|
23
|
+
hash[group_name] = {}
|
24
|
+
group.each do |key, value|
|
25
|
+
hash[group_name][key] = value
|
26
|
+
end
|
27
|
+
end
|
28
|
+
::JSON.generate(hash, @json_opts)
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Levels
|
2
|
+
module Output
|
3
|
+
class System
|
4
|
+
|
5
|
+
def initialize(key_formatter = nil)
|
6
|
+
@key_formatter = key_formatter || Levels::System::KeyFormatter.new
|
7
|
+
@key_generator = Levels::System::KeyGenerator.new(key_formatter)
|
8
|
+
end
|
9
|
+
|
10
|
+
def generate(enumerator)
|
11
|
+
flat_enum = Enumerator.new do |y|
|
12
|
+
enumerator.each do |group_name, group|
|
13
|
+
group.each do |key, value|
|
14
|
+
y << [group_name, key, value]
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
vars = @key_generator.generate(flat_enum)
|
19
|
+
vars.map { |k, v| "export #{k}=#{quote v}" }.join("\n")
|
20
|
+
end
|
21
|
+
|
22
|
+
protected
|
23
|
+
|
24
|
+
def quote(value)
|
25
|
+
%("#{value}")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Levels
|
2
|
+
module Output
|
3
|
+
class YAML
|
4
|
+
|
5
|
+
def generate(enumerator)
|
6
|
+
hash = {}
|
7
|
+
enumerator.each do |group_name, group|
|
8
|
+
hash[group_name.to_s] = {}
|
9
|
+
group.each do |key, value|
|
10
|
+
hash[group_name.to_s][key.to_s] = value
|
11
|
+
end
|
12
|
+
end
|
13
|
+
::YAML.dump(hash)
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Levels
|
2
|
+
# Public: Methods in this module are available within any Ruby input.
|
3
|
+
# You may extend it with any additonal methods you require.
|
4
|
+
module Runtime
|
5
|
+
|
6
|
+
FileNotFoundError = Class.new(RuntimeError)
|
7
|
+
|
8
|
+
# Public: Read the value from a file on disk. The file
|
9
|
+
# will not be read until the key is accessed.
|
10
|
+
#
|
11
|
+
# file_path - String path to the file. The path may be absolute,
|
12
|
+
# or relative to the Ruby file calling this function.
|
13
|
+
#
|
14
|
+
# Returns a Proc that reads the file when called.
|
15
|
+
# That proc raises Levels::Ruby::FileNotFoundError if the file does
|
16
|
+
# not exist.
|
17
|
+
def file(file_path)
|
18
|
+
return nil if file_path.nil?
|
19
|
+
caller_path = Pathname.new(caller[0]).dirname
|
20
|
+
-> do
|
21
|
+
path = caller_path + file_path
|
22
|
+
if path.exist?
|
23
|
+
path.read
|
24
|
+
else
|
25
|
+
raise FileNotFoundError, path.to_s
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
data/lib/levels/setup.rb
ADDED
@@ -0,0 +1,132 @@
|
|
1
|
+
module Levels
|
2
|
+
class Setup
|
3
|
+
|
4
|
+
# Internal: Initialize a new Levels::Setup.
|
5
|
+
def initialize
|
6
|
+
@inputs = []
|
7
|
+
end
|
8
|
+
|
9
|
+
# Public: Add a level of configuration.
|
10
|
+
#
|
11
|
+
# name - String name of the level.
|
12
|
+
# source - Anything that can be identified as an input source. File
|
13
|
+
# path, code or any object that responds to #read is a valid
|
14
|
+
# source.
|
15
|
+
#
|
16
|
+
# Returns nothing.
|
17
|
+
def add(name, source)
|
18
|
+
level = -> { Levels::Level.new(name) }
|
19
|
+
input = Input.new(source)
|
20
|
+
@inputs << [level, input]
|
21
|
+
end
|
22
|
+
|
23
|
+
# Public: Add the system environment as a level of configuration.
|
24
|
+
#
|
25
|
+
# prefix - String the prefix for environment variables (default
|
26
|
+
# none).
|
27
|
+
# name - String the name of the level (default: "System
|
28
|
+
# Environment").
|
29
|
+
# env_hash - Hash of environment variables (default: ENV).
|
30
|
+
#
|
31
|
+
# Returns nothing.
|
32
|
+
def add_system(prefix = nil, name = nil, env_hash = ENV)
|
33
|
+
key_formatter = Levels::System::KeyFormatter.new(prefix)
|
34
|
+
level = -> { Levels::Level.new(name || "System Environment") }
|
35
|
+
input = -> template { Levels::Input::System.new(template, key_formatter, env_hash) }
|
36
|
+
@inputs << [level, input]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Public: Add an already initialized Level.
|
40
|
+
#
|
41
|
+
# level - Levels::Level.
|
42
|
+
#
|
43
|
+
# Returns nothing.
|
44
|
+
def add_level(level)
|
45
|
+
@inputs << [-> { level }, NullInput.new]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Public: Parse all inputs sources and get a Configuration.
|
49
|
+
#
|
50
|
+
# Returns a Levels::Configuration.
|
51
|
+
def merge
|
52
|
+
levels = []
|
53
|
+
@inputs.each do |level_proc, input|
|
54
|
+
level = level_proc.call
|
55
|
+
case input
|
56
|
+
when Proc
|
57
|
+
template = Levels::Configuration.new(levels)
|
58
|
+
input = input.call(template.to_enum)
|
59
|
+
input.read(level)
|
60
|
+
else
|
61
|
+
input.read(level)
|
62
|
+
end
|
63
|
+
levels << level
|
64
|
+
end
|
65
|
+
Levels::Configuration.new(levels)
|
66
|
+
end
|
67
|
+
|
68
|
+
class NullInput
|
69
|
+
|
70
|
+
def read(level)
|
71
|
+
# noop
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# This class transforms any supported object into Level data. The object
|
76
|
+
# can be any of:
|
77
|
+
#
|
78
|
+
# * A file path to Ruby, JSON or YAML
|
79
|
+
# * Ruby, JSON or YAML code
|
80
|
+
# * An object that responds to `#read(level)`.
|
81
|
+
#
|
82
|
+
class Input
|
83
|
+
|
84
|
+
def initialize(source)
|
85
|
+
@source = source
|
86
|
+
end
|
87
|
+
|
88
|
+
# Read the input into a Level.
|
89
|
+
# Raises an ArgumentError if the source cannot be used as an input.
|
90
|
+
def read(level)
|
91
|
+
input.read(level)
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns a Levels::Input.
|
95
|
+
# Raises an ArgumentError if the format couldn't be determined.
|
96
|
+
def input
|
97
|
+
format, source, *args = identify
|
98
|
+
case format
|
99
|
+
when :custom then source
|
100
|
+
when :ruby then Levels::Input::Ruby.new(source, *args)
|
101
|
+
when :json then Levels::Input::JSON.new(source)
|
102
|
+
when :yaml then Levels::Input::YAML.new(source)
|
103
|
+
else raise ArgumentError, "Could not identify the format: #{format.inspect}"
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Determine the format of the source and read it from disk if
|
108
|
+
# it's a file.
|
109
|
+
def identify
|
110
|
+
if @source.respond_to?(:read)
|
111
|
+
return :custom, @source
|
112
|
+
end
|
113
|
+
pn = Pathname.new(@source)
|
114
|
+
if pn.exist?
|
115
|
+
case pn.extname
|
116
|
+
when ".rb" then [:ruby, pn.read, pn.to_s, 1]
|
117
|
+
when ".json" then [:json, pn.read]
|
118
|
+
when ".yaml", ".yml" then [:yaml, pn.read]
|
119
|
+
else raise ArgumentError, "Could not identify the file type: #{pn.extname}"
|
120
|
+
end
|
121
|
+
else
|
122
|
+
case @source
|
123
|
+
when /\A\w*{/ then [:json, @source]
|
124
|
+
when /\A---$/ then [:yaml, @source]
|
125
|
+
when /\A\w*group/ then [:ruby, @source, "Code from String", 1]
|
126
|
+
else raise ArgumentError, "Could not identify the source: #{@source.inspect}"
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|