typesafe_config 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (40) hide show
  1. data/LICENSE +202 -0
  2. data/README.md +2 -0
  3. data/lib/typesafe/config/config_error.rb +12 -0
  4. data/lib/typesafe/config/config_factory.rb +9 -0
  5. data/lib/typesafe/config/config_object.rb +4 -0
  6. data/lib/typesafe/config/config_parse_options.rb +53 -0
  7. data/lib/typesafe/config/config_render_options.rb +46 -0
  8. data/lib/typesafe/config/config_syntax.rb +7 -0
  9. data/lib/typesafe/config/config_value_type.rb +26 -0
  10. data/lib/typesafe/config/impl/abstract_config_object.rb +64 -0
  11. data/lib/typesafe/config/impl/abstract_config_value.rb +130 -0
  12. data/lib/typesafe/config/impl/config_concatenation.rb +136 -0
  13. data/lib/typesafe/config/impl/config_float.rb +9 -0
  14. data/lib/typesafe/config/impl/config_impl.rb +10 -0
  15. data/lib/typesafe/config/impl/config_impl_util.rb +78 -0
  16. data/lib/typesafe/config/impl/config_int.rb +31 -0
  17. data/lib/typesafe/config/impl/config_number.rb +27 -0
  18. data/lib/typesafe/config/impl/config_string.rb +37 -0
  19. data/lib/typesafe/config/impl/full_includer.rb +4 -0
  20. data/lib/typesafe/config/impl/origin_type.rb +9 -0
  21. data/lib/typesafe/config/impl/parseable.rb +151 -0
  22. data/lib/typesafe/config/impl/parser.rb +882 -0
  23. data/lib/typesafe/config/impl/path.rb +59 -0
  24. data/lib/typesafe/config/impl/path_builder.rb +36 -0
  25. data/lib/typesafe/config/impl/resolve_status.rb +18 -0
  26. data/lib/typesafe/config/impl/simple_config.rb +11 -0
  27. data/lib/typesafe/config/impl/simple_config_list.rb +70 -0
  28. data/lib/typesafe/config/impl/simple_config_object.rb +178 -0
  29. data/lib/typesafe/config/impl/simple_config_origin.rb +174 -0
  30. data/lib/typesafe/config/impl/simple_include_context.rb +7 -0
  31. data/lib/typesafe/config/impl/simple_includer.rb +19 -0
  32. data/lib/typesafe/config/impl/token.rb +32 -0
  33. data/lib/typesafe/config/impl/token_type.rb +42 -0
  34. data/lib/typesafe/config/impl/tokenizer.rb +370 -0
  35. data/lib/typesafe/config/impl/tokens.rb +157 -0
  36. data/lib/typesafe/config/impl/unmergeable.rb +4 -0
  37. data/lib/typesafe/config/impl.rb +5 -0
  38. data/lib/typesafe/config.rb +4 -0
  39. data/lib/typesafe.rb +2 -0
  40. metadata +85 -0
