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.
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
+