levels 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (90) hide show
  1. data/.gitignore +17 -0
  2. data/.rbenv-version +1 -0
  3. data/CHANGELOG.md +4 -0
  4. data/Gemfile +12 -0
  5. data/Guardfile +14 -0
  6. data/LICENSE +22 -0
  7. data/README.md +315 -0
  8. data/Rakefile +28 -0
  9. data/bin/levels +130 -0
  10. data/examples/01_base.rb +6 -0
  11. data/examples/01_merge_to_json.sh +27 -0
  12. data/examples/01_prod.json +8 -0
  13. data/examples/02_base.rb +4 -0
  14. data/examples/02_merge_with_file.sh +20 -0
  15. data/examples/02_value +1 -0
  16. data/levels.gemspec +20 -0
  17. data/lib/levels.rb +77 -0
  18. data/lib/levels/audit.rb +24 -0
  19. data/lib/levels/audit/group_observer.rb +26 -0
  20. data/lib/levels/audit/nested_group_observer.rb +37 -0
  21. data/lib/levels/audit/root_observer.rb +63 -0
  22. data/lib/levels/audit/value.rb +64 -0
  23. data/lib/levels/audit/value_observer.rb +46 -0
  24. data/lib/levels/audit/values.rb +66 -0
  25. data/lib/levels/configuration.rb +98 -0
  26. data/lib/levels/configured_group.rb +62 -0
  27. data/lib/levels/event_handler.rb +127 -0
  28. data/lib/levels/group.rb +61 -0
  29. data/lib/levels/input/json.rb +17 -0
  30. data/lib/levels/input/ruby.rb +120 -0
  31. data/lib/levels/input/system.rb +63 -0
  32. data/lib/levels/input/yaml.rb +17 -0
  33. data/lib/levels/key.rb +28 -0
  34. data/lib/levels/key_values.rb +54 -0
  35. data/lib/levels/lazy_evaluator.rb +54 -0
  36. data/lib/levels/level.rb +80 -0
  37. data/lib/levels/method_missing.rb +14 -0
  38. data/lib/levels/output/json.rb +33 -0
  39. data/lib/levels/output/system.rb +29 -0
  40. data/lib/levels/output/yaml.rb +19 -0
  41. data/lib/levels/runtime.rb +30 -0
  42. data/lib/levels/setup.rb +132 -0
  43. data/lib/levels/system/constants.rb +8 -0
  44. data/lib/levels/system/key_formatter.rb +15 -0
  45. data/lib/levels/system/key_generator.rb +50 -0
  46. data/lib/levels/system/key_parser.rb +67 -0
  47. data/lib/levels/version.rb +3 -0
  48. data/test/acceptance/audit_test.rb +105 -0
  49. data/test/acceptance/event_handler_test.rb +43 -0
  50. data/test/acceptance/read_json_test.rb +35 -0
  51. data/test/acceptance/read_ruby_test.rb +117 -0
  52. data/test/acceptance/read_system_test.rb +121 -0
  53. data/test/acceptance/read_yaml_test.rb +38 -0
  54. data/test/acceptance/setup_test.rb +115 -0
  55. data/test/acceptance/write_json_test.rb +39 -0
  56. data/test/acceptance/write_system_test.rb +68 -0
  57. data/test/acceptance/write_yaml_test.rb +33 -0
  58. data/test/bin/merge_test.rb +194 -0
  59. data/test/bin/options_test.rb +41 -0
  60. data/test/helper.rb +12 -0
  61. data/test/support/acceptance_spec.rb +58 -0
  62. data/test/support/base_spec.rb +14 -0
  63. data/test/support/bin_spec.rb +65 -0
  64. data/test/support/tempfile_helper.rb +35 -0
  65. data/test/unit/audit/group_observer_test.rb +24 -0
  66. data/test/unit/audit/nested_group_observer_test.rb +28 -0
  67. data/test/unit/audit/root_observer_test.rb +54 -0
  68. data/test/unit/audit/value_observer_test.rb +63 -0
  69. data/test/unit/audit/value_test.rb +41 -0
  70. data/test/unit/audit/values_test.rb +86 -0
  71. data/test/unit/configuration_test.rb +72 -0
  72. data/test/unit/configured_group_test.rb +75 -0
  73. data/test/unit/group_test.rb +105 -0
  74. data/test/unit/input/json_test.rb +32 -0
  75. data/test/unit/input/ruby_test.rb +140 -0
  76. data/test/unit/input/system_test.rb +59 -0
  77. data/test/unit/input/yaml_test.rb +33 -0
  78. data/test/unit/key_test.rb +45 -0
  79. data/test/unit/key_values_test.rb +106 -0
  80. data/test/unit/lazy_evaluator_test.rb +38 -0
  81. data/test/unit/level_test.rb +89 -0
  82. data/test/unit/levels_test.rb +23 -0
  83. data/test/unit/output/json_test.rb +55 -0
  84. data/test/unit/output/system_test.rb +32 -0
  85. data/test/unit/output/yaml_test.rb +38 -0
  86. data/test/unit/runtime_test.rb +40 -0
  87. data/test/unit/system/key_formatter_test.rb +43 -0
  88. data/test/unit/system/key_generator_test.rb +21 -0
  89. data/test/unit/system/key_parser_test.rb +207 -0
  90. metadata +215 -0