@@ -0,0 +1,59 @@
1
+ require 'typesafe/config/impl'
2
+ require 'stringio'
3
+
4
+ class Typesafe::Config::Impl::Path
5
+ # this doesn't have a very precise meaning, just to reduce
6
+ # noise from quotes in the rendered path for average cases
7
+ def self.has_funky_chars?(s)
8
+ length = s.length
9
+ if length == 0
10
+ return false
11
+ end
12
+
13
+ # if the path starts with something that could be a number,
14
+ # we need to quote it because the number could be invalid,
15
+ # for example it could be a hyphen with no digit afterward
16
+ # or the exponent "e" notation could be mangled.
17
+ first = s[0]
18
+ unless first =~ /[[:alpha:]]/
19
+ return true
20
+ end
21
+
22
+ s.chars.each do |c|
23
+ unless (c =~ /[[:alnum:]]/) || (c == '-') || (c == '_')
24
+ return true
25
+ end
26
+ end
27
+
28
+ false
29
+ end
30
+
31
+ def initialize(first, remainder)
32
+ @first = first
33
+ @remainder = remainder
34
+ end
35
+ attr_reader :first, :remainder
36
+
37
+ #
38
+ # toString() is a debugging-oriented version while this is an
39
+ # error-message-oriented human-readable one.
40
+ #
41
+ def render
42
+ sb = StringIO.new
43
+ append_to_string_builder(sb)
44
+ sb.string
45
+ end
46
+
47
+ def append_to_string_builder(sb)
48
+ if self.class.has_funky_chars?(@first) || @first.empty?
49
+ sb << ConfigImplUtil.render_json_string(@first)
50
+ else
51
+ sb << @first
52
+ end
53
+
54
+ unless @remainder.nil?
55
+ sb << "."
56
+ @remainder.append_to_string_builder(sb)
57
+ end
58
+ end
59
+ end
@@ -0,0 +1,36 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/impl/path'
3
+
4
+ class Typesafe::Config::Impl::PathBuilder
5
+ Path = Typesafe::Config::Impl::Path
6
+
7
+ def initialize
8
+ @keys = []
9
+ @result = nil
10
+ end
11
+
12
+ def check_can_append
13
+ if @result
14
+ raise ConfigBugError, "Adding to PathBuilder after getting result"
15
+ end
16
+ end
17
+
18
+ def append_key(key)
19
+ check_can_append
20
+ @keys.push(key)
21
+ end
22
+
23
+ def result
24
+ # note: if keys is empty, we want to return null, which is a valid
25
+ # empty path
26
+ if @result.nil?
27
+ remainder = nil
28
+ while !@keys.empty?
29
+ key = @keys.pop
30
+ remainder = Path.new(key, remainder)
31
+ end
32
+ @result = remainder
33
+ end
34
+ @result
35
+ end
36
+ end
@@ -0,0 +1,18 @@
1
+ require 'typesafe/config/impl'
2
+
3
+ class Typesafe::Config::Impl::ResolveStatus
4
+ UNRESOLVED = 0
5
+ RESOLVED = 1
6
+
7
+ def self.from_values(values)
8
+ if values.any? { |v| v.resolve_status == UNRESOLVED }
9
+ UNRESOLVED
10
+ else
11
+ RESOLVED
12
+ end
13
+ end
14
+
15
+ def self.from_boolean(resolved)
16
+ resolved ? RESOLVED : UNRESOLVED
17
+ end
18
+ end
@@ -0,0 +1,11 @@
1
+ require 'typesafe/config/impl'
2
+
3
+ class Typesafe::Config::Impl::SimpleConfig
4
+ def initialize(object)
5
+ @object = object
6
+ end
7
+
8
+ def root
9
+ @object
10
+ end
11
+ end
@@ -0,0 +1,70 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/impl/resolve_status'
3
+ require 'typesafe/config/config_value_type'
4
+
5
+ class Typesafe::Config::Impl::SimpleConfigList < Typesafe::Config::Impl::AbstractConfigValue
6
+ ResolveStatus = Typesafe::Config::Impl::ResolveStatus
7
+
8
+ def initialize(origin, value, status = ResolveStatus.from_values(value))
9
+ super(origin)
10
+ @value = value
11
+ @resolved = (status == ResolveStatus::RESOLVED)
12
+
13
+ # kind of an expensive debug check (makes this constructor pointless)
14
+ if status != ResolveStatus.from_values(value)
15
+ raise ConfigBugError, "SimpleConfigList created with wrong resolve status: #{self}"
16
+ end
17
+ end
18
+
19
+ def value_type
20
+ Typesafe::Config::ConfigValueType::LIST
21
+ end
22
+
23
+ def unwrapped
24
+ @value.map { |v| v.unwrapped }
25
+ end
26
+
27
+ def render_value_to_sb(sb, indent_size, at_root, options)
28
+ if @value.empty?
29
+ sb << "[]"
30
+ else
31
+ sb << "["
32
+ if options.formatted?
33
+ sb << "\n"
34
+ end
35
+ @value.each do |v|
36
+ if options.origin_comments?
37
+ indent(sb, indent_size + 1, options)
38
+ sb << "# "
39
+ sb << v.origin.description
40
+ sb << "\n"
41
+ end
42
+ if options.comments?
43
+ v.origin.comments.each do |comment|
44
+ sb << "# "
45
+ sb << comment
46
+ sb << "\n"
47
+ end
48
+ end
49
+ indent(sb, indent_size + 1, options)
50
+
51
+ v.render_value_to_sb(sb, indent_size + 1, at_root, options)
52
+ sb << ","
53
+ if options.formatted?
54
+ sb << "\n"
55
+ end
56
+ end
57
+
58
+ # couldn't figure out a better way to chop characters off of the end of
59
+ # the StringIO. This relies on making sure that, prior to returning the
60
+ # final string, we take a substring that ends at sb.pos.
61
+ sb.pos = sb.pos - 1 # chop or newline
62
+ if options.formatted?
63
+ sb.pos = sb.pos - 1 # also chop comma
64
+ sb << "\n"
65
+ indent(sb, indent_size, options)
66
+ end
67
+ sb << "]"
68
+ end
69
+ end
70
+ end
@@ -0,0 +1,178 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/impl/simple_config_origin'
3
+ require 'typesafe/config/impl/abstract_config_object'
4
+ require 'typesafe/config/impl/resolve_status'
5
+ require 'set'
6
+
7
+ class Typesafe::Config::Impl::SimpleConfigObject < Typesafe::Config::Impl::AbstractConfigObject
8
+ def self.empty_missing(base_origin)
9
+ self.new(
10
+ Typesafe::Config::Impl::SimpleConfigOrigin.new_simple("#{base_origin.description} (not found)"),
11
+ {})
12
+ end
13
+
14
+ def initialize(origin, value,
15
+ status = Typesafe::Config::Impl::ResolveStatus.from_values(value.values),
16
+ ignores_fallbacks = false)
17
+ super(origin)
18
+ if value.nil?
19
+ raise ConfigBugError, "creating config object with null map"
20
+ end
21
+ @value = value
22
+ @resolved = (status == Typesafe::Config::Impl::ResolveStatus::RESOLVED)
23
+ @ignores_fallbacks = ignores_fallbacks
24
+
25
+ # Kind of an expensive debug check. Comment out?
26
+ if status != Typesafe::Config::Impl::ResolveStatus.from_values(value.values)
27
+ raise ConfigBugError, "Wrong resolved status on #{self}"
28
+ end
29
+ end
30
+
31
+ attr_reader :value
32
+
33
+ def new_copy_with_status(new_status, new_origin, new_ignores_fallbacks = nil)
34
+ Typesafe::Config::Impl::SimpleConfigObject.new(new_origin,
35
+ @value, new_status, new_ignores_fallbacks)
36
+ end
37
+
38
+ def ignores_fallbacks?
39
+ @ignores_fallbacks
40
+ end
41
+
42
+ def unwrapped
43
+ @value.merge(@value) { |k,v| v.unwrapped }
44
+ end
45
+
46
+ def merged_with_object(abstract_fallback)
47
+ require_not_ignoring_fallbacks
48
+
49
+ unless abstract_fallback.is_a?(Typesafe::Config::Impl::SimpleConfigObject)
50
+ raise ConfigBugError, "should not be reached (merging non-SimpleConfigObject)"
51
+ end
52
+
53
+ fallback = abstract_fallback
54
+ changed = false
55
+ all_resolved = true
56
+ merged = {}
57
+ all_keys = key_set.union(fallback.key_set)
58
+ all_keys.each do |key|
59
+ first = @value[key]
60
+ second = fallback.value[key]
61
+ kept =
62
+ if first.nil?
63
+ second
64
+ elsif second.nil?
65
+ first
66
+ else
67
+ first.with_fallback(second)
68
+ end
69
+ merged[key] = kept
70
+
71
+ if first != kept
72
+ changed = true
73
+ end
74
+
75
+ if kept.resolve_status == Typesafe::Config::Impl::ResolveStatus::UNRESOLVED
76
+ all_resolved = false
77
+ end
78
+ end
79
+
80
+ new_resolve_status = Typesafe::Config::Impl::ResolveStatus.from_boolean(all_resolved)
81
+ new_ignores_fallbacks = fallback.ignores_fallbacks?
82
+
83
+ if changed
84
+ Typesafe::Config::Impl::SimpleConfigObject.new(merge_origins([self, fallback]),
85
+ merged, new_resolve_status,
86
+ new_ignores_fallbacks)
87
+ elsif (new_resolve_status != resolve_status) || (new_ignores_fallbacks != ignores_fallbacks?)
88
+ newCopy(new_resolve_status, origin, new_ignores_fallbacks)
89
+ else
90
+ self
91
+ end
92
+ end
93
+
94
+ def render_value_to_sb(sb, indent_size, at_root, options)
95
+ if empty?
96
+ sb << "{}"
97
+ else
98
+ outer_braces = options.json? || !at_root
99
+
100
+ inner_indent =
101
+ if outer_braces
102
+ sb << "{"
103
+ if options.formatted?
104
+ sb << "\n"
105
+ end
106
+ indent_size + 1
107
+ else
108
+ indent_size
109
+ end
110
+
111
+ separator_count = 0
112
+ key_set.each do |k|
113
+ v = @value[k]
114
+
115
+ if options.origin_comments?
116
+ indent(sb, inner_indent, options)
117
+ sb << "# "
118
+ sb << v.origin.description
119
+ sb << "\n"
120
+ end
121
+ if options.comments?
122
+ v.origin.comments.each do |comment|
123
+ indent(sb, inner_indent, options)
124
+ sb << "#"
125
+ if !comment.start_with?(" ")
126
+ sb << " "
127
+ end
128
+ sb << comment
129
+ sb << "\n"
130
+ end
131
+ end
132
+ indent(sb, inner_indent, options)
133
+ v.render_to_sb(sb, inner_indent, false, k.to_s, options)
134
+
135
+ if options.formatted?
136
+ if options.json?
137
+ sb << ","
138
+ separator_count = 2
139
+ else
140
+ separator_count = 1
141
+ end
142
+ sb << "\n"
143
+ else
144
+ sb << ","
145
+ separator_count = 1
146
+ end
147
+ end
148
+ # chop last commas/newlines
149
+ # couldn't figure out a better way to chop characters off of the end of
150
+ # the StringIO. This relies on making sure that, prior to returning the
151
+ # final string, we take a substring that ends at sb.pos.
152
+ sb.pos = sb.pos - separator_count
153
+
154
+ if outer_braces
155
+ if options.formatted?
156
+ sb << "\n" # put a newline back
157
+ if outer_braces
158
+ indent(sb, indent_size, options)
159
+ end
160
+ end
161
+ sb << "}"
162
+ end
163
+ end
164
+ if at_root && options.formatted?
165
+ sb << "\n"
166
+ end
167
+ end
168
+
169
+
170
+ def key_set
171
+ Set.new(@value.keys)
172
+ end
173
+
174
+ def empty?
175
+ @value.empty?
176
+ end
177
+
178
+ end
@@ -0,0 +1,174 @@
1
+ require 'uri'
2
+ require 'typesafe/config/impl'
3
+ require 'typesafe/config/impl/origin_type'
4
+
5
+ class Typesafe::Config::Impl::SimpleConfigOrigin
6
+
7
+ MERGE_OF_PREFIX = "merge of "
8
+
9
+ def self.new_file(file_path)
10
+ url = URI.join('file:///', file_path)
11
+ self.new(file_path, -1, -1,
12
+ Typesafe::Config::Impl::OriginType::FILE,
13
+ url, nil)
14
+ end
15
+
16
+ def self.new_simple(description)
17
+ self.new(description, -1, -1,
18
+ Typesafe::Config::Impl::OriginType::GENERIC,
19
+ nil, nil)
20
+ end
21
+
22
+ def self.remove_merge_of_prefix(desc)
23
+ if desc.start_with?(MERGE_OF_PREFIX)
24
+ desc = desc[MERGE_OF_PREFIX.length, desc.length - 1]
25
+ end
26
+ desc
27
+ end
28
+
29
+ def self.merge_two(a, b)
30
+ merged_desc = nil
31
+ merged_start_line = nil
32
+ merged_end_line = nil
33
+ merged_comments = nil
34
+
35
+ merged_type =
36
+ if a.origin_type == b.origin_type
37
+ a.origin_type
38
+ else
39
+ Typesafe::Config::Impl::OriginType.GENERIC
40
+ end
41
+
42
+ # first use the "description" field which has no line numbers
43
+ # cluttering it.
44
+ a_desc = remove_merge_of_prefix(a.description)
45
+ b_desc = remove_merge_of_prefix(b.description)
46
+
47
+ if a_desc == b_desc
48
+ merged_desc = a_desc
49
+ if a.line_number < 0
50
+ merged_start_line = b.line_number
51
+ elsif b.line_number < 0
52
+ merged_start_line = a.line_number
53
+ else
54
+ merged_start_line = [a.line_number, b.line_number].min
55
+ end
56
+
57
+ merged_end_line = [a.end_line_number, b.end_line_number].max
58
+ else
59
+ # this whole merge song-and-dance was intended to avoid this case
60
+ # whenever possible, but we've lost. Now we have to lose some
61
+ # structured information and cram into a string.
62
+ #
63
+ # description() method includes line numbers, so use it instead
64
+ # of description field.
65
+ a_full = remove_merge_of_prefix(a.description)
66
+ b_full = remove_merge_of_prefix(b.description)
67
+
68
+ merged_desc = "#{MERGE_OF_PREFIX}#{a_full},#{b_full}"
69
+ merged_start_line = -1
70
+ merged_end_line = -1
71
+ end
72
+
73
+ merged_url =
74
+ if Typesafe::Config::Impl::ConfigImplUtil.equals_handling_nil?(a.url_or_nil, b.url_or_nil)
75
+ a.url_or_nil
76
+ else
77
+ nil
78
+ end
79
+
80
+ if Typesafe::Config::Impl::ConfigImplUtil.equals_handling_nil?(a.comments_or_nil, b.comments_or_nil)
81
+ merged_comments = a.comments_or_nil
82
+ else
83
+ merged_comments = []
84
+ if a.comments_or_nil
85
+ merged_comments.concat(a.comments_or_nil)
86
+ end
87
+ if b.comments_or_nil
88
+ merged_comments.concat(b.comments_or_nil)
89
+ end
90
+ end
91
+
92
+ Typesafe::Config::Impl::SimpleConfigOrigin.new(
93
+ merged_desc, merged_start_line, merged_end_line,
94
+ merged_type, merged_url, merged_comments)
95
+ end
96
+
97
+ def self.merge_origins(stack)
98
+ if stack.empty?
99
+ raise ConfigBugError, "can't merge empty list of origins"
100
+ elsif stack.length == 1
101
+ stack[0]
102
+ elsif stack.length == 2
103
+ merge_two(stack[0], stack[1])
104
+ else
105
+ remaining = stack.clone
106
+ while remaining.length > 2
107
+ merged = merge_three(remaining[0], remaining[1], remaining[2])
108
+ remaining.pop
109
+ remaining.pop
110
+ remaining.pop
111
+ end
112
+
113
+ # should be down to either 1 or 2
114
+ merge_origins(remaining)
115
+ end
116
+ end
117
+
118
+
119
+ def initialize(description, line_number, end_line_number,
120
+ origin_type, url, comments)
121
+ if !description
122
+ raise ArgumentError, "description may not be nil"
123
+ end
124
+
125
+ @description = description
126
+ @line_number = line_number
127
+ @end_line_number = end_line_number
128
+ @origin_type = origin_type
129
+ @url_or_nil = url
130
+ @comments_or_nil = comments
131
+ end
132
+
133
+ attr_reader :description, :line_number, :end_line_number, :origin_type,
134
+ :url_or_nil, :comments_or_nil
135
+
136
+ def set_line_number(line_number)
137
+ if (line_number == @line_number) and
138
+ (line_number == @end_line_number)
139
+ self
140
+ else
141
+ Typesafe::Config::Impl::SimpleConfigOrigin.new(
142
+ @description, line_number, line_number,
143
+ @origin_type, @url_or_nil, @comments_or_nil)
144
+ end
145
+ end
146
+
147
+ def set_comments(comments)
148
+ if Typesafe::Config::Impl::ConfigImplUtil.equals_handling_nil?(comments, @comments_or_nil)
149
+ self
150
+ else
151
+ Typesafe::Config::Impl::SimpleConfigOrigin.new(
152
+ @description, @line_number, @end_line_number,
153
+ @origin_type, @url_or_nil, comments)
154
+ end
155
+ end
156
+
157
+ def prepend_comments(comments)
158
+ if Typesafe::Config::Impl::ConfigImplUtil.equals_handling_nil?(comments, @comments_or_nil)
159
+ self
160
+ elsif @comments_or_nil.nil?
161
+ set_comments(comments)
162
+ else
163
+ merged = []
164
+ merged.concat(comments)
165
+ merged.concat(@comments_or_nil)
166
+ set_comments(merged)
167
+ end
168
+ end
169
+
170
+ def comments
171
+ @comments_or_nil || []
172
+ end
173
+
174
+ end
@@ -0,0 +1,7 @@
1
+ require 'typesafe/config/impl'
2
+
3
+ class Typesafe::Config::Impl::SimpleIncludeContext
4
+ def initialize(parseable)
5
+ @parseable = parseable
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/impl/full_includer'
3
+
4
+ class Typesafe::Config::Impl::SimpleIncluder < Typesafe::Config::Impl::FullIncluder
5
+ class Proxy < Typesafe::Config::Impl::FullIncluder
6
+ def initialize(delegate)
7
+ @delegate = delegate
8
+ end
9
+ ## TODO: port remaining implementation when needed
10
+ end
11
+
12
+ def self.make_full(includer)
13
+ if includer.is_a?(Typesafe::Config::Impl::FullIncluder)
14
+ includer
15
+ else
16
+ Proxy.new(includer)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,32 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/impl/token_type'
3
+
4
+ class Typesafe::Config::Impl::Token
5
+ def self.new_without_origin(token_type, debug_string)
6
+ Typesafe::Config::Impl::Token.new(token_type, nil, debug_string)
7
+ end
8
+
9
+ def initialize(token_type, origin, debug_string = nil)
10
+ @token_type = token_type
11
+ @origin = origin
12
+ @debug_string = debug_string
13
+ end
14
+
15
+ attr_reader :origin
16
+
17
+ def line_number
18
+ if @origin
19
+ @origin.line_number
20
+ else
21
+ -1
22
+ end
23
+ end
24
+
25
+ def to_s
26
+ if !@debug_string.nil?
27
+ @debug_string
28
+ else
29
+ Typesafe::Config::Impl::TokenType.name(@token_type)
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,42 @@
1
+ require 'typesafe/config/impl'
2
+
3
+ class Typesafe::Config::Impl::TokenType
4
+ START = 0
5
+ EOF = 1
6
+ COMMA = 2
7
+ EQUALS = 3
8
+ COLON = 4
9
+ OPEN_CURLY = 5
10
+ CLOSE_CURLY = 6
11
+ OPEN_SQUARE = 7
12
+ CLOSE_SQUARE = 8
13
+ VALUE = 9
14
+ NEWLINE = 10
15
+ UNQUOTED_TEXT = 11
16
+ SUBSTITUTION = 12
17
+ PROBLEM = 13
18
+ COMMENT = 14
19
+ PLUS_EQUALS = 15
20
+
21
+ def self.name(token_type)
22
+ case token_type
23
+ when START then "START"
24
+ when EOF then "EOF"
25
+ when COMMA then "COMMA"
26
+ when EQUALS then "EQUALS"
27
+ when COLON then "COLON"
28
+ when OPEN_CURLY then "OPEN_CURLY"
29
+ when CLOSE_CURLY then "CLOSE_CURLY"
30
+ when OPEN_SQUARE then "OPEN_SQUARE"
31
+ when CLOSE_SQUARE then "CLOSE_SQUARE"
32
+ when VALUE then "VALUE"
33
+ when NEWLINE then "NEWLINE"
34
+ when UNQUOTED_TEXT then "UNQUOTED_TEXT"
35
+ when SUBSTITUTION then "SUBSTITUTION"
36
+ when PROBLEM then "PROBLEM"
37
+ when COMMENT then "COMMENT"
38
+ when PLUS_EQUALS then "PLUS_EQUALS"
39
+ else raise ConfigBugError, "Unrecognized token type #{token_type}"
40
+ end
41
+ end
42
+ end