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,136 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/impl/abstract_config_value'
3
+ require 'typesafe/config/impl/abstract_config_object'
4
+ require 'typesafe/config/impl/simple_config_list'
5
+ require 'typesafe/config/config_object'
6
+ require 'typesafe/config/impl/unmergeable'
7
+ require 'typesafe/config/impl/simple_config_origin'
8
+ require 'typesafe/config/impl/config_string'
9
+
10
+ class Typesafe::Config::Impl::ConfigConcatenation < Typesafe::Config::Impl::AbstractConfigValue
11
+ include Typesafe::Config::Impl::Unmergeable
12
+
13
+ SimpleConfigList = Typesafe::Config::Impl::SimpleConfigList
14
+ ConfigObject = Typesafe::Config::ConfigObject
15
+ Unmergeable = Typesafe::Config::Impl::Unmergeable
16
+ SimpleConfigOrigin = Typesafe::Config::Impl::SimpleConfigOrigin
17
+
18
+ #
19
+ # Add left and right, or their merger, to builder
20
+ #
21
+ def self.join(builder, orig_right)
22
+ left = builder[builder.size - 1]
23
+ right = orig_right
24
+
25
+ # check for an object which can be converted to a list
26
+ # (this will be an object with numeric keys, like foo.0, foo.1)
27
+ if (left.is_a?(ConfigObject)) && (right.is_a?(SimpleConfigList))
28
+ left = DefaultTransformer.transform(left, ConfigValueType::LIST)
29
+ elsif (left.is_a?(SimpleConfigList)) && (right.is_a?(ConfigObject))
30
+ right = DefaultTransformer.transform(right, ConfigValueType::LIST)
31
+ end
32
+
33
+ # Since this depends on the type of two instances, I couldn't think
34
+ # of much alternative to an instanceof chain. Visitors are sometimes
35
+ # used for multiple dispatch but seems like overkill.
36
+ joined = nil
37
+ if (left.is_a?(ConfigObject)) && (right.is_a?(ConfigObject))
38
+ joined = right.with_fallback(left)
39
+ elsif (left.is_a?(SimpleConfigList)) && (right.is_a?(SimpleConfigList))
40
+ joined = left.concatenate(right)
41
+ elsif (left.is_a?(Typesafe::Config::Impl::ConfigConcatenation)) ||
42
+ (right.is_a?(Typesafe::Config::Impl::ConfigConcatenation))
43
+ raise ConfigBugError, "unflattened ConfigConcatenation"
44
+ elsif (left.is_a?(Unmergeable)) || (right.is_a?(Unmergeable))
45
+ # leave joined=null, cannot join
46
+ else
47
+ # handle primitive type or primitive type mixed with object or list
48
+ s1 = left.transform_to_string
49
+ s2 = right.transform_to_string
50
+ if s1.nil? || s2.nil?
51
+ raise ConfigWrongTypeError.new(left.origin,
52
+ "Cannot concatenate object or list with a non-object-or-list, #{left} " +
53
+ "and #{right} are not compatible")
54
+ else
55
+ joined_origin = SimpleConfigOrigin.merge_origins([left.origin, right.origin])
56
+ joined = Typesafe::Config::Impl::ConfigString.new(joined_origin, s1 + s2)
57
+ end
58
+ end
59
+
60
+ if joined.nil?
61
+ builder.push(right)
62
+ else
63
+ builder.pop
64
+ builder.push(joined)
65
+ end
66
+ end
67
+
68
+ def self.consolidate(pieces)
69
+ if pieces.length < 2
70
+ pieces
71
+ else
72
+ flattened = []
73
+ pieces.each do |v|
74
+ if v.is_a?(Typesafe::Config::Impl::ConfigConcatenation)
75
+ flattened.concat(v.pieces)
76
+ else
77
+ flattened.push(v)
78
+ end
79
+ end
80
+
81
+ consolidated = []
82
+ flattened.each do |v|
83
+ if consolidated.empty?
84
+ consolidated.push(v)
85
+ else
86
+ join(consolidated, v)
87
+ end
88
+ end
89
+
90
+ consolidated
91
+ end
92
+ end
93
+
94
+ def self.concatenate(pieces)
95
+ consolidated = consolidate(pieces)
96
+ if consolidated.empty?
97
+ nil
98
+ elsif consolidated.length == 1
99
+ consolidated[0]
100
+ else
101
+ merged_origin = SimpleConfigOrigin.merge_origins(consolidated)
102
+ Typesafe::Config::Impl::ConfigConcatenation.new(merged_origin, consolidated)
103
+ end
104
+ end
105
+
106
+
107
+ def initialize(origin, pieces)
108
+ super(origin)
109
+ @pieces = pieces
110
+
111
+ if pieces.size < 2
112
+ raise ConfigBugError, "Created concatenation with less than 2 items: #{self}"
113
+ end
114
+
115
+ had_unmergeable = false
116
+ pieces.each do |p|
117
+ if p.is_a?(Typesafe::Config::Impl::ConfigConcatenation)
118
+ raise ConfigBugError, "ConfigConcatenation should never be nested: #{self}"
119
+ end
120
+ if p.is_a?(Unmergeable)
121
+ had_unmergeable = true
122
+ end
123
+ end
124
+
125
+ unless had_unmergeable
126
+ raise ConfigBugError, "Created concatenation without an unmergeable in it: #{self}"
127
+ end
128
+ end
129
+
130
+ def ignores_fallbacks?
131
+ # we can never ignore fallbacks because if a child ConfigReference
132
+ # is self-referential we have to look lower in the merge stack
133
+ # for its value.
134
+ false
135
+ end
136
+ end
@@ -0,0 +1,9 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/impl/config_number'
3
+
4
+ class Typesafe::Config::Impl::ConfigFloat < Typesafe::Config::Impl::ConfigNumber
5
+ def initialize(origin, value, original_text)
6
+ super(origin, original_text)
7
+ @value = value
8
+ end
9
+ end
@@ -0,0 +1,10 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/impl/simple_includer'
3
+
4
+ class Typesafe::Config::Impl::ConfigImpl
5
+ @default_includer = Typesafe::Config::Impl::SimpleIncluder.new
6
+
7
+ def self.default_includer
8
+ @default_includer
9
+ end
10
+ end
@@ -0,0 +1,78 @@
1
+ require 'typesafe/config/impl'
2
+ require 'stringio'
3
+
4
+ class Typesafe::Config::Impl::ConfigImplUtil
5
+ def self.equals_handling_nil?(a, b)
6
+ # This method probably doesn't make any sense in ruby... not sure
7
+ if a.nil? && !b.nil?
8
+ false
9
+ elsif !a.nil? && b.nil?
10
+ false
11
+ # in ruby, the == and .equal? are the opposite of what they are in Java
12
+ elsif a.equal?(b)
13
+ true
14
+ else
15
+ a == b
16
+ end
17
+ end
18
+
19
+ #
20
+ # This is public ONLY for use by the "config" package, DO NOT USE this ABI
21
+ # may change.
22
+ #
23
+ def self.render_json_string(s)
24
+ sb = StringIO.new
25
+ sb << '"'
26
+ s.chars.each do |c|
27
+ case c
28
+ when '"' then sb << "\\\""
29
+ when "\\" then sb << "\\\\"
30
+ when "\n" then sb << "\\n"
31
+ when "\b" then sb << "\\b"
32
+ when "\f" then sb << "\\f"
33
+ when "\r" then sb << "\\r"
34
+ when "\t" then sb << "\\t"
35
+ else
36
+ if c =~ /[[:cntrl:]]/
37
+ sb << ("\\u%04x" % c)
38
+ else
39
+ sb << c
40
+ end
41
+ end
42
+ end
43
+ sb << '"'
44
+ sb.string
45
+ end
46
+
47
+ def self.render_string_unquoted_if_possible(s)
48
+ # this can quote unnecessarily as long as it never fails to quote when
49
+ # necessary
50
+ if s.length == 0
51
+ return render_json_string(s)
52
+ end
53
+
54
+ # if it starts with a hyphen or number, we have to quote
55
+ # to ensure we end up with a string and not a number
56
+ first = s.chars.first
57
+ if (first =~ /[[:digit:]]/) || (first == '-')
58
+ return render_json_string(s)
59
+ end
60
+
61
+ # only unquote if it's pure alphanumeric
62
+ s.chars.each do |c|
63
+ unless (c =~ /[[:alnum:]]/) || (c == '-')
64
+ return render_json_string(s)
65
+ end
66
+ end
67
+
68
+ s
69
+ end
70
+
71
+ def self.whitespace?(c)
72
+ # this implementation is *not* a port of the java code, because it relied on
73
+ # the method java.lang.Character#isWhitespace. This is probably
74
+ # insanely slow (running a regex against every single character in the
75
+ # file).
76
+ c =~ /[[:space:]]/
77
+ end
78
+ end
@@ -0,0 +1,31 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/impl/config_number'
3
+ require 'typesafe/config/config_value_type'
4
+
5
+ class Typesafe::Config::Impl::ConfigInt < Typesafe::Config::Impl::ConfigNumber
6
+ def initialize(origin, value, original_text)
7
+ super(origin, original_text)
8
+ @value = value
9
+ end
10
+
11
+ def value_type
12
+ Typesafe::Config::ConfigValueType::NUMBER
13
+ end
14
+
15
+ def unwrapped
16
+ @value
17
+ end
18
+
19
+ def transform_to_string
20
+ s = super
21
+ if s.nil?
22
+ self.to_s
23
+ else
24
+ s
25
+ end
26
+ end
27
+
28
+ def new_copy(origin)
29
+ Typesafe::Config::Impl::ConfigInt.new(origin, @value, @original_text)
30
+ end
31
+ end
@@ -0,0 +1,27 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/impl/abstract_config_value'
3
+
4
+ class Typesafe::Config::Impl::ConfigNumber < Typesafe::Config::Impl::AbstractConfigValue
5
+ ## sigh... requiring these subclasses before this class
6
+ ## is declared would cause an error. Thanks, ruby.
7
+ require 'typesafe/config/impl/config_int'
8
+ require 'typesafe/config/impl/config_float'
9
+
10
+ def self.new_number(origin, number, original_text)
11
+ as_int = number.to_i
12
+ if as_int == number
13
+ Typesafe::Config::Impl::ConfigInt.new(origin, as_int, original_text)
14
+ else
15
+ Typesafe::Config::Impl::ConfigFloat.new(origin, number, original_text)
16
+ end
17
+ end
18
+
19
+ def initialize(origin, original_text)
20
+ super(origin)
21
+ @original_text = original_text
22
+ end
23
+
24
+ def transform_to_string
25
+ @original_text
26
+ end
27
+ end
@@ -0,0 +1,37 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/impl/abstract_config_value'
3
+ require 'typesafe/config/config_value_type'
4
+ require 'typesafe/config/impl/config_impl_util'
5
+
6
+ class Typesafe::Config::Impl::ConfigString < Typesafe::Config::Impl::AbstractConfigValue
7
+ ConfigImplUtil = Typesafe::Config::Impl::ConfigImplUtil
8
+
9
+ def initialize(origin, value)
10
+ super(origin)
11
+ @value = value
12
+ end
13
+
14
+ def value_type
15
+ Typesafe::Config::ConfigValueType::STRING
16
+ end
17
+
18
+ def unwrapped
19
+ @value
20
+ end
21
+
22
+ def transform_to_string
23
+ @value
24
+ end
25
+
26
+ def render_value_to_sb(sb, indent_size, at_root, options)
27
+ if options.json?
28
+ sb << ConfigImplUtil.render_json_string(@value)
29
+ else
30
+ sb << ConfigImplUtil.render_string_unquoted_if_possible(@value)
31
+ end
32
+ end
33
+
34
+ def new_copy(origin)
35
+ Typesafe::Config::Impl::ConfigString.new(origin, @value)
36
+ end
37
+ end
@@ -0,0 +1,4 @@
1
+ require 'typesafe/config/impl'
2
+
3
+ class Typesafe::Config::Impl::FullIncluder
4
+ end
@@ -0,0 +1,9 @@
1
+ require 'typesafe/config/impl'
2
+
3
+ module Typesafe::Config::Impl::OriginType
4
+ ## for now, we only support a subset of these
5
+ GENERIC = 0
6
+ FILE = 1
7
+ #URL = 2
8
+ #RESOURCE = 3
9
+ end
@@ -0,0 +1,151 @@
1
+ require 'typesafe/config/impl'
2
+ require 'typesafe/config/config_syntax'
3
+ require 'typesafe/config/impl/config_impl'
4
+ require 'typesafe/config/impl/simple_include_context'
5
+ require 'typesafe/config/impl/simple_config_object'
6
+ require 'typesafe/config/impl/simple_config_origin'
7
+ require 'typesafe/config/impl/tokenizer'
8
+ require 'typesafe/config/impl/parser'
9
+
10
+ class Typesafe::Config::Impl::Parseable
11
+ class ParseableFile < Typesafe::Config::Impl::Parseable
12
+ def initialize(file_path, options)
13
+ @input = file_path
14
+ post_construct(options)
15
+ end
16
+
17
+ def guess_syntax
18
+ Typesafe::Config::Impl::Parseable.syntax_from_extension(File.basename(@input))
19
+ end
20
+
21
+ def create_origin
22
+ Typesafe::Config::Impl::SimpleConfigOrigin.new_file(@input)
23
+ end
24
+
25
+ def reader
26
+ self
27
+ end
28
+
29
+ def open
30
+ if block_given?
31
+ File.open(@input) do |f|
32
+ yield f
33
+ end
34
+ else
35
+ File.open(@input)
36
+ end
37
+ end
38
+ end
39
+
40
+ def self.new_file(file_path, options)
41
+ ParseableFile.new(file_path, options)
42
+ end
43
+
44
+ def options
45
+ @initial_options
46
+ end
47
+
48
+ def include_context
49
+ @include_context
50
+ end
51
+
52
+ def self.force_parsed_to_object(value)
53
+ if value.is_a? Typesafe::Config::Impl::AbstractConfigObject
54
+ value
55
+ else
56
+ raise ConfigWrongTypeError.new(value.origin, "", "object at file root",
57
+ value.value_type.name)
58
+ end
59
+ end
60
+
61
+ def parse
62
+ self.class.force_parsed_to_object(parse_value(options))
63
+ end
64
+
65
+ def parse_value(base_options)
66
+ # note that we are NOT using our "initialOptions",
67
+ # but using the ones from the passed-in options. The idea is that
68
+ # callers can get our original options and then parse with different
69
+ # ones if they want.
70
+ options = fixup_options(base_options)
71
+
72
+ # passed-in options can override origin
73
+ origin =
74
+ if options.origin_description
75
+ Typesafe::Config::Impl::SimpleConfigOrigin.new_simple(options.origin_description)
76
+ else
77
+ @initial_origin
78
+ end
79
+ parse_value_from_origin(origin, options)
80
+ end
81
+
82
+
83
+ private
84
+
85
+ def self.syntax_from_extension(filename)
86
+ case File.extname(filename)
87
+ when ".json"
88
+ Typesafe::Config::ConfigSyntax::JSON
89
+ when ".conf"
90
+ Typesafe::Config::ConfigSyntax::CONF
91
+ when ".properties"
92
+ Typesafe::Config::ConfigSyntax::PROPERTIES
93
+ else
94
+ nil
95
+ end
96
+ end
97
+
98
+ def post_construct(base_options)
99
+ @initial_options = fixup_options(base_options)
100
+ @include_context = Typesafe::Config::Impl::SimpleIncludeContext.new(self)
101
+ if @initial_options.origin_description
102
+ @initial_origin = SimpleConfigOrigin.new_simple(@initial_options.origin_description)
103
+ else
104
+ @initial_origin = create_origin
105
+ end
106
+ end
107
+
108
+ def fixup_options(base_options)
109
+ syntax = base_options.syntax
110
+ if !syntax
111
+ syntax = guess_syntax
112
+ end
113
+ if !syntax
114
+ syntax = Typesafe::Config::ConfigSyntax.CONF
115
+ end
116
+
117
+ modified = base_options.with_syntax(syntax)
118
+ modified = modified.append_includer(Typesafe::Config::Impl::ConfigImpl.default_includer)
119
+ modified = modified.with_includer(Typesafe::Config::Impl::SimpleIncluder.make_full(modified.includer))
120
+
121
+ modified
122
+ end
123
+
124
+ def parse_value_from_origin(origin, final_options)
125
+ begin
126
+ raw_parse_value(origin, final_options)
127
+ rescue IOError => e
128
+ if final_options.allow_missing?
129
+ Typesafe::Config::Impl::SimpleConfigObject.empty_missing(origin)
130
+ else
131
+ raise ConfigIOError.new(origin, "#{e.class.name}: #{e.message}", e)
132
+ end
133
+ end
134
+ end
135
+
136
+ # this is parseValue without post-processing the IOException or handling
137
+ # options.getAllowMissing()
138
+ def raw_parse_value(origin, final_options)
139
+ ## TODO: if we were going to support loading from URLs, this
140
+ ## method would need to deal with the content-type.
141
+
142
+ reader.open { |io|
143
+ raw_parse_value_from_io(io, origin, final_options)
144
+ }
145
+ end
146
+
147
+ def raw_parse_value_from_io(io, origin, final_options)
148
+ tokens = Typesafe::Config::Impl::Tokenizer.tokenize(origin, io, final_options.syntax)
149
+ Typesafe::Config::Impl::Parser.parse(tokens, origin, final_options, include_context)
150
+ end
151
+ end