typesafe_config 0.0.1

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 (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