├── CODEOWNERS ├── spec ├── fixtures │ ├── test_utils │ │ └── resources │ │ │ ├── bom.conf │ │ │ ├── subdir │ │ │ ├── bar.conf │ │ │ ├── baz.conf │ │ │ └── foo.conf │ │ │ ├── cycle.conf │ │ │ ├── utf8.conf │ │ │ ├── ᚠᛇᚻ.conf │ │ │ ├── test01.json │ │ │ ├── utf16.conf │ │ │ ├── file-include.conf │ │ │ ├── include-from-list.conf │ │ │ ├── test03.conf │ │ │ └── test01.conf │ ├── parse_render │ │ ├── example3 │ │ │ ├── output.conf │ │ │ └── input.conf │ │ ├── example4 │ │ │ ├── output.conf │ │ │ └── input.json │ │ ├── example2 │ │ │ ├── input.conf │ │ │ ├── output.conf │ │ │ └── output_nocomments.conf │ │ └── example1 │ │ │ ├── output_nocomments.conf │ │ │ ├── input.conf │ │ │ └── output.conf │ └── hocon │ │ ├── by_extension │ │ ├── cat.test-json │ │ ├── cat.conf │ │ └── cat.test │ │ └── with_substitution │ │ └── subst.conf ├── unit │ ├── typesafe │ │ └── config │ │ │ ├── README.md │ │ │ ├── config_value_factory_spec.rb │ │ │ ├── config_factory_spec.rb │ │ │ ├── simple_config_spec.rb │ │ │ ├── token_spec.rb │ │ │ └── path_spec.rb │ ├── hocon │ │ ├── README.md │ │ └── hocon_spec.rb │ └── cli │ │ └── cli_spec.rb └── spec_helper.rb ├── lib ├── hocon │ ├── version.rb │ ├── impl.rb │ ├── parser.rb │ ├── impl │ │ ├── full_includer.rb │ │ ├── unsupported_operation_error.rb │ │ ├── config_include_kind.rb │ │ ├── mergeable_value.rb │ │ ├── config_node_array.rb │ │ ├── config_node_concatenation.rb │ │ ├── array_iterator.rb │ │ ├── config_node_single_token.rb │ │ ├── resolve_memos.rb │ │ ├── resolve_status.rb │ │ ├── abstract_config_node_value.rb │ │ ├── origin_type.rb │ │ ├── from_map_mode.rb │ │ ├── unmergeable.rb │ │ ├── config_null.rb │ │ ├── config_boolean.rb │ │ ├── config_node_comment.rb │ │ ├── abstract_config_node.rb │ │ ├── replaceable_merge_stack.rb │ │ ├── substitution_expression.rb │ │ ├── config_node_include.rb │ │ ├── config_int.rb │ │ ├── config_double.rb │ │ ├── resolve_result.rb │ │ ├── simple_include_context.rb │ │ ├── url.rb │ │ ├── memo_key.rb │ │ ├── token.rb │ │ ├── container.rb │ │ ├── path_builder.rb │ │ ├── token_type.rb │ │ ├── config_node_path.rb │ │ ├── config_node_simple_value.rb │ │ ├── simple_config_document.rb │ │ ├── config_node_complex_value.rb │ │ ├── config_number.rb │ │ ├── config_string.rb │ │ ├── config_node_field.rb │ │ ├── config_node_root.rb │ │ ├── config_impl_util.rb │ │ ├── default_transformer.rb │ │ ├── config_reference.rb │ │ ├── path.rb │ │ ├── abstract_config_object.rb │ │ ├── simple_includer.rb │ │ ├── resolve_context.rb │ │ └── path_parser.rb │ ├── config_syntax.rb │ ├── config_resolve_options.rb │ ├── config_value_type.rb │ ├── config_includer_file.rb │ ├── parser │ │ ├── config_node.rb │ │ ├── config_document_factory.rb │ │ └── config_document.rb │ ├── config_render_options.rb │ ├── config_parseable.rb │ ├── config_list.rb │ ├── config_error.rb │ ├── config_include_context.rb │ ├── config_parse_options.rb │ ├── config_util.rb │ ├── config_factory.rb │ ├── config_mergeable.rb │ ├── config_value_factory.rb │ ├── config_value.rb │ ├── config_object.rb │ └── cli.rb └── hocon.rb ├── bin └── hocon ├── Gemfile ├── .gitignore ├── .github └── workflows │ ├── rspec_tests.yaml │ └── mend.yaml ├── hocon.gemspec ├── CHANGELOG.md └── README.md /CODEOWNERS: -------------------------------------------------------------------------------- 1 | * @puppetlabs/phoenix 2 | -------------------------------------------------------------------------------- /spec/fixtures/test_utils/resources/bom.conf: -------------------------------------------------------------------------------- 1 | # 2 | foo = bar 3 | -------------------------------------------------------------------------------- /spec/fixtures/test_utils/resources/subdir/bar.conf: -------------------------------------------------------------------------------- 1 | bar=43 2 | -------------------------------------------------------------------------------- /spec/fixtures/test_utils/resources/subdir/baz.conf: -------------------------------------------------------------------------------- 1 | baz=45 2 | -------------------------------------------------------------------------------- /spec/fixtures/parse_render/example3/output.conf: -------------------------------------------------------------------------------- 1 | a=true 2 | b=true -------------------------------------------------------------------------------- /spec/fixtures/parse_render/example3/input.conf: -------------------------------------------------------------------------------- 1 | a: true 2 | b: ${a} 3 | -------------------------------------------------------------------------------- /spec/fixtures/test_utils/resources/cycle.conf: -------------------------------------------------------------------------------- 1 | include "cycle.conf" 2 | -------------------------------------------------------------------------------- /spec/fixtures/test_utils/resources/utf8.conf: -------------------------------------------------------------------------------- 1 | # 2 | ᚠᛇᚻ = ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢ 3 | -------------------------------------------------------------------------------- /spec/fixtures/test_utils/resources/ᚠᛇᚻ.conf: -------------------------------------------------------------------------------- 1 | # 2 | ᚠᛇᚻ = ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢ 3 | -------------------------------------------------------------------------------- /spec/fixtures/hocon/by_extension/cat.test-json: -------------------------------------------------------------------------------- 1 | { 2 | "meow": "cats" 3 | } -------------------------------------------------------------------------------- /spec/fixtures/hocon/by_extension/cat.conf: -------------------------------------------------------------------------------- 1 | # Comment 2 | { 3 | "meow": "cats" 4 | } -------------------------------------------------------------------------------- /spec/fixtures/hocon/by_extension/cat.test: -------------------------------------------------------------------------------- 1 | # Comment 2 | { 3 | "meow": "cats" 4 | } -------------------------------------------------------------------------------- /spec/fixtures/hocon/with_substitution/subst.conf: -------------------------------------------------------------------------------- 1 | a: true 2 | b: ${a} 3 | c: ${ENVARRAY} 4 | -------------------------------------------------------------------------------- /lib/hocon/version.rb: -------------------------------------------------------------------------------- 1 | module Hocon 2 | module Version 3 | STRING = '1.4.0' 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /spec/fixtures/test_utils/resources/test01.json: -------------------------------------------------------------------------------- 1 | { 2 | "fromJson1" : 1, 3 | "fromJsonA" : "A" 4 | } -------------------------------------------------------------------------------- /lib/hocon/impl.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../hocon' 4 | 5 | module Hocon::Impl 6 | 7 | end -------------------------------------------------------------------------------- /bin/hocon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require 'hocon/cli' 3 | 4 | Hocon::CLI.main(Hocon::CLI.parse_args(ARGV)) 5 | 6 | -------------------------------------------------------------------------------- /lib/hocon/parser.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../hocon' 4 | 5 | module Hocon::Parser 6 | 7 | end -------------------------------------------------------------------------------- /lib/hocon/impl/full_includer.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | 5 | class Hocon::Impl::FullIncluder 6 | end -------------------------------------------------------------------------------- /spec/fixtures/test_utils/resources/utf16.conf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/puppetlabs/ruby-hocon/HEAD/spec/fixtures/test_utils/resources/utf16.conf -------------------------------------------------------------------------------- /spec/fixtures/parse_render/example4/output.conf: -------------------------------------------------------------------------------- 1 | { 2 | "kermit"="frog", 3 | "miss"="piggy", 4 | "bert"="ernie", 5 | "janice"="guitar" 6 | } -------------------------------------------------------------------------------- /spec/fixtures/parse_render/example4/input.json: -------------------------------------------------------------------------------- 1 | { 2 | "kermit": "frog", 3 | "miss": "piggy", 4 | "bert": "ernie", 5 | "janice": "guitar" 6 | } -------------------------------------------------------------------------------- /spec/unit/typesafe/config/README.md: -------------------------------------------------------------------------------- 1 | ## TESTS PORTED FROM UPSTREAM 2 | 3 | This directory should only contain tests that are ported from the upstream 4 | Java library. -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Specify your gem's dependencies in hocon.gemspec 4 | gemspec 5 | 6 | group :tests do 7 | gem 'rspec', '~> 3.0' 8 | end 9 | -------------------------------------------------------------------------------- /spec/fixtures/test_utils/resources/subdir/foo.conf: -------------------------------------------------------------------------------- 1 | foo=42 2 | # included without file() 3 | include "bar.conf" 4 | # included using file() 5 | include file("bar-file.conf") 6 | -------------------------------------------------------------------------------- /lib/hocon/impl/unsupported_operation_error.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | 5 | class Hocon::Impl::UnsupportedOperationError < StandardError 6 | end 7 | -------------------------------------------------------------------------------- /spec/fixtures/test_utils/resources/file-include.conf: -------------------------------------------------------------------------------- 1 | base=41 2 | # included without file() in a subdir 3 | include "subdir/foo.conf" 4 | # included using file() in a subdir 5 | include file("subdir/baz.conf") 6 | -------------------------------------------------------------------------------- /spec/fixtures/test_utils/resources/include-from-list.conf: -------------------------------------------------------------------------------- 1 | // The {} inside the [] is needed because 2 | // just [ include ] means an array with the 3 | // string "include" in it. 4 | a = [ { include "test01.conf" } ] 5 | -------------------------------------------------------------------------------- /lib/hocon/impl/config_include_kind.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | 5 | module Hocon::Impl::ConfigIncludeKind 6 | URL = 0 7 | FILE = 1 8 | CLASSPATH = 2 9 | HEURISTIC = 3 10 | end 11 | -------------------------------------------------------------------------------- /lib/hocon/impl/mergeable_value.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/config_mergeable' 5 | 6 | class Hocon::Impl::MergeableValue < Hocon::ConfigMergeable 7 | # TODO 8 | end 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.gem 2 | *.rbc 3 | .bundle 4 | .config 5 | Gemfile.local 6 | Gemfile.lock 7 | coverage 8 | InstalledFiles 9 | lib/bundler/man 10 | pkg 11 | rdoc 12 | spec/reports 13 | test/tmp 14 | test/version_tmp 15 | tmp 16 | 17 | # YARD artifacts 18 | .yardoc 19 | _yardoc 20 | doc/ 21 | -------------------------------------------------------------------------------- /spec/fixtures/parse_render/example2/input.conf: -------------------------------------------------------------------------------- 1 | jruby-puppet: { 2 | jruby-pools: [{environment: production}] 3 | load-path: [/usr/lib/ruby/site_ruby/1.8, /usr/lib/ruby/site_ruby/1.8] 4 | master-conf-dir: /etc/puppet 5 | master-var-dir: /var/lib/puppet 6 | } 7 | 8 | webserver: { 9 | host: 1.2.3.4 10 | } 11 | -------------------------------------------------------------------------------- /spec/unit/hocon/README.md: -------------------------------------------------------------------------------- 1 | ## RUBY-SPECIFIC TESTS 2 | 3 | This directory should only contain tests that are specific to the Ruby library/API. 4 | Tests ported from the upstream Java library should live in spec/typesafe/config. 5 | 6 | Where possible it would be good to avoid sharing fixtures between the two types 7 | of tests as well. -------------------------------------------------------------------------------- /lib/hocon/impl/config_node_array.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/impl/config_node_complex_value' 5 | 6 | class Hocon::Impl::ConfigNodeArray 7 | include Hocon::Impl::ConfigNodeComplexValue 8 | def new_node(nodes) 9 | self.class.new(nodes) 10 | end 11 | end -------------------------------------------------------------------------------- /lib/hocon/impl/config_node_concatenation.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/impl/config_node_complex_value' 5 | 6 | class Hocon::Impl::ConfigNodeConcatenation 7 | include Hocon::Impl::ConfigNodeComplexValue 8 | def new_node(nodes) 9 | self.class.new(nodes) 10 | end 11 | end -------------------------------------------------------------------------------- /lib/hocon/config_syntax.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../hocon' 4 | 5 | module Hocon::ConfigSyntax 6 | JSON = 0 7 | CONF = 1 8 | # alias 'HOCON' to 'CONF' since some users may be more familiar with that 9 | HOCON = 1 10 | # we're not going to try to support .properties files any time soon :) 11 | #PROPERTIES = 2 12 | end 13 | -------------------------------------------------------------------------------- /lib/hocon/impl/array_iterator.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | 5 | class Hocon::Impl::ArrayIterator 6 | def initialize(a) 7 | @a = a 8 | @index = 0 9 | end 10 | 11 | def has_next? 12 | @index < @a.length 13 | end 14 | 15 | def next 16 | @index += 1 17 | @a[@index - 1] 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /spec/fixtures/parse_render/example1/output_nocomments.conf: -------------------------------------------------------------------------------- 1 | foo { 2 | bar { 3 | falsy=false 4 | truthy=true 5 | yahoo=yippee 6 | baz=42 7 | boom=[ 8 | 1, 9 | 2, 10 | { 11 | derp=duh 12 | }, 13 | 4 14 | ] 15 | abracadabra=hi 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /lib/hocon/impl/config_node_single_token.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/impl/abstract_config_node' 5 | 6 | class Hocon::Impl::ConfigNodeSingleToken 7 | include Hocon::Impl::AbstractConfigNode 8 | def initialize(t) 9 | @token = t 10 | end 11 | 12 | attr_reader :token 13 | 14 | def tokens 15 | [@token] 16 | end 17 | end -------------------------------------------------------------------------------- /spec/fixtures/parse_render/example2/output.conf: -------------------------------------------------------------------------------- 1 | jruby-puppet { 2 | jruby-pools=[ 3 | { 4 | environment=production 5 | } 6 | ] 7 | load-path=[ 8 | "/usr/lib/ruby/site_ruby/1.8", 9 | "/usr/lib/ruby/site_ruby/1.8" 10 | ] 11 | master-conf-dir="/etc/puppet" 12 | master-var-dir="/var/lib/puppet" 13 | } 14 | 15 | webserver { 16 | host="1.2.3.4" 17 | } 18 | -------------------------------------------------------------------------------- /spec/fixtures/parse_render/example2/output_nocomments.conf: -------------------------------------------------------------------------------- 1 | jruby-puppet { 2 | jruby-pools=[ 3 | { 4 | environment=production 5 | } 6 | ] 7 | load-path=[ 8 | "/usr/lib/ruby/site_ruby/1.8", 9 | "/usr/lib/ruby/site_ruby/1.8" 10 | ] 11 | master-conf-dir="/etc/puppet" 12 | master-var-dir="/var/lib/puppet" 13 | } 14 | 15 | webserver { 16 | host="1.2.3.4" 17 | } 18 | -------------------------------------------------------------------------------- /lib/hocon/impl/resolve_memos.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon' 4 | require_relative '../../hocon/impl' 5 | 6 | class Hocon::Impl::ResolveMemos 7 | 8 | def initialize(memos = {}) 9 | @memos = memos 10 | end 11 | 12 | def get(key) 13 | @memos[key] 14 | end 15 | 16 | def put(key, value) 17 | copy = @memos.clone 18 | copy[key] = value 19 | Hocon::Impl::ResolveMemos.new(copy) 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/hocon/impl/resolve_status.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | 5 | class Hocon::Impl::ResolveStatus 6 | UNRESOLVED = 0 7 | RESOLVED = 1 8 | 9 | def self.from_values(values) 10 | if values.any? { |v| v.resolve_status == UNRESOLVED } 11 | UNRESOLVED 12 | else 13 | RESOLVED 14 | end 15 | end 16 | 17 | def self.from_boolean(resolved) 18 | resolved ? RESOLVED : UNRESOLVED 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /spec/fixtures/parse_render/example1/input.conf: -------------------------------------------------------------------------------- 1 | # These are some opening comments 2 | # These are some additional opening comments 3 | foo.bar { 4 | // the baz is is blah blah 5 | baz = 42 6 | boom = [1, 2, {derp : duh }, 4] 7 | empty = [] 8 | 9 | # abracadabra setting 10 | abracadabra = "hi" 11 | } 12 | 13 | // as for the yippee 14 | # it entails some things 15 | foo.bar.yahoo = "yippee" 16 | 17 | # truthy 18 | foo.bar.truthy = true 19 | 20 | # falsy 21 | foo.bar.falsy = false -------------------------------------------------------------------------------- /lib/hocon/impl/abstract_config_node_value.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/impl/abstract_config_node' 5 | 6 | # This essentially exists in the upstream so we can ensure only certain types of 7 | # config nodes can be passed into some methods. That's not a problem in Ruby, so this is 8 | # unnecessary, but it seems best to keep it around for consistency 9 | module Hocon::Impl::AbstractConfigNodeValue 10 | include Hocon::Impl::AbstractConfigNode 11 | end -------------------------------------------------------------------------------- /lib/hocon/impl/origin_type.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | 5 | module Hocon::Impl::OriginType 6 | ## for now, we only support a subset of these 7 | GENERIC = 0 8 | FILE = 1 9 | #URL = 2 10 | # We don't actually support loading from the classpath / loadpath, which is 11 | # what 'RESOURCE' is about in the upstream library. However, some code paths 12 | # still flow through our simplistic implementation of `ParseableResource`, so 13 | # we need this constant. 14 | RESOURCE = 3 15 | end 16 | -------------------------------------------------------------------------------- /lib/hocon/impl/from_map_mode.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/config_error' 5 | 6 | module Hocon::Impl::FromMapMode 7 | KEYS_ARE_PATHS = 0 8 | KEYS_ARE_KEYS = 1 9 | ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError 10 | 11 | def self.map_mode_name(from_map_mode) 12 | case from_map_mode 13 | when KEYS_ARE_PATHS then "KEYS_ARE_PATHS" 14 | when KEYS_ARE_KEYS then "KEYS_ARE_KEYS" 15 | else raise ConfigBugOrBrokenError.new("Unrecognized FromMapMode #{from_map_mode}") 16 | end 17 | end 18 | end -------------------------------------------------------------------------------- /lib/hocon/impl/unmergeable.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/config_error' 5 | 6 | 7 | # 8 | # Interface that tags a ConfigValue that is not mergeable until after 9 | # substitutions are resolved. Basically these are special ConfigValue that 10 | # never appear in a resolved tree, like {@link ConfigSubstitution} and 11 | # {@link ConfigDelayedMerge}. 12 | # 13 | module Hocon::Impl::Unmergeable 14 | def unmerged_values 15 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Unmergeable` must implement `unmerged_values` (#{self.class})" 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /spec/fixtures/parse_render/example1/output.conf: -------------------------------------------------------------------------------- 1 | foo={ 2 | # These are some opening comments 3 | # These are some additional opening comments 4 | bar={ 5 | # falsy 6 | falsy=false 7 | # truthy 8 | truthy=true 9 | # as for the yippee 10 | # it entails some things 11 | yahoo=yippee 12 | # the baz is is blah blah 13 | baz=42 14 | boom=[ 15 | 1, 16 | 2, 17 | { 18 | derp=duh 19 | }, 20 | 4 21 | ] 22 | empty=[] 23 | # abracadabra setting 24 | abracadabra=hi 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /lib/hocon/impl/config_null.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/config_value_type' 5 | 6 | class Hocon::Impl::ConfigNull 7 | include Hocon::Impl::AbstractConfigValue 8 | 9 | def initialize(origin) 10 | super(origin) 11 | end 12 | 13 | def value_type 14 | Hocon::ConfigValueType::NULL 15 | end 16 | 17 | def unwrapped 18 | nil 19 | end 20 | 21 | def transform_to_string 22 | "null" 23 | end 24 | 25 | def render_value_to_sb(sb, indent, at_root, options) 26 | sb << "null" 27 | end 28 | 29 | def new_copy(origin) 30 | self.class.new(origin) 31 | end 32 | 33 | end 34 | -------------------------------------------------------------------------------- /lib/hocon/impl/config_boolean.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/impl/abstract_config_value' 5 | 6 | class Hocon::Impl::ConfigBoolean 7 | include Hocon::Impl::AbstractConfigValue 8 | 9 | def initialize(origin, value) 10 | super(origin) 11 | @value = value 12 | end 13 | 14 | attr_reader :value 15 | 16 | def value_type 17 | Hocon::ConfigValueType::BOOLEAN 18 | end 19 | 20 | def unwrapped 21 | @value 22 | end 23 | 24 | def transform_to_string 25 | @value.to_s 26 | end 27 | 28 | def new_copy(origin) 29 | Hocon::Impl::ConfigBoolean.new(origin, @value) 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/hocon/impl/config_node_comment.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/config_error' 5 | require_relative '../../hocon/impl/config_node_single_token' 6 | require_relative '../../hocon/impl/tokens' 7 | 8 | class Hocon::Impl::ConfigNodeComment < Hocon::Impl::ConfigNodeSingleToken 9 | def initialize(comment) 10 | super(comment) 11 | unless Hocon::Impl::Tokens.comment?(@token) 12 | raise Hocon::ConfigError::ConfigBugOrBrokenError, 'Tried to create a ConfigNodeComment from a non-comment token' 13 | end 14 | end 15 | 16 | def comment_text 17 | Hocon::Impl::Tokens.comment_text(@token) 18 | end 19 | end -------------------------------------------------------------------------------- /lib/hocon/config_resolve_options.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../hocon' 4 | 5 | class Hocon::ConfigResolveOptions 6 | attr_reader :use_system_environment, :allow_unresolved 7 | 8 | def initialize(use_system_environment, allow_unresolved) 9 | @use_system_environment = use_system_environment 10 | @allow_unresolved = allow_unresolved 11 | end 12 | 13 | def set_use_system_environment(value) 14 | self.class.new(value, @allow_unresolved) 15 | end 16 | 17 | def set_allow_unresolved(value) 18 | self.class.new(@use_system_environment, value) 19 | end 20 | 21 | class << self 22 | 23 | def defaults 24 | self.new(true, false) 25 | end 26 | 27 | def no_system 28 | defaults.set_use_system_environment(false) 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /lib/hocon/impl/abstract_config_node.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/parser/config_node' 5 | require_relative '../../hocon/config_error' 6 | 7 | module Hocon::Impl::AbstractConfigNode 8 | include Hocon::Parser::ConfigNode 9 | def tokens 10 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of AbstractConfigNode should override `tokens` (#{self.class})" 11 | end 12 | 13 | def render 14 | orig_text = StringIO.new 15 | tokens.each do |t| 16 | orig_text << t.token_text 17 | end 18 | orig_text.string 19 | end 20 | 21 | def ==(other) 22 | other.is_a?(Hocon::Impl::AbstractConfigNode) && 23 | (render == other.render) 24 | end 25 | 26 | def hash 27 | render.hash 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/hocon/config_value_type.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../hocon' 4 | require_relative '../hocon/config_error' 5 | 6 | # 7 | # The type of a configuration value (following the JSON type schema). 9 | # 10 | module Hocon::ConfigValueType 11 | OBJECT = 0 12 | LIST = 1 13 | NUMBER = 2 14 | BOOLEAN = 3 15 | NULL = 4 16 | STRING = 5 17 | 18 | def self.value_type_name(config_value_type) 19 | case config_value_type 20 | when OBJECT then "OBJECT" 21 | when LIST then "LIST" 22 | when NUMBER then "NUMBER" 23 | when BOOLEAN then "BOOLEAN" 24 | when NULL then "NULL" 25 | when STRING then "STRING" 26 | else raise Hocon::ConfigError::ConfigBugOrBrokenError, "Unrecognized value type '#{config_value_type}'" 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/hocon/impl/replaceable_merge_stack.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/impl/container' 5 | require_relative '../../hocon/config_error' 6 | 7 | # 8 | # Implemented by a merge stack (ConfigDelayedMerge, ConfigDelayedMergeObject) 9 | # that replaces itself during substitution resolution in order to implement 10 | # "look backwards only" semantics. 11 | # 12 | module Hocon::Impl::ReplaceableMergeStack 13 | include Hocon::Impl::Container 14 | 15 | # 16 | # Make a replacement for this object skipping the given number of elements 17 | # which are lower in merge priority. 18 | # 19 | def make_replacement(context, skipping) 20 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ReplaceableMergeStack` must implement `make_replacement` (#{self.class})" 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/hocon/impl/substitution_expression.rb: -------------------------------------------------------------------------------- 1 | require_relative '../../hocon/impl' 2 | 3 | 4 | class Hocon::Impl::SubstitutionExpression 5 | 6 | def initialize(path, optional) 7 | @path = path 8 | @optional = optional 9 | end 10 | attr_reader :path, :optional 11 | 12 | def change_path(new_path) 13 | if new_path == @path 14 | self 15 | else 16 | Hocon::Impl::SubstitutionExpression.new(new_path, @optional) 17 | end 18 | end 19 | 20 | def to_s 21 | "${#{@optional ? "?" : ""}#{@path.render}}" 22 | end 23 | 24 | def ==(other) 25 | if other.is_a? Hocon::Impl::SubstitutionExpression 26 | other.path == @path && other.optional == @optional 27 | else 28 | false 29 | end 30 | end 31 | 32 | def hash 33 | h = 41 * (41 + @path.hash) 34 | h = 41 * (h + (optional ? 1 : 0)) 35 | 36 | h 37 | end 38 | end -------------------------------------------------------------------------------- /.github/workflows/rspec_tests.yaml: -------------------------------------------------------------------------------- 1 | name: RSpec tests 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | rspec_tests: 14 | name: RSpec (Ruby ${{ matrix.ruby }}) 15 | runs-on: ubuntu-latest 16 | strategy: 17 | matrix: 18 | ruby: [ '2.7', '3.2' ] 19 | steps: 20 | - name: Checkout current PR 21 | uses: actions/checkout@v4 22 | with: 23 | fetch-depth: 0 24 | - name: Install Ruby version ${{ matrix.ruby }} 25 | uses: ruby/setup-ruby@v1 26 | with: 27 | ruby-version: ${{ matrix.ruby }} 28 | - name: Update rubygems and install gems 29 | run: | 30 | gem update --system --silent --no-document 31 | bundle install --jobs 4 --retry 3 32 | - run: bundle exec rspec spec 33 | -------------------------------------------------------------------------------- /lib/hocon/impl/config_node_include.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/config_error' 5 | require_relative '../../hocon/impl/abstract_config_node' 6 | require_relative '../../hocon/impl/config_node_simple_value' 7 | 8 | class Hocon::Impl::ConfigNodeInclude 9 | include Hocon::Impl::AbstractConfigNode 10 | def initialize(children, kind) 11 | @children = children 12 | @kind = kind 13 | end 14 | 15 | attr_reader :kind, :children 16 | 17 | def tokens 18 | tokens = [] 19 | @children.each do |child| 20 | tokens += child.tokens 21 | end 22 | tokens 23 | end 24 | 25 | def name 26 | @children.each do |child| 27 | if child.is_a?(Hocon::Impl::ConfigNodeSimpleValue) 28 | return Hocon::Impl::Tokens.value(child.token).unwrapped 29 | end 30 | end 31 | nil 32 | end 33 | end -------------------------------------------------------------------------------- /lib/hocon/impl/config_int.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/impl/config_number' 5 | require_relative '../../hocon/config_value_type' 6 | 7 | class Hocon::Impl::ConfigInt < Hocon::Impl::ConfigNumber 8 | def initialize(origin, value, original_text) 9 | super(origin, original_text) 10 | @value = value 11 | end 12 | 13 | attr_reader :value 14 | 15 | def value_type 16 | Hocon::ConfigValueType::NUMBER 17 | end 18 | 19 | def unwrapped 20 | @value 21 | end 22 | 23 | def transform_to_string 24 | s = super 25 | if s.nil? 26 | self.to_s 27 | else 28 | s 29 | end 30 | end 31 | 32 | def long_value 33 | @value 34 | end 35 | 36 | def double_value 37 | @value 38 | end 39 | 40 | def new_copy(origin) 41 | Hocon::Impl::ConfigInt.new(origin, @value, @original_text) 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /spec/fixtures/test_utils/resources/test03.conf: -------------------------------------------------------------------------------- 1 | { 2 | "test01" : { 3 | "ints" : 12, 4 | include "test01", 5 | "booleans" : 42 6 | }, 7 | 8 | "test02" : { 9 | include 10 | 11 | "test02.conf" 12 | }, 13 | 14 | "equiv01" : { 15 | include "equiv01/original.json" 16 | }, 17 | 18 | # missing includes are supposed to be silently ignored 19 | nonexistent { 20 | include "nothere" 21 | include "nothere.conf" 22 | include "nothere.json" 23 | include "nothere.properties" 24 | } 25 | 26 | # make sure included file substitutions fall back to parent file, 27 | # both when the include is at the root (so doesn't need to have 28 | # substitutions adjusted) and when it is not. 29 | foo="This is in the including file" 30 | bar="This is in the including file" 31 | include "test03-included.conf" 32 | 33 | subtree { 34 | include "test03-included.conf" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /hocon.gemspec: -------------------------------------------------------------------------------- 1 | $LOAD_PATH.unshift File.expand_path("../lib", __FILE__) 2 | require 'hocon/version' 3 | 4 | Gem::Specification.new do |s| 5 | s.name = 'hocon' 6 | s.version = Hocon::Version::STRING 7 | s.date = '2016-10-27' 8 | s.summary = "HOCON Config Library" 9 | s.description = "== A port of the Java {Typesafe Config}[https://github.com/typesafehub/config] library to Ruby" 10 | s.authors = ["Chris Price", "Wayne Warren", "Preben Ingvaldsen", "Joe Pinsonault", "Kevin Corcoran", "Jane Lu"] 11 | s.email = 'chris@puppetlabs.com' 12 | s.files = Dir["{lib}/**/*.rb", "bin/*", "LICENSE", "*.md"] 13 | s.require_paths = ["lib"] 14 | s.executables = ['hocon'] 15 | s.homepage = 'https://github.com/puppetlabs/ruby-hocon' 16 | s.license = 'Apache-2.0' 17 | s.required_ruby_version = '>=1.9.0' 18 | 19 | # Testing dependencies 20 | s.add_development_dependency 'rspec', '~> 2.14' 21 | end 22 | -------------------------------------------------------------------------------- /lib/hocon/impl/config_double.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/impl/config_number' 5 | 6 | class Hocon::Impl::ConfigDouble < Hocon::Impl::ConfigNumber 7 | def initialize(origin, value, original_text) 8 | super(origin, original_text) 9 | @value = value 10 | end 11 | 12 | attr_reader :value 13 | 14 | def value_type 15 | Hocon::ConfigValueType::NUMBER 16 | end 17 | 18 | def unwrapped 19 | @value 20 | end 21 | 22 | def transform_to_string 23 | s = super 24 | if s.nil? 25 | @value.to_s 26 | else 27 | s 28 | end 29 | end 30 | 31 | def long_value 32 | @value.to_i 33 | end 34 | 35 | def double_value 36 | @value 37 | end 38 | 39 | def new_copy(origin) 40 | self.class.new(origin, @value, original_text) 41 | end 42 | 43 | # NOTE: skipping `writeReplace` from upstream, because it involves serialization 44 | end 45 | -------------------------------------------------------------------------------- /lib/hocon/impl/resolve_result.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon' 4 | require_relative '../../hocon/impl' 5 | 6 | # value is allowed to be null 7 | class Hocon::Impl::ResolveResult 8 | ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError 9 | 10 | attr_accessor :context, :value 11 | 12 | def initialize(context, value) 13 | @context = context 14 | @value = value 15 | end 16 | 17 | def self.make(context, value) 18 | self.new(context, value) 19 | end 20 | 21 | def as_object_result 22 | unless @value.is_a?(Hocon::Impl::AbstractConfigObject) 23 | raise ConfigBugOrBrokenError.new("Expecting a resolve result to be an object, but it was #{@value}") 24 | end 25 | self 26 | end 27 | 28 | def as_value_result 29 | self 30 | end 31 | 32 | def pop_trace 33 | self.class.make(@context.pop_trace, value) 34 | end 35 | 36 | def to_s 37 | "ResolveResult(#{@value})" 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /lib/hocon/impl/simple_include_context.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/impl/simple_includer' 5 | require_relative '../../hocon/config_include_context' 6 | require_relative '../../hocon/impl/config_impl' 7 | 8 | class Hocon::Impl::SimpleIncludeContext 9 | include Hocon::ConfigIncludeContext 10 | 11 | def initialize(parseable) 12 | @parseable = parseable 13 | end 14 | 15 | def with_parseable(parseable) 16 | if parseable.equal?(@parseable) 17 | self 18 | else 19 | self.class.new(parseable) 20 | end 21 | end 22 | 23 | def relative_to(filename) 24 | if Hocon::Impl::ConfigImpl.trace_loads_enabled 25 | Hocon::Impl::ConfigImpl.trace("Looking for '#{filename}' relative to #{@parseable}") 26 | end 27 | if ! @parseable.nil? 28 | @parseable.relative_to(filename) 29 | else 30 | nil 31 | end 32 | end 33 | 34 | def parse_options 35 | Hocon::Impl::SimpleIncluder.clear_for_include(@parseable.options) 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/hocon/impl/url.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'uri' 4 | require_relative '../../hocon/impl' 5 | 6 | # There are several places in the Java codebase that 7 | # use Java's URL constructor, and rely on it to throw 8 | # a `MalformedURLException` if the URL isn't valid. 9 | # 10 | # Ruby doesn't really have a similar constructor / 11 | # validator, so this is a little shim to hopefully 12 | # make the ported code match up with the upstream more 13 | # closely. 14 | class Hocon::Impl::Url 15 | class MalformedUrlError < StandardError 16 | def initialize(msg, cause = nil) 17 | super(msg) 18 | @cause = cause 19 | end 20 | end 21 | 22 | def initialize(url) 23 | begin 24 | # URI::parse wants a string 25 | @url = URI.parse(url.to_s) 26 | if !(@url.kind_of?(URI::HTTP)) 27 | raise MalformedUrlError, "Unrecognized URL: '#{url}'" 28 | end 29 | rescue URI::InvalidURIError => e 30 | raise MalformedUrlError.new("Unrecognized URL: '#{url}' (error: #{e})", e) 31 | end 32 | end 33 | 34 | def to_s 35 | @url.to_s 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/hocon/config_includer_file.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../hocon' 4 | require_relative '../hocon/config_error' 5 | 6 | # 7 | # Implement this in addition to {@link ConfigIncluder} if you want to 8 | # support inclusion of files with the {@code include file("filename")} syntax. 9 | # If you do not implement this but do implement {@link ConfigIncluder}, 10 | # attempts to load files will use the default includer. 11 | # 12 | module Hocon::ConfigIncluderFile 13 | # 14 | # Parses another item to be included. The returned object typically would 15 | # not have substitutions resolved. You can throw a ConfigException here to 16 | # abort parsing, or return an empty object, but may not return null. 17 | # 18 | # @param context 19 | # some info about the include context 20 | # @param what 21 | # the include statement's argument 22 | # @return a non-null ConfigObject 23 | # 24 | def include_file(context, what) 25 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigIncluderFile` must implement `include_file` (#{self.class})" 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/hocon/impl/memo_key.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon' 4 | require_relative '../../hocon/impl' 5 | 6 | class Hocon::Impl::MemoKey 7 | 8 | def initialize(value, restrict_to_child_or_nil) 9 | @value = value 10 | @restrict_to_child_or_nil = restrict_to_child_or_nil 11 | end 12 | 13 | def hash 14 | h = @value.hash 15 | if @restrict_to_child_or_nil != nil 16 | h + 41 * (41 + @restrict_to_child_or_nil.hash) 17 | else 18 | h 19 | end 20 | end 21 | 22 | def ==(other) 23 | if other.is_a?(self.class) 24 | o = other 25 | if !o.value.equal?(@value) 26 | return false 27 | elsif o.restrict_to_child_or_nil.equals(@restrict_to_child_or_nil) 28 | return true 29 | elsif o.restrict_to_child_or_nil == nil || @restrict_to_child_or_nil == nil 30 | return false 31 | else 32 | return o.restrict_to_child_or_nil == @restrict_to_child_or_nil 33 | end 34 | else 35 | false 36 | end 37 | end 38 | 39 | def to_s 40 | "MemoKey(#{@value}@#{@value.hash},#{@restrict_to_child_or_nil})" 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /lib/hocon/impl/token.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/impl/token_type' 5 | 6 | class Hocon::Impl::Token 7 | attr_reader :token_type, :token_text 8 | def self.new_without_origin(token_type, debug_string, token_text) 9 | Hocon::Impl::Token.new(token_type, nil, token_text, debug_string) 10 | end 11 | 12 | def initialize(token_type, origin, token_text = nil, debug_string = nil) 13 | @token_type = token_type 14 | @origin = origin 15 | @token_text = token_text 16 | @debug_string = debug_string 17 | end 18 | 19 | attr_reader :origin 20 | 21 | def line_number 22 | if @origin 23 | @origin.line_number 24 | else 25 | -1 26 | end 27 | end 28 | 29 | def to_s 30 | if !@debug_string.nil? 31 | @debug_string 32 | else 33 | Hocon::Impl::TokenType.token_type_name(@token_type) 34 | end 35 | end 36 | 37 | def ==(other) 38 | # @origin deliberately left out 39 | other.is_a?(Hocon::Impl::Token) && @token_type == other.token_type 40 | end 41 | 42 | def hash 43 | @token_type.hash 44 | end 45 | end 46 | -------------------------------------------------------------------------------- /.github/workflows/mend.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Mend Monitor 3 | on: 4 | push: 5 | branches: 6 | - main 7 | jobs: 8 | mend_monitor: 9 | if: ${{ github.repository_owner == 'puppetlabs' }} 10 | runs-on: ubuntu-latest 11 | name: Mend Monitor 12 | steps: 13 | - name: Checkout current PR 14 | uses: actions/checkout@v4 15 | - name: Setup Ruby 16 | uses: ruby/setup-ruby@v1 17 | with: 18 | ruby-version: 2.7 19 | - name: Create lock 20 | run: bundle lock 21 | - uses: actions/setup-java@v3 22 | with: 23 | distribution: 'temurin' 24 | java-version: '17' 25 | - name: Download Mend 26 | run: curl -o wss-unified-agent.jar https://unified-agent.s3.amazonaws.com/wss-unified-agent.jar 27 | - name: Run Mend 28 | run: java -jar wss-unified-agent.jar 29 | env: 30 | WS_APIKEY: ${{ secrets.MEND_API_KEY }} 31 | WS_WSS_URL: https://saas-eu.whitesourcesoftware.com/agent 32 | WS_USERKEY: ${{ secrets.MEND_TOKEN }} 33 | WS_PRODUCTNAME: Puppet Agent 34 | WS_PROJECTNAME: ${{ github.event.repository.name }} 35 | -------------------------------------------------------------------------------- /lib/hocon/parser/config_node.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/parser' 4 | require_relative '../../hocon/config_error' 5 | 6 | # 7 | # An immutable node that makes up the ConfigDocument AST, and which can be 8 | # used to reproduce part or all of the original text of an input. 9 | # 10 | #
11 | # Because this object is immutable, it is safe to use from multiple threads and 12 | # there's no need for "defensive copies." 13 | # 14 | #
15 | # Do not implement interface {@code ConfigNode}; it should only be 16 | # implemented by the config library. Arbitrary implementations will not work 17 | # because the library internals assume a specific concrete implementation. 18 | # Also, this interface is likely to grow new methods over time, so third-party 19 | # implementations will break. 20 | # 21 | 22 | module Hocon::Parser::ConfigNode 23 | # 24 | # The original text of the input which was used to form this particular node. 25 | # @return the original text used to form this node as a String 26 | # 27 | def render 28 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigNode should override `render` (#{self.class})" 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/hocon/impl/container.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/config_value' 5 | require_relative '../../hocon/config_error' 6 | 7 | # An AbstractConfigValue which contains other values. Java has no way to 8 | # express "this has to be an AbstractConfigValue also" other than making 9 | # AbstractConfigValue an interface which would be aggravating. But we can say 10 | # we are a ConfigValue. 11 | module Hocon::Impl::Container 12 | include Hocon::ConfigValue 13 | # 14 | # Replace a child of this value. CAUTION if replacement is null, delete the 15 | # child, which may also delete the parent, or make the parent into a 16 | # non-container. 17 | # 18 | def replace_child(child, replacement) 19 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Container` must implement `replace_child` (#{self.class})" 20 | end 21 | 22 | # 23 | # Super-expensive full traversal to see if descendant is anywhere 24 | # underneath this container. 25 | # 26 | def has_descendant?(descendant) 27 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Container` must implement `has_descendant?` (#{self.class})" 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /lib/hocon/parser/config_document_factory.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/parser' 4 | require_relative '../../hocon/impl/parseable' 5 | require_relative '../../hocon/config_parse_options' 6 | 7 | # 8 | # Factory for creating {@link 9 | # com.typesafe.config.parser.ConfigDocument} instances. 10 | # 11 | class Hocon::Parser::ConfigDocumentFactory 12 | # 13 | # Parses a file into a ConfigDocument instance. 14 | # 15 | # @param file 16 | # the file to parse 17 | # @param options 18 | # parse options to control how the file is interpreted 19 | # @return the parsed configuration 20 | # @throws com.typesafe.config.ConfigException on IO or parse errors 21 | # 22 | def self.parse_file(file, options = Hocon::ConfigParseOptions.defaults) 23 | Hocon::Impl::Parseable.new_file(file, options).parse_config_document 24 | end 25 | 26 | # 27 | # Parses a string which should be valid HOCON or JSON. 28 | # 29 | # @param s string to parse 30 | # @param options parse options 31 | # @return the parsed configuration 32 | # 33 | def self.parse_string(s, options = Hocon::ConfigParseOptions.defaults) 34 | Hocon::Impl::Parseable.new_string(s, options).parse_config_document 35 | end 36 | end -------------------------------------------------------------------------------- /lib/hocon/impl/path_builder.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/impl/path' 5 | require_relative '../../hocon/config_error' 6 | 7 | class Hocon::Impl::PathBuilder 8 | 9 | def initialize 10 | @keys = [] 11 | @result = nil 12 | end 13 | 14 | def check_can_append 15 | if @result 16 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "Adding to PathBuilder after getting result" 17 | end 18 | end 19 | 20 | def append_key(key) 21 | check_can_append 22 | @keys.push(key) 23 | end 24 | 25 | def append_path(path) 26 | check_can_append 27 | 28 | first = path.first 29 | remainder = path.remainder 30 | 31 | loop do 32 | @keys.push(first) 33 | 34 | if !remainder.nil? 35 | first = remainder.first 36 | remainder = remainder.remainder 37 | else 38 | break 39 | end 40 | end 41 | end 42 | 43 | def result 44 | # note: if keys is empty, we want to return nil, which is a valid 45 | # empty path 46 | if @result.nil? 47 | remainder = nil 48 | while !@keys.empty? 49 | key = @keys.pop 50 | remainder = Hocon::Impl::Path.new(key, remainder) 51 | end 52 | @result = remainder 53 | end 54 | @result 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/hocon/impl/token_type.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | 5 | class Hocon::Impl::TokenType 6 | START = 0 7 | EOF = 1 8 | COMMA = 2 9 | EQUALS = 3 10 | COLON = 4 11 | OPEN_CURLY = 5 12 | CLOSE_CURLY = 6 13 | OPEN_SQUARE = 7 14 | CLOSE_SQUARE = 8 15 | VALUE = 9 16 | NEWLINE = 10 17 | UNQUOTED_TEXT = 11 18 | SUBSTITUTION = 12 19 | PROBLEM = 13 20 | COMMENT = 14 21 | PLUS_EQUALS = 15 22 | IGNORED_WHITESPACE = 16 23 | 24 | def self.token_type_name(token_type) 25 | case token_type 26 | when START then "START" 27 | when EOF then "EOF" 28 | when COMMA then "COMMA" 29 | when EQUALS then "EQUALS" 30 | when COLON then "COLON" 31 | when OPEN_CURLY then "OPEN_CURLY" 32 | when CLOSE_CURLY then "CLOSE_CURLY" 33 | when OPEN_SQUARE then "OPEN_SQUARE" 34 | when CLOSE_SQUARE then "CLOSE_SQUARE" 35 | when VALUE then "VALUE" 36 | when NEWLINE then "NEWLINE" 37 | when UNQUOTED_TEXT then "UNQUOTED_TEXT" 38 | when SUBSTITUTION then "SUBSTITUTION" 39 | when PROBLEM then "PROBLEM" 40 | when COMMENT then "COMMENT" 41 | when PLUS_EQUALS then "PLUS_EQUALS" 42 | when IGNORED_WHITESPACE then "IGNORED_WHITESPACE" 43 | else raise ConfigBugOrBrokenError, "Unrecognized token type #{token_type}" 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/hocon/impl/config_node_path.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/impl/tokens' 5 | require_relative '../../hocon/impl/abstract_config_node' 6 | 7 | class Hocon::Impl::ConfigNodePath 8 | include Hocon::Impl::AbstractConfigNode 9 | Tokens = Hocon::Impl::Tokens 10 | 11 | def initialize(path, tokens) 12 | @path = path 13 | @tokens = tokens 14 | end 15 | 16 | attr_reader :tokens 17 | 18 | def value 19 | @path 20 | end 21 | 22 | def sub_path(to_remove) 23 | period_count = 0 24 | tokens_copy = tokens.clone 25 | (0..tokens_copy.size - 1).each do |i| 26 | if Tokens.unquoted_text?(tokens_copy[i]) && 27 | tokens_copy[i].token_text == "." 28 | period_count += 1 29 | end 30 | 31 | if period_count == to_remove 32 | return self.class.new(@path.sub_path_to_end(to_remove), tokens_copy[i + 1..tokens_copy.size]) 33 | end 34 | end 35 | raise ConfigBugOrBrokenError, "Tried to remove too many elements from a Path node" 36 | end 37 | 38 | def first 39 | tokens_copy = tokens.clone 40 | (0..tokens_copy.size - 1).each do |i| 41 | if Tokens.unquoted_text?(tokens_copy[i]) && 42 | tokens_copy[i].token_text == "." 43 | return self.class.new(@path.sub_path(0, 1), tokens_copy[0, i]) 44 | end 45 | end 46 | self 47 | end 48 | end 49 | -------------------------------------------------------------------------------- /lib/hocon/config_render_options.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../hocon' 4 | 5 | class Hocon::ConfigRenderOptions 6 | def initialize(origin_comments, comments, formatted, json, key_value_separator=:equals) 7 | @origin_comments = origin_comments 8 | @comments = comments 9 | @formatted = formatted 10 | @json = json 11 | @key_value_separator = key_value_separator 12 | end 13 | 14 | attr_accessor :origin_comments, :comments, :formatted, :json, :key_value_separator 15 | 16 | def origin_comments? 17 | @origin_comments 18 | end 19 | def comments? 20 | @comments 21 | end 22 | def formatted? 23 | @formatted 24 | end 25 | def json? 26 | @json 27 | end 28 | 29 | # 30 | # Returns the default render options which are verbose (commented and 31 | # formatted). See {@link ConfigRenderOptions#concise} for stripped-down 32 | # options. This rendering will not be valid JSON since it has comments. 33 | # 34 | # @return the default render options 35 | # 36 | def self.defaults 37 | Hocon::ConfigRenderOptions.new(true, true, true, true) 38 | end 39 | 40 | # 41 | # Returns concise render options (no whitespace or comments). For a 42 | # resolved {@link Config}, the concise rendering will be valid JSON. 43 | # 44 | # @return the concise render options 45 | # 46 | def self.concise 47 | Hocon::ConfigRenderOptions.new(false, false, false, true) 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /spec/spec_helper.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures") 4 | 5 | EXAMPLE1 = { :hash => 6 | {"foo" => { 7 | "bar" => { 8 | "baz" => 42, 9 | "abracadabra" => "hi", 10 | "yahoo" => "yippee", 11 | "boom" => [1, 2, {"derp" => "duh"}, 4], 12 | "empty" => [], 13 | "truthy" => true, 14 | "falsy" => false 15 | }}}, 16 | :name => "example1", 17 | } 18 | 19 | EXAMPLE2 = { :hash => 20 | {"jruby-puppet"=> { 21 | "jruby-pools" => [{"environment" => "production"}], 22 | "load-path" => ["/usr/lib/ruby/site_ruby/1.8", "/usr/lib/ruby/site_ruby/1.8"], 23 | "master-conf-dir" => "/etc/puppet", 24 | "master-var-dir" => "/var/lib/puppet", 25 | }, 26 | "webserver" => {"host" => "1.2.3.4"}}, 27 | :name => "example2", 28 | } 29 | 30 | EXAMPLE3 = { :hash => 31 | {"a" => true, 32 | "b" => true}, 33 | :name => "example3", 34 | } 35 | 36 | EXAMPLE4 = { :hash => 37 | {"kermit" => "frog", 38 | "miss" => "piggy", 39 | "bert" => "ernie", 40 | "janice" => "guitar"}, 41 | :name => "example4", 42 | } 43 | 44 | # set values out of order to verify they return in-order 45 | # must be set prior to config_impl.rb loading 46 | ENV['ENVARRAY.1'] = 'bar' 47 | ENV['ENVARRAY.2'] = 'baz' 48 | ENV['ENVARRAY.0'] = 'foo' 49 | -------------------------------------------------------------------------------- /lib/hocon/impl/config_node_simple_value.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/config_error' 4 | require_relative '../../hocon/impl' 5 | require_relative '../../hocon/impl/abstract_config_node_value' 6 | require_relative '../../hocon/impl/array_iterator' 7 | require_relative '../../hocon/impl/config_reference' 8 | require_relative '../../hocon/impl/config_string' 9 | require_relative '../../hocon/impl/path_parser' 10 | require_relative '../../hocon/impl/substitution_expression' 11 | require_relative '../../hocon/impl/tokens' 12 | 13 | class Hocon::Impl::ConfigNodeSimpleValue 14 | include Hocon::Impl::AbstractConfigNodeValue 15 | 16 | Tokens = Hocon::Impl::Tokens 17 | 18 | def initialize(value) 19 | @token = value 20 | end 21 | 22 | attr_reader :token 23 | 24 | def tokens 25 | [@token] 26 | end 27 | 28 | def value 29 | if Tokens.value?(@token) 30 | return Tokens.value(@token) 31 | elsif Tokens.unquoted_text?(@token) 32 | return Hocon::Impl::ConfigString::Unquoted.new(@token.origin, Tokens.unquoted_text(@token)) 33 | elsif Tokens.substitution?(@token) 34 | expression = Tokens.get_substitution_path_expression(@token) 35 | path = Hocon::Impl::PathParser.parse_path_expression(Hocon::Impl::ArrayIterator.new(expression), @token.origin) 36 | optional = Tokens.get_substitution_optional(@token) 37 | 38 | return Hocon::Impl::ConfigReference.new(@token.origin, Hocon::Impl::SubstitutionExpression.new(path, optional)) 39 | end 40 | raise Hocon::ConfigError::ConfigBugOrBrokenError, 'ConfigNodeSimpleValue did not contain a valid value token' 41 | end 42 | end -------------------------------------------------------------------------------- /lib/hocon/impl/simple_config_document.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../../hocon/impl' 4 | require_relative '../../hocon/parser/config_document' 5 | require_relative '../../hocon/impl/config_document_parser' 6 | require_relative '../../hocon/config_render_options' 7 | 8 | class Hocon::Impl::SimpleConfigDocument 9 | include Hocon::Parser::ConfigDocument 10 | 11 | def initialize(parsed_node, parse_options) 12 | @config_node_tree = parsed_node 13 | @parse_options = parse_options 14 | end 15 | 16 | def set_value(path, new_value) 17 | origin = Hocon::Impl::SimpleConfigOrigin.new_simple("single value parsing") 18 | reader = StringIO.new(new_value) 19 | tokens = Hocon::Impl::Tokenizer.tokenize(origin, reader, @parse_options.syntax) 20 | parsed_value = Hocon::Impl::ConfigDocumentParser.parse_value(tokens, origin, @parse_options) 21 | reader.close 22 | 23 | self.class.new(@config_node_tree.set_value(path, parsed_value, @parse_options.syntax), @parse_options) 24 | end 25 | 26 | def set_config_value(path, new_value) 27 | options = Hocon::ConfigRenderOptions.defaults 28 | options.origin_comments = false 29 | set_value(path, new_value.render(options).strip) 30 | end 31 | 32 | def remove_value(path) 33 | self.class.new(@config_node_tree.set_value(path, nil, @parse_options.syntax), @parse_options) 34 | end 35 | 36 | def has_value?(path) 37 | @config_node_tree.has_value(path) 38 | end 39 | 40 | def render 41 | @config_node_tree.render 42 | end 43 | 44 | def ==(other) 45 | other.class.ancestors.include?(Hocon::Parser::ConfigDocument) && render == other.render 46 | end 47 | 48 | def hash 49 | render.hash 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /lib/hocon/config_parseable.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../hocon' 4 | require_relative '../hocon/config_error' 5 | 6 | # 7 | # An opaque handle to something that can be parsed, obtained from 8 | # {@link ConfigIncludeContext}. 9 | # 10 | #
11 | # Do not implement this interface; it should only be implemented by 12 | # the config library. Arbitrary implementations will not work because the 13 | # library internals assume a specific concrete implementation. Also, this 14 | # interface is likely to grow new methods over time, so third-party 15 | # implementations will break. 16 | # 17 | module Hocon::ConfigParseable 18 | # 19 | # Parse whatever it is. The options should come from 20 | # {@link ConfigParseable#options options()} but you could tweak them if you 21 | # like. 22 | # 23 | # @param options 24 | # parse options, should be based on the ones from 25 | # {@link ConfigParseable#options options()} 26 | # @return the parsed object 27 | # 28 | def parse(options) 29 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigParseable` must implement `parse` (#{self.class})" 30 | end 31 | 32 | # 33 | # Returns a {@link ConfigOrigin} describing the origin of the parseable 34 | # item. 35 | # @return the origin of the parseable item 36 | # 37 | def origin 38 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigParseable` must implement `origin` (#{self.class})" 39 | end 40 | 41 | # 42 | # Get the initial options, which can be modified then passed to parse(). 43 | # These options will have the right description, includer, and other 44 | # parameters already set up. 45 | # @return the initial options 46 | # 47 | def options 48 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigParseable` must implement `options` (#{self.class})" 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /lib/hocon/config_list.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require_relative '../hocon' 4 | require_relative '../hocon/config_value' 5 | require_relative '../hocon/config_error' 6 | 7 | # 8 | # Subtype of {@link ConfigValue} representing a list value, as in JSON's 9 | # {@code [1,2,3]} syntax. 10 | # 11 | #
12 | # {@code ConfigList} implements {@code java.util.List
17 | # Like all {@link ConfigValue} subtypes, {@code ConfigList} is immutable. This
18 | # makes it threadsafe and you never have to create "defensive copies." The
19 | # mutator methods from {@link java.util.List} all throw
20 | # {@link java.lang.UnsupportedOperationException}.
21 | #
22 | #
23 | # The {@link ConfigValue#valueType} method on a list returns
24 | # {@link ConfigValueType#LIST}.
25 | #
26 | #
27 | # Do not implement {@code ConfigList}; it should only be implemented
28 | # by the config library. Arbitrary implementations will not work because the
29 | # library internals assume a specific concrete implementation. Also, this
30 | # interface is likely to grow new methods over time, so third-party
31 | # implementations will break.
32 | #
33 | #
34 | module Hocon::ConfigList
35 | include Hocon::ConfigValue
36 |
37 | #
38 | # Recursively unwraps the list, returning a list of plain Java values such
39 | # as Integer or String or whatever is in the list.
40 | #
41 | def unwrapped
42 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigValue should provide their own implementation of `unwrapped` (#{self.class})"
43 | end
44 |
45 | def with_origin(origin)
46 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigValue should provide their own implementation of `with_origin` (#{self.class})"
47 | end
48 |
49 | end
50 |
--------------------------------------------------------------------------------
/lib/hocon/impl/config_node_complex_value.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../../hocon/impl'
4 | require_relative '../../hocon/impl/abstract_config_node_value'
5 | require_relative '../../hocon/impl/config_node_field'
6 | require_relative '../../hocon/impl/config_node_include'
7 | require_relative '../../hocon/impl/config_node_single_token'
8 | require_relative '../../hocon/impl/tokens'
9 | require_relative '../../hocon/config_error'
10 |
11 | module Hocon::Impl::ConfigNodeComplexValue
12 | include Hocon::Impl::AbstractConfigNodeValue
13 | def initialize(children)
14 | @children = children
15 | end
16 |
17 | attr_reader :children
18 |
19 | def tokens
20 | tokens = []
21 | @children.each do |child|
22 | tokens += child.tokens
23 | end
24 | tokens
25 | end
26 |
27 | def indent_text(indentation)
28 | children_copy = @children.clone
29 | i = 0
30 | while i < children_copy.size
31 | child = children_copy[i]
32 | if child.is_a?(Hocon::Impl::ConfigNodeSingleToken) && Hocon::Impl::Tokens.newline?(child.token)
33 | children_copy.insert(i + 1, indentation)
34 | i += 1
35 | elsif child.is_a?(Hocon::Impl::ConfigNodeField)
36 | value = child.value
37 | if value.is_a?(Hocon::Impl::ConfigNodeComplexValue)
38 | children_copy[i] = child.replace_value(value.indent_text(indentation))
39 | end
40 | elsif child.is_a?(Hocon::Impl::ConfigNodeComplexValue)
41 | children_copy[i] = child.indent_text(indentation)
42 | end
43 | i += 1
44 | end
45 | new_node(children_copy)
46 | end
47 |
48 | # This method will just call into the object's constructor, but it's needed
49 | # for use in the indentText() method so we can avoid a gross if/else statement
50 | # checking the type of this
51 | def new_node(nodes)
52 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigNodeComplexValue should override `new_node` (#{self.class})"
53 | end
54 | end
--------------------------------------------------------------------------------
/lib/hocon/impl/config_number.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../../hocon/impl'
4 | require_relative '../../hocon/impl/abstract_config_value'
5 |
6 | class Hocon::Impl::ConfigNumber
7 | include Hocon::Impl::AbstractConfigValue
8 | ## sigh... requiring these subclasses before this class
9 | ## is declared would cause an error. Thanks, ruby.
10 | require_relative '../../hocon/impl/config_int'
11 | require_relative '../../hocon/impl/config_double'
12 |
13 | def self.new_number(origin, number, original_text)
14 | as_int = number.to_i
15 | if as_int == number
16 | Hocon::Impl::ConfigInt.new(origin, as_int, original_text)
17 | else
18 | Hocon::Impl::ConfigDouble.new(origin, number, original_text)
19 | end
20 | end
21 |
22 | def initialize(origin, original_text)
23 | super(origin)
24 | @original_text = original_text
25 | end
26 | attr_reader :original_text
27 |
28 | def transform_to_string
29 | @original_text
30 | end
31 |
32 | def int_value_range_checked(path)
33 | # We don't need to do any range checking here due to the way Ruby handles
34 | # integers (doesn't have the 32-bit/64-bit distinction that Java does).
35 | long_value
36 | end
37 |
38 | def long_value
39 | raise "long_value needs to be overriden by sub-classes of #{Hocon::Impl::ConfigNumber}, in this case #{self.class}"
40 | end
41 |
42 | def can_equal(other)
43 | other.is_a?(Hocon::Impl::ConfigNumber)
44 | end
45 |
46 | def ==(other)
47 | if other.is_a?(Hocon::Impl::ConfigNumber) && can_equal(other)
48 | @value == other.value
49 | else
50 | false
51 | end
52 | end
53 |
54 | def hash
55 | # This hash function makes it so that a ConfigNumber with a 3.0
56 | # and one with a 3 will return the hash code
57 | to_int = @value.round
58 |
59 | # If the value is an integer or a floating point equal to an integer
60 | if to_int == @value
61 | to_int.hash
62 | else
63 | @value.hash
64 | end
65 | end
66 | end
67 |
--------------------------------------------------------------------------------
/lib/hocon/config_error.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../hocon'
4 |
5 | class Hocon::ConfigError < StandardError
6 | def initialize(origin, message, cause)
7 | msg =
8 | if origin.nil?
9 | message
10 | else
11 | "#{origin.description}: #{message}"
12 | end
13 | super(msg)
14 | @origin = origin
15 | @cause = cause
16 | end
17 |
18 | class ConfigMissingError < Hocon::ConfigError
19 | end
20 |
21 | class ConfigNullError < Hocon::ConfigError::ConfigMissingError
22 | def self.make_message(path, expected)
23 | if not expected.nil?
24 | "Configuration key '#{path}' is set to nil but expected #{expected}"
25 | else
26 | "Configuration key '#{path}' is nil"
27 | end
28 | end
29 | end
30 |
31 | class ConfigIOError < Hocon::ConfigError
32 | def initialize(origin, message, cause = nil)
33 | super(origin, message, cause)
34 | end
35 | end
36 |
37 | class ConfigParseError < Hocon::ConfigError
38 | end
39 |
40 | class ConfigWrongTypeError < Hocon::ConfigError
41 | def self.with_expected_actual(origin, path, expected, actual, cause = nil)
42 | ConfigWrongTypeError.new(origin, "#{path} has type #{actual} rather than #{expected}", cause)
43 | end
44 | end
45 |
46 | class ConfigBugOrBrokenError < Hocon::ConfigError
47 | def initialize(message, cause = nil)
48 | super(nil, message, cause)
49 | end
50 | end
51 |
52 | class ConfigNotResolvedError < Hocon::ConfigError::ConfigBugOrBrokenError
53 | end
54 |
55 | class ConfigBadPathError < Hocon::ConfigError
56 | def initialize(origin, path, message, cause = nil)
57 | error_message = !path.nil? ? "Invalid path '#{path}': #{message}" : message
58 | super(origin, error_message, cause)
59 | end
60 | end
61 |
62 | class UnresolvedSubstitutionError < ConfigParseError
63 | def initialize(origin, detail, cause = nil)
64 | super(origin, "Could not resolve substitution to a value: " + detail, cause)
65 | end
66 | end
67 | end
68 |
--------------------------------------------------------------------------------
/lib/hocon/config_include_context.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../hocon'
4 | require_relative '../hocon/config_error'
5 |
6 | #
7 | # Context provided to a {@link ConfigIncluder}; this interface is only useful
8 | # inside a {@code ConfigIncluder} implementation, and is not intended for apps
9 | # to implement.
10 | #
11 | #
12 | # Do not implement this interface; it should only be implemented by
13 | # the config library. Arbitrary implementations will not work because the
14 | # library internals assume a specific concrete implementation. Also, this
15 | # interface is likely to grow new methods over time, so third-party
16 | # implementations will break.
17 | #
18 | module Hocon::ConfigIncludeContext
19 | #
20 | # Tries to find a name relative to whatever is doing the including, for
21 | # example in the same directory as the file doing the including. Returns
22 | # null if it can't meaningfully create a relative name. The returned
23 | # parseable may not exist; this function is not required to do any IO, just
24 | # compute what the name would be.
25 | #
26 | # The passed-in filename has to be a complete name (with extension), not
27 | # just a basename. (Include statements in config files are allowed to give
28 | # just a basename.)
29 | #
30 | # @param filename
31 | # the name to make relative to the resource doing the including
32 | # @return parseable item relative to the resource doing the including, or
33 | # null
34 | #
35 | def relative_to(filename)
36 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigIncludeContext` must implement `relative_to` (#{self.class})"
37 | end
38 |
39 | #
40 | # Parse options to use (if you use another method to get a
41 | # {@link ConfigParseable} then use {@link ConfigParseable#options()}
42 | # instead though).
43 | #
44 | # @return the parse options
45 | #
46 | def parse_options
47 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigIncludeContext` must implement `parse_options` (#{self.class})"
48 | end
49 | end
50 |
--------------------------------------------------------------------------------
/lib/hocon/config_parse_options.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../hocon'
4 |
5 | class Hocon::ConfigParseOptions
6 | attr_accessor :syntax, :origin_description, :allow_missing, :includer
7 |
8 | def self.defaults
9 | self.new(nil, nil, true, nil)
10 | end
11 |
12 | def initialize(syntax, origin_description, allow_missing, includer)
13 | @syntax = syntax
14 | @origin_description = origin_description
15 | @allow_missing = allow_missing
16 | @includer = includer
17 | end
18 |
19 | def set_syntax(syntax)
20 | if @syntax == syntax
21 | self
22 | else
23 | Hocon::ConfigParseOptions.new(syntax,
24 | @origin_description,
25 | @allow_missing,
26 | @includer)
27 | end
28 | end
29 |
30 | def set_origin_description(origin_description)
31 | if @origin_description == origin_description
32 | self
33 | else
34 | Hocon::ConfigParseOptions.new(@syntax,
35 | origin_description,
36 | @allow_missing,
37 | @includer)
38 | end
39 | end
40 |
41 | def set_allow_missing(allow_missing)
42 | if allow_missing? == allow_missing
43 | self
44 | else
45 | Hocon::ConfigParseOptions.new(@syntax,
46 | @origin_description,
47 | allow_missing,
48 | @includer)
49 | end
50 | end
51 |
52 | def allow_missing?
53 | @allow_missing
54 | end
55 |
56 | def set_includer(includer)
57 | if @includer == includer
58 | self
59 | else
60 | Hocon::ConfigParseOptions.new(@syntax,
61 | @origin_description,
62 | @allow_missing,
63 | includer)
64 | end
65 | end
66 |
67 | def append_includer(includer)
68 | if @includer == includer
69 | self
70 | elsif @includer
71 | set_includer(@includer.with_fallback(includer))
72 | else
73 | set_includer(includer)
74 | end
75 | end
76 |
77 | end
78 |
--------------------------------------------------------------------------------
/lib/hocon/impl/config_string.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../../hocon/impl'
4 | require_relative '../../hocon/impl/abstract_config_value'
5 | require_relative '../../hocon/config_value_type'
6 | require_relative '../../hocon/impl/config_impl_util'
7 |
8 | class Hocon::Impl::ConfigString
9 | include Hocon::Impl::AbstractConfigValue
10 |
11 | ConfigImplUtil = Hocon::Impl::ConfigImplUtil
12 |
13 | attr_reader :value
14 |
15 | class Quoted < Hocon::Impl::ConfigString
16 | def initialize(origin, value)
17 | super(origin, value)
18 | end
19 |
20 | def new_copy(origin)
21 | self.class.new(origin, @value)
22 | end
23 |
24 | private
25 |
26 | # serialization all goes through SerializedConfigValue
27 | def write_replace
28 | Hocon::Impl::SerializedConfigValue.new(self)
29 | end
30 | end
31 |
32 | # this is sort of a hack; we want to preserve whether whitespace
33 | # was quoted until we process substitutions, so we can ignore
34 | # unquoted whitespace when concatenating lists or objects.
35 | # We dump this distinction when serializing and deserializing,
36 | # but that 's OK because it isn' t in equals/hashCode, and we
37 | # don 't allow serializing unresolved objects which is where
38 | # quoted-ness matters. If we later make ConfigOrigin point
39 | # to the original token range, we could use that to implement
40 | # wasQuoted()
41 | class Unquoted < Hocon::Impl::ConfigString
42 | def initialize(origin, value)
43 | super(origin, value)
44 | end
45 |
46 | def new_copy(origin)
47 | self.class.new(origin, @value)
48 | end
49 |
50 | def write_replace
51 | Hocon::Impl::SerializedConfigValue.new(self)
52 | end
53 | end
54 |
55 | def was_quoted?
56 | self.is_a?(Quoted)
57 | end
58 |
59 | def value_type
60 | Hocon::ConfigValueType::STRING
61 | end
62 |
63 | def unwrapped
64 | @value
65 | end
66 |
67 | def transform_to_string
68 | @value
69 | end
70 |
71 | def render_value_to_sb(sb, indent_size, at_root, options)
72 | if options.json?
73 | sb << ConfigImplUtil.render_json_string(@value)
74 | else
75 | sb << ConfigImplUtil.render_string_unquoted_if_possible(@value)
76 | end
77 | end
78 |
79 | private
80 |
81 | def initialize(origin, value)
82 | super(origin)
83 | @value = value
84 | end
85 | end
86 |
--------------------------------------------------------------------------------
/lib/hocon/impl/config_node_field.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../../hocon/impl'
4 | require_relative '../../hocon/config_error'
5 | require_relative '../../hocon/impl/abstract_config_node'
6 | require_relative '../../hocon/impl/abstract_config_node_value'
7 | require_relative '../../hocon/impl/config_node_comment'
8 | require_relative '../../hocon/impl/config_node_path'
9 | require_relative '../../hocon/impl/config_node_single_token'
10 | require_relative '../../hocon/impl/tokens'
11 |
12 | class Hocon::Impl::ConfigNodeField
13 | include Hocon::Impl::AbstractConfigNode
14 |
15 | Tokens = Hocon::Impl::Tokens
16 |
17 | def initialize(children)
18 | @children = children
19 | end
20 |
21 | attr_reader :children
22 |
23 | def tokens
24 | tokens = []
25 | @children.each do |child|
26 | tokens += child.tokens
27 | end
28 | tokens
29 | end
30 |
31 | def replace_value(new_value)
32 | children_copy = @children.clone
33 | children_copy.each_with_index do |child, i|
34 | if child.is_a?(Hocon::Impl::AbstractConfigNodeValue)
35 | children_copy[i] = new_value
36 | return self.class.new(children_copy)
37 | end
38 | end
39 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "Field node doesn't have a value"
40 | end
41 |
42 | def value
43 | @children.each do |child|
44 | if child.is_a?(Hocon::Impl::AbstractConfigNodeValue)
45 | return child
46 | end
47 | end
48 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "Field node doesn't have a value"
49 | end
50 |
51 | def path
52 | @children.each do |child|
53 | if child.is_a?(Hocon::Impl::ConfigNodePath)
54 | return child
55 | end
56 | end
57 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "Field node doesn't have a path"
58 | end
59 |
60 | def separator
61 | @children.each do |child|
62 | if child.is_a?(Hocon::Impl::ConfigNodeSingleToken)
63 | t = child.token
64 | if t == Tokens::PLUS_EQUALS or t == Tokens::COLON or t == Tokens::EQUALS
65 | return t
66 | end
67 | end
68 | end
69 | nil
70 | end
71 |
72 | def comments
73 | comments = []
74 | @children.each do |child|
75 | if child.is_a?(Hocon::Impl::ConfigNodeComment)
76 | comments << child.comment_text
77 | end
78 | end
79 | comments
80 | end
81 | end
--------------------------------------------------------------------------------
/lib/hocon.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | module Hocon
4 | # NOTE: the behavior of this load method differs a bit from the upstream public
5 | # API, where a file extension may be the preferred method of determining
6 | # the config syntax, even if you specify a Syntax value on ConfigParseOptions.
7 | # Here we prefer the syntax (optionally) specified by the user no matter what
8 | # the file extension is, and if they don't specify one and the file extension
9 | # is unrecognized, we raise an error.
10 | def self.load(file, opts = nil)
11 | # doing these requires lazily, because otherwise, classes that need to
12 | # `require_relative 'hocon'` to get the module into scope will end up recursing
13 | # through this require and probably ending up with circular dependencies.
14 | require_relative 'hocon/config_factory'
15 | require_relative 'hocon/impl/parseable'
16 | require_relative 'hocon/config_parse_options'
17 | require_relative 'hocon/config_resolve_options'
18 | require_relative 'hocon/config_error'
19 | syntax = opts ? opts[:syntax] : nil
20 |
21 | if syntax.nil?
22 | unless Hocon::Impl::Parseable.syntax_from_extension(file)
23 | raise Hocon::ConfigError::ConfigParseError.new(
24 | nil, "Unrecognized file extension '#{File.extname(file)}' and no value provided for :syntax option", nil)
25 | end
26 | config = Hocon::ConfigFactory.parse_file_any_syntax(
27 | file, Hocon::ConfigParseOptions.defaults)
28 | else
29 | config = Hocon::ConfigFactory.parse_file(
30 | file, Hocon::ConfigParseOptions.defaults.set_syntax(syntax))
31 | end
32 |
33 | resolved_config = Hocon::ConfigFactory.load_from_config(
34 | config, Hocon::ConfigResolveOptions.defaults)
35 |
36 | resolved_config.root.unwrapped
37 | end
38 |
39 | def self.parse(string)
40 | # doing these requires lazily, because otherwise, classes that need to
41 | # `require_relative 'hocon'` to get the module into scope will end up recursing
42 | # through this require and probably ending up with circular dependencies.
43 | require_relative 'hocon/config_factory'
44 | require_relative 'hocon/config_resolve_options'
45 | config = Hocon::ConfigFactory.parse_string(string)
46 | resolved_config = Hocon::ConfigFactory.load_from_config(
47 | config, Hocon::ConfigResolveOptions.defaults)
48 |
49 | resolved_config.root.unwrapped
50 | end
51 | end
52 |
--------------------------------------------------------------------------------
/spec/fixtures/test_utils/resources/test01.conf:
--------------------------------------------------------------------------------
1 | {
2 | "ints" : {
3 | "fortyTwo" : 42,
4 | "fortyTwoAgain" : ${ints.fortyTwo}
5 | },
6 |
7 | "floats" : {
8 | "fortyTwoPointOne" : 42.1,
9 | "fortyTwoPointOneAgain" : ${floats.fortyTwoPointOne}
10 | },
11 |
12 | "strings" : {
13 | "abcd" : "abcd",
14 | "abcdAgain" : ${strings.a}${strings.b}${strings.c}${strings.d},
15 | "a" : "a",
16 | "b" : "b",
17 | "c" : "c",
18 | "d" : "d",
19 | "concatenated" : null bar 42 baz true 3.14 hi,
20 | "double" : "3.14",
21 | "number" : "57",
22 | "null" : "null",
23 | "true" : "true",
24 | "yes" : "yes",
25 | "false" : "false",
26 | "no" : "no"
27 | },
28 |
29 | "arrays" : {
30 | "empty" : [],
31 | "ofInt" : [1, 2, 3],
32 | "ofString" : [ ${strings.a}, ${strings.b}, ${strings.c} ],
33 | "ofDouble" : [3.14, 4.14, 5.14],
34 | "ofNull" : [null, null, null],
35 | "ofBoolean" : [true, false],
36 | "ofArray" : [${arrays.ofString}, ${arrays.ofString}, ${arrays.ofString}],
37 | "ofObject" : [${ints}, ${booleans}, ${strings}],
38 | "firstElementNotASubst" : [ "a", ${strings.b} ]
39 | },
40 |
41 | "booleans" : {
42 | "true" : true,
43 | "trueAgain" : ${booleans.true},
44 | "false" : false,
45 | "falseAgain" : ${booleans.false}
46 | },
47 |
48 | "nulls" : {
49 | "null" : null,
50 | "nullAgain" : ${nulls.null}
51 | },
52 |
53 | "durations" : {
54 | "second" : 1s,
55 | "secondsList" : [1s,2seconds,3 s, 4000],
56 | "secondAsNumber" : 1000,
57 | "halfSecond" : 0.5s,
58 | "millis" : 1 milli,
59 | "micros" : 2000 micros
60 | },
61 |
62 | "memsizes" : {
63 | "meg" : 1M,
64 | "megsList" : [1M, 1024K, 1048576],
65 | "megAsNumber" : 1048576,
66 | "halfMeg" : 0.5M
67 | },
68 |
69 | "system" : {
70 | "javaversion" : ${?java.version},
71 | "userhome" : ${?user.home},
72 | "home" : ${?HOME},
73 | "pwd" : ${?PWD},
74 | "shell" : ${?SHELL},
75 | "lang" : ${?LANG},
76 | "path" : ${?PATH},
77 | "not_here" : ${?NOT_HERE},
78 | "concatenated" : Your Java version is ${?system.javaversion} and your user.home is ${?system.userhome}
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/lib/hocon/impl/config_node_root.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../../hocon/impl'
4 | require_relative '../../hocon/impl/config_node_array'
5 | require_relative '../../hocon/impl/config_node_complex_value'
6 | require_relative '../../hocon/impl/config_node_object'
7 |
8 | class Hocon::Impl::ConfigNodeRoot
9 | include Hocon::Impl::ConfigNodeComplexValue
10 | def initialize(children, origin)
11 | super(children)
12 | @origin = origin
13 | end
14 |
15 | def new_node(nodes)
16 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "Tried to indent the root object"
17 | end
18 |
19 | def value
20 | @children.each do |node|
21 | if node.is_a?(Hocon::Impl::ConfigNodeComplexValue)
22 | return node
23 | end
24 | end
25 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "ConfigNodeRoot did not contain a value"
26 | end
27 |
28 | def set_value(desired_path, value, flavor)
29 | children_copy = @children.clone
30 | children_copy.each_with_index do |node, index|
31 | if node.is_a?(Hocon::Impl::ConfigNodeComplexValue)
32 | if node.is_a?(Hocon::Impl::ConfigNodeArray)
33 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "The ConfigDocument had an array at the root level, and values cannot be modified inside an array."
34 | elsif node.is_a?(Hocon::Impl::ConfigNodeObject)
35 | if value.nil?
36 | children_copy[index] = node.remove_value_on_path(desired_path, flavor)
37 | else
38 | children_copy[index] = node.set_value_on_path(desired_path, value, flavor)
39 | end
40 | return self.class.new(children_copy, @origin)
41 | end
42 | end
43 | end
44 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "ConfigNodeRoot did not contain a value"
45 | end
46 |
47 | def has_value(desired_path)
48 | path = Hocon::Impl::PathParser.parse_path(desired_path)
49 | @children.each do |node|
50 | if node.is_a?(Hocon::Impl::ConfigNodeComplexValue)
51 | if node.is_a?(Hocon::Impl::ConfigNodeArray)
52 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "The ConfigDocument had an array at the root level, and values cannot be modified inside an array."
53 | elsif node.is_a?(Hocon::Impl::ConfigNodeObject)
54 | return node.has_value(path)
55 | end
56 | end
57 | end
58 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "ConfigNodeRoot did not contain a value"
59 | end
60 | end
61 |
--------------------------------------------------------------------------------
/lib/hocon/config_util.rb:
--------------------------------------------------------------------------------
1 | require_relative '../hocon/impl/config_impl_util'
2 |
3 |
4 | # Contains static utility methods
5 | class Hocon::ConfigUtil
6 | #
7 | # Quotes and escapes a string, as in the JSON specification.
8 | #
9 | # @param string
10 | # a string
11 | # @return the string quoted and escaped
12 | #
13 | def self.quote_string(string)
14 | Hocon::Impl::ConfigImplUtil.render_json_string(string)
15 | end
16 |
17 | #
18 | # Converts a list of keys to a path expression, by quoting the path
19 | # elements as needed and then joining them separated by a period. A path
20 | # expression is usable with a {@link Config}, while individual path
21 | # elements are usable with a {@link ConfigObject}.
22 | #
23 | # See the overview documentation for {@link Config} for more detail on path
24 | # expressions vs. keys.
25 | #
26 | # @param elements
27 | # the keys in the path
28 | # @return a path expression
29 | # @throws ConfigException
30 | # if there are no elements
31 | #
32 | def self.join_path(*elements)
33 | Hocon::Impl::ConfigImplUtil.join_path(*elements)
34 | end
35 |
36 | #
37 | # Converts a list of strings to a path expression, by quoting the path
38 | # elements as needed and then joining them separated by a period. A path
39 | # expression is usable with a {@link Config}, while individual path
40 | # elements are usable with a {@link ConfigObject}.
41 | #
42 | # See the overview documentation for {@link Config} for more detail on path
43 | # expressions vs. keys.
44 | #
45 | # @param elements
46 | # the keys in the path
47 | # @return a path expression
48 | # @throws ConfigException
49 | # if the list is empty
50 | #
51 | def self.join_path_from_list(elements)
52 | self.join_path(*elements)
53 | end
54 |
55 | #
56 | # Converts a path expression into a list of keys, by splitting on period
57 | # and unquoting the individual path elements. A path expression is usable
58 | # with a {@link Config}, while individual path elements are usable with a
59 | # {@link ConfigObject}.
60 | #
61 | # See the overview documentation for {@link Config} for more detail on path
62 | # expressions vs. keys.
63 | #
64 | # @param path
65 | # a path expression
66 | # @return the individual keys in the path
67 | # @throws ConfigException
68 | # if the path expression is invalid
69 | #
70 | def self.split_path(path)
71 | Hocon::Impl::ConfigImplUtil.split_path(path)
72 | end
73 | end
74 |
--------------------------------------------------------------------------------
/lib/hocon/config_factory.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../hocon'
4 | require_relative '../hocon/impl/parseable'
5 | require_relative '../hocon/config_parse_options'
6 | require_relative '../hocon/impl/config_impl'
7 | require_relative '../hocon/config_factory'
8 |
9 | ## Please note that the `parse` operations will simply create a ConfigValue
10 | ## and do nothing else, whereas the `load` operations will perform a higher-level
11 | ## operation and will resolve substitutions. If you have substitutions in your
12 | ## configuration, use a `load` function
13 | class Hocon::ConfigFactory
14 | def self.parse_file(file_path, options = Hocon::ConfigParseOptions.defaults)
15 | Hocon::Impl::Parseable.new_file(file_path, options).parse.to_config
16 | end
17 |
18 | def self.parse_string(string, options = Hocon::ConfigParseOptions.defaults)
19 | Hocon::Impl::Parseable.new_string(string, options).parse.to_config
20 | end
21 |
22 | def self.parse_file_any_syntax(file_base_name, options)
23 | Hocon::Impl::ConfigImpl.parse_file_any_syntax(file_base_name, options).to_config
24 | end
25 |
26 | def self.empty(origin_description = nil)
27 | Hocon::Impl::ConfigImpl.empty_config(origin_description)
28 | end
29 |
30 | # Because of how optional arguments work, if either parse or resolve options is supplied
31 | # both must be supplied. load_file_with_parse_options or load_file_with_resolve_options
32 | # can be used instead, or the argument you don't care about in load_file can be nil
33 | #
34 | # e.g.:
35 | # load_file("settings", my_parse_options, nil)
36 | # is equivalent to:
37 | # load_file_with_parse_options("settings", my_parse_options)
38 | def self.load_file(file_base_name, parse_options = nil, resolve_options = nil)
39 | parse_options ||= Hocon::ConfigParseOptions.defaults
40 | resolve_options ||= Hocon::ConfigResolveOptions.defaults
41 |
42 | config = Hocon::ConfigFactory.parse_file_any_syntax(file_base_name, parse_options)
43 |
44 | self.load_from_config(config, resolve_options)
45 | end
46 |
47 | def self.load_file_with_parse_options(file_base_name, parse_options)
48 | self.load_file(file_base_name, parse_options, nil)
49 | end
50 |
51 | def self.load_file_with_resolve_options(file_base_name, resolve_options)
52 | self.load_file(file_base_name, nil, resolve_options)
53 | end
54 |
55 | def self.load_from_config(config, resolve_options)
56 |
57 | config.with_fallback(self.default_reference).resolve(resolve_options)
58 | end
59 |
60 | def self.default_reference
61 | Hocon::Impl::ConfigImpl.default_reference
62 | end
63 | end
64 |
--------------------------------------------------------------------------------
/lib/hocon/config_mergeable.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../hocon'
4 | require_relative '../hocon/config_error'
5 |
6 | #
7 | # Marker for types whose instances can be merged, that is {@link Config} and
8 | # {@link ConfigValue}. Instances of {@code Config} and {@code ConfigValue} can
9 | # be combined into a single new instance using the
10 | # {@link ConfigMergeable#withFallback withFallback()} method.
11 | #
12 | #
13 | # Do not implement this interface; it should only be implemented by
14 | # the config library. Arbitrary implementations will not work because the
15 | # library internals assume a specific concrete implementation. Also, this
16 | # interface is likely to grow new methods over time, so third-party
17 | # implementations will break.
18 | #
19 | module Hocon::ConfigMergeable
20 | #
21 | # Returns a new value computed by merging this value with another, with
22 | # keys in this value "winning" over the other one.
23 | #
24 | #
25 | # This associative operation may be used to combine configurations from
26 | # multiple sources (such as multiple configuration files).
27 | #
28 | #
29 | # The semantics of merging are described in the spec
31 | # for HOCON. Merging typically occurs when either the same object is
32 | # created twice in the same file, or two config files are both loaded. For
33 | # example:
34 | #
35 | #
47 | # Only {@link ConfigObject} and {@link Config} instances do anything in
48 | # this method (they need to merge the fallback keys into themselves). All
49 | # other values just return the original value, since they automatically
50 | # override any fallback. This means that objects do not merge "across"
51 | # non-objects; if you write
52 | #
19 | # In a
26 | # The origin_description will be used to set the origin() field on the
27 | # ConfigValue. It should normally be the name of the file the values came
28 | # from, or something short describing the value such as "default settings".
29 | # The origin_description is prefixed to error messages so users can tell
30 | # where problematic values are coming from.
31 | #
32 | #
33 | # Supplying the result of ConfigValue.unwrapped() to this function is
34 | # guaranteed to work and should give you back a ConfigValue that matches
35 | # the one you unwrapped. The re-wrapped ConfigValue will lose some
36 | # information that was present in the original such as its origin, but it
37 | # will have matching values.
38 | #
39 | #
40 | # If you pass in a
46 | # This function throws if you supply a value that cannot be converted to a
47 | # ConfigValue, but supplying such a value is a bug in your program, so you
48 | # should never handle the exception. Just fix your program (or report a bug
49 | # against this library).
50 | #
51 | # @param object
52 | # object to convert to ConfigValue
53 | # @param origin_description
54 | # name of origin file or brief description of what the value is
55 | # @return a new value
56 | #
57 | def self.from_any_ref(object, origin_description = nil)
58 | if object.is_a?(Hash)
59 | from_map(object, origin_description)
60 | else
61 | ConfigImpl.from_any_ref(object, origin_description)
62 | end
63 | end
64 |
65 | #
66 | # See the {@link #from_any_ref(Object,String)} documentation for details
67 | #
68 | #
69 | # See also {@link ConfigFactory#parse_map(Map)} which interprets the keys in
70 | # the map as path expressions.
71 | #
72 | # @param values map from keys to plain ruby values
73 | # @return a new {@link ConfigObject}
74 | #
75 | def self.from_map(values, origin_description = nil)
76 | ConfigImpl.from_any_ref(process_hash(values), origin_description)
77 | end
78 |
79 | private
80 |
81 | def self.process_hash(hash)
82 | Hash[hash.map {|k, v| [k.is_a?(Symbol) ? k.to_s : k, v.is_a?(Hash) ? process_hash(v) : v]}]
83 | end
84 |
85 | end
86 |
--------------------------------------------------------------------------------
/spec/unit/typesafe/config/config_value_factory_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require 'spec_helper'
4 | require 'hocon/config_value_factory'
5 | require 'hocon/config_render_options'
6 | require 'hocon/config_error'
7 |
8 | describe Hocon::ConfigValueFactory do
9 | let(:render_options) { Hocon::ConfigRenderOptions.defaults }
10 |
11 | before do
12 | render_options.origin_comments = false
13 | render_options.json = false
14 | end
15 |
16 | context "converting objects to ConfigValue using ConfigValueFactory" do
17 | it "should convert true into a ConfigBoolean" do
18 | value = Hocon::ConfigValueFactory.from_any_ref(true, nil)
19 | expect(value).to be_instance_of(Hocon::Impl::ConfigBoolean)
20 | expect(value.unwrapped).to eql(true)
21 | end
22 |
23 | it "should convert false into a ConfigBoolean" do
24 | value = Hocon::ConfigValueFactory.from_any_ref(false, nil)
25 | expect(value).to be_instance_of(Hocon::Impl::ConfigBoolean)
26 | expect(value.unwrapped).to eql(false)
27 | end
28 |
29 | it "should convert nil into a ConfigNull object" do
30 | value = Hocon::ConfigValueFactory.from_any_ref(nil, nil)
31 | expect(value).to be_instance_of(Hocon::Impl::ConfigNull)
32 | expect(value.unwrapped).to be_nil
33 | end
34 |
35 | it "should convert an string into a ConfigString object" do
36 | value = Hocon::ConfigValueFactory.from_any_ref("Hello, World!", nil)
37 | expect(value).to be_a(Hocon::Impl::ConfigString)
38 | expect(value.unwrapped).to eq("Hello, World!")
39 | end
40 |
41 | it "should convert an integer into a ConfigInt object" do
42 | value = Hocon::ConfigValueFactory.from_any_ref(123, nil)
43 | expect(value).to be_instance_of(Hocon::Impl::ConfigInt)
44 | expect(value.unwrapped).to eq(123)
45 | end
46 |
47 | it "should convert a double into a ConfigDouble object" do
48 | value = Hocon::ConfigValueFactory.from_any_ref(123.456, nil)
49 | expect(value).to be_instance_of(Hocon::Impl::ConfigDouble)
50 | expect(value.unwrapped).to eq(123.456)
51 | end
52 |
53 | it "should convert a map into a SimpleConfigObject" do
54 | map = {"a" => 1, "b" => 2, "c" => 3}
55 | value = Hocon::ConfigValueFactory.from_any_ref(map, nil)
56 | expect(value).to be_instance_of(Hocon::Impl::SimpleConfigObject)
57 | expect(value.unwrapped).to eq(map)
58 | end
59 |
60 | it "should convert symbol keys in a map to string keys" do
61 | orig_map = {a: 1, b: 2, c: {a: 1, b: 2, c: {a: 1}}}
62 | map = {"a" => 1, "b" => 2, "c"=>{"a"=>1, "b"=>2, "c"=>{"a"=>1}}}
63 | value = Hocon::ConfigValueFactory.from_any_ref(orig_map, nil)
64 | expect(value).to be_instance_of(Hocon::Impl::SimpleConfigObject)
65 | expect(value.unwrapped).to eq(map)
66 |
67 | value = Hocon::ConfigValueFactory.from_map(orig_map, nil)
68 | expect(value).to be_instance_of(Hocon::Impl::SimpleConfigObject)
69 | expect(value.unwrapped).to eq(map)
70 | end
71 |
72 | it "should not parse maps with non-string and non-symbol keys" do
73 | map = {1 => "a", 2 => "b"}
74 | expect{ Hocon::ConfigValueFactory.from_any_ref(map, nil) }.to raise_error(Hocon::ConfigError::ConfigBugOrBrokenError)
75 | end
76 |
77 | it "should convert an Enumerable into a SimpleConfigList" do
78 | list = [1, 2, 3, 4, 5]
79 | value = Hocon::ConfigValueFactory.from_any_ref(list, nil)
80 | expect(value).to be_instance_of(Hocon::Impl::SimpleConfigList)
81 | expect(value.unwrapped).to eq(list)
82 | end
83 | end
84 |
85 | end
86 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | ## 1.3.1
2 | This is a bugfix release
3 |
4 | * Fix a bug when using the library in multiple threads ([HC-105](https://tickets.puppetlabs.com/browse/HC-105))
5 |
6 | ## 1.3.0
7 | This is a feature release
8 |
9 | * Support environment variable lists ([HC-104](https://tickets.puppetlabs.com/browse/HC-104))
10 |
11 | ## 1.2.6
12 | This is a bugfix release
13 |
14 | * Do not ship spec folder with gem ([PA-2942](https://tickets.puppetlabs.com/browse/PA-2942))
15 |
16 | ## 1.2.5
17 | This is a bugfix release
18 |
19 | * Fixed loading files with UTF-8 characters in their file paths
20 |
21 | ## 1.2.4
22 | This is a feature release.
23 |
24 | * Added a cli tool called `hocon` for reading and manipulating hocon files
25 |
26 | Note that the version numbers 1.2.0-1.2.3 were not used because of bugs in our
27 | release pipeline we were working out
28 |
29 | ## 1.1.3
30 | This is a bugfix release.
31 |
32 | * Fixed bug where Hocon.parse would throw a ConfigNotResolved error if you passed it a String
33 | that contained values with substitutions.
34 |
35 | ## 1.1.2
36 | This is a bugfix release.
37 |
38 | * Fixed bug where Hocon::ConfigFactory.parse_file was not handling files with BOMs on Windows,
39 | causing UTF-8 files to not load properly.
40 |
41 | ## 1.1.1
42 | This is a bugfix release.
43 |
44 | * Fixed a bug where an undefined method `value_type_name` error was being thrown due to
45 | improper calls to the class method.
46 |
47 | ## 1.1.0
48 | This is a bugfix/feature release
49 |
50 | * Fixed a bug where unrecognized config file extensions caused `Hocon.load` to return an empty
51 | hash instead of an error.
52 | * Added an optional `:syntax` key to the `Hocon.load` method to explicitly specify the file format
53 | * Renamed internal usage of `name` methods to avoid overriding built in `Object#name` method
54 |
55 | ## 1.0.1
56 |
57 | This is a bugfix release.
58 | The API is stable enough and the code is being used in production, so the version is also being bumped to 1.0.0
59 |
60 | * Fixed a bug wherein calling "Hocon.load" would not
61 | resolve substitutions.
62 | * Fixed a circular dependency between the Hocon and Hocon::ConfigFactory
63 | namespaces. Using the Hocon::ConfigFactory class now requires you to
64 | use a `require 'hocon/config_factory'` instead of `require hocon`
65 | * Add support for hashes with keyword keys
66 |
67 | ## 1.0.0
68 |
69 | This version number was burned.
70 |
71 | ## 0.9.3
72 |
73 | This is a bugfix release.
74 |
75 | * Fixed a bug wherein inserting an array or a hash into a ConfigDocument would cause
76 | "# hardcoded value" comments to be generated before every entry in the hash/array.
77 |
78 | ## 0.9.2
79 |
80 | This is a bugfix release
81 |
82 | * Fixed a bug wherein attempting to insert a complex value (such as an array or a hash) into an empty
83 | ConfigDocument would cause an undefined method error.
84 |
85 | ## 0.9.1
86 |
87 | This is a bugfix release.
88 | * Fixed a bug wherein ugly configurations were being generated due to the addition of new objects when a setting
89 | is set at a path that does not currently exist in the configuration. Previously, these new objects were being
90 | added as single-line objects. They will now be added as multi-line objects if the parent object is a multi-line
91 | object or is an empty root object.
92 |
93 | ## 0.9.0
94 |
95 | This is a promotion of the 0.1.0 release with one small bug fix:
96 | * Fixed bug wherein using the `set_config_value` method with some parsed values would cause a failure due to surrounding whitespace
97 |
98 | ## 0.1.0
99 |
100 | This is a feature release containing a large number of changes and improvements
101 |
102 | * Added support for concatenation
103 | * Added support for substitutions
104 | * Added support for file includes. Other types of includes are not supported
105 | * Added the new ConfigDocument API that was recently implemented in the upstream Java library
106 | * Improved JSON support
107 | * Fixed a large number of small bugs related to various pieces of implementation
108 |
--------------------------------------------------------------------------------
/spec/unit/typesafe/config/config_factory_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require 'spec_helper'
4 | require 'hocon/config_factory'
5 | require 'hocon/config_render_options'
6 | require 'hocon/config_error'
7 |
8 | def get_comment_config_hash(config_string)
9 | split_config_string = config_string.split("\n")
10 | r = Regexp.new('^\s*#')
11 |
12 | previous_string_comment = false
13 | hash = {}
14 | comment_list = []
15 |
16 | split_config_string.each do |s|
17 | if r.match(s)
18 | comment_list << s
19 | previous_string_comment = true
20 | else
21 | if previous_string_comment
22 | hash[s] = comment_list
23 | comment_list = []
24 | end
25 | previous_string_comment = false
26 | end
27 | end
28 | return hash
29 | end
30 |
31 | describe Hocon::ConfigFactory do
32 | let(:render_options) { Hocon::ConfigRenderOptions.defaults }
33 |
34 | before do
35 | render_options.origin_comments = false
36 | render_options.json = false
37 | end
38 |
39 | shared_examples_for "config_factory_parsing" do
40 | let(:input_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/input#{extension}" }
41 | let(:output_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/output.conf" }
42 | let(:expected) { example[:hash] }
43 | let(:reparsed) { Hocon::ConfigFactory.parse_file("#{output_file}") }
44 | let(:output) { File.read("#{output_file}") }
45 |
46 | it "should make the config data available as a map" do
47 | expect(conf.root.unwrapped).to eq(expected)
48 | end
49 |
50 | it "should render the config data to a string with comments intact" do
51 | rendered_conf = conf.root.render(render_options)
52 | rendered_conf_comment_hash = get_comment_config_hash(rendered_conf)
53 | output_comment_hash = get_comment_config_hash(output)
54 |
55 | expect(rendered_conf_comment_hash).to eq(output_comment_hash)
56 | end
57 |
58 | it "should generate the same conf data via re-parsing the rendered output" do
59 | expect(reparsed.root.unwrapped).to eq(expected)
60 | end
61 | end
62 |
63 | context "example1" do
64 | let(:example) { EXAMPLE1 }
65 | let (:extension) { ".conf" }
66 |
67 | context "parsing a HOCON string" do
68 | let(:string) { File.open(input_file).read }
69 | let(:conf) { Hocon::ConfigFactory.parse_string(string) }
70 | include_examples "config_factory_parsing"
71 | end
72 |
73 | context "parsing a .conf file" do
74 | let(:conf) { Hocon::ConfigFactory.parse_file(input_file) }
75 | include_examples "config_factory_parsing"
76 | end
77 | end
78 |
79 | context "example2" do
80 | let(:example) { EXAMPLE2 }
81 | let (:extension) { ".conf" }
82 |
83 | context "parsing a HOCON string" do
84 | let(:string) { File.open(input_file).read }
85 | let(:conf) { Hocon::ConfigFactory.parse_string(string) }
86 | include_examples "config_factory_parsing"
87 | end
88 |
89 | context "parsing a .conf file" do
90 | let(:conf) { Hocon::ConfigFactory.parse_file(input_file) }
91 | include_examples "config_factory_parsing"
92 | end
93 | end
94 |
95 | context "example3" do
96 | let (:example) { EXAMPLE3 }
97 | let (:extension) { ".conf" }
98 |
99 | context "loading a HOCON file with substitutions" do
100 | let(:conf) { Hocon::ConfigFactory.load_file(input_file) }
101 | include_examples "config_factory_parsing"
102 | end
103 | end
104 |
105 | context "example4" do
106 | let(:example) { EXAMPLE4 }
107 | let (:extension) { ".json" }
108 |
109 | context "parsing a .json file" do
110 | let (:conf) { Hocon::ConfigFactory.parse_file(input_file) }
111 | include_examples "config_factory_parsing"
112 | end
113 | end
114 |
115 | context "example5" do
116 | it "should raise a ConfigParseError when given an invalid .conf file" do
117 | expect{Hocon::ConfigFactory.parse_string("abcdefg")}.to raise_error(Hocon::ConfigError::ConfigParseError)
118 | end
119 | end
120 | end
121 |
--------------------------------------------------------------------------------
/lib/hocon/impl/default_transformer.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../../hocon/impl'
4 | require_relative '../../hocon/impl/config_string'
5 | require_relative '../../hocon/config_value_type'
6 | require_relative '../../hocon/impl/config_boolean'
7 |
8 | class Hocon::Impl::DefaultTransformer
9 |
10 | ConfigValueType = Hocon::ConfigValueType
11 | ConfigString = Hocon::Impl::ConfigString
12 | ConfigBoolean = Hocon::Impl::ConfigBoolean
13 |
14 | def self.transform(value, requested)
15 | if value.value_type == ConfigValueType::STRING
16 | s = value.unwrapped
17 | case requested
18 | when ConfigValueType::NUMBER
19 | begin
20 | v = Integer(s)
21 | return ConfigInt.new(value.origin, v, s)
22 | rescue ArgumentError
23 | # try Float
24 | end
25 | begin
26 | v = Float(s)
27 | return ConfigFloat.new(value.origin, v, s)
28 | rescue ArgumentError
29 | # oh well.
30 | end
31 | when ConfigValueType::NULL
32 | if s == "null"
33 | return ConfigNull.new(value.origin)
34 | end
35 | when ConfigValueType::BOOLEAN
36 | if s == "true" || s == "yes" || s == "on"
37 | return ConfigBoolean.new(value.origin, true)
38 | elsif s == "false" || s == "no" || s == "off"
39 | return ConfigBoolean.new(value.origin, false)
40 | end
41 | when ConfigValueType::LIST
42 | # can't go STRING to LIST automatically
43 | when ConfigValueType::OBJECT
44 | # can't go STRING to OBJECT automatically
45 | when ConfigValueType::STRING
46 | # no-op STRING to STRING
47 | end
48 | elsif requested == ConfigValueType::STRING
49 | # if we converted null to string here, then you wouldn't properly
50 | # get a missing-value error if you tried to get a null value
51 | # as a string.
52 | case value.value_type
53 | # Ruby note: can't fall through in ruby. In the java code, NUMBER
54 | # just rolls over to the BOOLEAN case
55 | when ConfigValueType::NUMBER
56 | return ConfigString::Quoted.new(value.origin, value.transform_to_string)
57 | when ConfigValueType::BOOLEAN
58 | return ConfigString::Quoted.new(value.origin, value.transform_to_string)
59 | when ConfigValueType::NULL
60 | # want to be sure this throws instead of returning "null" as a
61 | # string
62 | when ConfigValueType::OBJECT
63 | # no OBJECT to STRING automatically
64 | when ConfigValueType::LIST
65 | # no LIST to STRING automatically
66 | when ConfigValueType::STRING
67 | # no-op STRING to STRING
68 | end
69 | elsif requested == ConfigValueType::LIST && value.value_type == ConfigValueType::OBJECT
70 | # attempt to convert an array-like (numeric indices) object to a
71 | # list. This would be used with .properties syntax for example:
72 | # -Dfoo.0=bar -Dfoo.1=baz
73 | # To ensure we still throw type errors for objects treated
74 | # as lists in most cases, we'll refuse to convert if the object
75 | # does not contain any numeric keys. This means we don't allow
76 | # empty objects here though :-/
77 | o = value
78 | values = Hash.new
79 | values
80 | o.keys.each do |key|
81 | begin
82 | i = Integer(key, 10)
83 | if i < 0
84 | next
85 | end
86 | values[key] = i
87 | rescue ArgumentError
88 | next
89 | end
90 | end
91 | if not values.empty?
92 | entry_list = values.to_a
93 | # sort by numeric index
94 | entry_list.sort! {|a,b| b[0] <=> a[0]}
95 | # drop the indices (we allow gaps in the indices, for better or
96 | # worse)
97 | list = Array.new
98 | entry_list.each do |entry|
99 | list.push(entry[1])
100 | end
101 | return SimpleConfigList.new(value.origin, list)
102 | end
103 | end
104 | value
105 | end
106 | end
107 |
--------------------------------------------------------------------------------
/lib/hocon/parser/config_document.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../../hocon/parser'
4 | require_relative '../../hocon/config_error'
5 |
6 | #
7 | # Represents an individual HOCON or JSON file, preserving all
8 | # formatting and syntax details. This can be used to replace
9 | # individual values and exactly render the original text of the
10 | # input.
11 | #
12 | #
13 | # Because this object is immutable, it is safe to use from multiple threads and
14 | # there's no need for "defensive copies."
15 | #
16 | #
17 | # Do not implement interface {@code ConfigDocument}; it should only be
18 | # implemented by the config library. Arbitrary implementations will not work
19 | # because the library internals assume a specific concrete implementation.#
20 | # Also, this interface is likely to grow new methods over time, so third-party
21 | # implementations will break.
22 | #
23 |
24 | module Hocon::Parser::ConfigDocument
25 | #
26 | # Returns a new ConfigDocument that is a copy of the current ConfigDocument,
27 | # but with the desired value set at the desired path. If the path exists, it will
28 | # remove all duplicates before the final occurrence of the path, and replace the value
29 | # at the final occurrence of the path. If the path does not exist, it will be added. If
30 | # the document has an array as the root value, an exception will be thrown.
31 | #
32 | # @param path the path at which to set the desired value
33 | # @param newValue the value to set at the desired path, represented as a string. This
34 | # string will be parsed into a ConfigNode using the same options used to
35 | # parse the entire document, and the text will be inserted
36 | # as-is into the document. Leading and trailing comments, whitespace, or
37 | # newlines are not allowed, and if present an exception will be thrown.
38 | # If a concatenation is passed in for newValue but the document was parsed
39 | # with JSON, the first value in the concatenation will be parsed and inserted
40 | # into the ConfigDocument.
41 | # @return a copy of the ConfigDocument with the desired value at the desired path
42 | #
43 | def set_value(path, new_value)
44 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigDocument should override `render` (#{self.class})"
45 | end
46 |
47 | #
48 | # Returns a new ConfigDocument that is a copy of the current ConfigDocument,
49 | # but with the desired value set at the desired path as with {@link #setValue(String, String)},
50 | # but takes a ConfigValue instead of a string.
51 | #
52 | # @param path the path at which to set the desired value
53 | # @param newValue the value to set at the desired path, represented as a ConfigValue.
54 | # The rendered text of the ConfigValue will be inserted into the
55 | # ConfigDocument.
56 | # @return a copy of the ConfigDocument with the desired value at the desired path
57 | #
58 | def set_config_value(path, new_value)
59 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigDocument should override `render` (#{self.class})"
60 | end
61 |
62 | #
63 | # Returns a new ConfigDocument that is a copy of the current ConfigDocument, but with
64 | # the value at the desired path removed. If the desired path does not exist in the document,
65 | # a copy of the current document will be returned. If there is an array at the root, an exception
66 | # will be thrown.
67 | #
68 | # @param path the path to remove from the document
69 | # @return a copy of the ConfigDocument with the desired value removed from the document.
70 | #
71 | def remove_value(path)
72 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigDocument should override `render` (#{self.class})"
73 | end
74 |
75 | #
76 | # Returns a boolean indicating whether or not a ConfigDocument has a value at the desired path.
77 | # @param path the path to check
78 | # @return true if the path exists in the document, otherwise false
79 | #
80 | def has_value?(path)
81 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigDocument should override `render` (#{self.class})"
82 | end
83 |
84 | #
85 | # The original text of the input, modified if necessary with
86 | # any replaced or added values.
87 | # @return the modified original text
88 | #
89 | def render
90 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigDocument should override `render` (#{self.class})"
91 | end
92 | end
--------------------------------------------------------------------------------
/spec/unit/hocon/hocon_spec.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require 'spec_helper'
4 | require 'hocon'
5 | require 'hocon/config_render_options'
6 | require 'hocon/config_error'
7 | require 'hocon/config_syntax'
8 |
9 | ConfigParseError = Hocon::ConfigError::ConfigParseError
10 | ConfigWrongTypeError = Hocon::ConfigError::ConfigWrongTypeError
11 |
12 | describe Hocon do
13 | let(:render_options) { Hocon::ConfigRenderOptions.defaults }
14 |
15 | before do
16 | render_options.origin_comments = false
17 | render_options.json = false
18 | end
19 |
20 | RSpec.shared_examples "hocon_parsing" do
21 |
22 | it "should make the config data available as a map" do
23 | expect(conf).to eq(expected)
24 | end
25 |
26 | end
27 |
28 | [EXAMPLE1, EXAMPLE2].each do |example|
29 | let(:input_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/input.conf" }
30 | let(:output_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/output.conf" }
31 | let(:output) { File.read("#{output_file}") }
32 | let(:output_nocomments_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/output_nocomments.conf" }
33 | let(:output_nocomments) { File.read("#{output_nocomments_file}") }
34 | let(:expected) { example[:hash] }
35 | # TODO 'reparsed' appears to be unused
36 | let(:reparsed) { Hocon::ConfigFactory.parse_file("#{FIXTURE_DIR}/parse_render/#{example[:name]}/output.conf") }
37 |
38 | context "loading a HOCON file" do
39 | let(:conf) { Hocon.load(input_file) }
40 | include_examples "hocon_parsing"
41 | end
42 |
43 | context "parsing a HOCON string" do
44 | let(:string) { File.open(input_file).read }
45 | let(:conf) { Hocon.parse(string) }
46 | include_examples "hocon_parsing"
47 | end
48 | end
49 |
50 | it "should fail to parse an array" do
51 | puts
52 | expect{(Hocon.parse('[1,2,3]'))}.
53 | to raise_error(ConfigWrongTypeError)
54 | end
55 |
56 | it "should fail to parse an array" do
57 | expect{(Hocon.parse('["one", "two" "three"]'))}.
58 | to raise_error(ConfigWrongTypeError)
59 | end
60 |
61 | context "loading a HOCON file with a substitution" do
62 | conf = Hocon.load("#{FIXTURE_DIR}/parse_render/#{EXAMPLE3[:name]}/input.conf")
63 | expected = EXAMPLE3[:hash]
64 | it "should successfully resolve the substitution" do
65 | expect(conf).to eq(expected)
66 | end
67 | end
68 |
69 | context "loading a file with an unknown extension" do
70 | context "without specifying the config format" do
71 | it "should raise an error" do
72 | expect {
73 | Hocon.load("#{FIXTURE_DIR}/hocon/by_extension/cat.test")
74 | }.to raise_error(ConfigParseError, /Unrecognized file extension '.test'/)
75 | end
76 | end
77 |
78 | context "while specifying the config format" do
79 | it "should parse properly if the config format is correct" do
80 | expect(Hocon.load("#{FIXTURE_DIR}/hocon/by_extension/cat.test",
81 | {:syntax => Hocon::ConfigSyntax::HOCON})).
82 | to eq({"meow" => "cats"})
83 | expect(Hocon.load("#{FIXTURE_DIR}/hocon/by_extension/cat.test-json",
84 | {:syntax => Hocon::ConfigSyntax::HOCON})).
85 | to eq({"meow" => "cats"})
86 | end
87 | it "should parse properly if the config format is compatible" do
88 | expect(Hocon.load("#{FIXTURE_DIR}/hocon/by_extension/cat.test-json",
89 | {:syntax => Hocon::ConfigSyntax::JSON})).
90 | to eq({"meow" => "cats"})
91 | end
92 | it "should raise an error if the config format is incompatible" do
93 | expect {
94 | Hocon.load("#{FIXTURE_DIR}/hocon/by_extension/cat.test",
95 | {:syntax => Hocon::ConfigSyntax::JSON})
96 | }.to raise_error(ConfigParseError, /Document must have an object or array at root/)
97 | end
98 | end
99 | end
100 |
101 | context "loading config that includes substitutions" do
102 | it "should be able to `load` from a file" do
103 | expect(Hocon.load("#{FIXTURE_DIR}/hocon/with_substitution/subst.conf")).
104 | to eq({"a" => true, "b" => true, "c" => ["foo", "bar", "baz"]})
105 | end
106 | it "should be able to `parse` from a string" do
107 | expect(Hocon.parse(File.read("#{FIXTURE_DIR}/hocon/with_substitution/subst.conf"))).
108 | to eq({"a" => true, "b" => true, "c" => ["foo", "bar", "baz"]})
109 | end
110 | end
111 |
112 |
113 | end
114 |
115 |
--------------------------------------------------------------------------------
/lib/hocon/config_value.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../hocon'
4 | require_relative '../hocon/config_mergeable'
5 |
6 | #
7 | # An immutable value, following the JSON type
8 | # schema.
9 | #
10 | #
11 | # Because this object is immutable, it is safe to use from multiple threads and
12 | # there's no need for "defensive copies."
13 | #
14 | #
15 | # Do not implement interface {@code ConfigValue}; it should only be
16 | # implemented by the config library. Arbitrary implementations will not work
17 | # because the library internals assume a specific concrete implementation.
18 | # Also, this interface is likely to grow new methods over time, so third-party
19 | # implementations will break.
20 | #
21 | module Hocon::ConfigValue
22 | include Hocon::ConfigMergeable
23 |
24 | #
25 | # The origin of the value (file, line number, etc.), for debugging and
26 | # error messages.
27 | #
28 | # @return where the value came from
29 | #
30 | def origin
31 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigValue` must implement `origin` (#{self.class})"
32 | end
33 |
34 |
35 | #
36 | # The {@link ConfigValueType} of the value; matches the JSON type schema.
37 | #
38 | # @return value's type
39 | #
40 | def value_type
41 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigValue` must implement `value_type` (#{self.class})"
42 | end
43 |
44 | #
45 | # Returns the value as a plain Java boxed value, that is, a {@code String},
46 | # {@code Number}, {@code Boolean}, {@code Map
36 | # foo = { a: 42 }
37 | # foo = { b: 43 }
38 | #
39 | #
40 | # Here, the two objects are merged as if you had written:
41 | #
42 | #
43 | # foo = { a: 42, b: 43 }
44 | #
45 | #
46 | # object.withFallback(nonObject).withFallback(otherObject),
53 | # then otherObject will simply be ignored. This is an
54 | # intentional part of how merging works, because non-objects such as
55 | # strings and integers replace (rather than merging with) any prior value:
56 | #
57 | #
58 | # foo = { a: 42 }
59 | # foo = 10
60 | #
61 | #
62 | # Here, the number 10 "wins" and the value of foo would be
63 | # simply 10. Again, for details see the spec.
64 | #
65 | # @param other
66 | # an object whose keys should be used as fallbacks, if the keys
67 | # are not present in this one
68 | # @return a new object (or the original one, if the fallback doesn't get
69 | # used)
70 | #
71 | def with_fallback(other)
72 | raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigMergeable` must implement `with_fallback` (#{self.class})"
73 | end
74 | end
75 |
--------------------------------------------------------------------------------
/lib/hocon/impl/config_impl_util.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../../hocon/impl'
4 | require 'stringio'
5 |
6 | class Hocon::Impl::ConfigImplUtil
7 | def self.equals_handling_nil?(a, b)
8 | # This method probably doesn't make any sense in ruby... not sure
9 | if a.nil? && !b.nil?
10 | false
11 | elsif !a.nil? && b.nil?
12 | false
13 | # in ruby, the == and .equal? are the opposite of what they are in Java
14 | elsif a.equal?(b)
15 | true
16 | else
17 | a == b
18 | end
19 | end
20 |
21 | #
22 | # This is public ONLY for use by the "config" package, DO NOT USE this ABI
23 | # may change.
24 | #
25 | def self.render_json_string(s)
26 | sb = StringIO.new
27 | sb << '"'
28 | s.chars.each do |c|
29 | case c
30 | when '"' then sb << "\\\""
31 | when "\\" then sb << "\\\\"
32 | when "\n" then sb << "\\n"
33 | when "\b" then sb << "\\b"
34 | when "\f" then sb << "\\f"
35 | when "\r" then sb << "\\r"
36 | when "\t" then sb << "\\t"
37 | else
38 | if c =~ /[[:cntrl:]]/
39 | sb << ("\\u%04x" % c)
40 | else
41 | sb << c
42 | end
43 | end
44 | end
45 | sb << '"'
46 | sb.string
47 | end
48 |
49 | def self.render_string_unquoted_if_possible(s)
50 | # this can quote unnecessarily as long as it never fails to quote when
51 | # necessary
52 | if s.length == 0
53 | return render_json_string(s)
54 | end
55 |
56 | # if it starts with a hyphen or number, we have to quote
57 | # to ensure we end up with a string and not a number
58 | first = s.chars.first
59 | if (first =~ /[[:digit:]]/) || (first == '-')
60 | return render_json_string(s)
61 | end
62 |
63 | # only unquote if it's pure alphanumeric
64 | s.chars.each do |c|
65 | unless (c =~ /[[:alnum:]]/) || (c == '-')
66 | return render_json_string(s)
67 | end
68 | end
69 |
70 | s
71 | end
72 |
73 | def self.join_path(*elements)
74 | Hocon::Impl::Path.from_string_list(elements).render
75 | end
76 |
77 | def self.split_path(path)
78 | p = Hocon::Impl::Path.new_path(path)
79 | elements = []
80 |
81 | until p.nil?
82 | elements << p.first
83 | p = p.remainder
84 | end
85 |
86 | elements
87 | end
88 |
89 | def self.whitespace?(c)
90 | # this implementation is *not* a port of the java code, because it relied on
91 | # the method java.lang.Character#isWhitespace. This is probably
92 | # insanely slow (running a regex against every single character in the
93 | # file).
94 | c =~ /[[:space:]]/
95 | end
96 |
97 | def self.unicode_trim(s)
98 | # this implementation is *not* a port of the java code. Ruby can strip
99 | # unicode whitespace much easier than Java can, and relies on a lot of
100 | # Java functions that don't really have straight equivalents in Ruby.
101 | s.gsub(/[:space]/, ' ')
102 | s.strip
103 | end
104 | end
105 |
--------------------------------------------------------------------------------
/lib/hocon/config_value_factory.rb:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require_relative '../hocon'
4 | require_relative '../hocon/impl/config_impl'
5 |
6 | class Hocon::ConfigValueFactory
7 | ConfigImpl = Hocon::Impl::ConfigImpl
8 |
9 | #
10 | # Creates a {@link ConfigValue} from a plain value, which may be
11 | # a Boolean, Number, String,
12 | # Hash, or nil. A
13 | # Hash must be a Hash from String to more values
14 | # that can be supplied to from_any_ref(). A Hash
15 | # will become a {@link ConfigObject} and an Array will become a
16 | # {@link ConfigList}.
17 | #
18 | # Hash passed to from_any_ref(), the map's keys
20 | # are plain keys, not path expressions. So if your Hash has a
21 | # key "foo.bar" then you will get one object with a key called "foo.bar",
22 | # rather than an object with a key "foo" containing another object with a
23 | # key "bar".
24 | #
25 | # ConfigValue to this
41 | # function, it will be returned unmodified. (The
42 | # origin_description will be ignored in this
43 | # case.)
44 | #
45 | #