@@ -0,0 +1,98 @@
1
+ module Levels
2
+ # A Configuration is the merging of one or more levels. This is the top level
3
+ # object that you will interact with most.
4
+ #
5
+ # Examples
6
+ #
7
+ # # In this example we'll represent a Level as a Hash.
8
+ # level1 = { group1: { key1: 1 } }
9
+ # level2 = { group1: { key1: 2, key2: 2 }, group2: { x: 9 } }
10
+ #
11
+ # # The configuration exposes each group that exists in a Level.
12
+ # config = Levels::Configuration.new([level1, level2])
13
+ #
14
+ # # A Group may be accessed via hash syntax.
15
+ # config[:group1] # => { key1: 2, key2: 2 }
16
+ # # Or method syntax.
17
+ # config.group1 # => { key1: 2, key2: 2 }
18
+ #
19
+ # # You can check if a Group exists.
20
+ # config.defined?(:group1) # => true
21
+ # config.group1? # => true
22
+ #
23
+ class Configuration
24
+ include Levels::MethodMissing
25
+
26
+ # Internal: Initialze a new configuration.
27
+ #
28
+ # levels - Array of Levels::Level.
29
+ #
30
+ def initialize(levels, event_handler = nil)
31
+ @levels = levels
32
+ @event_handler = event_handler || NullEventHandler.new
33
+ @root_observer = Levels::Audit.start(LazyEvaluator.new(self))
34
+ end
35
+
36
+ # Public: Set the event handler. The event handler is notified whenever a
37
+ # key is read; allowing you to track exactly what is and isn't used at
38
+ # runtime.
39
+ #
40
+ # event_handler - Levels::EventHandler.
41
+ #
42
+ # Returns nothing.
43
+ def event_handler=(event_handler)
44
+ @event_handler = event_handler
45
+ end
46
+
47
+ # Public: Retrieve a group. The resulting group is the union of the named
48
+ # group from each Level that defines that group.
49
+ #
50
+ # group_key - Symbol name of the group.
51
+ #
52
+ # Examples
53
+ #
54
+ # # In this example we'll represent a Level as a Hash.
55
+ # level1 = { group: { key1: 1 } }
56
+ # level2 = { group: { key1: 2, key2: 2 } }
57
+ #
58
+ # config = Levels::Configuration.new([level1, level2])
59
+ # config[:group] # => { key1: 2, key2: 2 }
60
+ #
61
+ # Returns a Levels::ConfiguredGroup.
62
+ # Raises Levels::UnknownGroup if the group is not defined.
63
+ def [](group_key)
64
+ raise UnknownGroup unless self.defined?(group_key)
65
+ group_observer = @root_observer.observe_group(@event_handler)
66
+ Levels::ConfiguredGroup.new(@levels, group_key, group_observer)
67
+ end
68
+
69
+ # Public: Determine if a group is defined. A group is defined if it exists
70
+ # in any Level.
71
+ #
72
+ # group_key - Symbol name of the group.
73
+ #
74
+ # Returns a Boolean.
75
+ def defined?(group_key)
76
+ @levels.any? { |level| level.defined?(group_key) }
77
+ end
78
+
79
+ def to_s
80
+ "<Levels::Configuration #{@levels.map { |l| l._level_name }.join(', ')}>"
81
+ end
82
+
83
+ # Returns an Enumerator which yields [gruop_name, Group#to_enum].
84
+ def to_enum
85
+ Enumerator.new do |y|
86
+ group_keys = Set.new
87
+ @levels.each do |level|
88
+ level.to_enum.each do |name, group|
89
+ group_keys << name
90
+ end
91
+ end
92
+ group_keys.each do |name|
93
+ y << [name, self[name].to_enum]
94
+ end
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,62 @@
1
+ module Levels
2
+ # A ConfiguredGroup is the union of one or more Groups. When a key is read,
3
+ # the value returned is the value from the last Level that defines that key.
4
+ class ConfiguredGroup
5
+ include Levels::MethodMissing
6
+
7
+ # Internal: Initialize a configured group.
8
+ #
9
+ def initialize(levels, group_key, group_observer)
10
+ @levels = levels
11
+ @group_key = group_key
12
+ @group_observer = group_observer
13
+ end
14
+
15
+ # Public: Retrieve a value.
16
+ #
17
+ # value_key - Symbol name of the value.
18
+ #
19
+ # Returns the value stored at that key.
20
+ # Raises Levels::UnknownKey if the key is not defined.
21
+ def [](value_key)
22
+ raise UnknownKey unless self.defined?(value_key)
23
+ values = @group_observer.observe_values(@levels, @group_key, value_key)
24
+ values.final_value
25
+ end
26
+
27
+ # Public: Determine if a key is defined.
28
+ #
29
+ # value_key - Symbol name of the key.
30
+ #
31
+ # Returns a Boolean.
32
+ def defined?(value_key)
33
+ groups.any? { |group| group.defined?(value_key) }
34
+ end
35
+
36
+ def to_s
37
+ "<Levels::ConfiguredGroup #{@group_key}>"
38
+ end
39
+
40
+ # Returns an Enumerator which yields [key, value].
41
+ def to_enum
42
+ Enumerator.new do |y|
43
+ value_keys = Set.new
44
+ groups.each do |group|
45
+ group.to_enum.each do |key, value|
46
+ value_keys << key
47
+ end
48
+ end
49
+ value_keys.each do |key|
50
+ y << [key, self[key]]
51
+ end
52
+ end
53
+ end
54
+
55
+ protected
56
+
57
+ def groups
58
+ levels = @levels.find_all { |level| level.defined?(@group_key) }
59
+ levels.map { |level| level[@group_key] }
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,127 @@
1
+ module Levels
2
+ # This is the interface for capturing what happens when a value is read.
3
+ # Capturing events exposes you to the audit trail system implemented in
4
+ # Levels::Audit.
5
+ #
6
+ # class MyEventHandler
7
+ #
8
+ # def on_values(values)
9
+ # # This method is called any time a value is accessed.
10
+ # # The argument `values` is a Levels::Audit::Values representing the
11
+ # # set of all possible values.
12
+ # end
13
+ #
14
+ # def on_nested_values(values)
15
+ # # Similar to `on_values`, but called when the values were found
16
+ # # during the evaluation of another value.
17
+ # end
18
+ # end
19
+ #
20
+ module EventHandler
21
+
22
+ def on_values(values)
23
+ end
24
+
25
+ def on_nested_values(values)
26
+ end
27
+ end
28
+
29
+ # A null implementation.
30
+ class NullEventHandler
31
+ include EventHandler
32
+ end
33
+
34
+ module Colorizer
35
+
36
+ RESET = "\033[0m"
37
+ FOREGROUND = {
38
+ black: "\033[30m",
39
+ red: "\033[31m",
40
+ green: "\033[32m",
41
+ brown: "\033[33m",
42
+ blue: "\033[34m",
43
+ magenta: "\033[35m",
44
+ cyan: "\033[36m",
45
+ white: "\033[37m"
46
+ }
47
+
48
+ def foreground_color(name, str)
49
+ code = FOREGROUND[name] or raise ArgumentError, "Unknown color #{name.inspect}"
50
+ "#{code}#{str}#{RESET}"
51
+ end
52
+ end
53
+
54
+ class CliEventHandler
55
+ include Colorizer
56
+
57
+ def initialize(stream, color = false)
58
+ @stream = stream
59
+ @color = color
60
+ @indent = 0
61
+ end
62
+
63
+ def on_values(values)
64
+ write :white, "> #{values.group_key}.#{values.value_key}"
65
+ values.each do |value|
66
+ value.notify(self)
67
+ if value.final?
68
+ write :green, " + #{value.inspect} from #{value.level_name}"
69
+ else
70
+ write :red, " - #{value.inspect} from #{value.level_name}"
71
+ end
72
+ end
73
+ end
74
+
75
+ def on_nested_values(values)
76
+ indent do
77
+ on_values(values)
78
+ end
79
+ end
80
+
81
+ protected
82
+
83
+ def indent
84
+ begin
85
+ @indent += 1
86
+ yield
87
+ ensure
88
+ @indent -= 1
89
+ end
90
+ end
91
+
92
+ def write(color, str)
93
+ prefix = " " * @indent
94
+ str = foreground_color(color, str) if @color
95
+ @stream.puts prefix + str
96
+ end
97
+ end
98
+
99
+ # A temporary implementation that will be moved back to config to
100
+ # restore its original behavior.
101
+ class ConfigCoreEventHandler
102
+
103
+ def initialize(log)
104
+ @log = log
105
+ @base_color = :magenta
106
+ @alt_color = :cyan
107
+ end
108
+
109
+ def on_read_from_merged_group(group_name, key, levels)
110
+ final_level_name, final_value = levels.last
111
+
112
+ if levels.size == 1
113
+ @log << @log.colorize("Read #{group_name}.#{key} => #{final_value.inspect} from #{final_level_name}", @base_color)
114
+ else
115
+ skipped_levels = levels[0..-2]
116
+
117
+ @log << @log.colorize("Read #{group_name}.#{key}", @base_color)
118
+ @log.indent do
119
+ skipped_levels.each do |level_name, value|
120
+ @log << @log.colorize("Skip #{value.inspect} from #{level_name}", @alt_color)
121
+ end
122
+ @log << @log.colorize("Use #{final_value.inspect} from #{final_level_name}", @base_color)
123
+ end
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,61 @@
1
+ module Levels
2
+ # A group is a set of key/value pairs containing your data.
3
+ #
4
+ # Examples
5
+ #
6
+ # group = Levels::Group.new(name: "Joe", age: 33)
7
+ #
8
+ # group.name # => "Joe"
9
+ # group.age # => 33
10
+ #
11
+ class Group
12
+ include Levels::MethodMissing
13
+
14
+ # Internal: Initialize a new group.
15
+ #
16
+ # data - Hash of key/values for the group.
17
+ # value_transformer - Proc that takes (key, value) and returns value.
18
+ #
19
+ def initialize(data = {}, value_transformer = nil)
20
+ @values = Levels::KeyValues.new(data)
21
+ @value_transformer = value_transformer || -> key, value { value }
22
+ end
23
+
24
+ # Public: Get the value for a key.
25
+ #
26
+ # Returns the value.
27
+ # Raises Levels::UnknownKey if the key is not defined.
28
+ def [](value_key)
29
+ if @values.key?(value_key)
30
+ key, value = @values.pair(value_key)
31
+ @value_transformer.call(key.to_sym, value)
32
+ else
33
+ raise UnknownKey, "#{value_key.inspect} is not defined in #{self}"
34
+ end
35
+ end
36
+
37
+ # Public: Determine if a key is defined.
38
+ #
39
+ # Returns a Boolean.
40
+ def defined?(key)
41
+ @values.key?(key)
42
+ end
43
+
44
+ def to_s
45
+ "<Levels::Group>"
46
+ end
47
+
48
+ # Returns an Enumerator which yields [key, value].
49
+ def to_enum
50
+ Enumerator.new do |y|
51
+ @values.each do |key, value|
52
+ y << [key.to_sym, self[key]]
53
+ end
54
+ end
55
+ end
56
+
57
+ def eql_hash?(hash)
58
+ @values == Levels::KeyValues.new(hash)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,17 @@
1
+ module Levels
2
+ module Input
3
+ # This input
4
+ class JSON
5
+
6
+ def initialize(json_string)
7
+ @json = ::JSON.parse(json_string)
8
+ end
9
+
10
+ def read(level)
11
+ @json.each do |group_name, group|
12
+ level.set_group(group_name, group)
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,120 @@
1
+ module Levels
2
+ module Input
3
+ # This input provides a DSL for writing levels in ruby.
4
+ class Ruby
5
+
6
+ def initialize(ruby_string, file = "Code in Memory", line = 1)
7
+ @ruby_string = ruby_string
8
+ @file = file
9
+ @line = line
10
+ end
11
+
12
+ def read(level)
13
+ dsl = DSL.new(level)
14
+ dsl.instance_eval(@ruby_string, @file, @line)
15
+ dsl.close_current_group
16
+ end
17
+
18
+ # The Ruby syntax DSL. This syntax aims to be very readable and writable.
19
+ # A stateful syntax and no trailing commas allows values to be easily
20
+ # moved around without excess text editing.
21
+ #
22
+ # Examples
23
+ #
24
+ # group :webserver
25
+ # set hostname: "localhost"
26
+ # group :task_queue
27
+ # set workers: 1
28
+ # set queues: -> { ["high", "low", webserver.hostname] }
29
+ #
30
+ class DSL
31
+ include Levels::Runtime
32
+
33
+ def initialize(level)
34
+ @level = level
35
+ end
36
+
37
+ # Public: Start a group.
38
+ #
39
+ # name - Symbol or String name of the group.
40
+ #
41
+ # Returns nothing.
42
+ # Raises a RuntimeError if the group has already been defined.
43
+ def group(name)
44
+ close_current_group
45
+ if @level.defined?(name)
46
+ raise RuntimeError, "Group has already been created: #{name.inspect}"
47
+ end
48
+ @group = name
49
+ @hash = {}
50
+ nil
51
+ end
52
+
53
+ # Public: Set a key/value pair in the current group.
54
+ #
55
+ # Arguments
56
+ #
57
+ # hash - Hash of key/value pairs. The keys may be a Symbol or String.
58
+ #
59
+ # key - Symbol or String key.
60
+ # value - Any value.
61
+ #
62
+ # Examples
63
+ #
64
+ # # Ruby 1.9 Hash.
65
+ # set key: "value"
66
+ #
67
+ # # Ruby 1.9 Hash.
68
+ # set :key => "value"
69
+ #
70
+ # # Key can be a String.
71
+ # set "key" => "value"
72
+ #
73
+ # # Multiple key/values.
74
+ # set :key => "value", :more => "values"
75
+ #
76
+ # # Key, value.
77
+ # set :key, "value"
78
+ #
79
+ # Returns nothing.
80
+ # Raises SyntaxError if no current group is defined.
81
+ # Raises ArgumentError if other forms of arguments are given
82
+ # Raises RuntimeError if a key has already been set.
83
+ def set(*args)
84
+ raise SyntaxError, "No group is defined" if @hash.nil?
85
+ case
86
+ when args.size == 1 && Hash === args.first
87
+ hash = args.first
88
+ when args.size == 2 && !(args.any? { |a| Hash === a })
89
+ hash = { args.first => args.last }
90
+ else
91
+ raise ArgumentError, "Set must be given a Hash or two arguments. Got #{args.inspect}"
92
+ end
93
+ key_values = Levels::KeyValues.new(@hash)
94
+ hash.keys.each do |key|
95
+ if key_values.key?(key)
96
+ raise RuntimeError, "Key has already been set: #{key.inspect}"
97
+ end
98
+ end
99
+ @hash.update(hash)
100
+ nil
101
+ end
102
+
103
+ def close_current_group
104
+ @level.set_group(@group, @hash) if @group
105
+ @group = nil
106
+ @hash = nil
107
+ end
108
+
109
+ def to_s
110
+ "<Levels>"
111
+ end
112
+
113
+ def inspect
114
+ "<Levels>"
115
+ end
116
+ end
117
+ end
118
+ end
119
+ end
120
+