hocon-1.2.5/0000755000175000017500000000000013154611745012530 5ustar apoikosapoikoshocon-1.2.5/bin/0000755000175000017500000000000013154611745013300 5ustar apoikosapoikoshocon-1.2.5/bin/hocon0000755000175000017500000000012713154611745014334 0ustar apoikosapoikos#!/usr/bin/env ruby require 'hocon/cli' Hocon::CLI.main(Hocon::CLI.parse_args(ARGV)) hocon-1.2.5/CHANGELOG.md0000644000175000017500000000616513154611745014351 0ustar apoikosapoikos## 1.2.5 This is a bugfix release * Fixed loading files with UTF-8 characters in their file paths ## 1.2.4 This is a feature release. * Added a cli tool called `hocon` for reading and manipulating hocon files Note that the version numbers 1.2.0-1.2.3 were not used because of bugs in our release pipeline we were working out ## 1.1.3 This is a bugfix release. * Fixed bug where Hocon.parse would throw a ConfigNotResolved error if you passed it a String that contained values with substitutions. ## 1.1.2 This is a bugfix release. * Fixed bug where Hocon::ConfigFactory.parse_file was not handling files with BOMs on Windows, causing UTF-8 files to not load properly. ## 1.1.1 This is a bugfix release. * Fixed a bug where an undefined method `value_type_name` error was being thrown due to improper calls to the class method. ## 1.1.0 This is a bugfix/feature release * Fixed a bug where unrecognized config file extensions caused `Hocon.load` to return an empty hash instead of an error. * Added an optional `:syntax` key to the `Hocon.load` method to explicitly specify the file format * Renamed internal usage of `name` methods to avoid overriding built in `Object#name` method ## 1.0.1 This is a bugfix release. The API is stable enough and the code is being used in production, so the version is also being bumped to 1.0.0 * Fixed a bug wherein calling "Hocon.load" would not resolve substitutions. * Fixed a circular dependency between the Hocon and Hocon::ConfigFactory namespaces. Using the Hocon::ConfigFactory class now requires you to use a `require 'hocon/config_factory'` instead of `require hocon` * Add support for hashes with keyword keys ## 1.0.0 This version number was burned. ## 0.9.3 This is a bugfix release. * Fixed a bug wherein inserting an array or a hash into a ConfigDocument would cause "# hardcoded value" comments to be generated before every entry in the hash/array. ## 0.9.2 This is a bugfix release * Fixed a bug wherein attempting to insert a complex value (such as an array or a hash) into an empty ConfigDocument would cause an undefined method error. ## 0.9.1 This is a bugfix release. * Fixed a bug wherein ugly configurations were being generated due to the addition of new objects when a setting is set at a path that does not currently exist in the configuration. Previously, these new objects were being added as single-line objects. They will now be added as multi-line objects if the parent object is a multi-line object or is an empty root object. ## 0.9.0 This is a promotion of the 0.1.0 release with one small bug fix: * Fixed bug wherein using the `set_config_value` method with some parsed values would cause a failure due to surrounding whitespace ## 0.1.0 This is a feature release containing a large number of changes and improvements * Added support for concatenation * Added support for substitutions * Added support for file includes. Other types of includes are not supported * Added the new ConfigDocument API that was recently implemented in the upstream Java library * Improved JSON support * Fixed a large number of small bugs related to various pieces of implementation hocon-1.2.5/hocon.gemspec0000644000175000017500000001763113154611745015213 0ustar apoikosapoikos######################################################### # This file has been automatically generated by gem2tgz # ######################################################### # -*- encoding: utf-8 -*- # stub: hocon 1.2.5 ruby lib Gem::Specification.new do |s| s.name = "hocon".freeze s.version = "1.2.5" s.required_rubygems_version = Gem::Requirement.new(">= 0".freeze) if s.respond_to? :required_rubygems_version= s.require_paths = ["lib".freeze] s.authors = ["Chris Price".freeze, "Wayne Warren".freeze, "Preben Ingvaldsen".freeze, "Joe Pinsonault".freeze, "Kevin Corcoran".freeze, "Jane Lu".freeze] s.date = "2016-10-27" s.description = "== A port of the Java {Typesafe Config}[https://github.com/typesafehub/config] library to Ruby".freeze s.email = "chris@puppetlabs.com".freeze s.executables = ["hocon".freeze] s.files = ["CHANGELOG.md".freeze, "HISTORY.md".freeze, "LICENSE".freeze, "README.md".freeze, "bin/hocon".freeze, "lib/hocon.rb".freeze, "lib/hocon/cli.rb".freeze, "lib/hocon/config.rb".freeze, "lib/hocon/config_error.rb".freeze, "lib/hocon/config_factory.rb".freeze, "lib/hocon/config_include_context.rb".freeze, "lib/hocon/config_includer_file.rb".freeze, "lib/hocon/config_list.rb".freeze, "lib/hocon/config_mergeable.rb".freeze, "lib/hocon/config_object.rb".freeze, "lib/hocon/config_parse_options.rb".freeze, "lib/hocon/config_parseable.rb".freeze, "lib/hocon/config_render_options.rb".freeze, "lib/hocon/config_resolve_options.rb".freeze, "lib/hocon/config_syntax.rb".freeze, "lib/hocon/config_util.rb".freeze, "lib/hocon/config_value.rb".freeze, "lib/hocon/config_value_factory.rb".freeze, "lib/hocon/config_value_type.rb".freeze, "lib/hocon/impl.rb".freeze, "lib/hocon/impl/abstract_config_node.rb".freeze, "lib/hocon/impl/abstract_config_node_value.rb".freeze, "lib/hocon/impl/abstract_config_object.rb".freeze, "lib/hocon/impl/abstract_config_value.rb".freeze, "lib/hocon/impl/array_iterator.rb".freeze, "lib/hocon/impl/config_boolean.rb".freeze, "lib/hocon/impl/config_concatenation.rb".freeze, "lib/hocon/impl/config_delayed_merge.rb".freeze, "lib/hocon/impl/config_delayed_merge_object.rb".freeze, "lib/hocon/impl/config_document_parser.rb".freeze, "lib/hocon/impl/config_double.rb".freeze, "lib/hocon/impl/config_impl.rb".freeze, "lib/hocon/impl/config_impl_util.rb".freeze, "lib/hocon/impl/config_include_kind.rb".freeze, "lib/hocon/impl/config_int.rb".freeze, "lib/hocon/impl/config_node_array.rb".freeze, "lib/hocon/impl/config_node_comment.rb".freeze, "lib/hocon/impl/config_node_complex_value.rb".freeze, "lib/hocon/impl/config_node_concatenation.rb".freeze, "lib/hocon/impl/config_node_field.rb".freeze, "lib/hocon/impl/config_node_include.rb".freeze, "lib/hocon/impl/config_node_object.rb".freeze, "lib/hocon/impl/config_node_path.rb".freeze, "lib/hocon/impl/config_node_root.rb".freeze, "lib/hocon/impl/config_node_simple_value.rb".freeze, "lib/hocon/impl/config_node_single_token.rb".freeze, "lib/hocon/impl/config_null.rb".freeze, "lib/hocon/impl/config_number.rb".freeze, "lib/hocon/impl/config_parser.rb".freeze, "lib/hocon/impl/config_reference.rb".freeze, "lib/hocon/impl/config_string.rb".freeze, "lib/hocon/impl/container.rb".freeze, "lib/hocon/impl/default_transformer.rb".freeze, "lib/hocon/impl/from_map_mode.rb".freeze, "lib/hocon/impl/full_includer.rb".freeze, "lib/hocon/impl/memo_key.rb".freeze, "lib/hocon/impl/mergeable_value.rb".freeze, "lib/hocon/impl/origin_type.rb".freeze, "lib/hocon/impl/parseable.rb".freeze, "lib/hocon/impl/path.rb".freeze, "lib/hocon/impl/path_builder.rb".freeze, "lib/hocon/impl/path_parser.rb".freeze, "lib/hocon/impl/replaceable_merge_stack.rb".freeze, "lib/hocon/impl/resolve_context.rb".freeze, "lib/hocon/impl/resolve_memos.rb".freeze, "lib/hocon/impl/resolve_result.rb".freeze, "lib/hocon/impl/resolve_source.rb".freeze, "lib/hocon/impl/resolve_status.rb".freeze, "lib/hocon/impl/simple_config.rb".freeze, "lib/hocon/impl/simple_config_document.rb".freeze, "lib/hocon/impl/simple_config_list.rb".freeze, "lib/hocon/impl/simple_config_object.rb".freeze, "lib/hocon/impl/simple_config_origin.rb".freeze, "lib/hocon/impl/simple_include_context.rb".freeze, "lib/hocon/impl/simple_includer.rb".freeze, "lib/hocon/impl/substitution_expression.rb".freeze, "lib/hocon/impl/token.rb".freeze, "lib/hocon/impl/token_type.rb".freeze, "lib/hocon/impl/tokenizer.rb".freeze, "lib/hocon/impl/tokens.rb".freeze, "lib/hocon/impl/unmergeable.rb".freeze, "lib/hocon/impl/unsupported_operation_error.rb".freeze, "lib/hocon/impl/url.rb".freeze, "lib/hocon/parser.rb".freeze, "lib/hocon/parser/config_document.rb".freeze, "lib/hocon/parser/config_document_factory.rb".freeze, "lib/hocon/parser/config_node.rb".freeze, "lib/hocon/version.rb".freeze, "spec/fixtures/hocon/by_extension/cat.conf".freeze, "spec/fixtures/hocon/by_extension/cat.test".freeze, "spec/fixtures/hocon/by_extension/cat.test-json".freeze, "spec/fixtures/hocon/with_substitution/subst.conf".freeze, "spec/fixtures/parse_render/example1/input.conf".freeze, "spec/fixtures/parse_render/example1/output.conf".freeze, "spec/fixtures/parse_render/example1/output_nocomments.conf".freeze, "spec/fixtures/parse_render/example2/input.conf".freeze, "spec/fixtures/parse_render/example2/output.conf".freeze, "spec/fixtures/parse_render/example2/output_nocomments.conf".freeze, "spec/fixtures/parse_render/example3/input.conf".freeze, "spec/fixtures/parse_render/example3/output.conf".freeze, "spec/fixtures/parse_render/example4/input.json".freeze, "spec/fixtures/parse_render/example4/output.conf".freeze, "spec/fixtures/test_utils/resources/bom.conf".freeze, "spec/fixtures/test_utils/resources/cycle.conf".freeze, "spec/fixtures/test_utils/resources/file-include.conf".freeze, "spec/fixtures/test_utils/resources/include-from-list.conf".freeze, "spec/fixtures/test_utils/resources/subdir/bar.conf".freeze, "spec/fixtures/test_utils/resources/subdir/baz.conf".freeze, "spec/fixtures/test_utils/resources/subdir/foo.conf".freeze, "spec/fixtures/test_utils/resources/test01.conf".freeze, "spec/fixtures/test_utils/resources/test01.json".freeze, "spec/fixtures/test_utils/resources/test03.conf".freeze, "spec/fixtures/test_utils/resources/utf16.conf".freeze, "spec/fixtures/test_utils/resources/utf8.conf".freeze, "spec/fixtures/test_utils/resources/\u{16a0}\u{16c7}\u{16bb}.conf".freeze, "spec/spec_helper.rb".freeze, "spec/test_utils.rb".freeze, "spec/unit/cli/cli_spec.rb".freeze, "spec/unit/hocon/README.md".freeze, "spec/unit/hocon/hocon_spec.rb".freeze, "spec/unit/typesafe/config/README.md".freeze, "spec/unit/typesafe/config/concatenation_spec.rb".freeze, "spec/unit/typesafe/config/conf_parser_spec.rb".freeze, "spec/unit/typesafe/config/config_document_parser_spec.rb".freeze, "spec/unit/typesafe/config/config_document_spec.rb".freeze, "spec/unit/typesafe/config/config_factory_spec.rb".freeze, "spec/unit/typesafe/config/config_node_spec.rb".freeze, "spec/unit/typesafe/config/config_value_factory_spec.rb".freeze, "spec/unit/typesafe/config/config_value_spec.rb".freeze, "spec/unit/typesafe/config/path_spec.rb".freeze, "spec/unit/typesafe/config/public_api_spec.rb".freeze, "spec/unit/typesafe/config/simple_config_spec.rb".freeze, "spec/unit/typesafe/config/token_spec.rb".freeze, "spec/unit/typesafe/config/tokenizer_spec.rb".freeze] s.homepage = "https://github.com/puppetlabs/ruby-hocon".freeze s.licenses = ["Apache License, v2".freeze] s.required_ruby_version = Gem::Requirement.new(">= 1.9.0".freeze) s.rubygems_version = "2.5.2".freeze s.summary = "HOCON Config Library".freeze if s.respond_to? :specification_version then s.specification_version = 4 if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then s.add_development_dependency(%q.freeze, ["~> 1.5"]) s.add_development_dependency(%q.freeze, ["~> 2.14"]) else s.add_dependency(%q.freeze, ["~> 1.5"]) s.add_dependency(%q.freeze, ["~> 2.14"]) end else s.add_dependency(%q.freeze, ["~> 1.5"]) s.add_dependency(%q.freeze, ["~> 2.14"]) end end hocon-1.2.5/README.md0000644000175000017500000001404313154611745014011 0ustar apoikosapoikosruby-hocon ========== [![Gem Version](https://badge.fury.io/rb/hocon.svg)](https://badge.fury.io/rb/hocon) [![Build Status](https://travis-ci.org/puppetlabs/ruby-hocon.png?branch=master)](https://travis-ci.org/puppetlabs/ruby-hocon) This is a port of the [Typesafe Config](https://github.com/typesafehub/config) library to Ruby. The library provides Ruby support for the [HOCON](https://github.com/typesafehub/config/blob/master/HOCON.md) configuration file format. At present, it supports parsing and modification of existing HOCON/JSON files via the `ConfigFactory` class and the `ConfigValueFactory` class, and rendering parsed config objects back to a String ([see examples below](#basic-usage)). It also supports the parsing and modification of HOCON/JSON files via `ConfigDocumentFactory`. **Note:** While the project is production ready, since not all features in the Typesafe library are supported, you may still run into some issues. If you find a problem, feel free to open a github issue. The implementation is intended to be as close to a line-for-line port as the two languages allow, in hopes of making it fairly easy to port over new changesets from the Java code base over time. Support ======= For best results, if you find an issue with this library, please open an issue on our [Jira issue tracker](https://tickets.puppetlabs.com/browse/HC). Issues filed there tend to be more visible to the current maintainers than issues on the Github issue tracker. Basic Usage =========== ```sh gem install hocon ``` To use the simple API, for reading config values: ```rb require 'hocon' conf = Hocon.load("myapp.conf") puts "Here's a setting: #{conf["foo"]["bar"]["baz"]}" ``` By default, the simple API will determine the configuration file syntax/format based on the filename extension of the file; `.conf` will be interpreted as HOCON, `.json` will be interpreted as strict JSON, and any other extension will cause an error to be raised since the syntax is unknown. If you'd like to use a different file extension, you manually specify the syntax, like this: ```rb require 'hocon' require 'hocon/config_syntax' conf = Hocon.load("myapp.blah", {:syntax => Hocon::ConfigSyntax::HOCON}) ``` Supported values for `:syntax` are: JSON, CONF, and HOCON. (CONF and HOCON are aliases, and both map to the underlying HOCON syntax.) To use the ConfigDocument API, if you need both read/write capability for modifying settings in a config file, or if you want to retain access to things like comments and line numbers: ```rb require 'hocon/parser/config_document_factory' require 'hocon/config_value_factory' # The below 4 variables will all be ConfigDocument instances doc = Hocon::Parser::ConfigDocumentFactory.parse_file("myapp.conf") doc2 = doc.set_value("a.b", "[1, 2, 3, 4, 5]") doc3 = doc.remove_value("a") doc4 = doc.set_config_value("a.b", Hocon::ConfigValueFactory.from_any_ref([1, 2, 3, 4, 5])) doc_has_value = doc.has_value?("a") # returns boolean orig_doc_text = doc.render # returns string ``` Note that a `ConfigDocument` is used primarily for simple configuration manipulation while preserving whitespace and comments. As such, it is not powerful as the regular `Config` API, and will not resolve substitutions. CLI Tool ======== The `hocon` gem comes bundles with a `hocon` command line tool which can be used to get and set values from hocon files ``` Usage: hocon [options] {get,set,unset} PATH [VALUE] Example usages: hocon -i settings.conf -o new_settings.conf set some.nested.value 42 hocon -f settings.conf set some.nested.value 42 cat settings.conf | hocon get some.nested.value Subcommands: get PATH - Returns the value at the given path set PATH VALUE - Sets or adds the given value at the given path unset PATH - Removes the value at the given path Options: -i, --in-file HOCON_FILE HOCON file to read/modify. If omitted, STDIN assumed -o, --out-file HOCON_FILE File to be written to. If omitted, STDOUT assumed -f, --file HOCON_FILE File to read/write to. Equivalent to setting -i/-o to the same file -j, --json Output values from the 'get' subcommand in json format -h, --help Show this message -v, --version Show version ``` CLI Examples -------- ### Basic Usage ``` $ cat settings.conf { foo: bar } $ hocon -i settings.conf get foo bar $ hocon -i settings.conf set foo baz $ cat settings.conf { foo: baz } # Write to a different file $ hocon -i settings.conf -o new_settings.conf set some.nested.value 42 $ cat new_settings.conf { foo: bar some: { nested: { value: 42 } } } # Write back to the same file $ hocon -f settings.conf set some.nested.value 42 $ cat settings.conf { foo: bar some: { nested: { value: 42 } } } ``` ### Complex Values If you give `set` a properly formatted hocon dictionary or array, it will try to accept it ``` $ hocon -i settings.conf set foo "{one: [1, 2, 3], two: {hello: world}}" { foo: {one: [1, 2, 3], two: {hello: world}} } ``` ### Chaining If `--in-file` or `--out-file` aren't specified, STDIN and STDOUT are used for the missing options. Therefore it's possible to chain `hocon` calls ``` $ cat settings.conf { foo: bar } $ cat settings.conf | hocon set foo 42 | hocon set one.two three { foo: 42 one: { two: three } } ``` ### JSON Output Calls to the `get` subcommand will return the data in HOCON format by default, but setting the `-j/--json` flag will cause it to return a valid JSON object ``` $ cat settings.conf foo: { bar: { baz: 42 } } $ hocon -i settings.conf get foo --json { "bar": { "baz": 42 } } ``` Testing ======= ```sh bundle install --path .bundle bundle exec rspec spec ``` Unsupported Features ==================== This supports many of the same things as the Java library, but there are some notable exceptions. Unsupported features include: * Non file includes * Loading resources from the class path or URLs * Properties files * Parsing anything other than files and strings * Duration and size settings * Java system properties hocon-1.2.5/LICENSE0000644000175000017500000002607513154611745013547 0ustar apoikosapoikosApache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright {yyyy} {name of copyright owner} Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. hocon-1.2.5/lib/0000755000175000017500000000000013154611745013276 5ustar apoikosapoikoshocon-1.2.5/lib/hocon.rb0000644000175000017500000000420413154611745014731 0ustar apoikosapoikos# encoding: utf-8 module Hocon # NOTE: the behavior of this load method differs a bit from the upstream public # API, where a file extension may be the preferred method of determining # the config syntax, even if you specify a Syntax value on ConfigParseOptions. # Here we prefer the syntax (optionally) specified by the user no matter what # the file extension is, and if they don't specify one and the file extension # is unrecognized, we raise an error. def self.load(file, opts = nil) # doing these requires lazily, because otherwise, classes that need to # `require 'hocon'` to get the module into scope will end up recursing # through this require and probably ending up with circular dependencies. require 'hocon/config_factory' require 'hocon/impl/parseable' require 'hocon/config_parse_options' require 'hocon/config_resolve_options' require 'hocon/config_error' syntax = opts ? opts[:syntax] : nil if syntax.nil? unless Hocon::Impl::Parseable.syntax_from_extension(file) raise Hocon::ConfigError::ConfigParseError.new( nil, "Unrecognized file extension '#{File.extname(file)}' and no value provided for :syntax option", nil) end config = Hocon::ConfigFactory.parse_file_any_syntax( file, Hocon::ConfigParseOptions.defaults) else config = Hocon::ConfigFactory.parse_file( file, Hocon::ConfigParseOptions.defaults.set_syntax(syntax)) end resolved_config = Hocon::ConfigFactory.load_from_config( config, Hocon::ConfigResolveOptions.defaults) resolved_config.root.unwrapped end def self.parse(string) # doing these requires lazily, because otherwise, classes that need to # `require 'hocon'` to get the module into scope will end up recursing # through this require and probably ending up with circular dependencies. require 'hocon/config_factory' require 'hocon/config_resolve_options' config = Hocon::ConfigFactory.parse_string(string) resolved_config = Hocon::ConfigFactory.load_from_config( config, Hocon::ConfigResolveOptions.defaults) resolved_config.root.unwrapped end end hocon-1.2.5/lib/hocon/0000755000175000017500000000000013154611745014404 5ustar apoikosapoikoshocon-1.2.5/lib/hocon/parser.rb0000644000175000017500000000007513154611745016227 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' module Hocon::Parser endhocon-1.2.5/lib/hocon/config_includer_file.rb0000644000175000017500000000171713154611745021070 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/config_error' # # Implement this in addition to {@link ConfigIncluder} if you want to # support inclusion of files with the {@code include file("filename")} syntax. # If you do not implement this but do implement {@link ConfigIncluder}, # attempts to load files will use the default includer. # module Hocon::ConfigIncluderFile # # Parses another item to be included. The returned object typically would # not have substitutions resolved. You can throw a ConfigException here to # abort parsing, or return an empty object, but may not return null. # # @param context # some info about the include context # @param what # the include statement's argument # @return a non-null ConfigObject # def include_file(context, what) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigIncluderFile` must implement `include_file` (#{self.class})" end end hocon-1.2.5/lib/hocon/config_include_context.rb0000644000175000017500000000350413154611745021447 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/config_error' # # Context provided to a {@link ConfigIncluder}; this interface is only useful # inside a {@code ConfigIncluder} implementation, and is not intended for apps # to implement. # #

# Do not implement this interface; it should only be implemented by # the config library. Arbitrary implementations will not work because the # library internals assume a specific concrete implementation. Also, this # interface is likely to grow new methods over time, so third-party # implementations will break. # module Hocon::ConfigIncludeContext # # Tries to find a name relative to whatever is doing the including, for # example in the same directory as the file doing the including. Returns # null if it can't meaningfully create a relative name. The returned # parseable may not exist; this function is not required to do any IO, just # compute what the name would be. # # The passed-in filename has to be a complete name (with extension), not # just a basename. (Include statements in config files are allowed to give # just a basename.) # # @param filename # the name to make relative to the resource doing the including # @return parseable item relative to the resource doing the including, or # null # def relative_to(filename) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigIncludeContext` must implement `relative_to` (#{self.class})" end # # Parse options to use (if you use another method to get a # {@link ConfigParseable} then use {@link ConfigParseable#options()} # instead though). # # @return the parse options # def parse_options raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigIncludeContext` must implement `parse_options` (#{self.class})" end end hocon-1.2.5/lib/hocon/config_list.rb0000644000175000017500000000320613154611745017232 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/config_value' require 'hocon/config_error' # # Subtype of {@link ConfigValue} representing a list value, as in JSON's # {@code [1,2,3]} syntax. # #

# {@code ConfigList} implements {@code java.util.List} so you can # use it like a regular Java list. Or call {@link #unwrapped()} to unwrap the # list elements into plain Java values. # #

# Like all {@link ConfigValue} subtypes, {@code ConfigList} is immutable. This # makes it threadsafe and you never have to create "defensive copies." The # mutator methods from {@link java.util.List} all throw # {@link java.lang.UnsupportedOperationException}. # #

# The {@link ConfigValue#valueType} method on a list returns # {@link ConfigValueType#LIST}. # #

# Do not implement {@code ConfigList}; it should only be implemented # by the config library. Arbitrary implementations will not work because the # library internals assume a specific concrete implementation. Also, this # interface is likely to grow new methods over time, so third-party # implementations will break. # # module Hocon::ConfigList include Hocon::ConfigValue # # Recursively unwraps the list, returning a list of plain Java values such # as Integer or String or whatever is in the list. # def unwrapped raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigValue should provide their own implementation of `unwrapped` (#{self.class})" end def with_origin(origin) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigValue should provide their own implementation of `with_origin` (#{self.class})" end end hocon-1.2.5/lib/hocon/config_util.rb0000644000175000017500000000431513154611745017236 0ustar apoikosapoikosrequire 'hocon/impl/config_impl_util' # Contains static utility methods class Hocon::ConfigUtil # # Quotes and escapes a string, as in the JSON specification. # # @param string # a string # @return the string quoted and escaped # def self.quote_string(string) Hocon::Impl::ConfigImplUtil.render_json_string(string) end # # Converts a list of keys to a path expression, by quoting the path # elements as needed and then joining them separated by a period. A path # expression is usable with a {@link Config}, while individual path # elements are usable with a {@link ConfigObject}. #

# See the overview documentation for {@link Config} for more detail on path # expressions vs. keys. # # @param elements # the keys in the path # @return a path expression # @throws ConfigException # if there are no elements # def self.join_path(*elements) Hocon::Impl::ConfigImplUtil.join_path(*elements) end # # Converts a list of strings to a path expression, by quoting the path # elements as needed and then joining them separated by a period. A path # expression is usable with a {@link Config}, while individual path # elements are usable with a {@link ConfigObject}. #

# See the overview documentation for {@link Config} for more detail on path # expressions vs. keys. # # @param elements # the keys in the path # @return a path expression # @throws ConfigException # if the list is empty # def self.join_path_from_list(elements) self.join_path(*elements) end # # Converts a path expression into a list of keys, by splitting on period # and unquoting the individual path elements. A path expression is usable # with a {@link Config}, while individual path elements are usable with a # {@link ConfigObject}. #

# See the overview documentation for {@link Config} for more detail on path # expressions vs. keys. # # @param path # a path expression # @return the individual keys in the path # @throws ConfigException # if the path expression is invalid # def self.split_path(path) Hocon::Impl::ConfigImplUtil.split_path(path) end end hocon-1.2.5/lib/hocon/parser/0000755000175000017500000000000013154611745015700 5ustar apoikosapoikoshocon-1.2.5/lib/hocon/parser/config_document.rb0000644000175000017500000001021613154611745021370 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/parser' require 'hocon/config_error' # # Represents an individual HOCON or JSON file, preserving all # formatting and syntax details. This can be used to replace # individual values and exactly render the original text of the # input. # #

# Because this object is immutable, it is safe to use from multiple threads and # there's no need for "defensive copies." # #

# Do not implement interface {@code ConfigDocument}; it should only be # implemented by the config library. Arbitrary implementations will not work # because the library internals assume a specific concrete implementation.# # Also, this interface is likely to grow new methods over time, so third-party # implementations will break. # module Hocon::Parser::ConfigDocument # # Returns a new ConfigDocument that is a copy of the current ConfigDocument, # but with the desired value set at the desired path. If the path exists, it will # remove all duplicates before the final occurrence of the path, and replace the value # at the final occurrence of the path. If the path does not exist, it will be added. If # the document has an array as the root value, an exception will be thrown. # # @param path the path at which to set the desired value # @param newValue the value to set at the desired path, represented as a string. This # string will be parsed into a ConfigNode using the same options used to # parse the entire document, and the text will be inserted # as-is into the document. Leading and trailing comments, whitespace, or # newlines are not allowed, and if present an exception will be thrown. # If a concatenation is passed in for newValue but the document was parsed # with JSON, the first value in the concatenation will be parsed and inserted # into the ConfigDocument. # @return a copy of the ConfigDocument with the desired value at the desired path # def set_value(path, new_value) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigDocument should override `render` (#{self.class})" end # # Returns a new ConfigDocument that is a copy of the current ConfigDocument, # but with the desired value set at the desired path as with {@link #setValue(String, String)}, # but takes a ConfigValue instead of a string. # # @param path the path at which to set the desired value # @param newValue the value to set at the desired path, represented as a ConfigValue. # The rendered text of the ConfigValue will be inserted into the # ConfigDocument. # @return a copy of the ConfigDocument with the desired value at the desired path # def set_config_value(path, new_value) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigDocument should override `render` (#{self.class})" end # # Returns a new ConfigDocument that is a copy of the current ConfigDocument, but with # the value at the desired path removed. If the desired path does not exist in the document, # a copy of the current document will be returned. If there is an array at the root, an exception # will be thrown. # # @param path the path to remove from the document # @return a copy of the ConfigDocument with the desired value removed from the document. # def remove_value(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigDocument should override `render` (#{self.class})" end # # Returns a boolean indicating whether or not a ConfigDocument has a value at the desired path. # @param path the path to check # @return true if the path exists in the document, otherwise false # def has_value?(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigDocument should override `render` (#{self.class})" end # # The original text of the input, modified if necessary with # any replaced or added values. # @return the modified original text # def render raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigDocument should override `render` (#{self.class})" end endhocon-1.2.5/lib/hocon/parser/config_node.rb0000644000175000017500000000200013154611745020467 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/parser' require 'hocon/config_error' # # An immutable node that makes up the ConfigDocument AST, and which can be # used to reproduce part or all of the original text of an input. # #

# Because this object is immutable, it is safe to use from multiple threads and # there's no need for "defensive copies." # #

# Do not implement interface {@code ConfigNode}; it should only be # implemented by the config library. Arbitrary implementations will not work # because the library internals assume a specific concrete implementation. # Also, this interface is likely to grow new methods over time, so third-party # implementations will break. # module Hocon::Parser::ConfigNode # # The original text of the input which was used to form this particular node. # @return the original text used to form this node as a String # def render raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigNode should override `render` (#{self.class})" end end hocon-1.2.5/lib/hocon/parser/config_document_factory.rb0000644000175000017500000000176713154611745023132 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/parser' require 'hocon/impl/parseable' require 'hocon/config_parse_options' # # Factory for creating {@link # com.typesafe.config.parser.ConfigDocument} instances. # class Hocon::Parser::ConfigDocumentFactory # # Parses a file into a ConfigDocument instance. # # @param file # the file to parse # @param options # parse options to control how the file is interpreted # @return the parsed configuration # @throws com.typesafe.config.ConfigException on IO or parse errors # def self.parse_file(file, options = Hocon::ConfigParseOptions.defaults) Hocon::Impl::Parseable.new_file(file, options).parse_config_document end # # Parses a string which should be valid HOCON or JSON. # # @param s string to parse # @param options parse options # @return the parsed configuration # def self.parse_string(s, options = Hocon::ConfigParseOptions.defaults) Hocon::Impl::Parseable.new_string(s, options).parse_config_document end endhocon-1.2.5/lib/hocon/config_parseable.rb0000644000175000017500000000317713154611745020224 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/config_error' # # An opaque handle to something that can be parsed, obtained from # {@link ConfigIncludeContext}. # #

# Do not implement this interface; it should only be implemented by # the config library. Arbitrary implementations will not work because the # library internals assume a specific concrete implementation. Also, this # interface is likely to grow new methods over time, so third-party # implementations will break. # module Hocon::ConfigParseable # # Parse whatever it is. The options should come from # {@link ConfigParseable#options options()} but you could tweak them if you # like. # # @param options # parse options, should be based on the ones from # {@link ConfigParseable#options options()} # @return the parsed object # def parse(options) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigParseable` must implement `parse` (#{self.class})" end # # Returns a {@link ConfigOrigin} describing the origin of the parseable # item. # @return the origin of the parseable item # def origin raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigParseable` must implement `origin` (#{self.class})" end # # Get the initial options, which can be modified then passed to parse(). # These options will have the right description, includer, and other # parameters already set up. # @return the initial options # def options raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigParseable` must implement `options` (#{self.class})" end end hocon-1.2.5/lib/hocon/version.rb0000644000175000017500000000007513154611745016420 0ustar apoikosapoikosmodule Hocon module Version STRING = '1.2.5' end end hocon-1.2.5/lib/hocon/config_resolve_options.rb0000644000175000017500000000116413154611745021512 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' class Hocon::ConfigResolveOptions attr_reader :use_system_environment, :allow_unresolved def initialize(use_system_environment, allow_unresolved) @use_system_environment = use_system_environment @allow_unresolved = allow_unresolved end def set_use_system_environment(value) self.class.new(value, @allow_unresolved) end def set_allow_unresolved(value) self.class.new(@use_system_environment, value) end class << self def defaults self.new(true, false) end def no_system defaults.set_use_system_environment(false) end end end hocon-1.2.5/lib/hocon/config_parse_options.rb0000644000175000017500000000352713154611745021152 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' class Hocon::ConfigParseOptions attr_accessor :syntax, :origin_description, :allow_missing, :includer def self.defaults self.new(nil, nil, true, nil) end def initialize(syntax, origin_description, allow_missing, includer) @syntax = syntax @origin_description = origin_description @allow_missing = allow_missing @includer = includer end def set_syntax(syntax) if @syntax == syntax self else Hocon::ConfigParseOptions.new(syntax, @origin_description, @allow_missing, @includer) end end def set_origin_description(origin_description) if @origin_description == origin_description self else Hocon::ConfigParseOptions.new(@syntax, origin_description, @allow_missing, @includer) end end def set_allow_missing(allow_missing) if allow_missing? == allow_missing self else Hocon::ConfigParseOptions.new(@syntax, @origin_description, allow_missing, @includer) end end def allow_missing? @allow_missing end def set_includer(includer) if @includer == includer self else Hocon::ConfigParseOptions.new(@syntax, @origin_description, @allow_missing, includer) end end def append_includer(includer) if @includer == includer self elsif @includer set_includer(@includer.with_fallback(includer)) else set_includer(includer) end end end hocon-1.2.5/lib/hocon/config_syntax.rb0000644000175000017500000000041413154611745017603 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' module Hocon::ConfigSyntax JSON = 0 CONF = 1 # alias 'HOCON' to 'CONF' since some users may be more familiar with that HOCON = 1 # we're not going to try to support .properties files any time soon :) #PROPERTIES = 2 end hocon-1.2.5/lib/hocon/config_value_type.rb0000644000175000017500000000123013154611745020427 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/config_error' # # The type of a configuration value (following the JSON type schema). # module Hocon::ConfigValueType OBJECT = 0 LIST = 1 NUMBER = 2 BOOLEAN = 3 NULL = 4 STRING = 5 def self.value_type_name(config_value_type) case config_value_type when OBJECT then "OBJECT" when LIST then "LIST" when NUMBER then "NUMBER" when BOOLEAN then "BOOLEAN" when NULL then "NULL" when STRING then "STRING" else raise Hocon::ConfigError::ConfigBugOrBrokenError, "Unrecognized value type '#{config_value_type}'" end end end hocon-1.2.5/lib/hocon/config_factory.rb0000644000175000017500000000437413154611745017735 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/impl/parseable' require 'hocon/config_parse_options' require 'hocon/impl/config_impl' require 'hocon/config_factory' ## Please note that the `parse` operations will simply create a ConfigValue ## and do nothing else, whereas the `load` operations will perform a higher-level ## operation and will resolve substitutions. If you have substitutions in your ## configuration, use a `load` function class Hocon::ConfigFactory def self.parse_file(file_path, options = Hocon::ConfigParseOptions.defaults) Hocon::Impl::Parseable.new_file(file_path, options).parse.to_config end def self.parse_string(string, options = Hocon::ConfigParseOptions.defaults) Hocon::Impl::Parseable.new_string(string, options).parse.to_config end def self.parse_file_any_syntax(file_base_name, options) Hocon::Impl::ConfigImpl.parse_file_any_syntax(file_base_name, options).to_config end def self.empty(origin_description = nil) Hocon::Impl::ConfigImpl.empty_config(origin_description) end # Because of how optional arguments work, if either parse or resolve options is supplied # both must be supplied. load_file_with_parse_options or load_file_with_resolve_options # can be used instead, or the argument you don't care about in load_file can be nil # # e.g.: # load_file("settings", my_parse_options, nil) # is equivalent to: # load_file_with_parse_options("settings", my_parse_options) def self.load_file(file_base_name, parse_options = nil, resolve_options = nil) parse_options ||= Hocon::ConfigParseOptions.defaults resolve_options ||= Hocon::ConfigResolveOptions.defaults config = Hocon::ConfigFactory.parse_file_any_syntax(file_base_name, parse_options) self.load_from_config(config, resolve_options) end def self.load_file_with_parse_options(file_base_name, parse_options) self.load_file(file_base_name, parse_options, nil) end def self.load_file_with_resolve_options(file_base_name, resolve_options) self.load_file(file_base_name, nil, resolve_options) end def self.load_from_config(config, resolve_options) config.with_fallback(self.default_reference).resolve(resolve_options) end def self.default_reference Hocon::Impl::ConfigImpl.default_reference end end hocon-1.2.5/lib/hocon/config_object.rb0000644000175000017500000001317513154611745017533 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/config_value' # # Subtype of {@link ConfigValue} representing an object (AKA dictionary or map) # value, as in JSON's curly brace { "a" : 42 } syntax. # #

# An object may also be viewed as a {@link Config} by calling # {@link ConfigObject#toConfig()}. # #

# {@code ConfigObject} implements {@code java.util.Map} so # you can use it like a regular Java map. Or call {@link #unwrapped()} to # unwrap the map to a map with plain Java values rather than # {@code ConfigValue}. # #

# Like all {@link ConfigValue} subtypes, {@code ConfigObject} is immutable. # This makes it threadsafe and you never have to create "defensive copies." The # mutator methods from {@link java.util.Map} all throw # {@link java.lang.UnsupportedOperationException}. # #

# The {@link ConfigValue#valueType} method on an object returns # {@link ConfigValueType#OBJECT}. # #

# In most cases you want to use the {@link Config} interface rather than this # one. Call {@link #toConfig()} to convert a {@code ConfigObject} to a # {@code Config}. # #

# The API for a {@code ConfigObject} is in terms of keys, while the API for a # {@link Config} is in terms of path expressions. Conceptually, # {@code ConfigObject} is a tree of maps from keys to values, while a # {@code Config} is a one-level map from paths to values. # #

# Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert # between path expressions and individual path elements (keys). # #

# A {@code ConfigObject} may contain null values, which will have # {@link ConfigValue#valueType()} equal to {@link ConfigValueType#NULL}. If # {@link ConfigObject#get(Object)} returns Java's null then the key was not # present in the parsed file (or wherever this value tree came from). If # {@code get("key")} returns a {@link ConfigValue} with type # {@code ConfigValueType#NULL} then the key was set to null explicitly in the # config file. # #

# Do not implement interface {@code ConfigObject}; it should only be # implemented by the config library. Arbitrary implementations will not work # because the library internals assume a specific concrete implementation. # Also, this interface is likely to grow new methods over time, so third-party # implementations will break. # module Hocon::ConfigObject include Hocon::ConfigValue # # Converts this object to a {@link Config} instance, enabling you to use # path expressions to find values in the object. This is a constant-time # operation (it is not proportional to the size of the object). # # @return a {@link Config} with this object as its root # def to_config raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigObject should provide their own implementation of `to_config` (#{self.class})" end # # Recursively unwraps the object, returning a map from String to whatever # plain Java values are unwrapped from the object's values. # # @return a {@link java.util.Map} containing plain Java objects # def unwrapped raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigObject should provide their own implementation of `unwrapped` (#{self.class})" end def with_fallback(other) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigObject should provide their own implementation of `with_fallback` (#{self.class})" end # # Gets a {@link ConfigValue} at the given key, or returns null if there is # no value. The returned {@link ConfigValue} may have # {@link ConfigValueType#NULL} or any other type, and the passed-in key # must be a key in this object (rather than a path expression). # # @param key # key to look up # # @return the value at the key or null if none # def get(key) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigObject should provide their own implementation of `get` (#{self.class})" end # # Clone the object with only the given key (and its children) retained; all # sibling keys are removed. # # @param key # key to keep # @return a copy of the object minus all keys except the one specified # def with_only_key(key) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigObject should provide their own implementation of `with_only_key` (#{self.class})" end # # Clone the object with the given key removed. # # @param key # key to remove # @return a copy of the object minus the specified key # def without_key(key) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigObject should provide their own implementation of `without_key` (#{self.class})" end # # Returns a {@code ConfigObject} based on this one, but with the given key # set to the given value. Does not modify this instance (since it's # immutable). If the key already has a value, that value is replaced. To # remove a value, use {@link ConfigObject#withoutKey(String)}. # # @param key # key to add # @param value # value at the new key # @return the new instance with the new map entry # def with_value(key, value) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigObject should provide their own implementation of `with_value` (#{self.class})" end def with_origin(origin) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigObject should provide their own implementation of `with_origin` (#{self.class})" end end hocon-1.2.5/lib/hocon/cli.rb0000644000175000017500000001660613154611745015511 0ustar apoikosapoikosrequire 'optparse' require 'hocon' require 'hocon/version' require 'hocon/config_render_options' require 'hocon/config_factory' require 'hocon/config_value_factory' require 'hocon/parser/config_document_factory' require 'hocon/config_error' module Hocon::CLI # Aliases ConfigMissingError = Hocon::ConfigError::ConfigMissingError ConfigWrongTypeError = Hocon::ConfigError::ConfigWrongTypeError # List of valid subcommands SUBCOMMANDS = ['get', 'set', 'unset'] # For when a path can't be found in a hocon config class MissingPathError < StandardError end # Parses the command line flags and argument # Returns a options hash with values for each option and argument def self.parse_args(args) options = {} opt_parser = OptionParser.new do |opts| subcommands = SUBCOMMANDS.join(',') opts.banner = "Usage: hocon [options] {#{subcommands}} PATH [VALUE]\n\n" + "Example usages:\n" + " hocon -i settings.conf -o new_settings.conf set some.nested.value 42\n" + " hocon -f settings.conf set some.nested.value 42\n" + " cat settings.conf | hocon get some.nested.value\n\n" + "Subcommands:\n" + " get PATH - Returns the value at the given path\n" + " set PATH VALUE - Sets or adds the given value at the given path\n" + " unset PATH - Removes the value at the given path" opts.separator('') opts.separator('Options:') in_file_description = 'HOCON file to read/modify. If omitted, STDIN assumed' opts.on('-i', '--in-file HOCON_FILE', in_file_description) do |in_file| options[:in_file] = in_file end out_file_description = 'File to be written to. If omitted, STDOUT assumed' opts.on('-o', '--out-file HOCON_FILE', out_file_description) do |out_file| options[:out_file] = out_file end file_description = 'File to read/write to. Equivalent to setting -i/-o to the same file' opts.on('-f', '--file HOCON_FILE', file_description) do |file| options[:file] = file end json_description = "Output values from the 'get' subcommand in json format" opts.on('-j', '--json', json_description) do |json| options[:json] = json end opts.on_tail('-h', '--help', 'Show this message') do puts opts exit end opts.on_tail('-v', '--version', 'Show version') do puts Hocon::Version::STRING exit end end # parse! returns the argument list minus all the flags it found remaining_args = opt_parser.parse!(args) # Ensure -i and -o aren't used at the same time as -f if (options[:in_file] || options[:out_file]) && options[:file] exit_with_usage_and_error(opt_parser, "--file can't be used with --in-file or --out-file") end # If --file is used, set --in/out-file to the same file if options[:file] options[:in_file] = options[:file] options[:out_file] = options[:file] end no_subcommand_error(opt_parser) unless remaining_args.size > 0 # Assume the first arg is the subcommand subcommand = remaining_args.shift options[:subcommand] = subcommand case subcommand when 'set' subcommand_arguments_error(subcommand, opt_parser) unless remaining_args.size >= 2 options[:path] = remaining_args.shift options[:new_value] = remaining_args.shift when 'get', 'unset' subcommand_arguments_error(subcommand, opt_parser) unless remaining_args.size >= 1 options[:path] = remaining_args.shift else invalid_subcommand_error(subcommand, opt_parser) end options end # Main entry point into the script # Calls the appropriate subcommand and handles errors raised from the subcommands def self.main(opts) hocon_text = get_hocon_file(opts[:in_file]) begin case opts[:subcommand] when 'get' puts do_get(opts, hocon_text) when 'set' print_or_write(do_set(opts, hocon_text), opts[:out_file]) when 'unset' print_or_write(do_unset(opts, hocon_text), opts[:out_file]) end rescue MissingPathError exit_with_error("Can't find the given path: '#{opts[:path]}'") end exit end # Entry point for the 'get' subcommand # Returns a string representation of the the value at the path given on the # command line def self.do_get(opts, hocon_text) config = Hocon::ConfigFactory.parse_string(hocon_text) unless config.has_path?(opts[:path]) raise MissingPathError.new end value = config.get_any_ref(opts[:path]) render_options = Hocon::ConfigRenderOptions.defaults # Otherwise weird comments show up in the output render_options.origin_comments = false # If json is false, the hocon format is used render_options.json = opts[:json] # Output colons between keys and values render_options.key_value_separator = :colon Hocon::ConfigValueFactory.from_any_ref(value).render(render_options) end # Entry point for the 'set' subcommand # Returns a string representation of the HOCON config after adding/replacing # the value at the given path with the given value def self.do_set(opts, hocon_text) config_doc = Hocon::Parser::ConfigDocumentFactory.parse_string(hocon_text) modified_config_doc = config_doc.set_value(opts[:path], opts[:new_value]) modified_config_doc.render end # Entry point for the 'unset' subcommand # Returns a string representation of the HOCON config after removing the # value at the given path def self.do_unset(opts, hocon_text) config_doc = Hocon::Parser::ConfigDocumentFactory.parse_string(hocon_text) unless config_doc.has_value?(opts[:path]) raise MissingPathError.new end modified_config_doc = config_doc.remove_value(opts[:path]) modified_config_doc.render end # If a file is provided, return it's contents. Otherwise read from STDIN def self.get_hocon_file(in_file) if in_file File.read(in_file) else STDIN.read end end # Print an error message and exit the program def self.exit_with_error(message) STDERR.puts "Error: #{message}" exit(1) end # Print an error message and usage, then exit the program def self.exit_with_usage_and_error(opt_parser, message) STDERR.puts opt_parser exit_with_error(message) end # Exits with an error saying there aren't enough arguments found for a given # subcommand. Prints the usage def self.subcommand_arguments_error(subcommand, opt_parser) error_message = "Too few arguments for '#{subcommand}' subcommand" exit_with_usage_and_error(opt_parser, error_message) end # Exits with an error for when no subcommand is supplied on the command line. # Prints the usage def self.no_subcommand_error(opt_parser) error_message = "Must specify subcommand from [#{SUBCOMMANDS.join(', ')}]" exit_with_usage_and_error(opt_parser, error_message) end # Exits with an error for when a subcommand doesn't exist. Prints the usage def self.invalid_subcommand_error(subcommand, opt_parser) error_message = "Invalid subcommand '#{subcommand}', must be one of [#{SUBCOMMANDS.join(', ')}]" exit_with_usage_and_error(opt_parser, error_message) end # If out_file is not nil, write to that file. Otherwise print to STDOUT def self.print_or_write(string, out_file) if out_file File.open(out_file, 'w') { |file| file.write(string) } else puts string end end end hocon-1.2.5/lib/hocon/config_value_factory.rb0000644000175000017500000000600013154611745021115 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/impl/config_impl' class Hocon::ConfigValueFactory ConfigImpl = Hocon::Impl::ConfigImpl # # Creates a {@link ConfigValue} from a plain value, which may be # a Boolean, Number, String, # Hash, or nil. A # Hash must be a Hash from String to more values # that can be supplied to from_any_ref(). A Hash # will become a {@link ConfigObject} and an Array will become a # {@link ConfigList}. # #

# In a Hash passed to from_any_ref(), the map's keys # are plain keys, not path expressions. So if your Hash has a # key "foo.bar" then you will get one object with a key called "foo.bar", # rather than an object with a key "foo" containing another object with a # key "bar". # #

# The origin_description will be used to set the origin() field on the # ConfigValue. It should normally be the name of the file the values came # from, or something short describing the value such as "default settings". # The origin_description is prefixed to error messages so users can tell # where problematic values are coming from. # #

# Supplying the result of ConfigValue.unwrapped() to this function is # guaranteed to work and should give you back a ConfigValue that matches # the one you unwrapped. The re-wrapped ConfigValue will lose some # information that was present in the original such as its origin, but it # will have matching values. # #

# If you pass in a ConfigValue to this # function, it will be returned unmodified. (The # origin_description will be ignored in this # case.) # #

# This function throws if you supply a value that cannot be converted to a # ConfigValue, but supplying such a value is a bug in your program, so you # should never handle the exception. Just fix your program (or report a bug # against this library). # # @param object # object to convert to ConfigValue # @param origin_description # name of origin file or brief description of what the value is # @return a new value # def self.from_any_ref(object, origin_description = nil) if object.is_a?(Hash) from_map(object, origin_description) else ConfigImpl.from_any_ref(object, origin_description) end end # # See the {@link #from_any_ref(Object,String)} documentation for details # #

# See also {@link ConfigFactory#parse_map(Map)} which interprets the keys in # the map as path expressions. # # @param values map from keys to plain ruby values # @return a new {@link ConfigObject} # def self.from_map(values, origin_description = nil) ConfigImpl.from_any_ref(process_hash(values), origin_description) end private def self.process_hash(hash) Hash[hash.map {|k, v| [k.is_a?(Symbol) ? k.to_s : k, v.is_a?(Hash) ? process_hash(v) : v]}] end end hocon-1.2.5/lib/hocon/impl.rb0000644000175000017500000000007313154611745015672 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' module Hocon::Impl endhocon-1.2.5/lib/hocon/config_mergeable.rb0000644000175000017500000000503513154611745020204 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/config_error' # # Marker for types whose instances can be merged, that is {@link Config} and # {@link ConfigValue}. Instances of {@code Config} and {@code ConfigValue} can # be combined into a single new instance using the # {@link ConfigMergeable#withFallback withFallback()} method. # #

# Do not implement this interface; it should only be implemented by # the config library. Arbitrary implementations will not work because the # library internals assume a specific concrete implementation. Also, this # interface is likely to grow new methods over time, so third-party # implementations will break. # module Hocon::ConfigMergeable # # Returns a new value computed by merging this value with another, with # keys in this value "winning" over the other one. # #

# This associative operation may be used to combine configurations from # multiple sources (such as multiple configuration files). # #

# The semantics of merging are described in the spec # for HOCON. Merging typically occurs when either the same object is # created twice in the same file, or two config files are both loaded. For # example: # #

#  foo = { a: 42 }
#  foo = { b: 43 }
# 
# # Here, the two objects are merged as if you had written: # #
#  foo = { a: 42, b: 43 }
# 
# #

# Only {@link ConfigObject} and {@link Config} instances do anything in # this method (they need to merge the fallback keys into themselves). All # other values just return the original value, since they automatically # override any fallback. This means that objects do not merge "across" # non-objects; if you write # object.withFallback(nonObject).withFallback(otherObject), # then otherObject will simply be ignored. This is an # intentional part of how merging works, because non-objects such as # strings and integers replace (rather than merging with) any prior value: # #

# foo = { a: 42 }
# foo = 10
# 
# # Here, the number 10 "wins" and the value of foo would be # simply 10. Again, for details see the spec. # # @param other # an object whose keys should be used as fallbacks, if the keys # are not present in this one # @return a new object (or the original one, if the fallback doesn't get # used) # def with_fallback(other) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigMergeable` must implement `with_fallback` (#{self.class})" end end hocon-1.2.5/lib/hocon/config_error.rb0000644000175000017500000000332513154611745017412 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' class Hocon::ConfigError < StandardError def initialize(origin, message, cause) msg = if origin.nil? message else "#{origin.description}: #{message}" end super(msg) @origin = origin @cause = cause end class ConfigMissingError < Hocon::ConfigError end class ConfigNullError < Hocon::ConfigError::ConfigMissingError def self.make_message(path, expected) if not expected.nil? "Configuration key '#{path}' is set to nil but expected #{expected}" else "Configuration key '#{path}' is nil" end end end class ConfigIOError < Hocon::ConfigError def initialize(origin, message, cause = nil) super(origin, message, cause) end end class ConfigParseError < Hocon::ConfigError end class ConfigWrongTypeError < Hocon::ConfigError def self.with_expected_actual(origin, path, expected, actual, cause = nil) ConfigWrongTypeError.new(origin, "#{path} has type #{actual} rather than #{expected}", cause) end end class ConfigBugOrBrokenError < Hocon::ConfigError def initialize(message, cause = nil) super(nil, message, cause) end end class ConfigNotResolvedError < Hocon::ConfigError::ConfigBugOrBrokenError end class ConfigBadPathError < Hocon::ConfigError def initialize(origin, path, message, cause = nil) error_message = !path.nil? ? "Invalid path '#{path}': #{message}" : message super(origin, error_message, cause) end end class UnresolvedSubstitutionError < ConfigParseError def initialize(origin, detail, cause = nil) super(origin, "Could not resolve substitution to a value: " + detail, cause) end end end hocon-1.2.5/lib/hocon/config.rb0000644000175000017500000011473513154611745016211 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/config_mergeable' require 'hocon/config_error' # # An immutable map from config paths to config values. Paths are dot-separated # expressions such as foo.bar.baz. Values are as in JSON # (booleans, strings, numbers, lists, or objects), represented by # {@link ConfigValue} instances. Values accessed through the # Config interface are never null. # #

# {@code Config} is an immutable object and thus safe to use from multiple # threads. There's never a need for "defensive copies." # #

# Fundamental operations on a {@code Config} include getting configuration # values, resolving substitutions with {@link Config#resolve()}, and # merging configs using {@link Config#withFallback(ConfigMergeable)}. # #

# All operations return a new immutable {@code Config} rather than modifying # the original instance. # #

# Examples # #

# You can find an example app and library on # GitHub. Also be sure to read the package overview which # describes the big picture as shown in those examples. # #

# Paths, keys, and Config vs. ConfigObject # #

# Config is a view onto a tree of {@link ConfigObject}; the # corresponding object tree can be found through {@link Config#root()}. # ConfigObject is a map from config keys, rather than # paths, to config values. Think of ConfigObject as a JSON object # and Config as a configuration API. # #

# The API tries to consistently use the terms "key" and "path." A key is a key # in a JSON object; it's just a string that's the key in a map. A "path" is a # parseable expression with a syntax and it refers to a series of keys. Path # expressions are described in the spec for # Human-Optimized Config Object Notation. In brief, a path is # period-separated so "a.b.c" looks for key c in object b in object a in the # root object. Sometimes double quotes are needed around special characters in # path expressions. # #

# The API for a {@code Config} is in terms of path expressions, while the API # for a {@code ConfigObject} is in terms of keys. Conceptually, {@code Config} # is a one-level map from paths to values, while a # {@code ConfigObject} is a tree of nested maps from keys to values. # #

# Use {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath} to convert # between path expressions and individual path elements (keys). # #

# Another difference between {@code Config} and {@code ConfigObject} is that # conceptually, {@code ConfigValue}s with a {@link ConfigValue#valueType() # valueType()} of {@link ConfigValueType#NULL NULL} exist in a # {@code ConfigObject}, while a {@code Config} treats null values as if they # were missing. # #

# Getting configuration values # #

# The "getters" on a {@code Config} all work in the same way. They never return # null, nor do they return a {@code ConfigValue} with # {@link ConfigValue#valueType() valueType()} of {@link ConfigValueType#NULL # NULL}. Instead, they throw {@link ConfigException.Missing} if the value is # completely absent or set to null. If the value is set to null, a subtype of # {@code ConfigException.Missing} called {@link ConfigException.Null} will be # thrown. {@link ConfigException.WrongType} will be thrown anytime you ask for # a type and the value has an incompatible type. Reasonable type conversions # are performed for you though. # #

# Iteration # #

# If you want to iterate over the contents of a {@code Config}, you can get its # {@code ConfigObject} with {@link #root()}, and then iterate over the # {@code ConfigObject} (which implements java.util.Map). Or, you # can use {@link #entrySet()} which recurses the object tree for you and builds # up a Set of all path-value pairs where the value is not null. # #

# Resolving substitutions # #

# Substitutions are the ${foo.bar} syntax in config # files, described in the specification. Resolving substitutions replaces these references with real # values. # #

# Before using a {@code Config} it's necessary to call {@link Config#resolve()} # to handle substitutions (though {@link ConfigFactory#load()} and similar # methods will do the resolve for you already). # #

# Merging # #

# The full Config for your application can be constructed using # the associative operation {@link Config#withFallback(ConfigMergeable)}. If # you use {@link ConfigFactory#load()} (recommended), it merges system # properties over the top of application.conf over the top of # reference.conf, using withFallback. You can add in # additional sources of configuration in the same way (usually, custom layers # should go either just above or just below application.conf, # keeping reference.conf at the bottom and system properties at # the top). # #

# Serialization # #

# Convert a Config to a JSON or HOCON string by calling # {@link ConfigObject#render()} on the root object, # myConfig.root().render(). There's also a variant # {@link ConfigObject#render(ConfigRenderOptions)} which allows you to control # the format of the rendered string. (See {@link ConfigRenderOptions}.) Note # that Config does not remember the formatting of the original # file, so if you load, modify, and re-save a config file, it will be # substantially reformatted. # #

# As an alternative to {@link ConfigObject#render()}, the # toString() method produces a debug-output-oriented # representation (which is not valid JSON). # #

# Java serialization is supported as well for Config and all # subtypes of ConfigValue. # #

# This is an interface but don't implement it yourself # #

# Do not implement {@code Config}; it should only be implemented by # the config library. Arbitrary implementations will not work because the # library internals assume a specific concrete implementation. Also, this # interface is likely to grow new methods over time, so third-party # implementations will break. # class Config < Hocon::ConfigMergeable # # Gets the {@code Config} as a tree of {@link ConfigObject}. This is a # constant-time operation (it is not proportional to the number of values # in the {@code Config}). # # @return the root object in the configuration # def root raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `root` (#{self.class})" end # # Gets the origin of the {@code Config}, which may be a file, or a file # with a line number, or just a descriptive phrase. # # @return the origin of the {@code Config} for use in error messages # def origin raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `origin` (#{self.class})" end def with_fallback(other) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `with_fallback` (#{self.class})" end # # Returns a replacement config with all substitutions (the # ${foo.bar} syntax, see the # spec) resolved. Substitutions are looked up using this # Config as the root object, that is, a substitution # ${foo.bar} will be replaced with the result of # getValue("foo.bar"). # #

# This method uses {@link ConfigResolveOptions#defaults()}, there is # another variant {@link Config#resolve(ConfigResolveOptions)} which lets # you specify non-default options. # #

# A given {@link Config} must be resolved before using it to retrieve # config values, but ideally should be resolved one time for your entire # stack of fallbacks (see {@link Config#withFallback}). Otherwise, some # substitutions that could have resolved with all fallbacks available may # not resolve, which will be potentially confusing for your application's # users. # #

# resolve() should be invoked on root config objects, rather # than on a subtree (a subtree is the result of something like # config.getConfig("foo")). The problem with # resolve() on a subtree is that substitutions are relative to # the root of the config and the subtree will have no way to get values # from the root. For example, if you did # config.getConfig("foo").resolve() on the below config file, # it would not work: # #

  #   common-value = 10
  #   foo {
  #      whatever = ${common-value}
  #   }
  # 
# #

# Many methods on {@link ConfigFactory} such as # {@link ConfigFactory#load()} automatically resolve the loaded # Config on the loaded stack of config files. # #

# Resolving an already-resolved config is a harmless no-op, but again, it # is best to resolve an entire stack of fallbacks (such as all your config # files combined) rather than resolving each one individually. # # @return an immutable object with substitutions resolved # @throws ConfigException.UnresolvedSubstitution # if any substitutions refer to nonexistent paths # @throws ConfigException # some other config exception if there are other problems # def resolve(options) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `resolve` (#{self.class})" end # # Checks whether the config is completely resolved. After a successful call # to {@link Config#resolve()} it will be completely resolved, but after # calling {@link Config#resolve(ConfigResolveOptions)} with # allowUnresolved set in the options, it may or may not be # completely resolved. A newly-loaded config may or may not be completely # resolved depending on whether there were substitutions present in the # file. # # @return true if there are no unresolved substitutions remaining in this # configuration. # @since 1.2.0 # def resolved? raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `resolved?` (#{self.class})" end # # Like {@link Config#resolveWith(Config)} but allows you to specify # non-default options. # # @param source # source configuration to pull values from # @param options # resolve options # @return the resolved Config (may be only partially resolved # if options are set to allow unresolved) # @since 1.2.0 # def resolve_with(source, options) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `resolve_with` (#{self.class})" end # # Validates this config against a reference config, throwing an exception # if it is invalid. The purpose of this method is to "fail early" with a # comprehensive list of problems; in general, anything this method can find # would be detected later when trying to use the config, but it's often # more user-friendly to fail right away when loading the config. # #

# Using this method is always optional, since you can "fail late" instead. # #

# You must restrict validation to paths you "own" (those whose meaning are # defined by your code module). If you validate globally, you may trigger # errors about paths that happen to be in the config but have nothing to do # with your module. It's best to allow the modules owning those paths to # validate them. Also, if every module validates only its own stuff, there # isn't as much redundant work being done. # #

# If no paths are specified in checkValid()'s parameter list, # validation is for the entire config. # #

# If you specify paths that are not in the reference config, those paths # are ignored. (There's nothing to validate.) # #

# Here's what validation involves: # #

    #
  • All paths found in the reference config must be present in this # config or an exception will be thrown. #
  • # Some changes in type from the reference config to this config will cause # an exception to be thrown. Not all potential type problems are detected, # in particular it's assumed that strings are compatible with everything # except objects and lists. This is because string types are often "really" # some other type (system properties always start out as strings, or a # string like "5ms" could be used with {@link #getMilliseconds}). Also, # it's allowed to set any type to null or override null with any type. #
  • # Any unresolved substitutions in this config will cause a validation # failure; both the reference config and this config should be resolved # before validation. If the reference config is unresolved, it's a bug in # the caller of this method. #
# #

# If you want to allow a certain setting to have a flexible type (or # otherwise want validation to be looser for some settings), you could # either remove the problematic setting from the reference config provided # to this method, or you could intercept the validation exception and # screen out certain problems. Of course, this will only work if all other # callers of this method are careful to restrict validation to their own # paths, as they should be. # #

# If validation fails, the thrown exception contains a list of all problems # found. See {@link ConfigException.ValidationFailed#problems}. The # exception's getMessage() will have all the problems # concatenated into one huge string, as well. # #

# Again, checkValid() can't guess every domain-specific way a # setting can be invalid, so some problems may arise later when attempting # to use the config. checkValid() is limited to reporting # generic, but common, problems such as missing settings and blatant type # incompatibilities. # # @param reference # a reference configuration # @param restrictToPaths # only validate values underneath these paths that your code # module owns and understands # @throws ConfigException.ValidationFailed # if there are any validation issues # @throws ConfigException.NotResolved # if this config is not resolved # @throws ConfigException.BugOrBroken # if the reference config is unresolved or caller otherwise # misuses the API # def check_valid(reference, restrict_to_paths) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `check_valid` (#{self.class})" end # # Checks whether a value is present and non-null at the given path. This # differs in two ways from {@code Map.containsKey()} as implemented by # {@link ConfigObject}: it looks for a path expression, not a key; and it # returns false for null values, while {@code containsKey()} returns true # indicating that the object contains a null value for the key. # #

# If a path exists according to {@link #hasPath(String)}, then # {@link #getValue(String)} will never throw an exception. However, the # typed getters, such as {@link #getInt(String)}, will still throw if the # value is not convertible to the requested type. # #

# Note that path expressions have a syntax and sometimes require quoting # (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). # # @param path # the path expression # @return true if a non-null value is present at the path # @throws ConfigException.BadPath # if the path expression is invalid # def has_path(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `has_path` (#{self.class})" end # # Returns true if the {@code Config}'s root object contains no key-value # pairs. # # @return true if the configuration is empty # def empty? raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `empty` (#{self.class})" end # # Returns the set of path-value pairs, excluding any null values, found by # recursing {@link #root() the root object}. Note that this is very # different from root().entrySet() which returns the set of # immediate-child keys in the root object and includes null values. #

# Entries contain path expressions meaning there may be quoting # and escaping involved. Parse path expressions with # {@link ConfigUtil#splitPath}. #

# Because a Config is conceptually a single-level map from # paths to values, there will not be any {@link ConfigObject} values in the # entries (that is, all entries represent leaf nodes). Use # {@link ConfigObject} rather than Config if you want a tree. # (OK, this is a slight lie: Config entries may contain # {@link ConfigList} and the lists may contain objects. But no objects are # directly included as entry values.) # # @return set of paths with non-null values, built up by recursing the # entire tree of {@link ConfigObject} and creating an entry for # each leaf value. # def entry_set raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `entry_set` (#{self.class})" end # # # @param path # path expression # @return the boolean value at the requested path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to boolean # def get_boolean(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_boolean` (#{self.class})" end # # @param path # path expression # @return the numeric value at the requested path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a number # def get_number(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_number` (#{self.class})" end # # Gets the integer at the given path. If the value at the # path has a fractional (floating point) component, it # will be discarded and only the integer part will be # returned (it works like a "narrowing primitive conversion" # in the Java language specification). # # @param path # path expression # @return the 32-bit integer value at the requested path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to an int (for example it is out # of range, or it's a boolean value) # def get_int(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_int` (#{self.class})" end # # Gets the long integer at the given path. If the value at # the path has a fractional (floating point) component, it # will be discarded and only the integer part will be # returned (it works like a "narrowing primitive conversion" # in the Java language specification). # # @param path # path expression # @return the 64-bit long value at the requested path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a long # def get_long(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_long` (#{self.class})" end # # @param path # path expression # @return the floating-point value at the requested path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a double # def get_double(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_double` (#{self.class})" end # # @param path # path expression # @return the string value at the requested path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a string # def get_string(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_string` (#{self.class})" end # # @param path # path expression # @return the {@link ConfigObject} value at the requested path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to an object # def get_object(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_object` (#{self.class})" end # # @param path # path expression # @return the nested {@code Config} value at the requested path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a Config # def get_config(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_config` (#{self.class})" end # # Gets the value at the path as an unwrapped Java boxed value ( # {@link java.lang.Boolean Boolean}, {@link java.lang.Integer Integer}, and # so on - see {@link ConfigValue#unwrapped()}). # # @param path # path expression # @return the unwrapped value at the requested path # @throws ConfigException.Missing # if value is absent or null # def get_any_ref(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_any_ref` (#{self.class})" end # # Gets the value at the given path, unless the value is a # null value or missing, in which case it throws just like # the other getters. Use {@code get()} on the {@link # Config#root()} object (or other object in the tree) if you # want an unprocessed value. # # @param path # path expression # @return the value at the requested path # @throws ConfigException.Missing # if value is absent or null # def get_value(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_value`path(#{self.class})" end # # Gets a value as a size in bytes (parses special strings like "128M"). If # the value is already a number, then it's left alone; if it's a string, # it's parsed understanding unit suffixes such as "128K", as documented in # the the # spec. # # @param path # path expression # @return the value at the requested path, in bytes # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to Long or String # @throws ConfigException.BadValue # if value cannot be parsed as a size in bytes # def get_bytes(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_bytes` (#{self.class})" end # # Gets a value as an amount of memory (parses special strings like "128M"). If # the value is already a number, then it's left alone; if it's a string, # it's parsed understanding unit suffixes such as "128K", as documented in # the the # spec. # # @since 1.3.0 # # @param path # path expression # @return the value at the requested path, in bytes # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to Long or String # @throws ConfigException.BadValue # if value cannot be parsed as a size in bytes # def get_memory_size(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_memory_size`path(#{self.class})" end # # Get value as a duration in milliseconds. If the value is already a # number, then it's left alone; if it's a string, it's parsed understanding # units suffixes like "10m" or "5ns" as documented in the the # spec. # # @deprecated As of release 1.1, replaced by {@link #getDuration(String, TimeUnit)} # # @param path # path expression # @return the duration value at the requested path, in milliseconds # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to Long or String # @throws ConfigException.BadValue # if value cannot be parsed as a number of milliseconds # def get_milliseconds(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_milliseconds` (#{self.class})" end # # Get value as a duration in nanoseconds. If the value is already a number # it's taken as milliseconds and converted to nanoseconds. If it's a # string, it's parsed understanding unit suffixes, as for # {@link #getDuration(String, TimeUnit)}. # # @deprecated As of release 1.1, replaced by {@link #getDuration(String, TimeUnit)} # # @param path # path expression # @return the duration value at the requested path, in nanoseconds # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to Long or String # @throws ConfigException.BadValue # if value cannot be parsed as a number of nanoseconds # def get_nanoseconds(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_nanoseconds` (#{self.class})" end # # Gets a value as a duration in a specified # {@link java.util.concurrent.TimeUnit TimeUnit}. If the value is already a # number, then it's taken as milliseconds and then converted to the # requested TimeUnit; if it's a string, it's parsed understanding units # suffixes like "10m" or "5ns" as documented in the the # spec. # # @since 1.2.0 # # @param path # path expression # @param unit # convert the return value to this time unit # @return the duration value at the requested path, in the given TimeUnit # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to Long or String # @throws ConfigException.BadValue # if value cannot be parsed as a number of the given TimeUnit # def get_duration(path, unit) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_duration` (#{self.class})" end # # Gets a list value (with any element type) as a {@link ConfigList}, which # implements {@code java.util.List}. Throws if the path is # unset or null. # # @param path # the path to the list value. # @return the {@link ConfigList} at the path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a ConfigList # def get_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_list` (#{self.class})" end # # Gets a list value with boolean elements. Throws if the # path is unset or null or not a list or contains values not # convertible to boolean. # # @param path # the path to the list value. # @return the list at the path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a list of booleans # def get_boolean_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_boolean_list` (#{self.class})" end # # Gets a list value with number elements. Throws if the # path is unset or null or not a list or contains values not # convertible to number. # # @param path # the path to the list value. # @return the list at the path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a list of numbers # def get_number_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_number_list` (#{self.class})" end # # Gets a list value with int elements. Throws if the # path is unset or null or not a list or contains values not # convertible to int. # # @param path # the path to the list value. # @return the list at the path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a list of ints # def get_int_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_int_list` (#{self.class})" end # # Gets a list value with long elements. Throws if the # path is unset or null or not a list or contains values not # convertible to long. # # @param path # the path to the list value. # @return the list at the path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a list of longs # def get_long_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_long_list` (#{self.class})" end # # Gets a list value with double elements. Throws if the # path is unset or null or not a list or contains values not # convertible to double. # # @param path # the path to the list value. # @return the list at the path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a list of doubles # def get_double_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_double_list` (#{self.class})" end # # Gets a list value with string elements. Throws if the # path is unset or null or not a list or contains values not # convertible to string. # # @param path # the path to the list value. # @return the list at the path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a list of strings # def get_string_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_string_list` (#{self.class})" end # # Gets a list value with object elements. Throws if the # path is unset or null or not a list or contains values not # convertible to ConfigObject. # # @param path # the path to the list value. # @return the list at the path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a list of objects # def get_object_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_object_list` (#{self.class})" end # # Gets a list value with Config elements. # Throws if the path is unset or null or not a list or # contains values not convertible to Config. # # @param path # the path to the list value. # @return the list at the path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a list of configs # def get_config_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_config_list` (#{self.class})" end # # Gets a list value with any kind of elements. Throws if the # path is unset or null or not a list. Each element is # "unwrapped" (see {@link ConfigValue#unwrapped()}). # # @param path # the path to the list value. # @return the list at the path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a list # def get_any_ref_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_any_ref_list`path(#{self.class})" end # # Gets a list value with elements representing a size in # bytes. Throws if the path is unset or null or not a list # or contains values not convertible to memory sizes. # # @param path # the path to the list value. # @return the list at the path # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a list of memory sizes # def get_bytes_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_bytes_list` (#{self.class})" end # # Gets a list, converting each value in the list to a memory size, using the # same rules as {@link #getMemorySize(String)}. # # @since 1.3.0 # @param path # a path expression # @return list of memory sizes # @throws ConfigException.Missing # if value is absent or null # @throws ConfigException.WrongType # if value is not convertible to a list of memory sizes # def get_memory_size_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_memory_size_list` (#{self.class})" end # # @deprecated As of release 1.1, replaced by {@link #getDurationList(String, TimeUnit)} # @param path the path # @return list of millisecond values # def get_milliseconds_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_milliseconds_list` (#{self.class})" end # # @deprecated As of release 1.1, replaced by {@link #getDurationList(String, TimeUnit)} # @param path the path # @return list of nanosecond values # def get_nanoseconds_list(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_nanoseconds_list` (#{self.class})" end # # Gets a list, converting each value in the list to a duration, using the # same rules as {@link #getDuration(String, TimeUnit)}. # # @since 1.2.0 # @param path # a path expression # @param unit # time units of the returned values # @return list of durations, in the requested units # def get_duration_list(path, unit) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `get_duration_list` (#{self.class})" end # # Clone the config with only the given path (and its children) retained; # all sibling paths are removed. #

# Note that path expressions have a syntax and sometimes require quoting # (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). # # @param path # path to keep # @return a copy of the config minus all paths except the one specified # def with_only_path(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `with_only_path` (#{self.class})" end # # Clone the config with the given path removed. #

# Note that path expressions have a syntax and sometimes require quoting # (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). # # @param path # path expression to remove # @return a copy of the config minus the specified path # def without_path(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `without_path` (#{self.class})" end # # Places the config inside another {@code Config} at the given path. #

# Note that path expressions have a syntax and sometimes require quoting # (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). # # @param path # path expression to store this config at. # @return a {@code Config} instance containing this config at the given # path. # def at_path(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `at_path` (#{self.class})" end # # Places the config inside a {@code Config} at the given key. See also # atPath(). Note that a key is NOT a path expression (see # {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). # # @param key # key to store this config at. # @return a {@code Config} instance containing this config at the given # key. # def at_key(key) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `at_key` (#{self.class})" end # # Returns a {@code Config} based on this one, but with the given path set # to the given value. Does not modify this instance (since it's immutable). # If the path already has a value, that value is replaced. To remove a # value, use withoutPath(). #

# Note that path expressions have a syntax and sometimes require quoting # (see {@link ConfigUtil#joinPath} and {@link ConfigUtil#splitPath}). # # @param path # path expression for the value's new location # @param value # value at the new path # @return the new instance with the new map entry # def with_value(path, value) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Config` must implement `with_value` (#{self.class})" end end hocon-1.2.5/lib/hocon/impl/0000755000175000017500000000000013154611745015345 5ustar apoikosapoikoshocon-1.2.5/lib/hocon/impl/config_delayed_merge.rb0000644000175000017500000002367613154611745022023 0ustar apoikosapoikosrequire 'hocon/impl' require 'hocon/impl/replaceable_merge_stack' require 'hocon/impl/config_delayed_merge_object' require 'hocon/impl/config_impl' require 'hocon/impl/resolve_result' require 'hocon/impl/abstract_config_value' # # The issue here is that we want to first merge our stack of config files, and # then we want to evaluate substitutions. But if two substitutions both expand # to an object, we might need to merge those two objects. Thus, we can't ever # "override" a substitution when we do a merge; instead we have to save the # stack of values that should be merged, and resolve the merge when we evaluate # substitutions. # class Hocon::Impl::ConfigDelayedMerge include Hocon::Impl::Unmergeable include Hocon::Impl::ReplaceableMergeStack include Hocon::Impl::AbstractConfigValue ConfigImpl = Hocon::Impl::ConfigImpl ResolveResult = Hocon::Impl::ResolveResult def initialize(origin, stack) super(origin) @stack = stack if stack.empty? raise Hocon::ConfigError::ConfigBugOrBrokenError.new("creating empty delayed merge value", nil) end stack.each do |v| if v.is_a?(Hocon::Impl::ConfigDelayedMerge) || v.is_a?(Hocon::Impl::ConfigDelayedMergeObject) error_message = "placed nested DelayedMerge in a ConfigDelayedMerge, should have consolidated stack" raise Hocon::ConfigError::ConfigBugOrBrokenError.new(error_message, nil) end end end attr_reader :stack def value_type error_message = "called value_type() on value with unresolved substitutions, need to Config#resolve() first, see API docs" raise Hocon::ConfigError::ConfigNotResolvedError.new(error_message, nil) end def unwrapped error_message = "called unwrapped() on value with unresolved substitutions, need to Config#resolve() first, see API docs" raise Hocon::ConfigError::ConfigNotResolvedError.new(error_message, nil) end def resolve_substitutions(context, source) self.class.resolve_substitutions(self, stack, context, source) end def self.resolve_substitutions(replaceable, stack, context, source) if ConfigImpl.trace_substitution_enabled ConfigImpl.trace("delayed merge stack has #{stack.size} items:", context.depth) count = 0 stack.each do |v| ConfigImpl.trace("#{count}: #{v}", context.depth) count += 1 end end # to resolve substitutions, we need to recursively resolve # the stack of stuff to merge, and merge the stack so # we won't be a delayed merge anymore. If restrictToChildOrNull # is non-null, or resolve options allow partial resolves, # we may remain a delayed merge though. new_context = context count = 0 merged = nil stack.each do |stack_end| # the end value may or may not be resolved already if stack_end.is_a?(Hocon::Impl::ReplaceableMergeStack) raise ConfigBugOrBrokenError, "A delayed merge should not contain another one: #{replaceable}" elsif stack_end.is_a?(Hocon::Impl::Unmergeable) # the remainder could be any kind of value, including another # ConfigDelayedMerge remainder = replaceable.make_replacement(context, count + 1) if ConfigImpl.trace_substitution_enabled ConfigImpl.trace("remainder portion: #{remainder}", new_context.depth) end # If, while resolving 'end' we come back to the same # merge stack, we only want to look _below_ 'end' # in the stack. So we arrange to replace the # ConfigDelayedMerge with a value that is only # the remainder of the stack below this one. if ConfigImpl.trace_substitution_enabled ConfigImpl.trace("building sourceForEnd", new_context.depth) end # we resetParents() here because we'll be resolving "end" # against a root which does NOT contain "end" source_for_end = source.replace_within_current_parent(replaceable, remainder) if ConfigImpl.trace_substitution_enabled ConfigImpl.trace(" sourceForEnd before reset parents but after replace: #{source_for_end}", new_context.depth) end source_for_end = source_for_end.reset_parents else if ConfigImpl.trace_substitution_enabled ConfigImpl.trace("will resolve end against the original source with parent pushed", new_context.depth) end source_for_end = source.push_parent(replaceable) end if ConfigImpl.trace_substitution_enabled ConfigImpl.trace("sourceForEnd =#{source_for_end}", new_context.depth) end if ConfigImpl.trace_substitution_enabled ConfigImpl.trace("Resolving highest-priority item in delayed merge #{stack_end}" + " against #{source_for_end} endWasRemoved=#{(source != source_for_end)}") end result = new_context.resolve(stack_end, source_for_end) resolved_end = result.value new_context = result.context if ! resolved_end.nil? if merged.nil? merged = resolved_end else if ConfigImpl.trace_substitution_enabled ConfigImpl.trace("merging #{merged} with fallback #{resolved_end}", new_context.depth + 1) end merged = merged.with_fallback(resolved_end) end end count += 1 if ConfigImpl.trace_substitution_enabled ConfigImpl.trace("stack merged, yielding: #{merged}", new_context.depth) end end ResolveResult.make(new_context, merged) end def make_replacement(context, skipping) self.class.make_replacement(context, @stack, skipping) end # static method also used by ConfigDelayedMergeObject; end may be null def self.make_replacement(context, stack, skipping) sub_stack = stack.slice(skipping..stack.size) if sub_stack.empty? if ConfigImpl.trace_substitution_enabled ConfigImpl.trace("Nothing else in the merge stack, replacing with null", context.depth) return nil end else # generate a new merge stack from only the remaining items merged = nil sub_stack.each do |v| if merged.nil? merged = v else merged = merged.with_fallback(v) end end merged end end def resolve_status Hocon::Impl::ResolveStatus::UNRESOLVED end def replace_child(child, replacement) new_stack = replace_child_in_list(stack, child, replacement) if new_stack.nil? nil else self.class.new(origin, new_stack) end end def has_descendant?(descendant) Hocon::Impl::AbstractConfigValue.has_descendant_in_list?(stack, descendant) end def relativized(prefix) new_stack = stack.map { |o| o.relativized(prefix) } self.class.new(origin, new_stack) end # static utility shared with ConfigDelayedMergeObject def self.stack_ignores_fallbacks?(stack) last = stack[-1] last.ignores_fallbacks? end def ignores_fallbacks? self.class.stack_ignores_fallbacks?(stack) end def new_copy(new_origin) self.class.new(new_origin, stack) end def merged_with_the_unmergeable(fallback) merged_stack_with_the_unmergeable(stack, fallback) end def merged_with_object(fallback) merged_stack_with_object(stack, fallback) end def merged_with_non_object(fallback) merged_stack_with_non_object(stack, fallback) end def unmerged_values stack end def can_equal(other) other.is_a? Hocon::Impl::ConfigDelayedMerge end def ==(other) # note that "origin" is deliberately NOT part of equality if other.is_a? Hocon::Impl::ConfigDelayedMerge can_equal(other) && (@stack == other.stack || @stack.equal?(other.stack)) else false end end def hash # note that "origin" is deliberately NOT part of equality @stack.hash end def render_to_sb(sb, indent, at_root, at_key, options) self.class.render_value_to_sb_from_stack(stack, sb, indent, at_root, at_key, options) end # static method also used by ConfigDelayedMergeObject. def self.render_value_to_sb_from_stack(stack, sb, indent, at_root, at_key, options) comment_merge = options.comments if comment_merge sb << "# unresolved merge of #{stack.size} values follows (\n" if at_key.nil? self.indent(sb, indent, options) sb << "# this unresolved merge will not be parseable because it's at the root of the object\n" self.indent(sb, indent, options) sb << "# the HOCON format has no way to list multiple root objects in a single file\n" end end reversed = stack.reverse i = 0 reversed.each do |v| if comment_merge self.indent(sb, indent, options) if !at_key.nil? rendered_key = Hocon::Impl::ConfigImplUtil.render_json_string(at_key) sb << "# unmerged value #{i} for key #{rendered_key}" else sb << "# unmerged value #{i} from " end i += 1 sb << v.origin.description sb << "\n" v.origin.comments.each do |comment| self.indent(sb, indent, options) sb << "# " sb << comment sb << "\n" end end Hocon::Impl::AbstractConfigValue.indent(sb, indent, options) if !at_key.nil? sb << Hocon::Impl::ConfigImplUtil.render_json_string(at_key) if options.formatted sb << ": " else sb << ":" end end v.render_value_to_sb(sb, indent, at_root, options) sb << "," if options.formatted sb.append "\n" end end # chop comma or newline # couldn't figure out a better way to chop characters off of the end of # the StringIO. This relies on making sure that, prior to returning the # final string, we take a substring that ends at sb.pos. sb.pos = sb.pos - 1 if options.formatted sb.pos = sb.pos - 1 sb << "\n" end if comment_merge self.indent(sb, indent, options) sb << "# ) end of unresolved merge\n" end end end hocon-1.2.5/lib/hocon/impl/config_node_simple_value.rb0000644000175000017500000000243013154611745022710 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/config_error' require 'hocon/impl' require 'hocon/impl/abstract_config_node_value' require 'hocon/impl/array_iterator' require 'hocon/impl/config_reference' require 'hocon/impl/config_string' require 'hocon/impl/path_parser' require 'hocon/impl/substitution_expression' require 'hocon/impl/tokens' class Hocon::Impl::ConfigNodeSimpleValue include Hocon::Impl::AbstractConfigNodeValue Tokens = Hocon::Impl::Tokens def initialize(value) @token = value end attr_reader :token def tokens [@token] end def value if Tokens.value?(@token) return Tokens.value(@token) elsif Tokens.unquoted_text?(@token) return Hocon::Impl::ConfigString::Unquoted.new(@token.origin, Tokens.unquoted_text(@token)) elsif Tokens.substitution?(@token) expression = Tokens.get_substitution_path_expression(@token) path = Hocon::Impl::PathParser.parse_path_expression(Hocon::Impl::ArrayIterator.new(expression), @token.origin) optional = Tokens.get_substitution_optional(@token) return Hocon::Impl::ConfigReference.new(@token.origin, Hocon::Impl::SubstitutionExpression.new(path, optional)) end raise Hocon::ConfigError::ConfigBugOrBrokenError, 'ConfigNodeSimpleValue did not contain a valid value token' end endhocon-1.2.5/lib/hocon/impl/simple_config_object.rb0000644000175000017500000003515413154611745022046 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/simple_config_origin' require 'hocon/impl/abstract_config_object' require 'hocon/impl/resolve_status' require 'hocon/impl/resolve_result' require 'hocon/impl/path' require 'hocon/config_error' require 'set' require 'forwardable' class Hocon::Impl::SimpleConfigObject include Hocon::Impl::AbstractConfigObject extend Forwardable ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError ResolveStatus = Hocon::Impl::ResolveStatus ResolveResult = Hocon::Impl::ResolveResult SimpleConfigOrigin = Hocon::Impl::SimpleConfigOrigin Path = Hocon::Impl::Path def initialize(origin, value, status = Hocon::Impl::ResolveStatus.from_values(value.values), ignores_fallbacks = false) super(origin) if value.nil? raise ConfigBugOrBrokenError, "creating config object with null map" end @value = value @resolved = (status == Hocon::Impl::ResolveStatus::RESOLVED) @ignores_fallbacks = ignores_fallbacks # Kind of an expensive debug check. Comment out? if status != Hocon::Impl::ResolveStatus.from_values(value.values) raise ConfigBugOrBrokenError, "Wrong resolved status on #{self}" end end attr_reader :value # To support accessing ConfigObjects like a hash def_delegators :@value, :[], :has_key?, :has_value?, :empty?, :size, :keys, :values, :each, :map def with_only_key(key) with_only_path(Path.new_key(key)) end def without_key(key) without_path(Path.new_key(key)) end # gets the object with only the path if the path # exists, otherwise null if it doesn't. this ensures # that if we have { a : { b : 42 } } and do # withOnlyPath("a.b.c") that we don't keep an empty # "a" object. def with_only_path_or_nil(path) key = path.first path_next = path.remainder v = value[key] if ! path_next.nil? if (!v.nil?) && (v.is_a?(Hocon::Impl::AbstractConfigObject)) v = v.with_only_path_or_nil(path_next) else # if the path has more elements but we don't have an object, # then the rest of the path does not exist. v = nil end end if v.nil? nil else self.class.new(origin, {key => v}, v.resolve_status, @ignores_fallbacks) end end def with_only_path(path) o = with_only_path_or_nil(path) if o.nil? self.class.new(origin, {}, ResolveStatus::RESOLVED, @ignores_fallbacks) else o end end def without_path(path) key = path.first remainder = path.remainder v = @value[key] if (not v.nil?) && (not remainder.nil?) && v.is_a?(Hocon::Impl::AbstractConfigObject) v = v.without_path(remainder) updated = @value.clone updated[key] = v self.class.new(origin, updated, ResolveStatus.from_values(updated.values), @ignores_fallbacks) elsif (not remainder.nil?) || v.nil? return self else smaller = Hash.new @value.each do |old_key, old_value| unless old_key == key smaller[old_key] = old_value end end self.class.new(origin, smaller, ResolveStatus.from_values(smaller.values), @ignores_fallbacks) end end def with_value(path, v) key = path.first remainder = path.remainder if remainder.nil? with_key_value(key, v) else child = @value[key] if (not child.nil?) && child.is_a?(Hocon::Impl::AbstractConfigObject) return with_key_value(key, child.with_value(remainder, v)) else subtree = v.at_path_with_origin( SimpleConfigOrigin.new_simple("with_value(#{remainder.render})"), remainder) with_key_value(key, subtree.root) end end end def with_key_value(key, v) if v.nil? raise ConfigBugOrBrokenError.new("Trying to store null ConfigValue in a ConfigObject") end new_map = Hash.new if @value.empty? new_map[key] = v else new_map = @value.clone new_map[key] = v end self.class.new(origin, new_map, ResolveStatus.from_values(new_map.values), @ignores_fallbacks) end def attempt_peek_with_partial_resolve(key) @value[key] end def new_copy_with_status(new_status, new_origin, new_ignores_fallbacks = nil) self.class.new(new_origin, @value, new_status, new_ignores_fallbacks) end def with_fallbacks_ignored() if @ignores_fallbacks self else new_copy_with_status(resolve_status, origin, true) end end def resolve_status ResolveStatus.from_boolean(@resolved) end def replace_child(child, replacement) new_children = @value.clone new_children.each do |old, old_value| if old_value.equal?(child) if replacement != nil new_children[old] = replacement else new_children.delete(old) end return self.class.new(origin, new_children, ResolveStatus.from_values(new_children.values), @ignores_fallbacks) end end raise ConfigBugOrBrokenError, "SimpleConfigObject.replaceChild did not find #{child} in #{self}" end def has_descendant?(descendant) value.values.each do |child| if child.equal?(descendant) return true end end # now do the expensive search value.values.each do |child| if child.is_a?(Hocon::Impl::Container) && child.has_descendant?(descendant) return true end end false end def ignores_fallbacks? @ignores_fallbacks end def unwrapped m = {} @value.each do |k,v| m[k] = v.unwrapped end m end def merged_with_object(abstract_fallback) require_not_ignoring_fallbacks unless abstract_fallback.is_a?(Hocon::Impl::SimpleConfigObject) raise ConfigBugOrBrokenError, "should not be reached (merging non-SimpleConfigObject)" end fallback = abstract_fallback changed = false all_resolved = true merged = {} all_keys = key_set.union(fallback.key_set) all_keys.each do |key| first = @value[key] second = fallback.value[key] kept = if first.nil? second elsif second.nil? first else first.with_fallback(second) end merged[key] = kept if first != kept changed = true end if kept.resolve_status == Hocon::Impl::ResolveStatus::UNRESOLVED all_resolved = false end end new_resolve_status = Hocon::Impl::ResolveStatus.from_boolean(all_resolved) new_ignores_fallbacks = fallback.ignores_fallbacks? if changed Hocon::Impl::SimpleConfigObject.new(Hocon::Impl::AbstractConfigObject.merge_origins([self, fallback]), merged, new_resolve_status, new_ignores_fallbacks) elsif (new_resolve_status != resolve_status) || (new_ignores_fallbacks != ignores_fallbacks?) new_copy_with_status(new_resolve_status, origin, new_ignores_fallbacks) else self end end def modify(modifier) begin modify_may_throw(modifier) rescue Hocon::ConfigError => e raise e end end def modify_may_throw(modifier) changes = nil keys.each do |k| v = value[k] # "modified" may be null, which means remove the child; # to do that we put null in the "changes" map. modified = modifier.modify_child_may_throw(k, v) if ! modified.equal?(v) if changes.nil? changes = {} end changes[k] = modified end end if changes.nil? self else modified = {} saw_unresolved = false keys.each do |k| if changes.has_key?(k) new_value = changes[k] if ! new_value.nil? modified[k] = new_value if new_value.resolve_status == ResolveStatus::UNRESOLVED saw_unresolved = true end else # remove this child; don't put it in the new map end else new_value = value[k] modified[k] = new_value if new_value.resolve_status == ResolveStatus::UNRESOLVED saw_unresolved = true end end end self.class.new(origin, modified, saw_unresolved ? ResolveStatus::UNRESOLVED : ResolveStatus::RESOLVED, @ignores_fallbacks) end end class ResolveModifier attr_accessor :context attr_reader :source def initialize(context, source) @context = context @source = source @original_restrict = context.restrict_to_child end def modify_child_may_throw(key, v) if @context.is_restricted_to_child if key == @context.restrict_to_child.first remainder = @context.restrict_to_child.remainder if remainder != nil result = @context.restrict(remainder).resolve(v, @source) @context = result.context.unrestricted.restrict(@original_restrict) return result.value else # we don't want to resolve the leaf child return v end else # not in the restrictToChild path return v end else # no restrictToChild, resolve everything result = @context.unrestricted.resolve(v, @source) @context = result.context.unrestricted.restrict(@original_restrict) result.value end end end def resolve_substitutions(context, source) if resolve_status == ResolveStatus::RESOLVED return ResolveResult.make(context, self) end source_with_parent = source.push_parent(self) begin modifier = ResolveModifier.new(context, source_with_parent) value = modify_may_throw(modifier) ResolveResult.make(modifier.context, value) rescue NotPossibleToResolve => e raise e rescue Hocon::ConfigError => e raise e end end def relativized(prefix) modifier = Class.new do include Hocon::Impl::AbstractConfigValue::NoExceptionsModifier # prefix isn't in scope inside of a def, but it is in scope inside of Class.new # so manually define a method that has access to prefix # I feel dirty define_method(:modify_child) do |key, v| v.relativized(prefix) end end modify(modifier.new) end class RenderComparator def self.all_digits?(s) s =~ /^\d+$/ end # This is supposed to sort numbers before strings, # and sort the numbers numerically. The point is # to make objects which are really list-like # (numeric indices) appear in order. def self.sort(arr) arr.sort do |a, b| a_digits = all_digits?(a) b_digits = all_digits?(b) if a_digits && b_digits Integer(a) <=> Integer(b) elsif a_digits -1 elsif b_digits 1 else a <=> b end end end end def render_value_to_sb(sb, indent, at_root, options) if empty? sb << "{}" else outer_braces = options.json? || !at_root if outer_braces inner_indent = indent + 1 sb << "{" if options.formatted? sb << "\n" end else inner_indent = indent end separator_count = 0 sorted_keys = RenderComparator.sort(keys) sorted_keys.each do |k| v = @value[k] if options.origin_comments? lines = v.origin.description.split("\n") lines.each { |l| Hocon::Impl::AbstractConfigValue.indent(sb, indent + 1, options) sb << '#' unless l.empty? sb << ' ' end sb << l sb << "\n" } end if options.comments? v.origin.comments.each do |comment| Hocon::Impl::AbstractConfigValue.indent(sb, inner_indent, options) sb << "#" if !comment.start_with?(" ") sb << " " end sb << comment sb << "\n" end end Hocon::Impl::AbstractConfigValue.indent(sb, inner_indent, options) v.render_to_sb(sb, inner_indent, false, k.to_s, options) if options.formatted? if options.json? sb << "," separator_count = 2 else separator_count = 1 end sb << "\n" else sb << "," separator_count = 1 end end # chop last commas/newlines # couldn't figure out a better way to chop characters off of the end of # the StringIO. This relies on making sure that, prior to returning the # final string, we take a substring that ends at sb.pos. sb.pos = sb.pos - separator_count if outer_braces if options.formatted? sb << "\n" # put a newline back if outer_braces Hocon::Impl::AbstractConfigValue.indent(sb, indent, options) end end sb << "}" end end if at_root && options.formatted? sb << "\n" end end def self.map_equals(a, b) if a.equal?(b) return true end # Hashes aren't ordered in ruby, so sort first if not a.keys.sort == b.keys.sort return false end a.keys.each do |key| if a[key] != b[key] return false end end true end def get(key) @value[key] end def self.map_hash(m) # the keys have to be sorted, otherwise we could be equal # to another map but have a different hashcode. keys = m.keys.sort value_hash = 0 keys.each do |key| value_hash += m[key].hash end 41 * (41 + keys.hash) + value_hash end def can_equal(other) other.is_a? Hocon::ConfigObject end def ==(other) # note that "origin" is deliberately NOT part of equality. # neither are other "extras" like ignoresFallbacks or resolve status. if other.is_a? Hocon::ConfigObject # optimization to avoid unwrapped() for two ConfigObject, # which is what AbstractConfigValue does. can_equal(other) && self.class.map_equals(self, other) else false end end def hash self.class.map_hash(@value) end def contains_key?(key) @value.has_key?(key) end def key_set Set.new(@value.keys) end def contains_value?(v) @value.has_value?(v) end def self.empty(origin = nil) if origin.nil? empty(Hocon::Impl::SimpleConfigOrigin.new_simple("empty config")) else self.new(origin, {}) end end def self.empty_missing(base_origin) self.new( Hocon::Impl::SimpleConfigOrigin.new_simple("#{base_origin.description} (not found)"), {}) end end hocon-1.2.5/lib/hocon/impl/abstract_config_object.rb0000644000175000017500000001434413154611745022356 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/abstract_config_value' require 'hocon/impl/simple_config' require 'hocon/config_object' require 'hocon/config_value_type' require 'hocon/impl/resolve_status' require 'hocon/impl/simple_config_origin' require 'hocon/config_error' require 'hocon/impl/config_impl' require 'hocon/impl/unsupported_operation_error' require 'hocon/impl/container' module Hocon::Impl::AbstractConfigObject include Hocon::ConfigObject include Hocon::Impl::Container include Hocon::Impl::AbstractConfigValue ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError ConfigNotResolvedError = Hocon::ConfigError::ConfigNotResolvedError def initialize(origin) super(origin) @config = Hocon::Impl::SimpleConfig.new(self) end def to_config @config end def to_fallback_value self end def with_only_key(key) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `with_only_key`" end def without_key(key) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `without_key`" end def with_value(key, value) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `with_value`" end def with_only_path_or_nil(path) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `with_only_path_or_nil`" end def with_only_path(path) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `with_only_path`" end def without_path(path) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `without_path`" end def with_path_value(path, value) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `with_path_value`" end # This looks up the key with no transformation or type conversion of any # kind, and returns null if the key is not present. The object must be # resolved along the nodes needed to get the key or # ConfigNotResolvedError will be thrown. # # @param key # @return the unmodified raw value or null def peek_assuming_resolved(key, original_path) begin attempt_peek_with_partial_resolve(key) rescue ConfigNotResolvedError => e raise Hocon::Impl::ConfigImpl.improve_not_resolved(original_path, e) end end # Look up the key on an only-partially-resolved object, with no # transformation or type conversion of any kind; if 'this' is not resolved # then try to look up the key anyway if possible. # # @param key # key to look up # @return the value of the key, or null if known not to exist # @throws ConfigNotResolvedError # if can't figure out key's value (or existence) without more # resolving def attempt_peek_with_partial_resolve(key) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `attempt_peek_with_partial_resolve`" end # Looks up the path with no transformation or type conversion. Returns null # if the path is not found; throws ConfigException.NotResolved if we need # to go through an unresolved node to look up the path. def peek_path(path) peek_path_from_obj(self, path) end def peek_path_from_obj(obj, path) begin # we'll fail if anything along the path can't be looked at without resolving path_next = path.remainder v = obj.attempt_peek_with_partial_resolve(path.first) if path_next.nil? v else if v.is_a?(Hocon::Impl::AbstractConfigObject) peek_path_from_obj(v, path_next) else nil end end rescue ConfigNotResolvedError => e raise Hocon::Impl::ConfigImpl.improve_not_resolved(path, e) end end def value_type Hocon::ConfigValueType::OBJECT end def new_copy_with_status(status, origin) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `new_copy_with_status`" end def new_copy(origin) new_copy_with_status(resolve_status, origin) end def construct_delayed_merge(origin, stack) Hocon::Impl::ConfigDelayedMergeObject.new(origin, stack) end def merged_with_object(fallback) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `merged_with_object`" end def with_fallback(mergeable) super(mergeable) end def self.merge_origins(stack) if stack.empty? raise ConfigBugOrBrokenError, "can't merge origins on empty list" end origins = [] first_origin = nil num_merged = 0 stack.each do |v| if first_origin.nil? first_origin = v.origin end if (v.is_a?(Hocon::Impl::AbstractConfigObject)) && (v.resolve_status == Hocon::Impl::ResolveStatus::RESOLVED) && v.empty? # don't include empty files or the .empty() # config in the description, since they are # likely to be "implementation details" else origins.push(v.origin) num_merged += 1 end end if num_merged == 0 # the configs were all empty, so just use the first one origins.push(first_origin) end Hocon::Impl::SimpleConfigOrigin.merge_origins(origins) end def resolve_substitutions(context, source) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `resolve_substituions`" end def relativized(path) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `relativized`" end def [](key) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `[]`" end def render_value_to_sb(sb, indent, at_root, options) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigObject should override `render_value_to_sb`" end def we_are_immutable(method) Hocon::Impl::UnsupportedOperationError.new("ConfigObject is immutable, you can't call Map.#{method}") end def clear raise we_are_immutable("clear") end def []=(key, value) raise we_are_immutable("[]=") end def putAll(map) raise we_are_immutable("putAll") end def remove(key) raise we_are_immutable("remove") end def delete(key) raise we_are_immutable("delete") end def with_origin(origin) super(origin) end end hocon-1.2.5/lib/hocon/impl/container.rb0000644000175000017500000000202013154611745017646 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/config_value' require 'hocon/config_error' # An AbstractConfigValue which contains other values. Java has no way to # express "this has to be an AbstractConfigValue also" other than making # AbstractConfigValue an interface which would be aggravating. But we can say # we are a ConfigValue. module Hocon::Impl::Container include Hocon::ConfigValue # # Replace a child of this value. CAUTION if replacement is null, delete the # child, which may also delete the parent, or make the parent into a # non-container. # def replace_child(child, replacement) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Container` must implement `replace_child` (#{self.class})" end # # Super-expensive full traversal to see if descendant is anywhere # underneath this container. # def has_descendant?(descendant) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Container` must implement `has_descendant?` (#{self.class})" end end hocon-1.2.5/lib/hocon/impl/path_parser.rb0000644000175000017500000002002213154611745020176 0ustar apoikosapoikos# encoding: utf-8 require 'stringio' require 'hocon/impl' require 'hocon/config_syntax' require 'hocon/impl/tokenizer' require 'hocon/impl/config_node_path' require 'hocon/impl/tokens' require 'hocon/config_value_type' require 'hocon/config_error' class Hocon::Impl::PathParser ConfigSyntax = Hocon::ConfigSyntax SimpleConfigOrigin = Hocon::Impl::SimpleConfigOrigin Tokenizer = Hocon::Impl::Tokenizer Tokens = Hocon::Impl::Tokens ConfigNodePath = Hocon::Impl::ConfigNodePath ConfigValueType = Hocon::ConfigValueType ConfigBadPathError = Hocon::ConfigError::ConfigBadPathError class Element def initialize(initial, can_be_empty) @can_be_empty = can_be_empty @sb = StringIO.new(initial) end attr_accessor :can_be_empty, :sb def to_string "Element(#{@sb.string},#{@can_be_empty})" end end def self.api_origin SimpleConfigOrigin.new_simple("path parameter") end def self.parse_path_node(path, flavor = ConfigSyntax::CONF) reader = StringIO.new(path) begin tokens = Tokenizer.tokenize(api_origin, reader, flavor) tokens.next # drop START parse_path_node_expression(tokens, api_origin, path, flavor) ensure reader.close end end def self.parse_path(path) speculated = speculative_fast_parse_path(path) if not speculated.nil? return speculated end reader = StringIO.new(path) begin tokens = Tokenizer.tokenize(api_origin, reader, ConfigSyntax::CONF) tokens.next # drop START return parse_path_expression(tokens, api_origin, path) ensure reader.close end end def self.parse_path_node_expression(expression, origin, original_text = nil, flavor = ConfigSyntax::CONF) path_tokens = [] path = parse_path_expression(expression, origin, original_text, path_tokens, flavor) ConfigNodePath.new(path, path_tokens); end def self.parse_path_expression(expression, origin, original_text = nil, path_tokens = nil, flavor = ConfigSyntax::CONF) # each builder in "buf" is an element in the path buf = [] buf.push(Element.new("", false)) if !expression.has_next? raise ConfigBadPathError.new( origin, original_text, "Expecting a field name or path here, but got nothing") end while expression.has_next? t = expression.next if ! path_tokens.nil? path_tokens << t end # Ignore all IgnoredWhitespace tokens next if Tokens.ignored_whitespace?(t) if Tokens.value_with_type?(t, ConfigValueType::STRING) v = Tokens.value(t) # this is a quoted string; so any periods # in here don't count as path separators s = v.transform_to_string add_path_text(buf, true, s) elsif t == Tokens::EOF # ignore this; when parsing a file, it should not happen # since we're parsing a token list rather than the main # token iterator, and when parsing a path expression from the # API, it's expected to have an EOF. else # any periods outside of a quoted string count as # separators text = nil if Tokens.value?(t) # appending a number here may add # a period, but we _do_ count those as path # separators, because we basically want # "foo 3.0bar" to parse as a string even # though there's a number in it. The fact that # we tokenize non-string values is largely an # implementation detail. v = Tokens.value(t) # We need to split the tokens on a . so that we can get sub-paths but still preserve # the original path text when doing an insertion if ! path_tokens.nil? path_tokens.delete_at(path_tokens.size - 1) path_tokens.concat(split_token_on_period(t, flavor)) end text = v.transform_to_string elsif Tokens.unquoted_text?(t) # We need to split the tokens on a . so that we can get sub-paths but still preserve # the original path text when doing an insertion on ConfigNodeObjects if ! path_tokens.nil? path_tokens.delete_at(path_tokens.size - 1) path_tokens.concat(split_token_on_period(t, flavor)) end text = Tokens.unquoted_text(t) else raise ConfigBadPathError.new( origin, original_text, "Token not allowed in path expression: #{t}" + " (you can double-quote this token if you really want it here)") end add_path_text(buf, false, text) end end pb = Hocon::Impl::PathBuilder.new buf.each do |e| if (e.sb.length == 0) && !e.can_be_empty raise Hocon::ConfigError::ConfigBadPathError.new( origin, original_text, "path has a leading, trailing, or two adjacent period '.' (use quoted \"\" empty string if you want an empty element)") else pb.append_key(e.sb.string) end end pb.result end def self.split_token_on_period(t, flavor) token_text = t.token_text if token_text == "." return [t] end split_token = token_text.split('.') split_tokens = [] split_token.each do |s| if flavor == ConfigSyntax::CONF split_tokens << Tokens.new_unquoted_text(t.origin, s) else split_tokens << Tokens.new_string(t.origin, s, "\"#{s}\"") end split_tokens << Tokens.new_unquoted_text(t.origin, ".") end if token_text[-1] != "." split_tokens.delete_at(split_tokens.size - 1) end split_tokens end def self.add_path_text(buf, was_quoted, new_text) i = if was_quoted -1 else new_text.index('.') || -1 end current = buf.last if i < 0 # add to current path element current.sb << new_text # any empty quoted string means this element can # now be empty. if was_quoted && (current.sb.length == 0) current.can_be_empty = true end else # "buf" plus up to the period is an element current.sb << new_text[0, i] # then start a new element buf.push(Element.new("", false)) # recurse to consume remainder of new_text add_path_text(buf, false, new_text[i + 1, new_text.length - 1]) end end # the idea is to see if the string has any chars or features # that might require the full parser to deal with. def self.looks_unsafe_for_fast_parser(s) last_was_dot = true # // start of path is also a "dot" len = s.length if s.empty? return true end if s[0] == "." return true end if s[len - 1] == "." return true end (0..len).each do |i| c = s[i] if c =~ /^\w$/ last_was_dot = false next elsif c == '.' if last_was_dot return true # ".." means we need to throw an error end last_was_dot = true elsif c == '-' if last_was_dot return true end next else return true end end if last_was_dot return true end false end def self.fast_path_build(tail, s, path_end) # rindex takes last index it should look at, end - 1 not end split_at = s.rindex(".", path_end - 1) tokens = [] tokens << Tokens.new_unquoted_text(nil, s) # this works even if split_at is -1; then we start the substring at 0 with_one_more_element = Path.new(s[split_at + 1..path_end], tail) if split_at < 0 with_one_more_element else fast_path_build(with_one_more_element, s, split_at) end end # do something much faster than the full parser if # we just have something like "foo" or "foo.bar" def self.speculative_fast_parse_path(path) s = Hocon::Impl::ConfigImplUtil.unicode_trim(path) if looks_unsafe_for_fast_parser(s) return nil end fast_path_build(nil, s, s.length) end end hocon-1.2.5/lib/hocon/impl/substitution_expression.rb0000644000175000017500000000122413154611745022724 0ustar apoikosapoikosrequire 'hocon/impl' class Hocon::Impl::SubstitutionExpression def initialize(path, optional) @path = path @optional = optional end attr_reader :path, :optional def change_path(new_path) if new_path == @path self else Hocon::Impl::SubstitutionExpression.new(new_path, @optional) end end def to_s "${#{@optional ? "?" : ""}#{@path.render}}" end def ==(other) if other.is_a? Hocon::Impl::SubstitutionExpression other.path == @path && other.optional == @optional else false end end def hash h = 41 * (41 + @path.hash) h = 41 * (h + (optional ? 1 : 0)) h end endhocon-1.2.5/lib/hocon/impl/tokens.rb0000644000175000017500000002355313154611745017205 0ustar apoikosapoikos# encoding: utf-8 require 'stringio' require 'hocon/impl' require 'hocon/impl/token' require 'hocon/impl/token_type' require 'hocon/impl/config_number' require 'hocon/impl/config_string' require 'hocon/impl/config_null' require 'hocon/impl/config_boolean' require 'hocon/config_error' require 'hocon/impl/resolve_status' require 'hocon/config_value_type' # FIXME the way the subclasses of Token are private with static isFoo and accessors is kind of ridiculous. class Hocon::Impl::Tokens Token = Hocon::Impl::Token TokenType = Hocon::Impl::TokenType ConfigNumber = Hocon::Impl::ConfigNumber ConfigInt = Hocon::Impl::ConfigInt ConfigDouble = Hocon::Impl::ConfigDouble ConfigString = Hocon::Impl::ConfigString ConfigNull = Hocon::Impl::ConfigNull ConfigBoolean = Hocon::Impl::ConfigBoolean ResolveStatus = Hocon::Impl::ResolveStatus ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError class Value < Token def initialize(value, orig_text = nil) super(TokenType::VALUE, value.origin, orig_text) @value = value end attr_reader :value def to_s if value.resolve_status == ResolveStatus::RESOLVED "'#{value.unwrapped}' (#{Hocon::ConfigValueType.value_type_name(value.value_type)})" else "'' ((#{Hocon::ConfigValueType.value_type_name(value.value_type)})" end end def can_equal(o) o.is_a?(Value) end def ==(other) super(other) && other.value == @value end def hash 41 * (41 + super) + value.hash end end class Line < Token def initialize(origin) super(TokenType::NEWLINE, origin) end def can_equal(other) o.is_a?(Line) end def ==(other) super(other) && other.line_number == line_number end def hash 41 * (41 + super) + line_number end def token_text "\n" end end class UnquotedText < Token def initialize(origin, s) super(TokenType::UNQUOTED_TEXT, origin) @value = s end attr_reader :value def to_s "'#{value}'" end def can_equal(o) o.is_a?(UnquotedText) end def ==(other) super(other) && other.value == @value end def hash 41 * (41 + super) + value.hash end def token_text @value end end class IgnoredWhitespace < Token def initialize(origin, s) super(TokenType::IGNORED_WHITESPACE, origin) @value = s end attr_reader :value def to_s "'#{value}' (WHITESPACE)" end def can_equal(o) o.is_a?(IgnoredWhitespace) end def ==(other) super(other) && other.value == value end def hash 41 * (41 + super) + value.hash end def token_text @value end end class Problem < Token def initialize(origin, what, message, suggest_quotes, cause) super(TokenType::PROBLEM, origin) @what = what @message = message @suggest_quotes = suggest_quotes @cause = cause end def what @what end def message @message end def suggest_quotes @suggest_quotes end def cause @cause end def to_s sb = StringIO.new sb << "'" sb << what sb << "'" sb << " (" sb << message sb << ")" sb.string end def can_equal(other) other.is_a?(Problem) end def ==(other) super(other) && other.what == @what && other.message == @message && other.suggest_quotes == @suggest_quotes && Hocon::Impl::ConfigImplUtil.equals_handling_nil?(other.cause, @cause) end def hash hashcode = 41 * (41 + super) hashcode = 41 * (hashcode + @what.hash) hashcode = 41 * (hashcode + @message.hash) hashcode = 41 * (hashcode + @suggest_quotes.hash) unless @cause.nil? hashcode = 41 * (hashcode + @cause.hash) end hashcode end end class Comment < Token def initialize(origin, text) super(TokenType::COMMENT, origin) @text = text end class DoubleSlashComment < Comment def initialize(origin, text) super(origin, text) end def token_text "//" + @text end end class HashComment < Comment def initialize(origin, text) super(origin, text) end def token_text "#" + @text end end attr_reader :text def to_s sb = StringIO.new sb << "'#" sb << text sb << "' (COMMENT)" sb.string end def can_equal(other) other.is_a?(Comment) end def ==(other) super(other) && other.text == @text end def hash hashcode = 41 * (41 + super) hashcode = 41 * (hashcode + @text.hash) hashcode end end # This is not a Value, because it requires special processing class Substitution < Token def initialize(origin, optional, expression) super(TokenType::SUBSTITUTION, origin) @optional = optional @value = expression end def optional? @optional end attr_reader :value def token_text sub_text = "" @value.each { |t| sub_text << t.token_text } "${" + (@optional ? "?" : "") + sub_text + "}" end def to_s sb = StringIO.new value.each do |t| sb << t.to_s end "'${#{sb.to_s}}'" end def can_equal(other) other.is_a?(Substitution) end def ==(other) super(other) && other.value == @value end def hash 41 * (41 + super + @value.hash) end end def self.value?(token) token.is_a?(Value) end def self.value(token) if token.is_a?(Value) token.value else raise ConfigBugOrBrokenError, "tried to get value of non-value token #{token}" end end def self.value_with_type?(t, value_type) value?(t) && (value(t).value_type == value_type) end def self.newline?(t) t.is_a?(Line) end def self.problem?(t) t.is_a?(Problem) end def self.get_problem_what(token) if token.is_a?(Problem) token.what else raise ConfigBugOrBrokenError, "tried to get problem what from #{token}" end end def self.get_problem_message(token) if token.is_a?(Problem) token.message else raise ConfigBugOrBrokenError.new("tried to get problem message from #{token}") end end def self.get_problem_suggest_quotes(token) if token.is_a?(Problem) token.suggest_quotes else raise ConfigBugOrBrokenError.new("tried to get problem suggest_quotes from #{token}") end end def self.get_problem_cause(token) if token.is_a?(Problem) token.cause else raise ConfigBugOrBrokenError.new("tried to get problem cause from #{token}") end end def self.comment?(t) t.is_a?(Comment) end def self.comment_text(token) if comment?(token) token.text else raise ConfigBugOrBrokenError, "tried to get comment text from #{token}" end end def self.unquoted_text?(token) token.is_a?(UnquotedText) end def self.unquoted_text(token) if unquoted_text?(token) token.value else raise ConfigBugOrBrokenError, "tried to get unquoted text from #{token}" end end def self.ignored_whitespace?(token) token.is_a?(IgnoredWhitespace) end def self.substitution?(token) token.is_a?(Substitution) end def self.get_substitution_path_expression(token) if token.is_a?(Substitution) token.value else raise ConfigBugOrBrokenError, "tried to get substitution from #{token}" end end def self.get_substitution_optional(token) if token.is_a?(Substitution) token.optional? else raise ConfigBugOrBrokenError, "tried to get substitution optionality from #{token}" end end START = Token.new_without_origin(TokenType::START, "start of file", "") EOF = Token.new_without_origin(TokenType::EOF, "end of file", "") COMMA = Token.new_without_origin(TokenType::COMMA, "','", ",") EQUALS = Token.new_without_origin(TokenType::EQUALS, "'='", "=") COLON = Token.new_without_origin(TokenType::COLON, "':'", ":") OPEN_CURLY = Token.new_without_origin(TokenType::OPEN_CURLY, "'{'", "{") CLOSE_CURLY = Token.new_without_origin(TokenType::CLOSE_CURLY, "'}'", "}") OPEN_SQUARE = Token.new_without_origin(TokenType::OPEN_SQUARE, "'['", "[") CLOSE_SQUARE = Token.new_without_origin(TokenType::CLOSE_SQUARE, "']'", "]") PLUS_EQUALS = Token.new_without_origin(TokenType::PLUS_EQUALS, "'+='", "+=") def self.new_line(origin) Line.new(origin) end def self.new_problem(origin, what, message, suggest_quotes, cause) Problem.new(origin, what, message, suggest_quotes, cause) end def self.new_comment_double_slash(origin, text) Comment::DoubleSlashComment.new(origin, text) end def self.new_comment_hash(origin, text) Comment::HashComment.new(origin, text) end def self.new_unquoted_text(origin, s) UnquotedText.new(origin, s) end def self.new_ignored_whitespace(origin, s) IgnoredWhitespace.new(origin, s) end def self.new_substitution(origin, optional, expression) Substitution.new(origin, optional, expression) end def self.new_value(value, orig_text = nil) Value.new(value, orig_text) end def self.new_string(origin, value, orig_text) new_value(ConfigString::Quoted.new(origin, value), orig_text) end def self.new_int(origin, value, orig_text) new_value(ConfigNumber.new_number(origin, value, orig_text), orig_text) end def self.new_double(origin, value, orig_text) new_value(ConfigNumber.new_number(origin, value, orig_text), orig_text) end def self.new_long(origin, value, orig_text) new_value(ConfigNumber.new_number(origin, value, orig_text), orig_text) end def self.new_null(origin) new_value(ConfigNull.new(origin), "null") end def self.new_boolean(origin, value) new_value(ConfigBoolean.new(origin, value), value.to_s) end end hocon-1.2.5/lib/hocon/impl/config_document_parser.rb0000644000175000017500000005410413154611745022415 0ustar apoikosapoikos# encoding: utf-8 require 'stringio' require 'hocon/impl' require 'hocon/config_error' require 'hocon/impl/tokens' require 'hocon/impl/config_node_single_token' require 'hocon/impl/config_node_comment' require 'hocon/impl/abstract_config_node_value' require 'hocon/impl/config_node_concatenation' require 'hocon/impl/config_include_kind' require 'hocon/impl/config_node_object' require 'hocon/impl/config_node_array' require 'hocon/impl/config_node_root' class Hocon::Impl::ConfigDocumentParser ConfigSyntax = Hocon::ConfigSyntax ConfigParseError = Hocon::ConfigError::ConfigParseError ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError ConfigValueType = Hocon::ConfigValueType Tokens = Hocon::Impl::Tokens PathParser = Hocon::Impl::PathParser ArrayIterator = Hocon::Impl::ArrayIterator ConfigImplUtil = Hocon::Impl::ConfigImplUtil ConfigIncludeKind = Hocon::Impl::ConfigIncludeKind ConfigNodeSingleToken = Hocon::Impl::ConfigNodeSingleToken ConfigNodeSimpleValue = Hocon::Impl::ConfigNodeSimpleValue ConfigNodeInclude = Hocon::Impl::ConfigNodeInclude ConfigNodeField = Hocon::Impl::ConfigNodeField ConfigNodeObject = Hocon::Impl::ConfigNodeObject ConfigNodeArray = Hocon::Impl::ConfigNodeArray ConfigNodeRoot = Hocon::Impl::ConfigNodeRoot def self.parse(tokens, origin, options) syntax = options.syntax.nil? ? ConfigSyntax::CONF : options.syntax context = Hocon::Impl::ConfigDocumentParser::ParseContext.new(syntax, origin, tokens) context.parse end def self.parse_value(tokens, origin, options) syntax = options.syntax.nil? ? ConfigSyntax::CONF : options.syntax context = Hocon::Impl::ConfigDocumentParser::ParseContext.new(syntax, origin, tokens) context.parse_single_value end class ParseContext def initialize(flavor, origin, tokens) @line_number = 1 @buffer = [] @tokens = tokens @flavor = flavor @equals_count = 0 @base_origin = origin end def pop_token if @buffer.empty? return @tokens.next end @buffer.pop end def next_token t = pop_token if @flavor.equal?(ConfigSyntax::JSON) if Tokens.unquoted_text?(t) && !unquoted_whitespace?(t) raise parse_error("Token not allowed in valid JSON: '#{Tokens.unquoted_text(t)}'") elsif Tokens.substitution?(t) raise parse_error("Substitutions (${} syntax) not allowed in JSON") end end t end def next_token_collecting_whitespace(nodes) while true t = next_token if Tokens.ignored_whitespace?(t) || Tokens.newline?(t) || unquoted_whitespace?(t) nodes.push(ConfigNodeSingleToken.new(t)) if Tokens.newline?(t) @line_number = t.line_number + 1 end elsif Tokens.comment?(t) nodes.push(Hocon::Impl::ConfigNodeComment.new(t)) else new_number = t.line_number if new_number >= 0 @line_number = new_number end return t end end end def put_back(token) @buffer.push(token) end # In arrays and objects, comma can be omitted # as long as there's at least one newline instead. # this skips any newlines in front of a comma, # skips the comma, and returns true if it found # either a newline or a comma. The iterator # is left just after the comma or the newline. def check_element_separator(nodes) if @flavor.equal?(ConfigSyntax::JSON) t = next_token_collecting_whitespace(nodes) if t.equal?(Tokens::COMMA) nodes.push(ConfigNodeSingleToken.new(t)) return true else put_back(t) return false end else saw_separator_or_new_line = false t = next_token while true if Tokens.ignored_whitespace?(t) || unquoted_whitespace?(t) nodes.push(ConfigNodeSingleToken.new(t)) elsif Tokens.comment?(t) nodes.push(Hocon::Impl::ConfigNodeComment.new(t)) elsif Tokens.newline?(t) saw_separator_or_new_line = true @line_number += 1 nodes.push(ConfigNodeSingleToken.new(t)) # we want to continue to also eat # a comma if there is one. elsif t.equal?(Tokens::COMMA) nodes.push(ConfigNodeSingleToken.new(t)) return true else # non-newline-or-comma put_back(t) return saw_separator_or_new_line end t = next_token end end end # parse a concatenation. If there is no concatenation, return the next value def consolidate_values(nodes) # this trick is not done in JSON if @flavor.equal?(ConfigSyntax::JSON) return nil end # create only if we have value tokens values = [] value_count = 0 # ignore a newline up front t = next_token_collecting_whitespace(nodes) while true v = nil if Tokens.ignored_whitespace?(t) values.push(ConfigNodeSingleToken.new(t)) t = next_token next elsif Tokens.value?(t) || Tokens.unquoted_text?(t) || Tokens.substitution?(t) || t == Tokens::OPEN_CURLY || t == Tokens::OPEN_SQUARE # there may be newlines _within_ the objects and arrays v = parse_value(t) value_count += 1 else break end if v.nil? raise ConfigBugOrBrokenError, "no value" end values.push(v) t = next_token # but don't consolidate across a newline end put_back(t) # No concatenation was seen, but a single value may have been parsed, so return it, and put back # all succeeding tokens if value_count < 2 value = nil values.each do |node| if node.is_a?(Hocon::Impl::AbstractConfigNodeValue) value = node elsif value.nil? nodes.add(node) else put_back(node.tokens[0]) end end return value end # Put back any trailing whitespace, as the parent object is responsible for tracking # any leading/trailing whitespace for i in (0..values.size - 1).reverse_each if values[i].is_a?(ConfigNodeSingleToken) put_back(values[i].token) values.delete_at(i) else break end end Hocon::Impl::ConfigNodeConcatenation.new(values) end def parse_error(message, cause = nil) ConfigParseError.new(@base_origin.with_line_number(@line_number), message, cause) end def add_quote_suggestion(bad_token, message, last_path = nil, inside_equals = nil) if inside_equals.nil? inside_equals = @equals_count > 0 end previous_field_name = last_path != nil ? last_path.render : nil if bad_token == Tokens::EOF.to_s # EOF requires special handling for the error to make sense. if previous_field_name != nil part = "#{message} (if you intended '#{previous_field_name}'" + "' to be part of a value, instead of a key, " + "try adding double quotes around the whole value" else return message end else if previous_field_name != nil part = "#{message} (if you intended #{bad_token}" + " to be part of the value for '#{previous_field_name}', " + "try enclosing the value in double quotes" else part = "#{message} (if you intended #{bad_token}" + " to be part of a key or string value, " + "try enclosing the key or value in double quotes" end end # Don't have a special case to throw a message about changing the file to .properties, since # we don't support that format part end def parse_value(t) v = nil starting_equals_count = @equals_count if Tokens.value?(t) || Tokens.unquoted_text?(t) || Tokens.substitution?(t) v = Hocon::Impl::ConfigNodeSimpleValue.new(t) elsif t.equal?(Tokens::OPEN_CURLY) v = parse_object(true) elsif t.equal?(Tokens::OPEN_SQUARE) v = parse_array else raise parse_error(add_quote_suggestion(t.to_s, "Expecting a value but got wrong token: #{t}")) end if @equals_count != starting_equals_count raise ConfigBugOrBrokenError, "Bug in config parser: unbalanced equals count" end v end def parse_key(token) if @flavor.equal?(ConfigSyntax::JSON) if Tokens.value_with_type?(token, ConfigValueType::STRING) return PathParser.parse_path_node_expression(Hocon::Impl::ArrayIterator.new([token]), nil) else raise ConfigParseError, "Expecting close brace } or a field name here, got #{token}" end else expression = [] t = token while Tokens.value?(t) || Tokens.unquoted_text?(t) expression.push(t) t = next_token # note: don't cross a newline end if expression.empty? raise parse_error("expecting a close brace or a field name here, got #{t}") end put_back(t) # put back the token we ended with PathParser.parse_path_node_expression(ArrayIterator.new(expression), nil) end end def include_keyword?(t) Tokens.unquoted_text?(t) && Tokens.unquoted_text(t) == "include" end def unquoted_whitespace?(t) unless Tokens.unquoted_text?(t) return false end s = Tokens.unquoted_text(t) s.each_char do |c| unless ConfigImplUtil.whitespace?(c) return false end end true end def key_value_separator?(t) if @flavor.equal?(ConfigSyntax::JSON) t.equal?(Tokens::COLON) else t.equal?(Tokens::COLON) || t.equal?(Tokens::EQUALS) || t.equal?(Tokens::PLUS_EQUALS) end end def parse_include(children) t = next_token_collecting_whitespace(children) # we either have a quoted string or the "file()" syntax if Tokens.unquoted_text?(t) # get foo( kind_text = Tokens.unquoted_text(t) if kind_text == "url(" kind = ConfigIncludeKind::URL elsif kind_text == "file(" kind = ConfigIncludeKind::FILE elsif kind_text == "classpath(" kind = ConfigIncludeKind::CLASSPATH else raise parse_error("expecting include parameter to be quoted filename, file(), classpath(), or url(). No spaces are allowed before the open paren. Not expecting: #{t}") end children.push(ConfigNodeSingleToken.new(t)) # skip space inside parens t = next_token_collecting_whitespace(children) # quoted string unless Tokens.value_with_type?(t, ConfigValueType::STRING) raise parse_error("expecting a quoted string inside file(), classpath(), or url(), rather than: #{t}") end children.push(ConfigNodeSimpleValue.new(t)) # skip space after string, inside parens t = next_token_collecting_whitespace(children) if Tokens.unquoted_text?(t) && Tokens.unquoted_text(t) == ")" # OK, close paren else raise parse_error("expecting a close parentheses ')' here, not: #{t}") end ConfigNodeInclude.new(children, kind) elsif Tokens.value_with_type?(t, ConfigValueType::STRING) children.push(ConfigNodeSimpleValue.new(t)) ConfigNodeInclude.new(children, ConfigIncludeKind::HEURISTIC) else raise parse_error("include keyword is not followed by a quoted string, but by: #{t}") end end def parse_object(had_open_curly) # invoked just after the OPEN_CURLY (or START, if !hadOpenCurly) after_comma = false last_path = nil last_inside_equals = false object_nodes = [] keys = Hash.new if had_open_curly object_nodes.push(ConfigNodeSingleToken.new(Tokens::OPEN_CURLY)) end while true t = next_token_collecting_whitespace(object_nodes) if t.equal?(Tokens::CLOSE_CURLY) if @flavor.equal?(ConfigSyntax::JSON) && after_comma raise parse_error(add_quote_suggestion(t.to_s, "expecting a field name after a comma, got a close brace } instead")) elsif !had_open_curly raise parse_error(add_quote_suggestion(t.to_s, "unbalanced close brace '}' with no open brace")) end object_nodes.push(ConfigNodeSingleToken.new(Tokens::CLOSE_CURLY)) break elsif t.equal?(Tokens::EOF) && !had_open_curly put_back(t) break elsif !@flavor.equal?(ConfigSyntax::JSON) && include_keyword?(t) include_nodes = [] include_nodes.push(ConfigNodeSingleToken.new(t)) object_nodes.push(parse_include(include_nodes)) after_comma = false else key_value_nodes = [] key_token = t path = parse_key(key_token) key_value_nodes.push(path) after_key = next_token_collecting_whitespace(key_value_nodes) inside_equals = false if @flavor.equal?(ConfigSyntax::CONF) && after_key.equal?(Tokens::OPEN_CURLY) # can omit the ':' or '=' before an object value next_value = parse_value(after_key) else unless key_value_separator?(after_key) raise parse_error(add_quote_suggestion(after_key.to_s, "Key '#{path.render()}' may not be followed by token: #{after_key}")) end key_value_nodes.push(ConfigNodeSingleToken.new(after_key)) if after_key.equal?(Tokens::EQUALS) inside_equals = true @equals_count += 1 end next_value = consolidate_values(key_value_nodes) if next_value.nil? next_value = parse_value(next_token_collecting_whitespace(key_value_nodes)) end end key_value_nodes.push(next_value) if inside_equals @equals_count -= 1 end last_inside_equals = inside_equals key = path.value.first remaining = path.value.remainder if remaining.nil? existing = keys[key] unless existing.nil? # In strict JSON, dups should be an error; while in # our custom config language, they should be merged # if the value is an object (or substitution that # could become an object). if @flavor.equal?(ConfigSyntax::JSON) raise parse_error("JSON does not allow duplicate fields: '#{key}' was already seen") end end keys[key] = true else if @flavor.equal?(ConfigSyntax::JSON) raise ConfigBugOrBrokenError, "somehow got multi-element path in JSON mode" end keys[key] = true end after_comma = false object_nodes.push(ConfigNodeField.new(key_value_nodes)) end if check_element_separator(object_nodes) # continue looping after_comma = true else t = next_token_collecting_whitespace(object_nodes) if t.equal?(Tokens::CLOSE_CURLY) unless had_open_curly raise parse_error(add_quote_suggestion(t.to_s, "unbalanced close brace '}' with no open brace", last_path, last_inside_equals,)) end object_nodes.push(ConfigNodeSingleToken.new(t)) break elsif had_open_curly raise parse_error(add_quote_suggestion(t.to_s, "Expecting close brace } or a comma, got #{t}", last_path, last_inside_equals,)) else if t.equal?(Tokens::EOF) put_back(t) break else raise parse_error(add_quote_suggestion(t.to_s, "Expecting close brace } or a comma, got #{t}", last_path, last_inside_equals,)) end end end end ConfigNodeObject.new(object_nodes) end def parse_array children = [] children.push(ConfigNodeSingleToken.new(Tokens::OPEN_SQUARE)) # invoked just after the OPEN_SQUARE t = nil next_value = consolidate_values(children) unless next_value.nil? children.push(next_value) else t = next_token_collecting_whitespace(children) # special-case the first element if t.equal?(Tokens::CLOSE_SQUARE) children.push(ConfigNodeSingleToken.new(t)) return ConfigNodeArray.new(children) elsif Tokens.value?(t) || t.equal?(Tokens::OPEN_CURLY) || t.equal?(Tokens::OPEN_SQUARE) || Tokens.unquoted_text?(t) || Tokens.substitution?(t) next_value = parse_value(t) children.push(next_value) else raise parse_error("List should have ] or a first element after the open [, instead had token: #{t}" + " (if you want #{t} to be part of a string value, then double-quote it)") end end # now remaining elements while true # just after a value if check_element_separator(children) # comma (or newline equivalent) consumed else t = next_token_collecting_whitespace(children) if t.equal?(Tokens::CLOSE_SQUARE) children.push(ConfigNodeSingleToken.new(t)) return ConfigNodeArray.new(children) else raise parse_error("List should have ended with ] or had a comma, instead had token: #{t}" + " (if you want #{t} to be part of a string value, then double-quote it)") end end # now just after a comma next_value = consolidate_values(children) unless next_value.nil? children.push(next_value) else t = next_token_collecting_whitespace(children) if Tokens.value?(t) || t.equal?(Tokens::OPEN_CURLY) || t.equal?(Tokens::OPEN_SQUARE) || Tokens.unquoted_text?(t) || Tokens.substitution?(t) next_value = parse_value(t) children.push(next_value) elsif !@flavor.equal?(ConfigSyntax::JSON) && t.equal?(Tokens::CLOSE_SQUARE) # we allow one trailing comma put_back(t) else raise parse_error("List should have had new element after a comma, instead had token: #{t}" + " (if you want the comma or #{t} to be part of a string value, then double-quote it)") end end end end def parse children = [] t = next_token if t.equal?(Tokens::START) # OK else raise ConfigBugOrBrokenException, "token stream did not begin with START, had #{t}" end t = next_token_collecting_whitespace(children) result = nil missing_curly = false if t.equal?(Tokens::OPEN_CURLY) || t.equal?(Tokens::OPEN_SQUARE) result = parse_value(t) else if @flavor.equal?(ConfigSyntax::JSON) if t.equal?(Tokens::EOF) raise parse_error("Empty document") else raise parse_error("Document must have an object or array at root, unexpected token: #{t}") end else # the root object can omit the surrounding braces. # this token should be the first field's key, or part # of it, so put it back. put_back(t) missing_curly = true result = parse_object(false) end end # Need to pull the children out of the resulting node so we can keep leading # and trailing whitespace if this was a no-brace object. Otherwise, we need to add # the result into the list of children. if result.is_a?(ConfigNodeObject) && missing_curly children += result.children else children.push(result) end t = next_token_collecting_whitespace(children) if t.equal?(Tokens::EOF) if missing_curly # If there were no braces, the entire document should be treated as a single object ConfigNodeRoot.new([ConfigNodeObject.new(children)], @base_origin) else ConfigNodeRoot.new(children, @base_origin) end else raise parse_error("Document has trailing tokens after first object or array: #{t}") end end # Parse a given input stream into a single value node. Used when doing a replace inside a ConfigDocument. def parse_single_value t = next_token if t.equal?(Tokens::START) # OK else raise ConfigBugOrBrokenError, "token stream did not begin with START, had #{t}" end t = next_token if Tokens.ignored_whitespace?(t) || Tokens.newline?(t) || unquoted_whitespace?(t) || Tokens.comment?(t) raise parse_error("The value from setValue cannot have leading or trailing newlines, whitespace, or comments") end if t.equal?(Tokens::EOF) raise parse_error("Empty value") end if @flavor.equal?(ConfigSyntax::JSON) node = parse_value(t) t = next_token if t.equal?(Tokens::EOF) return node else raise parse_error("Parsing JSON and the value set in setValue was either a concatenation or had trailing whitespace, newlines, or comments") end else put_back(t) nodes = [] node = consolidate_values(nodes) t = next_token if t.equal?(Tokens::EOF) node else raise parse_error("The value from setValue cannot have leading or trailing newlines, whitespace, or comments") end end end end endhocon-1.2.5/lib/hocon/impl/config_number.rb0000644000175000017500000000320713154611745020511 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/abstract_config_value' class Hocon::Impl::ConfigNumber include Hocon::Impl::AbstractConfigValue ## sigh... requiring these subclasses before this class ## is declared would cause an error. Thanks, ruby. require 'hocon/impl/config_int' require 'hocon/impl/config_double' def self.new_number(origin, number, original_text) as_int = number.to_i if as_int == number Hocon::Impl::ConfigInt.new(origin, as_int, original_text) else Hocon::Impl::ConfigDouble.new(origin, number, original_text) end end def initialize(origin, original_text) super(origin) @original_text = original_text end attr_reader :original_text def transform_to_string @original_text end def int_value_range_checked(path) # We don't need to do any range checking here due to the way Ruby handles # integers (doesn't have the 32-bit/64-bit distinction that Java does). long_value end def long_value raise "long_value needs to be overriden by sub-classes of #{Hocon::Impl::ConfigNumber}, in this case #{self.class}" end def can_equal(other) other.is_a?(Hocon::Impl::ConfigNumber) end def ==(other) if other.is_a?(Hocon::Impl::ConfigNumber) && can_equal(other) @value == other.value else false end end def hash # This hash function makes it so that a ConfigNumber with a 3.0 # and one with a 3 will return the hash code to_int = @value.round # If the value is an integer or a floating point equal to an integer if to_int == @value to_int.hash else @value.hash end end end hocon-1.2.5/lib/hocon/impl/simple_config_origin.rb0000644000175000017500000002331513154611745022063 0ustar apoikosapoikos# encoding: utf-8 require 'uri' require 'hocon/impl' require 'hocon/impl/url' require 'hocon/impl/origin_type' require 'hocon/config_error' class Hocon::Impl::SimpleConfigOrigin OriginType = Hocon::Impl::OriginType def initialize(description, line_number, end_line_number, origin_type, url_or_nil, resource_or_nil, comments_or_nil) if !description raise ArgumentError, "description may not be nil" end # HACK: naming this variable with an underscore, because the upstream library # has both a member var and a no-arg method by the name "description", and # I don't think Ruby can handle that. @_description = description @line_number = line_number @end_line_number = end_line_number @origin_type = origin_type # TODO: Currently ruby hocon does not support URLs, and so this variable # is not actually a URL/URI, but a string @url_or_nil = url_or_nil @resource_or_nil = resource_or_nil @comments_or_nil = comments_or_nil end attr_reader :_description, :line_number, :end_line_number, :origin_type, :url_or_nil, :resource_or_nil, :comments_or_nil def self.new_simple(description) self.new(description, -1, -1, OriginType::GENERIC, nil, nil, nil) end def self.new_file(file_path) self.new(file_path, -1, -1, OriginType::FILE, file_path, nil, nil) end # NOTE: not porting `new_url` because we're not going to support URLs for now def self.new_resource(resource, url = nil) desc = nil if ! url.nil? desc = resource + " @ " + url.to_external_form else desc = resource end Hocon::Impl::SimpleConfigOrigin.new(desc, -1, -1, OriginType::RESOURCE, url.nil? ? nil : url.to_external_form, resource, nil) end def with_line_number(line_number) if (line_number == @line_number) and (line_number == @end_line_number) self else Hocon::Impl::SimpleConfigOrigin.new( @_description, line_number, line_number, @origin_type, @url_or_nil, @resource_or_nil, @comments_or_nil) end end def add_url(url) SimpleConfigOrigin.new(@_description, line_number, end_line_number, origin_type, url.nil? ? nil : url.to_s, resource_or_nil, comments_or_nil) end def with_comments(comments) if Hocon::Impl::ConfigImplUtil.equals_handling_nil?(comments, @comments_or_nil) self else Hocon::Impl::SimpleConfigOrigin.new( @_description, @line_number, @end_line_number, @origin_type, @url_or_nil, @resource_or_nil, comments) end end def prepend_comments(comments) if Hocon::Impl::ConfigImplUtil.equals_handling_nil?(comments, @comments_or_nil) self elsif @comments_or_nil.nil? with_comments(comments) else merged = comments + @comments_or_nil with_comments(merged) end end def append_comments(comments) if Hocon::Impl::ConfigImplUtil.equals_handling_nil?(comments, @comments_or_nil) self elsif comments_or_nil.nil? with_comments(comments) else merged = comments_or_nil + comments with_comments(merged) end end def description if @line_number < 0 _description elsif end_line_number == line_number "#{_description}: #{line_number}" else "#{_description}: #{line_number}-#{end_line_number}" end end def ==(other) if other.is_a? Hocon::Impl::SimpleConfigOrigin @_description == other._description && @line_number == other.line_number && @end_line_number == other.end_line_number && @origin_type == other.origin_type && Hocon::Impl::ConfigImplUtil.equals_handling_nil?(@url_or_nil, other.url_or_nil) && Hocon::Impl::ConfigImplUtil.equals_handling_nil?(@resource_or_nil, other.resource_or_nil) else false end end def hash h = 41 * (41 + @_description.hash) h = 41 * (h + @line_number) h = 41 * (h + @end_line_number) h = 41 * (h + @origin_type.hash) unless @url_or_nil.nil? h = 41 * (h + @url_or_nil.hash) end unless @resource_or_nil.nil? h = 41 * (h + @resource_or_nil.hash) end h end def to_s "ConfigOrigin(#{_description})" end def filename # TODO because we don't support URLs, this function's URL handling # is completely pointless. It will only return _description (a string that # is the file path) or nil. # It should be cleaned up if origin_type == OriginType::FILE _description elsif ! url_or_nil.nil? url = nil begin url = Hocon::Impl::Url.new(url_or_nil) rescue Hocon::Impl::Url::MalformedUrlError => e return nil end if url.get_protocol == "file" url.get_file else nil end else nil end end def url if url_or_nil.nil? nil else begin Hocon::Impl::Url.new(url_or_nil) rescue Hocon::Impl::Url::MalformedUrlError => e nil end end end def resource resource_or_nil end def comments @comments_or_nil || [] end MERGE_OF_PREFIX = "merge of " def self.remove_merge_of_prefix(desc) if desc.start_with?(MERGE_OF_PREFIX) desc = desc[MERGE_OF_PREFIX.length, desc.length - 1] end desc end def self.merge_two(a, b) merged_desc = nil merged_start_line = nil merged_end_line = nil merged_comments = nil merged_type = if a.origin_type == b.origin_type a.origin_type else Hocon::Impl::OriginType::GENERIC end # first use the "description" field which has no line numbers # cluttering it. a_desc = remove_merge_of_prefix(a._description) b_desc = remove_merge_of_prefix(b._description) if a_desc == b_desc merged_desc = a_desc if a.line_number < 0 merged_start_line = b.line_number elsif b.line_number < 0 merged_start_line = a.line_number else merged_start_line = [a.line_number, b.line_number].min end merged_end_line = [a.end_line_number, b.end_line_number].max else # this whole merge song-and-dance was intended to avoid this case # whenever possible, but we've lost. Now we have to lose some # structured information and cram into a string. # # description() method includes line numbers, so use it instead # of description field. a_full = remove_merge_of_prefix(a._description) b_full = remove_merge_of_prefix(b._description) merged_desc = "#{MERGE_OF_PREFIX}#{a_full},#{b_full}" merged_start_line = -1 merged_end_line = -1 end merged_url = if Hocon::Impl::ConfigImplUtil.equals_handling_nil?(a.url_or_nil, b.url_or_nil) a.url_or_nil else nil end merged_resource = if Hocon::Impl::ConfigImplUtil.equals_handling_nil?(a.resource_or_nil, b.resource_or_nil) a.resource_or_nil else nil end if Hocon::Impl::ConfigImplUtil.equals_handling_nil?(a.comments_or_nil, b.comments_or_nil) merged_comments = a.comments_or_nil else merged_comments = [] if a.comments_or_nil merged_comments.concat(a.comments_or_nil) end if b.comments_or_nil merged_comments.concat(b.comments_or_nil) end end Hocon::Impl::SimpleConfigOrigin.new( merged_desc, merged_start_line, merged_end_line, merged_type, merged_url, merged_resource, merged_comments) end def self.similarity(a, b) count = 0 if a.origin_type == b.origin_type count += 1 end if a._description == b._description count += 1 # only count these if the description field (which is the file # or resource name) also matches. if a.line_number == b.line_number count += 1 end if a.end_line_number == b.end_line_number count += 1 end if Hocon::Impl::ConfigImplUtil.equals_handling_nil?(a.url_or_nil, b.url_or_nil) count += 1 end if Hocon::Impl::ConfigImplUtil.equals_handling_nil?(a.resource_or_nil, b.resource_or_nil) count += 1 end end count end def self.merge_three(a, b, c) if similarity(a, b) >= similarity(b, c) merge_two(merge_two(a, b), c) else merge_two(a, merge_two(b, c)) end end def self.merge_two_origins(a, b) # a, b are ConfigOrigins merge_two(a, b) end def self.merge_value_origins(stack) # stack is an array of AbstractConfigValue origins = stack.map { |v| v.origin} merge_origins(origins) end # see also 'merge_two_origins' and 'merge_three_origins' def self.merge_origins(stack) # stack is an array of ConfigOrigin if stack.empty? raise Hocon::ConfigError::ConfigBugOrBrokenError, "can't merge empty list of origins" elsif stack.length == 1 stack[0] elsif stack.length == 2 merge_two(stack[0], stack[1]) else remaining = [] stack.each do |o| remaining << o end while remaining.size > 2 c = remaining.last remaining.delete_at(remaining.size - 1) b = remaining.last remaining.delete_at(remaining.size - 1) a = remaining.last remaining.delete_at(remaining.size - 1) merged = merge_three(a, b, c) remaining << merged end # should be down to either 1 or 2 self.merge_origins(remaining) end end # NOTE: skipping 'toFields', 'toFieldsDelta', 'fieldsDelta', 'fromFields', # 'applyFieldsDelta', and 'fromBase' from upstream for now, because they appear # to be about serialization and we probably won't be supporting that. end hocon-1.2.5/lib/hocon/impl/config_include_kind.rb0000644000175000017500000000021013154611745021640 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' module Hocon::Impl::ConfigIncludeKind URL = 0 FILE = 1 CLASSPATH = 2 HEURISTIC = 3 end hocon-1.2.5/lib/hocon/impl/path_builder.rb0000644000175000017500000000176213154611745020342 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/path' require 'hocon/config_error' class Hocon::Impl::PathBuilder def initialize @keys = [] @result = nil end def check_can_append if @result raise Hocon::ConfigError::ConfigBugOrBrokenError, "Adding to PathBuilder after getting result" end end def append_key(key) check_can_append @keys.push(key) end def append_path(path) check_can_append first = path.first remainder = path.remainder loop do @keys.push(first) if !remainder.nil? first = remainder.first remainder = remainder.remainder else break end end end def result # note: if keys is empty, we want to return nil, which is a valid # empty path if @result.nil? remainder = nil while !@keys.empty? key = @keys.pop remainder = Hocon::Impl::Path.new(key, remainder) end @result = remainder end @result end end hocon-1.2.5/lib/hocon/impl/config_null.rb0000644000175000017500000000072713154611745020177 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/config_value_type' class Hocon::Impl::ConfigNull include Hocon::Impl::AbstractConfigValue def initialize(origin) super(origin) end def value_type Hocon::ConfigValueType::NULL end def unwrapped nil end def transform_to_string "null" end def render_value_to_sb(sb, indent, at_root, options) sb << "null" end def new_copy(origin) self.class.new(origin) end end hocon-1.2.5/lib/hocon/impl/simple_includer.rb0000644000175000017500000001516113154611745021054 0ustar apoikosapoikos# encoding: utf-8 require 'stringio' require 'hocon/impl' require 'hocon/impl/full_includer' require 'hocon/impl/url' require 'hocon/impl/config_impl' require 'hocon/config_error' require 'hocon/config_syntax' require 'hocon/impl/simple_config_object' require 'hocon/impl/simple_config_origin' require 'hocon/config_includer_file' require 'hocon/config_factory' require 'hocon/impl/parseable' class Hocon::Impl::SimpleIncluder < Hocon::Impl::FullIncluder ConfigBugorBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError ConfigIOError = Hocon::ConfigError::ConfigIOError SimpleConfigObject = Hocon::Impl::SimpleConfigObject SimpleConfigOrigin = Hocon::Impl::SimpleConfigOrigin def initialize(fallback) @fallback = fallback end # ConfigIncludeContext does this for us on its options def self.clear_for_include(options) # the class loader and includer are inherited, but not this other stuff options.set_syntax(nil).set_origin_description(nil).set_allow_missing(true) end # this is the heuristic includer def include(context, name) obj = self.class.include_without_fallback(context, name) # now use the fallback includer if any and merge its result if ! (@fallback.nil?) obj.with_fallback(@fallback.include(context, name)) else obj end end # the heuristic includer in static form def self.include_without_fallback(context, name) # the heuristic is valid URL then URL, else relative to including file; # relativeTo in a file falls back to classpath inside relativeTo(). url = nil begin url = Hocon::Impl::Url.new(name) rescue Hocon::Impl::Url::MalformedUrlError => e url = nil end if !(url.nil?) include_url_without_fallback(context, url) else source = RelativeNameSource.new(context) from_basename(source, name, context.parse_options) end end # NOTE: not porting `include_url` or `include_url_without_fallback` from upstream, # because we probably won't support URL includes for now. def include_file(context, file) obj = self.class.include_file_without_fallback(context, file) # now use the fallback includer if any and merge its result if (!@fallback.nil?) && @fallback.is_a?(Hocon::ConfigIncluderFile) obj.with_fallback(@fallback).include_file(context, file) else obj end end def self.include_file_without_fallback(context, file) Hocon::ConfigFactory.parse_file_any_syntax(file, context.parse_options).root end # NOTE: not porting `include_resources` or `include_resources_without_fallback` # for now because we're not going to support looking for things on the ruby # load path for now. def with_fallback(fallback) if self.equal?(fallback) raise ConfigBugOrBrokenError, "trying to create includer cycle" elsif @fallback.equal?(fallback) self elsif @fallback.nil? self.class.new(@fallback.with_fallback(fallback)) else self.class.new(fallback) end end class NameSource def name_to_parseable(name, parse_options) raise Hocon::ConfigError::ConfigBugOrBrokenError, "name_to_parseable must be implemented by subclass (#{self.class})" end end class RelativeNameSource < NameSource def initialize(context) @context = context end def name_to_parseable(name, options) p = @context.relative_to(name) if p.nil? # avoid returning nil Hocon::Impl::Parseable.new_not_found(name, "include was not found: '#{name}'", options) else p end end end # this function is a little tricky because there are three places we're # trying to use it; for 'include "basename"' in a .conf file, for # loading app.{conf,json,properties} from classpath, and for # loading app.{conf,json,properties} from the filesystem. def self.from_basename(source, name, options) obj = nil if name.end_with?(".conf") || name.end_with?(".json") || name.end_with?(".properties") p = source.name_to_parseable(name, options) obj = p.parse(p.options.set_allow_missing(options.allow_missing?)) else conf_handle = source.name_to_parseable(name + ".conf", options) json_handle = source.name_to_parseable(name + ".json", options) got_something = false fails = [] syntax = options.syntax obj = SimpleConfigObject.empty(SimpleConfigOrigin.new_simple(name)) if syntax.nil? || (syntax == Hocon::ConfigSyntax::CONF) begin obj = conf_handle.parse(conf_handle.options.set_allow_missing(false). set_syntax(Hocon::ConfigSyntax::CONF)) got_something = true rescue ConfigIOError => e fails << e end end if syntax.nil? || (syntax == Hocon::ConfigSyntax::JSON) begin parsed = json_handle.parse(json_handle.options.set_allow_missing(false). set_syntax(Hocon::ConfigSyntax::JSON)) obj = obj.with_fallback(parsed) got_something = true rescue ConfigIOError => e fails << e end end # NOTE: skipping the upstream block here that would attempt to parse # a java properties file. if (! options.allow_missing?) && (! got_something) if Hocon::Impl::ConfigImpl.trace_loads_enabled # the individual exceptions should have been logged already # with tracing enabled Hocon::Impl::ConfigImpl.trace("Did not find '#{name}'" + " with any extension (.conf, .json, .properties); " + "exceptions should have been logged above.") end if fails.empty? # this should not happen raise ConfigBugOrBrokenError, "should not be reached: nothing found but no exceptions thrown" else sb = StringIO.new fails.each do |t| sb << t sb << ", " end raise ConfigIOError.new(SimpleConfigOrigin.new_simple(name), sb.string, fails[0]) end elsif !got_something if Hocon::Impl::ConfigImpl.trace_loads_enabled Hocon::Impl::ConfigImpl.trace("Did not find '#{name}'" + " with any extension (.conf, .json, .properties); but '#{name}'" + " is allowed to be missing. Exceptions from load attempts should have been logged above.") end end end obj end class Proxy < Hocon::Impl::FullIncluder def initialize(delegate) @delegate = delegate end ## TODO: port remaining implementation when needed end def self.make_full(includer) if includer.is_a?(Hocon::Impl::FullIncluder) includer else Proxy.new(includer) end end end hocon-1.2.5/lib/hocon/impl/unsupported_operation_error.rb0000644000175000017500000000015213154611745023551 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' class Hocon::Impl::UnsupportedOperationError < StandardError end hocon-1.2.5/lib/hocon/impl/url.rb0000644000175000017500000000162313154611745016476 0ustar apoikosapoikos# encoding: utf-8 require 'uri' require 'hocon/impl' # There are several places in the Java codebase that # use Java's URL constructor, and rely on it to throw # a `MalformedURLException` if the URL isn't valid. # # Ruby doesn't really have a similar constructor / # validator, so this is a little shim to hopefully # make the ported code match up with the upstream more # closely. class Hocon::Impl::Url class MalformedUrlError < StandardError def initialize(msg, cause = nil) super(msg) @cause = cause end end def initialize(url) begin # URI::parse wants a string @url = URI.parse(url.to_s) if !(@url.kind_of?(URI::HTTP)) raise MalformedUrlError, "Unrecognized URL: '#{url}'" end rescue URI::InvalidURIError => e raise MalformedUrlError.new("Unrecognized URL: '#{url}' (error: #{e})", e) end end def to_s @url.to_s end end hocon-1.2.5/lib/hocon/impl/config_node_single_token.rb0000644000175000017500000000041113154611745022701 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/abstract_config_node' class Hocon::Impl::ConfigNodeSingleToken include Hocon::Impl::AbstractConfigNode def initialize(t) @token = t end attr_reader :token def tokens [@token] end endhocon-1.2.5/lib/hocon/impl/config_string.rb0000644000175000017500000000363513154611745020534 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/abstract_config_value' require 'hocon/config_value_type' require 'hocon/impl/config_impl_util' class Hocon::Impl::ConfigString include Hocon::Impl::AbstractConfigValue ConfigImplUtil = Hocon::Impl::ConfigImplUtil attr_reader :value class Quoted < Hocon::Impl::ConfigString def initialize(origin, value) super(origin, value) end def new_copy(origin) self.class.new(origin, @value) end private # serialization all goes through SerializedConfigValue def write_replace Hocon::Impl::SerializedConfigValue.new(self) end end # this is sort of a hack; we want to preserve whether whitespace # was quoted until we process substitutions, so we can ignore # unquoted whitespace when concatenating lists or objects. # We dump this distinction when serializing and deserializing, # but that 's OK because it isn' t in equals/hashCode, and we # don 't allow serializing unresolved objects which is where # quoted-ness matters. If we later make ConfigOrigin point # to the original token range, we could use that to implement # wasQuoted() class Unquoted < Hocon::Impl::ConfigString def initialize(origin, value) super(origin, value) end def new_copy(origin) self.class.new(origin, @value) end def write_replace Hocon::Impl::SerializedConfigValue.new(self) end end def was_quoted? self.is_a?(Quoted) end def value_type Hocon::ConfigValueType::STRING end def unwrapped @value end def transform_to_string @value end def render_value_to_sb(sb, indent_size, at_root, options) if options.json? sb << ConfigImplUtil.render_json_string(@value) else sb << ConfigImplUtil.render_string_unquoted_if_possible(@value) end end private def initialize(origin, value) super(origin) @value = value end end hocon-1.2.5/lib/hocon/impl/resolve_context.rb0000644000175000017500000002021013154611745021110 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/config_error' require 'hocon/impl/resolve_source' require 'hocon/impl/resolve_memos' require 'hocon/impl/memo_key' require 'hocon/impl/abstract_config_value' require 'hocon/impl/config_impl' class Hocon::Impl::ResolveContext ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError NotPossibleToResolve = Hocon::Impl::AbstractConfigValue::NotPossibleToResolve attr_reader :restrict_to_child def initialize(memos, options, restrict_to_child, resolve_stack, cycle_markers) @memos = memos @options = options @restrict_to_child = restrict_to_child @resolve_stack = resolve_stack @cycle_markers = cycle_markers end def self.new_cycle_markers # This looks crazy, but wtf else should we do with # return Collections.newSetFromMap(new IdentityHashMap()); Set.new end def add_cycle_marker(value) if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("++ Cycle marker #{value}@#{value.hash}", depth) end if @cycle_markers.include?(value) raise ConfigBugOrBrokenError.new("Added cycle marker twice " + value) end copy = self.class.new_cycle_markers copy.merge(@cycle_markers) copy.add(value) self.class.new(@memos, @options, @restrict_to_child, @resolve_stack, copy) end def remove_cycle_marker(value) if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("-- Cycle marker #{value}@#{value.hash}", depth) end copy = self.class.new_cycle_markers copy.merge(@cycle_markers) copy.delete(value) self.class.new(@memos, @options, @restrict_to_child, @resolve_stack, copy) end def memoize(key, value) changed = @memos.put(key, value) self.class.new(changed, @options, @restrict_to_child, @resolve_stack, @cycle_markers) end def options @options end def is_restricted_to_child @restrict_to_child != nil end def restrict(restrict_to) if restrict_to.equal?(@restrict_to_child) self else Hocon::Impl::ResolveContext.new(@memos, @options, restrict_to, @resolve_stack, @cycle_markers) end end def unrestricted restrict(nil) end def resolve(original, source) if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( "resolving #{original} restrict_to_child=#{@restrict_to_child} in #{source}", depth) end push_trace(original).real_resolve(original, source).pop_trace end def real_resolve(original, source) # a fully-resolved (no restrict_to_child) object can satisfy a # request for a restricted object, so always check that first. full_key = Hocon::Impl::MemoKey.new(original, nil) restricted_key = nil cached = @memos.get(full_key) # but if there was no fully-resolved object cached, we'll only # compute the restrictToChild object so use a more limited # memo key if cached == nil && is_restricted_to_child restricted_key = Hocon::Impl::MemoKey.new(original, @restrict_to_child) cached = @memos.get(restricted_key) end if cached != nil if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( "using cached resolution #{cached} for #{original} restrict_to_child #{@restrict_to_child}", depth) end Hocon::Impl::ResolveResult.make(self, cached) else if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( "not found in cache, resolving #{original}@#{original.hash}", depth) end if @cycle_markers.include?(original) if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( "Cycle detected, can't resolve; #{original}@#{original.hash}", depth) end raise NotPossibleToResolve.new(self) end result = original.resolve_substitutions(self, source) resolved = result.value if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( "resolved to #{resolved}@#{resolved.hash} from #{original}@#{resolved.hash}", depth) end with_memo = result.context if resolved == nil || resolved.resolve_status == Hocon::Impl::ResolveStatus::RESOLVED # if the resolved object is fully resolved by resolving # only the restrictToChildOrNull, then it can be cached # under fullKey since the child we were restricted to # turned out to be the only unresolved thing. if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( "caching #{full_key} result #{resolved}", depth) end with_memo = with_memo.memoize(full_key, resolved) else # if we have an unresolved object then either we did a # partial resolve restricted to a certain child, or we are # allowing incomplete resolution, or it's a bug. if is_restricted_to_child if restricted_key == nil raise ConfigBugOrBrokenError.new("restricted_key should not be null here") end if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( "caching #{restricted_key} result #{resolved}", depth) end with_memo = with_memo.memoize(restricted_key, resolved) elsif @options.allow_unresolved if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( "caching #{full_key} result #{resolved}", depth) end with_memo = with_memo.memoize(full_key, resolved) else raise ConfigBugOrBrokenError.new( "resolve_substitutions did not give us a resolved object") end end Hocon::Impl::ResolveResult.make(with_memo, resolved) end end # This method is a translation of the constructor in the Java version with signature # ResolveContext(ConfigResolveOptions options, Path restrictToChild) def self.construct(options, restrict_to_child) context = self.new(Hocon::Impl::ResolveMemos.new, options, restrict_to_child, [], new_cycle_markers) if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( "ResolveContext restrict to child #{restrict_to_child}", context.depth) end context end def trace_string separator = ", " sb = "" @resolve_stack.each { |value| if value.instance_of?(Hocon::Impl::ConfigReference) sb << value.expression.to_s sb << separator end } if sb.length > 0 sb.chomp!(separator) end sb end def depth if @resolve_stack.size > 30 raise Hocon::ConfigError::ConfigBugOrBrokenError.new("resolve getting too deep") end @resolve_stack.size end def self.resolve(value, root, options) source = Hocon::Impl::ResolveSource.new(root) context = construct(options, nil) begin context.resolve(value, source).value rescue NotPossibleToResolve => e # ConfigReference was supposed to catch NotPossibleToResolve raise ConfigBugOrBrokenError( "NotPossibleToResolve was thrown from an outermost resolve", e) end end def pop_trace copy = @resolve_stack.clone old = copy.delete_at(@resolve_stack.size - 1) if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("popped trace #{old}", depth - 1) end Hocon::Impl::ResolveContext.new(@memos, @options, @restrict_to_child, copy, @cycle_markers) end private def push_trace(value) if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("pushing trace #{value}", depth) end copy = @resolve_stack.clone copy << value Hocon::Impl::ResolveContext.new(@memos, @options, @restrict_to_child, copy, @cycle_markers) end end hocon-1.2.5/lib/hocon/impl/memo_key.rb0000644000175000017500000000161113154611745017476 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/impl' class Hocon::Impl::MemoKey def initialize(value, restrict_to_child_or_nil) @value = value @restrict_to_child_or_nil = restrict_to_child_or_nil end def hash h = @value.hash if @restrict_to_child_or_nil != nil h + 41 * (41 + @restrict_to_child_or_nil.hash) else h end end def ==(other) if other.is_a?(self.class) o = other if !o.value.equal?(@value) return false elsif o.restrict_to_child_or_nil.equals(@restrict_to_child_or_nil) return true elsif o.restrict_to_child_or_nil == nil || @restrict_to_child_or_nil == nil return false else return o.restrict_to_child_or_nil == @restrict_to_child_or_nil end else false end end def to_s "MemoKey(#{@value}@#{@value.hash},#{@restrict_to_child_or_nil})" end end hocon-1.2.5/lib/hocon/impl/abstract_config_node.rb0000644000175000017500000000114013154611745022023 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/parser/config_node' require 'hocon/config_error' module Hocon::Impl::AbstractConfigNode include Hocon::Parser::ConfigNode def tokens raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of AbstractConfigNode should override `tokens` (#{self.class})" end def render orig_text = StringIO.new tokens.each do |t| orig_text << t.token_text end orig_text.string end def ==(other) other.is_a?(Hocon::Impl::AbstractConfigNode) && (render == other.render) end def hash render.hash end end hocon-1.2.5/lib/hocon/impl/default_transformer.rb0000644000175000017500000000706613154611745021751 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/config_string' require 'hocon/config_value_type' require 'hocon/impl/config_boolean' class Hocon::Impl::DefaultTransformer ConfigValueType = Hocon::ConfigValueType ConfigString = Hocon::Impl::ConfigString ConfigBoolean = Hocon::Impl::ConfigBoolean def self.transform(value, requested) if value.value_type == ConfigValueType::STRING s = value.unwrapped case requested when ConfigValueType::NUMBER begin v = Integer(s) return ConfigInt.new(value.origin, v, s) rescue ArgumentError # try Float end begin v = Float(s) return ConfigFloat.new(value.origin, v, s) rescue ArgumentError # oh well. end when ConfigValueType::NULL if s == "null" return ConfigNull.new(value.origin) end when ConfigValueType::BOOLEAN if s == "true" || s == "yes" || s == "on" return ConfigBoolean.new(value.origin, true) elsif s == "false" || s == "no" || s == "off" return ConfigBoolean.new(value.origin, false) end when ConfigValueType::LIST # can't go STRING to LIST automatically when ConfigValueType::OBJECT # can't go STRING to OBJECT automatically when ConfigValueType::STRING # no-op STRING to STRING end elsif requested == ConfigValueType::STRING # if we converted null to string here, then you wouldn't properly # get a missing-value error if you tried to get a null value # as a string. case value.value_type # Ruby note: can't fall through in ruby. In the java code, NUMBER # just rolls over to the BOOLEAN case when ConfigValueType::NUMBER return ConfigString::Quoted.new(value.origin, value.transform_to_string) when ConfigValueType::BOOLEAN return ConfigString::Quoted.new(value.origin, value.transform_to_string) when ConfigValueType::NULL # want to be sure this throws instead of returning "null" as a # string when ConfigValueType::OBJECT # no OBJECT to STRING automatically when ConfigValueType::LIST # no LIST to STRING automatically when ConfigValueType::STRING # no-op STRING to STRING end elsif requested == ConfigValueType::LIST && value.value_type == ConfigValueType::OBJECT # attempt to convert an array-like (numeric indices) object to a # list. This would be used with .properties syntax for example: # -Dfoo.0=bar -Dfoo.1=baz # To ensure we still throw type errors for objects treated # as lists in most cases, we'll refuse to convert if the object # does not contain any numeric keys. This means we don't allow # empty objects here though :-/ o = value values = Hash.new values o.keys.each do |key| begin i = Integer(key, 10) if i < 0 next end values[key] = i rescue ArgumentError next end end if not values.empty? entry_list = values.to_a # sort by numeric index entry_list.sort! {|a,b| b[0] <=> a[0]} # drop the indices (we allow gaps in the indices, for better or # worse) list = Array.new entry_list.each do |entry| list.push(entry[1]) end return SimpleConfigList.new(value.origin, list) end end value end end hocon-1.2.5/lib/hocon/impl/simple_include_context.rb0000644000175000017500000000144213154611745022433 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/simple_includer' require 'hocon/config_include_context' require 'hocon/impl/config_impl' class Hocon::Impl::SimpleIncludeContext include Hocon::ConfigIncludeContext def initialize(parseable) @parseable = parseable end def with_parseable(parseable) if parseable.equal?(@parseable) self else self.class.new(parseable) end end def relative_to(filename) if Hocon::Impl::ConfigImpl.trace_loads_enabled Hocon::Impl::ConfigImpl.trace("Looking for '#{filename}' relative to #{@parseable}") end if ! @parseable.nil? @parseable.relative_to(filename) else nil end end def parse_options Hocon::Impl::SimpleIncluder.clear_for_include(@parseable.options) end end hocon-1.2.5/lib/hocon/impl/simple_config.rb0000644000175000017500000002213513154611745020513 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/config_value_type' require 'hocon/config_resolve_options' require 'hocon/impl/path' require 'hocon/impl/default_transformer' require 'hocon/impl/config_impl' require 'hocon/impl/resolve_context' require 'hocon/config_mergeable' class Hocon::Impl::SimpleConfig include Hocon::ConfigMergeable ConfigMissingError = Hocon::ConfigError::ConfigMissingError ConfigNotResolvedError = Hocon::ConfigError::ConfigNotResolvedError ConfigNullError = Hocon::ConfigError::ConfigNullError ConfigWrongTypeError = Hocon::ConfigError::ConfigWrongTypeError ConfigValueType = Hocon::ConfigValueType Path = Hocon::Impl::Path DefaultTransformer = Hocon::Impl::DefaultTransformer attr_reader :object def initialize(object) @object = object end attr_reader :object def root @object end def origin @object.origin end def resolve(options = Hocon::ConfigResolveOptions.defaults) resolve_with(self, options) end def resolve_with(source, options) resolved = Hocon::Impl::ResolveContext.resolve(@object, source.object, options) if resolved.eql?(@object) self else Hocon::Impl::SimpleConfig.new(resolved) end end def self.find_key(me, key, expected, original_path) v = me.peek_assuming_resolved(key, original_path) if v.nil? raise ConfigMissingError.new(nil, "No configuration setting found for key '#{original_path.render}'", nil) end if not expected.nil? v = DefaultTransformer.transform(v, expected) end if v.value_type == ConfigValueType::NULL raise ConfigNullError.new(v.origin, (ConfigNullError.make_message(original_path.render, (not expected.nil?) ? ConfigValueType.value_type_name(expected) : nil)), nil) elsif (not expected.nil?) && v.value_type != expected raise ConfigWrongTypeError.new(v.origin, "#{original_path.render} has type #{ConfigValueType.value_type_name(v.value_type)} " + "rather than #{ConfigValueType.value_type_name(expected)}", nil) else return v end end def find(me, path, expected, original_path) key = path.first rest = path.remainder if rest.nil? self.class.find_key(me, key, expected, original_path) else o = self.class.find_key(me, key, ConfigValueType::OBJECT, original_path.sub_path(0, original_path.length - rest.length)) raise "Error: object o is nil" unless not o.nil? find(o, rest, expected, original_path) end end def find3(path_expression, expected, original_path) find(@object, path_expression, expected, original_path) end def find2(path_expression, expected) path = Path.new_path(path_expression) find3(path, expected, path) end def ==(other) if other.is_a? Hocon::Impl::SimpleConfig @object == other.object else false end end def hash 41 * @object.hash end def self.find_key_or_null(me, key, expected, original_path) v = me.peek_assuming_resolved(key, original_path) if v.nil? raise Hocon::ConfigError::ConfigMissingError.new(nil, original_path.render, nil) end if not expected.nil? v = Hocon::Impl::DefaultTransformer.transform(v, expected) end if (not expected.nil?) && (v.value_type != expected && v.value_type != ConfigValueType::NULL) raise Hocon::ConfigError::ConfigWrongTypeError.with_expected_actual(v.origin, original_path.render, ConfigValueType.value_type_name(expected), ConfigValueType.value_type_name(v.value_type)) else return v end end def self.find_or_null(me, path, expected, original_path) begin key = path.first remainder = path.remainder if remainder.nil? return self.find_key_or_null(me, key, expected, original_path) else o = find_key(me, key, ConfigValueType::OBJECT, original_path.sub_path(0, original_path.length - remainder.length)) if o.nil? raise "Missing key: #{key} on path: #{path}" end find_or_null(o, remainder, expected, original_path) end rescue Hocon::ConfigError::ConfigNotResolvedError raise Hocon::Impl::ConfigImpl::improved_not_resolved(path, e) end end def is_null?(path_expression) path = Path.new_path(path_expression) v = self.class.find_or_null(@object, path, nil, path) v.value_type == ConfigValueType::NULL end def get_value(path) parsed_path = Path.new_path(path) find(@object, parsed_path, nil, parsed_path) end def get_boolean(path) v = find2(path, ConfigValueType::BOOLEAN) v.unwrapped end def get_config_number(path_expression) path = Path.new_path(path_expression) v = find(@object, path, ConfigValueType::NUMBER, path) v.unwrapped end def get_int(path) get_config_number(path) end def get_string(path) v = find2(path, ConfigValueType::STRING) v.unwrapped end def get_list(path) find2(path, ConfigValueType::LIST) end def get_object(path) find2(path, ConfigValueType::OBJECT) end def get_config(path) get_object(path).to_config end def get_any_ref(path) v = find2(path, nil) v.unwrapped end def get_bytes(path) size = null begin size = get_long(path) rescue ConfigWrongTypeError => e v = find2(path, ConfigValueType::STRING) size = self.class.parse_bytes(v.unwrapped, v.origin, path) end size end def get_homogeneous_unwrapped_list(path, expected) l = [] list = get_list(path) list.each do |cv| if !expected.nil? v = DefaultTransformer.transform(cv, expected) end if v.value_type != expected raise ConfigWrongTypeError.with_expected_actual(origin, path, "list of #{ConfigValueType.value_type_name(expected)}", "list of #{ConfigValueType.value_type_name(v.value_type)}") end l << v.unwrapped end l end def get_boolean_list(path) get_homogeneous_unwrapped_list(path, ConfigValueType::BOOLEAN) end def get_number_list(path) get_homogeneous_unwrapped_list(path, ConfigValueType::NUMBER) end def get_int_list(path) l = [] numbers = get_homogeneous_wrapped_list(path, ConfigValueType::NUMBER) numbers.each do |v| l << v.int_value_range_checked(path) end l end def get_double_list(path) l = [] numbers = get_number_list(path) numbers.each do |n| l << n.double_value end l end def get_string_list(path) get_homogeneous_unwrapped_list(path, ConfigValueType::STRING) end def get_object_list(path) get_homogeneous_wrapped_list(path, ConfigValueType::OBJECT) end def get_homogeneous_wrapped_list(path, expected) l = [] list = get_list(path) list.each do |cv| if !expected.nil? v = DefaultTransformer.transform(cv, expected) end if v.value_type != expected raise ConfigWrongTypeError.with_expected_actual(origin, path, "list of #{ConfigValueType.value_type_name(expected)}", "list of #{ConfigValueType.value_type_name(v.value_type)}") end l << v end l end def has_path_peek(path_expression) path = Path.new_path(path_expression) begin peeked = @object.peek_path(path) rescue Hocon::ConfigError::ConfigNotResolvedError raise Hocon::Impl::ConfigImpl.improved_not_resolved(path, e) end peeked end def has_path?(path_expression) peeked = has_path_peek(path_expression) (not peeked.nil?) && peeked.value_type != ConfigValueType::NULL end def has_path_or_null?(path) peeked = has_path_peek(path) not peeked.nil? end def empty? @object.empty? end def at_key(key) root.at_key(key) end # In java this is an overloaded version of atKey def at_key_with_origin(origin, key) root.at_key_with_origin(origin, key) end def with_only_path(path_expression) path = Path.new_path(path_expression) self.class.new(root.with_only_path(path)) end def without_path(path_expression) path = Path.new_path(path_expression) self.class.new(root.without_path(path)) end def with_value(path_expression, v) path = Path.new_path(path_expression) self.class.new(root.with_value(path, v)) end def to_fallback_value @object end def with_fallback(other) @object.with_fallback(other).to_config end end hocon-1.2.5/lib/hocon/impl/config_node_include.rb0000644000175000017500000000122613154611745021650 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/config_error' require 'hocon/impl/abstract_config_node' require 'hocon/impl/config_node_simple_value' class Hocon::Impl::ConfigNodeInclude include Hocon::Impl::AbstractConfigNode def initialize(children, kind) @children = children @kind = kind end attr_reader :kind, :children def tokens tokens = [] @children.each do |child| tokens += child.tokens end tokens end def name @children.each do |child| if child.is_a?(Hocon::Impl::ConfigNodeSimpleValue) return Hocon::Impl::Tokens.value(child.token).unwrapped end end nil end endhocon-1.2.5/lib/hocon/impl/from_map_mode.rb0000644000175000017500000000073513154611745020503 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/config_error' module Hocon::Impl::FromMapMode KEYS_ARE_PATHS = 0 KEYS_ARE_KEYS = 1 ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError def self.map_mode_name(from_map_mode) case from_map_mode when KEYS_ARE_PATHS then "KEYS_ARE_PATHS" when KEYS_ARE_KEYS then "KEYS_ARE_KEYS" else raise ConfigBugOrBrokenError.new("Unrecognized FromMapMode #{from_map_mode}") end end endhocon-1.2.5/lib/hocon/impl/abstract_config_value.rb0000644000175000017500000002621513154611745022224 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'stringio' require 'hocon/config_render_options' require 'hocon/config_object' require 'hocon/impl/resolve_status' require 'hocon/impl/resolve_result' require 'hocon/impl/unmergeable' require 'hocon/impl/config_impl_util' require 'hocon/config_error' require 'hocon/config_value' ## ## Trying very hard to avoid a parent reference in config values; when you have ## a tree like this, the availability of parent() tends to result in a lot of ## improperly-factored and non-modular code. Please don't add parent(). ## module Hocon::Impl::AbstractConfigValue include Hocon::ConfigValue ConfigImplUtil = Hocon::Impl::ConfigImplUtil ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError ResolveStatus = Hocon::Impl::ResolveStatus def initialize(origin) @origin = origin end attr_reader :origin # This exception means that a value is inherently not resolveable, at the # moment the only known cause is a cycle of substitutions. This is a # checked exception since it's internal to the library and we want to be # sure we handle it before passing it out to public API. This is only # supposed to be thrown by the target of a cyclic reference and it's # supposed to be caught by the ConfigReference looking up that reference, # so it should be impossible for an outermost resolve() to throw this. # # Contrast with ConfigException.NotResolved which just means nobody called # resolve(). class NotPossibleToResolve < Exception def initialize(context) super("was not possible to resolve") @trace_string = context.trace_string end attr_reader :trace_string end # Called only by ResolveContext.resolve # # @param context # state of the current resolve # @param source # where to look up values # @return a new value if there were changes, or this if no changes def resolve_substitutions(context, source) Hocon::Impl::ResolveResult.make(context, self) end def resolve_status Hocon::Impl::ResolveStatus::RESOLVED end def self.replace_child_in_list(list, child, replacement) i = 0 while (i < list.size) && (! list[i].equal?(child)) i += 1 end if (i == list.size) raise ConfigBugOrBrokenError, "tried to replace #{child} which is not in #{list}" end new_stack = list.clone if ! replacement.nil? new_stack[i] = replacement else new_stack.delete(i) end if new_stack.empty? nil else new_stack end end def self.has_descendant_in_list?(list, descendant) list.each do |v| if v.equal?(descendant) return true end end # now the expensive traversal list.each do |v| if v.is_a?(Hocon::Impl::Container) && v.has_descendant?(descendant) return true end end false end # This is used when including one file in another; the included file is # relativized to the path it's included into in the parent file. The point # is that if you include a file at foo.bar in the parent, and the included # file as a substitution ${a.b.c}, the included substitution now needs to # be ${foo.bar.a.b.c} because we resolve substitutions globally only after # parsing everything. # # @param prefix # @return value relativized to the given path or the same value if nothing # to do def relativized(prefix) self end module NoExceptionsModifier def modify_child_may_throw(key_or_nil, v) begin modify_child(key_or_nil, v) rescue Hocon::ConfigError => e raise e end end end def to_fallback_value self end def new_copy(origin) raise ConfigBugOrBrokenError, "subclasses of AbstractConfigValue should provide their own implementation of `new_copy` (#{self.class})" end # this is virtualized rather than a field because only some subclasses # really need to store the boolean, and they may be able to pack it # with another boolean to save space. def ignores_fallbacks? # if we are not resolved, then somewhere in this value there's # a substitution that may need to look at the fallbacks. resolve_status == Hocon::Impl::ResolveStatus::RESOLVED end def with_fallbacks_ignored if ignores_fallbacks? self else raise ConfigBugOrBrokenError, "value class doesn't implement forced fallback-ignoring #{self}" end end # the withFallback() implementation is supposed to avoid calling # mergedWith* if we're ignoring fallbacks. def require_not_ignoring_fallbacks if ignores_fallbacks? raise ConfigBugOrBrokenError, "method should not have been called with ignoresFallbacks=true #{self.class.name}" end end def construct_delayed_merge(origin, stack) # TODO: this might not work because ConfigDelayedMerge inherits # from this class, so we can't `require` it from this file require 'hocon/impl/config_delayed_merge' Hocon::Impl::ConfigDelayedMerge.new(origin, stack) end def merged_stack_with_the_unmergeable(stack, fallback) require_not_ignoring_fallbacks # if we turn out to be an object, and the fallback also does, # then a merge may be required; delay until we resolve. new_stack = stack.clone new_stack.concat(fallback.unmerged_values) # TODO: this might not work because AbstractConfigObject inherits # from this class, so we can't `require` it from this file construct_delayed_merge(Hocon::Impl::AbstractConfigObject.merge_origins(new_stack), new_stack) end def delay_merge(stack, fallback) # if we turn out to be an object, and the fallback also does, # then a merge may be required. # if we contain a substitution, resolving it may need to look # back to the fallback new_stack = stack.clone new_stack << fallback # TODO: this might not work because AbstractConfigObject inherits # from this class, so we can't `require` it from this file construct_delayed_merge(Hocon::Impl::AbstractConfigObject.merge_origins(new_stack), new_stack) end def merged_stack_with_object(stack, fallback) require_not_ignoring_fallbacks # TODO: this might not work because AbstractConfigObject inherits # from this class, so we can't `require` it from this file if self.is_a?(Hocon::Impl::AbstractConfigObject) raise ConfigBugOrBrokenError, "Objects must reimplement merged_with_object" end merged_stack_with_non_object(stack, fallback) end def merged_stack_with_non_object(stack, fallback) require_not_ignoring_fallbacks if resolve_status == ResolveStatus::RESOLVED # falling back to a non-object doesn't merge anything, and also # prohibits merging any objects that we fall back to later. # so we have to switch to ignoresFallbacks mode. with_fallbacks_ignored else # if unresolved we may have to look back to fallbacks as part of # the resolution process, so always delay delay_merge(stack, fallback) end end def merged_with_the_unmergeable(fallback) require_not_ignoring_fallbacks merged_stack_with_the_unmergeable([self], fallback) end def merged_with_object(fallback) require_not_ignoring_fallbacks merged_stack_with_object([self], fallback) end def merged_with_non_object(fallback) require_not_ignoring_fallbacks merged_stack_with_non_object([self], fallback) end def with_origin(origin) if @origin.equal?(origin) self else new_copy(origin) end end def with_fallback(mergeable) if ignores_fallbacks? self else other = mergeable.to_fallback_value if other.is_a?(Hocon::Impl::Unmergeable) merged_with_the_unmergeable(other) # TODO: this probably isn't going to work because AbstractConfigObject inherits # from this class, so we can't `require` it from this file elsif other.is_a?(Hocon::Impl::AbstractConfigObject) merged_with_object(other) else merged_with_non_object(other) end end end def can_equal(other) other.is_a?(Hocon::Impl::AbstractConfigValue) end def ==(other) # note that "origin" is deliberately NOT part of equality if other.is_a?(Hocon::Impl::AbstractConfigValue) can_equal(other) && value_type == other.value_type && ConfigImplUtil.equals_handling_nil?(unwrapped, other.unwrapped) else false end end def hash # note that "origin" is deliberately NOT part of equality unwrapped_value = unwrapped if unwrapped_value.nil? 0 else unwrapped_value.hash end end def to_s sb = StringIO.new render_to_sb(sb, 0, true, nil, Hocon::ConfigRenderOptions.concise) "#{self.class.name.split('::').last}(#{sb.string})" end def inspect to_s end def self.indent(sb, indent_size, options) if options.formatted? remaining = indent_size while remaining > 0 sb << " " remaining -= 1 end end end def render_to_sb(sb, indent, at_root, at_key, options) if !at_key.nil? rendered_key = if options.json? ConfigImplUtil.render_json_string(at_key) else ConfigImplUtil.render_string_unquoted_if_possible(at_key) end sb << rendered_key if options.json? if options.formatted? sb << ": " else sb << ":" end else case options.key_value_separator when :colon sb << ": " else sb << "=" end end end render_value_to_sb(sb, indent, at_root, options) end # to be overridden by subclasses def render_value_to_sb(sb, indent, at_root, options) u = unwrapped sb << u.to_s end def render(options = Hocon::ConfigRenderOptions.defaults) sb = StringIO.new render_to_sb(sb, 0, true, nil, options) # We take a substring that ends at sb.pos, because we've been decrementing # sb.pos at various points in the code as a means to remove characters from # the end of the StringIO sb.string[0, sb.pos] end # toString() is a debugging-oriented string but this is defined # to create a string that would parse back to the value in JSON. # It only works for primitive values (that would be a single # which are auto-converted to strings when concatenating with # other strings or by the DefaultTransformer. def transform_to_string nil end def at_key(key) at_key_with_origin(Hocon::Impl::SimpleConfigOrigin.new_simple("at_key(#{key})"), key) end # Renamed this to be consistent with the other at_key* overloaded methods def at_key_with_origin(origin, key) m = {key=>self} Hocon::Impl::SimpleConfigObject.new(origin, m).to_config end # In java this is an overloaded version of atPath def at_path_with_origin(origin, path) parent = path.parent result = at_key_with_origin(origin, path.last) while not parent.nil? do key = parent.last result = result.at_key_with_origin(origin, key) parent = parent.parent end result end def at_path(path_expression) origin = Hocon::Impl::SimpleConfigOrigin.new_simple("at_path(#{path_expression})") at_path_with_origin(origin, Hocon::Impl::Path.new_path(path_expression)) end end hocon-1.2.5/lib/hocon/impl/resolve_result.rb0000644000175000017500000000135413154611745020752 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/impl' # value is allowed to be null class Hocon::Impl::ResolveResult ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError attr_accessor :context, :value def initialize(context, value) @context = context @value = value end def self.make(context, value) self.new(context, value) end def as_object_result unless @value.is_a?(Hocon::Impl::AbstractConfigObject) raise ConfigBugOrBrokenError.new("Expecting a resolve result to be an object, but it was #{@value}") end self end def as_value_result self end def pop_trace self.class.make(@context.pop_trace, value) end def to_s "ResolveResult(#{@value})" end end hocon-1.2.5/lib/hocon/impl/replaceable_merge_stack.rb0000644000175000017500000000126213154611745022476 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/container' require 'hocon/config_error' # # Implemented by a merge stack (ConfigDelayedMerge, ConfigDelayedMergeObject) # that replaces itself during substitution resolution in order to implement # "look backwards only" semantics. # module Hocon::Impl::ReplaceableMergeStack include Hocon::Impl::Container # # Make a replacement for this object skipping the given number of elements # which are lower in merge priority. # def make_replacement(context, skipping) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ReplaceableMergeStack` must implement `make_replacement` (#{self.class})" end end hocon-1.2.5/lib/hocon/impl/config_node_object.rb0000644000175000017500000002526413154611745021503 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/config_syntax' require 'hocon/impl' require 'hocon/impl/config_node_complex_value' require 'hocon/impl/config_node_field' require 'hocon/impl/config_node_single_token' require 'hocon/impl/tokens' class Hocon::Impl::ConfigNodeObject include Hocon::Impl::ConfigNodeComplexValue ConfigSyntax = Hocon::ConfigSyntax Tokens = Hocon::Impl::Tokens def new_node(nodes) self.class.new(nodes) end def has_value(desired_path) @children.each do |node| if node.is_a?(Hocon::Impl::ConfigNodeField) field = node key = field.path.value if key == desired_path || key.starts_with(desired_path) return true elsif desired_path.starts_with(key) if field.value.is_a?(self.class) obj = field.value remaining_path = desired_path.sub_path_to_end(key.length) if obj.has_value(remaining_path) return true end end end end end false end def change_value_on_path(desired_path, value, flavor) children_copy = @children.clone seen_non_matching = false # Copy the value so we can change it to null but not modify the original parameter value_copy = value i = children_copy.size while i >= 0 do if children_copy[i].is_a?(Hocon::Impl::ConfigNodeSingleToken) t = children_copy[i].token # Ensure that, when we are removing settings in JSON, we don't end up with a trailing comma if flavor.equal?(ConfigSyntax::JSON) && !seen_non_matching && t.equal?(Tokens::COMMA) children_copy.delete_at(i) end i -= 1 next elsif !children_copy[i].is_a?(Hocon::Impl::ConfigNodeField) i -= 1 next end node = children_copy[i] key = node.path.value # Delete all multi-element paths that start with the desired path, since technically they are duplicates if (value_copy.nil? && key == desired_path) || (key.starts_with(desired_path) && !(key == desired_path)) children_copy.delete_at(i) # Remove any whitespace or commas after the deleted setting j = i while j < children_copy.size if children_copy[j].is_a?(Hocon::Impl::ConfigNodeSingleToken) t = children_copy[j].token if Tokens.ignored_whitespace?(t) || t.equal?(Tokens::COMMA) children_copy.delete_at(j) j -= 1 else break end else break end j += 1 end elsif key == desired_path seen_non_matching = true before = i - 1 > 0 ? children_copy[i - 1] : nil if value.is_a?(Hocon::Impl::ConfigNodeComplexValue) && before.is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.ignored_whitespace?(before.token) indented_value = value.indent_text(before) else indented_value = value end children_copy[i] = node.replace_value(indented_value) value_copy = nil elsif desired_path.starts_with(key) seen_non_matching = true if node.value.is_a?(self.class) remaining_path = desired_path.sub_path_to_end(key.length) children_copy[i] = node.replace_value(node.value.change_value_on_path(remaining_path, value_copy, flavor)) if !value_copy.nil? && !(node == @children[i]) value_copy = nil end end else seen_non_matching = true end i -= 1 end self.class.new(children_copy) end def set_value_on_path(desired_path, value, flavor = ConfigSyntax::CONF) path = Hocon::Impl::PathParser.parse_path_node(desired_path, flavor) set_value_on_path_node(path, value, flavor) end def set_value_on_path_node(desired_path, value, flavor) node = change_value_on_path(desired_path.value, value, flavor) # If the desired Path did not exist, add it unless node.has_value(desired_path.value) return node.add_value_on_path(desired_path, value, flavor) end node end def indentation seen_new_line = false indentation = [] if @children.empty? return indentation end @children.each_index do |i| unless seen_new_line if @children[i].is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.newline?(@children[i].token) seen_new_line = true indentation.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens.new_line(nil))) end else if @children[i].is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.ignored_whitespace?(@children[i].token) && i + 1 < @children.size && (@children[i + 1].is_a?(Hocon::Impl::ConfigNodeField) || @children[i + 1].is_a?(Hocon::Impl::ConfigNodeInclude)) # Return the indentation of the first setting on its own line indentation.push(@children[i]) return indentation end end end if indentation.empty? indentation.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens.new_ignored_whitespace(nil, " "))) return indentation else # Calculate the indentation of the ending curly-brace to get the indentation of the root object last = @children[-1] if last.is_a?(Hocon::Impl::ConfigNodeSingleToken) && last.token.equal?(Tokens::CLOSE_CURLY) beforeLast = @children[-2] indent = "" if beforeLast.is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.ignored_whitespace?(beforeLast.token) indent = beforeLast.token.token_text end indent += " " indentation.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens.new_ignored_whitespace(nil, indent))) return indentation end end # The object has no curly braces and is at the root level, so don't indent indentation end def add_value_on_path(desired_path, value, flavor) path = desired_path.value children_copy = @children.clone indentation = indentation().clone # If the value we're inserting is a complex value, we'll need to indent it for insertion if value.is_a?(Hocon::Impl::ConfigNodeComplexValue) && indentation.length > 0 indented_value = value.indent_text(indentation[-1]) else indented_value = value end same_line = !(indentation.length > 0 && indentation[0].is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.newline?(indentation[0].token)) # If the path is of length greater than one, see if the value needs to be added further down if path.length > 1 (0..@children.size - 1).reverse_each do |i| unless @children[i].is_a?(Hocon::Impl::ConfigNodeField) next end node = @children[i] key = node.path.value if path.starts_with(key) && node.value.is_a?(self.class) remaining_path = desired_path.sub_path(key.length) new_value = node.value children_copy[i] = node.replace_value(new_value.add_value_on_path(remaining_path, value, flavor)) return self.class.new(children_copy) end end end # Otherwise, construct the new setting starts_with_brace = @children[0].is_a?(Hocon::Impl::ConfigNodeSingleToken) && @children[0].token.equal?(Tokens::OPEN_CURLY) new_nodes = [] new_nodes += indentation new_nodes.push(desired_path.first) new_nodes.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens::COLON)) new_nodes.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens.new_ignored_whitespace(nil, ' '))) if path.length == 1 new_nodes.push(indented_value) else # If the path is of length greater than one add the required new objects along the path new_object_nodes = [] new_object_nodes.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens::OPEN_CURLY)) if indentation.empty? new_object_nodes.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens.new_line(nil))) end new_object_nodes += indentation new_object_nodes.push(Hocon::Impl::ConfigNodeSingleToken.new(Tokens::CLOSE_CURLY)) new_object = self.class.new(new_object_nodes) new_nodes.push(new_object.add_value_on_path(desired_path.sub_path(1), indented_value, flavor)) end # Combine these two cases so that we only have to iterate once if flavor.equal?(ConfigSyntax::JSON) || starts_with_brace || same_line i = children_copy.size - 1 while i >= 0 # If we are in JSON or are adding a setting on the same line, we need to add a comma to the # last setting if (flavor.equal?(ConfigSyntax::JSON) || same_line) && children_copy[i].is_a?(Hocon::Impl::ConfigNodeField) if i + 1 >= children_copy.size || !(children_copy[i + 1].is_a?(Hocon::Impl::ConfigNodeSingleToken) && children_copy[i + 1].token.equal?(Tokens::COMMA)) children_copy.insert(i + 1, Hocon::Impl::ConfigNodeSingleToken.new(Tokens::COMMA)) break end end # Add the value into the copy of the children map, keeping any whitespace/newlines # before the close curly brace if starts_with_brace && children_copy[i].is_a?(Hocon::Impl::ConfigNodeSingleToken) && children_copy[i].token == Tokens::CLOSE_CURLY previous = children_copy[i - 1] if previous.is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.newline?(previous.token) children_copy.insert(i - 1, Hocon::Impl::ConfigNodeField.new(new_nodes)) i -= 1 elsif previous.is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.ignored_whitespace?(previous.token) before_previous = children_copy[i - 2] if same_line children_copy.insert(i - 1, Hocon::Impl::ConfigNodeField.new(new_nodes)) i -= 1 elsif before_previous.is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.newline?(before_previous.token) children_copy.insert(i - 2, Hocon::Impl::ConfigNodeField.new(new_nodes)) i -= 2 else children_copy.insert(i, Hocon::Impl::ConfigNodeField.new(new_nodes)) end else children_copy.insert(i, Hocon::Impl::ConfigNodeField.new(new_nodes)) end end i -= 1 end end unless starts_with_brace if children_copy[-1].is_a?(Hocon::Impl::ConfigNodeSingleToken) && Tokens.newline?(children_copy[-1].token) children_copy.insert(-2, Hocon::Impl::ConfigNodeField.new(new_nodes)) else children_copy.push(Hocon::Impl::ConfigNodeField.new(new_nodes)) end end self.class.new(children_copy) end def remove_value_on_path(desired_path, flavor) path = Hocon::Impl::PathParser.parse_path_node(desired_path, flavor).value change_value_on_path(path, nil, flavor) end end hocon-1.2.5/lib/hocon/impl/config_parser.rb0000644000175000017500000003263513154611745020524 0ustar apoikosapoikos# encoding: utf-8 require 'stringio' require 'hocon/impl' require 'hocon/impl/path_builder' require 'hocon/config_syntax' require 'hocon/impl/config_string' require 'hocon/impl/config_concatenation' require 'hocon/config_error' require 'hocon/impl/simple_config_list' require 'hocon/impl/simple_config_object' require 'hocon/impl/path' require 'hocon/impl/url' require 'hocon/impl/config_reference' require 'hocon/impl/substitution_expression' require 'hocon/impl/config_node_simple_value' require 'hocon/impl/config_node_object' require 'hocon/impl/config_node_array' require 'hocon/impl/config_node_concatenation' require 'hocon/impl/config_include_kind' class Hocon::Impl::ConfigParser ConfigSyntax = Hocon::ConfigSyntax ConfigConcatenation = Hocon::Impl::ConfigConcatenation ConfigReference = Hocon::Impl::ConfigReference ConfigParseError = Hocon::ConfigError::ConfigParseError ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError SimpleConfigObject = Hocon::Impl::SimpleConfigObject SimpleConfigList = Hocon::Impl::SimpleConfigList Path = Hocon::Impl::Path ConfigIncludeKind = Hocon::Impl::ConfigIncludeKind ConfigNodeInclude = Hocon::Impl::ConfigNodeInclude ConfigNodeComment = Hocon::Impl::ConfigNodeComment ConfigNodeSingleToken = Hocon::Impl::ConfigNodeSingleToken Tokens = Hocon::Impl::Tokens def self.parse(document, origin, options, include_context) context = Hocon::Impl::ConfigParser::ParseContext.new( options.syntax, origin, document, Hocon::Impl::SimpleIncluder.make_full(options.includer), include_context) context.parse end class ParseContext def initialize(flavor, origin, document, includer, include_context) @line_number = 1 @document = document @flavor = flavor @base_origin = origin @includer = includer @include_context = include_context @path_stack = [] @array_count = 0 end # merge a bunch of adjacent values into one # value; change unquoted text into a string # value. def parse_concatenation(n) # this trick is not done in JSON if @flavor.equal?(ConfigSyntax::JSON) raise ConfigBugOrBrokenError, "Found a concatenation node in JSON" end values = [] n.children.each do |node| if node.is_a?(Hocon::Impl::AbstractConfigNodeValue) v = parse_value(node, nil) values.push(v) end end ConfigConcatenation.concatenate(values) end def line_origin @base_origin.with_line_number(@line_number) end def parse_error(message, cause = nil) ConfigParseError.new(line_origin, message, cause) end def full_current_path # pathStack has top of stack at front if @path_stack.empty? raise ConfigBugOrBrokenError, "Bug in parser; tried to get current path when at root" else Path.from_path_list(@path_stack.reverse) end end def parse_value(n, comments) starting_array_count = @array_count if n.is_a?(Hocon::Impl::ConfigNodeSimpleValue) v = n.value elsif n.is_a?(Hocon::Impl::ConfigNodeObject) v = parse_object(n) elsif n.is_a?(Hocon::Impl::ConfigNodeArray) v = parse_array(n) elsif n.is_a?(Hocon::Impl::ConfigNodeConcatenation) v = parse_concatenation(n) else raise parse_error("Expecting a value but got wrong node type: #{n.class}") end unless comments.nil? || comments.empty? v = v.with_origin(v.origin.prepend_comments(comments.clone)) comments.clear end unless @array_count == starting_array_count raise ConfigBugOrBrokenError, "Bug in config parser: unbalanced array count" end v end def create_value_under_path(path, value) # for path foo.bar, we are creating # { "foo" : { "bar" : value } } keys = [] key = path.first remaining = path.remainder until key.nil? keys.push(key) if remaining.nil? break else key = remaining.first remaining = remaining.remainder end end # the setComments(null) is to ensure comments are only # on the exact leaf node they apply to. # a comment before "foo.bar" applies to the full setting # "foo.bar" not also to "foo" keys = keys.reverse # this is just a ruby means for doing first/rest deepest, *rest = *keys o = SimpleConfigObject.new(value.origin.with_comments(nil), {deepest => value}) while !rest.empty? deepest, *rest = *rest o = SimpleConfigObject.new(value.origin.with_comments(nil), {deepest => o}) end o end def parse_include(values, n) case n.kind when ConfigIncludeKind::URL url = nil begin url = Hocon::Impl::Url.new(n.name) rescue Hocon::Impl::Url::MalformedUrlError => e raise parse_error("include url() specifies an invalid URL: #{n.name}", e) end obj = @includer.include_url(@include_context, url) when ConfigIncludeKind::FILE obj = @includer.include_file(@include_context, n.name) when ConfigIncludeKind::CLASSPATH obj = @includer.include_resources(@include_context, n.name) when ConfigIncludeKind::HEURISTIC obj = @includer.include(@include_context, n.name) else raise ConfigBugOrBrokenError, "should not be reached" end # we really should make this work, but for now throwing an # exception is better than producing an incorrect result. # See https://github.com/typesafehub/config/issues/160 if @array_count > 0 && (obj.resolve_status != Hocon::Impl::ResolveStatus::RESOLVED) raise parse_error("Due to current limitations of the config parser, when an include statement is nested inside a list value, " + "${} substitutions inside the included file cannot be resolved correctly. Either move the include outside of the list value or " + "remove the ${} statements from the included file.") end if !(@path_stack.empty?) prefix = full_current_path obj = obj.relativized(prefix) end obj.key_set.each do |key| v = obj.get(key) existing = values[key] if !(existing.nil?) values[key] = v.with_fallback(existing) else values[key] = v end end end def parse_object(n) values = Hash.new object_origin = line_origin last_was_new_line = false nodes = n.children.clone comments = [] i = 0 while i < nodes.size node = nodes[i] if node.is_a?(ConfigNodeComment) last_was_new_line = false comments.push(node.comment_text) elsif node.is_a?(ConfigNodeSingleToken) && Tokens.newline?(node.token) @line_number += 1 if last_was_new_line # Drop all comments if there was a blank line and start a new comment block comments.clear end last_was_new_line = true elsif !@flavor.equal?(ConfigSyntax::JSON) && node.is_a?(ConfigNodeInclude) parse_include(values, node) last_was_new_line = false elsif node.is_a?(Hocon::Impl::ConfigNodeField) last_was_new_line = false path = node.path.value comments += node.comments # path must be on-stack while we parse the value # Note that, in upstream, pathStack is a LinkedList, so use unshift instead of push @path_stack.unshift(path) if node.separator.equal?(Tokens::PLUS_EQUALS) # we really should make this work, but for now throwing # an exception is better than producing an incorrect # result. See # https://github.com/typesafehub/config/issues/160 if @array_count > 0 raise parse_error("Due to current limitations of the config parser, += does not work nested inside a list. " + "+= expands to a ${} substitution and the path in ${} cannot currently refer to list elements. " + "You might be able to move the += outside of the list and then refer to it from inside the list with ${}.") end # because we will put it in an array after the fact so # we want this to be incremented during the parseValue # below in order to throw the above exception. @array_count += 1 end value_node = node.value # comments from the key token go to the value token new_value = parse_value(value_node, comments) if node.separator.equal?(Tokens::PLUS_EQUALS) @array_count -= 1 concat = [] previous_ref = ConfigReference.new(new_value.origin, Hocon::Impl::SubstitutionExpression.new(full_current_path, true)) list = SimpleConfigList.new(new_value.origin, [new_value]) concat << previous_ref concat << list new_value = ConfigConcatenation.concatenate(concat) end # Grab any trailing comments on the same line if i < nodes.size - 1 i += 1 while i < nodes.size if nodes[i].is_a?(ConfigNodeComment) comment = nodes[i] new_value = new_value.with_origin(new_value.origin.append_comments([comment.comment_text])) break elsif nodes[i].is_a?(ConfigNodeSingleToken) curr = nodes[i] if curr.token.equal?(Tokens::COMMA) || Tokens.ignored_whitespace?(curr.token) # keep searching, as there could still be a comment else i -= 1 break end else i -= 1 break end i += 1 end end @path_stack.shift key = path.first remaining = path.remainder if remaining.nil? existing = values[key] unless existing.nil? # In strict JSON, dups should be an error; while in # our custom config language, they should be merged # if the value is an object (or substitution that # could become an object). if @flavor.equal?(ConfigSyntax::JSON) raise parse_error("JSON does not allow duplicate fields: '#{key}'" + " was already seen at #{existing.origin().description()}") else new_value = new_value.with_fallback(existing) end end values[key] = new_value else if @flavor == ConfigSyntax::JSON raise Hocon::ConfigError::ConfigBugOrBrokenError, "somehow got multi-element path in JSON mode" end obj = create_value_under_path(remaining, new_value) existing = values[key] if !existing.nil? obj = obj.with_fallback(existing) end values[key] = obj end end i += 1 end SimpleConfigObject.new(object_origin, values) end def parse_array(n) @array_count += 1 array_origin = line_origin values = [] last_was_new_line = false comments = [] v = nil n.children.each do |node| if node.is_a?(ConfigNodeComment) comments << node.comment_text last_was_new_line = false elsif node.is_a?(ConfigNodeSingleToken) && Tokens.newline?(node.token) @line_number += 1 if last_was_new_line && v.nil? comments.clear elsif !v.nil? values << v.with_origin(v.origin.append_comments(comments.clone)) comments.clear v = nil end last_was_new_line = true elsif node.is_a?(Hocon::Impl::AbstractConfigNodeValue) last_was_new_line = false unless v.nil? values << v.with_origin(v.origin.append_comments(comments.clone)) comments.clear end v = parse_value(node, comments) end end # There shouldn't be any comments at this point, but add them just in case unless v.nil? values << v.with_origin(v.origin.append_comments(comments.clone)) end @array_count -= 1 SimpleConfigList.new(array_origin, values) end def parse result = nil comments = [] last_was_new_line = false @document.children.each do |node| if node.is_a?(ConfigNodeComment) comments << node.comment_text last_was_new_line = false elsif node.is_a?(ConfigNodeSingleToken) t = node.token if Tokens.newline?(t) @line_number += 1 if last_was_new_line && result.nil? comments.clear elsif !result.nil? result = result.with_origin(result.origin.append_comments(comments.clone)) comments.clear break end last_was_new_line = true end elsif node.is_a?(Hocon::Impl::ConfigNodeComplexValue) result = parse_value(node, comments) last_was_new_line = false end end result end end end hocon-1.2.5/lib/hocon/impl/config_double.rb0000644000175000017500000000130113154611745020464 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/config_number' class Hocon::Impl::ConfigDouble < Hocon::Impl::ConfigNumber def initialize(origin, value, original_text) super(origin, original_text) @value = value end attr_reader :value def value_type Hocon::ConfigValueType::NUMBER end def unwrapped @value end def transform_to_string s = super if s.nil? @value.to_s else s end end def long_value @value.to_i end def double_value @value end def new_copy(origin) self.class.new(origin, @value, original_text) end # NOTE: skipping `writeReplace` from upstream, because it involves serialization end hocon-1.2.5/lib/hocon/impl/resolve_memos.rb0000644000175000017500000000045513154611745020555 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/impl' class Hocon::Impl::ResolveMemos def initialize(memos = {}) @memos = memos end def get(key) @memos[key] end def put(key, value) copy = @memos.clone copy[key] = value Hocon::Impl::ResolveMemos.new(copy) end end hocon-1.2.5/lib/hocon/impl/full_includer.rb0000644000175000017500000000011413154611745020515 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' class Hocon::Impl::FullIncluder endhocon-1.2.5/lib/hocon/impl/tokenizer.rb0000644000175000017500000004246213154611745017714 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/config_impl_util' require 'hocon/impl/tokens' require 'hocon/config_error' require 'stringio' require 'forwardable' class Hocon::Impl::Tokenizer Tokens = Hocon::Impl::Tokens ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError class TokenizerProblemError < StandardError def initialize(problem) @problem = problem end def problem @problem end end def self.as_string(codepoint) if codepoint == "\n" "newline" elsif codepoint == "\t" "tab" elsif codepoint == -1 "end of file" elsif codepoint =~ /[[:cntrl:]]/ "control character 0x%x" % codepoint else "%c" % codepoint end end # Tokenizes a Reader. Does not close the reader; you have to arrange to do # that after you're done with the returned iterator. def self.tokenize(origin, input, syntax) TokenIterator.new(origin, input, syntax != Hocon::ConfigSyntax::JSON) end def self.render(tokens) rendered_text = "" while (t = tokens.next) rendered_text << t.token_text end rendered_text end class TokenIterator class WhitespaceSaver def initialize @whitespace = StringIO.new @last_token_was_simple_value = false end def add(c) @whitespace << c end def check(t, base_origin, line_number) if TokenIterator.simple_value?(t) next_is_a_simple_value(base_origin, line_number) else next_is_not_a_simple_value(base_origin, line_number) end end private # called if the next token is not a simple value; # discards any whitespace we were saving between # simple values. def next_is_not_a_simple_value(base_origin, line_number) @last_token_was_simple_value = false create_whitespace_token_from_saver(base_origin, line_number) end # called if the next token IS a simple value, # so creates a whitespace token if the previous # token also was. def next_is_a_simple_value(base_origin, line_number) t = create_whitespace_token_from_saver(base_origin, line_number) @last_token_was_simple_value = true unless @last_token_was_simple_value t end def create_whitespace_token_from_saver(base_origin, line_number) return nil unless @whitespace.length > 0 if (@last_token_was_simple_value) t = Tokens.new_unquoted_text( Hocon::Impl::Tokenizer::TokenIterator.line_origin(base_origin, line_number), String.new(@whitespace.string) ) else t = Tokens.new_ignored_whitespace( Hocon::Impl::Tokenizer::TokenIterator.line_origin(base_origin, line_number), String.new(@whitespace.string) ) end @whitespace.string = "" t end end def initialize(origin, input, allow_comments) @origin = origin @input = input @allow_comments = allow_comments @buffer = [] @line_number = 1 @line_origin = @origin.with_line_number(@line_number) @tokens = [] @tokens << Tokens::START @whitespace_saver = WhitespaceSaver.new end # this should ONLY be called from nextCharSkippingComments # or when inside a quoted string, or when parsing a sequence # like ${ or +=, everything else should use # nextCharSkippingComments(). def next_char_raw if @buffer.empty? begin @input.readchar.chr rescue EOFError -1 end else @buffer.pop end end def put_back(c) if @buffer.length > 2 raise ConfigBugOrBrokenError, "bug: putBack() three times, undesirable look-ahead" end @buffer.push(c) end def self.whitespace?(c) Hocon::Impl::ConfigImplUtil.whitespace?(c) end def self.whitespace_not_newline?(c) (c != "\n") and (Hocon::Impl::ConfigImplUtil.whitespace?(c)) end def start_of_comment?(c) if c == -1 false else if @allow_comments if c == '#' true elsif c == '/' maybe_second_slash = next_char_raw # we want to predictably NOT consume any chars put_back(maybe_second_slash) if maybe_second_slash == '/' true else false end end else false end end end # get next char, skipping non-newline whitespace def next_char_after_whitespace(saver) while true c = next_char_raw if c == -1 return -1 else if self.class.whitespace_not_newline?(c) saver.add(c) else return c end end end end def self.problem(origin, what, message, suggest_quotes, cause) if what.nil? || message.nil? raise ConfigBugOrBrokenError.new("internal error, creating bad TokenizerProblemError") end TokenizerProblemError.new(Tokens.new_problem(origin, what, message, suggest_quotes, cause)) end def self.line_origin(base_origin, line_number) base_origin.with_line_number(line_number) end # ONE char has always been consumed, either the # or the first /, but not # both slashes def pull_comment(first_char) double_slash = false if first_char == '/' discard = next_char_raw if discard != '/' raise ConfigBugOrBrokenError, "called pullComment but // not seen" end double_slash = true end io = StringIO.new while true c = next_char_raw if (c == -1) || (c == "\n") put_back(c) if (double_slash) return Tokens.new_comment_double_slash(@line_origin, io.string) else return Tokens.new_comment_hash(@line_origin, io.string) end else io << c end end end # chars JSON allows a number to start with FIRST_NUMBER_CHARS = "0123456789-" # chars JSON allows to be part of a number NUMBER_CHARS = "0123456789eE+-." # chars that stop an unquoted string NOT_IN_UNQUOTED_TEXT = "$\"{}[]:=,+#`^?!@*&\\" # The rules here are intended to maximize convenience while # avoiding confusion with real valid JSON. Basically anything # that parses as JSON is treated the JSON way and otherwise # we assume it's a string and let the parser sort it out. def pull_unquoted_text origin = @line_origin io = StringIO.new c = next_char_raw while true if (c == -1) or (NOT_IN_UNQUOTED_TEXT.index(c)) or (self.class.whitespace?(c)) or (start_of_comment?(c)) break else io << c end # we parse true/false/null tokens as such no matter # what is after them, as long as they are at the # start of the unquoted token. if io.length == 4 if io.string == "true" return Tokens.new_boolean(origin, true) elsif io.string == "null" return Tokens.new_null(origin) end elsif io.length == 5 if io.string == "false" return Tokens.new_boolean(origin, false) end end c = next_char_raw end # put back the char that ended the unquoted text put_back(c) Tokens.new_unquoted_text(origin, io.string) end def pull_number(first_char) sb = StringIO.new sb << first_char contained_decimal_or_e = false c = next_char_raw while (c != -1) && (NUMBER_CHARS.index(c)) if (c == '.') || (c == 'e') || (c == 'E') contained_decimal_or_e = true end sb << c c = next_char_raw end # the last character we looked at wasn't part of the number, put it # back put_back(c) s = sb.string begin if contained_decimal_or_e # force floating point representation Tokens.new_double(@line_origin, Float(s), s) else Tokens.new_long(@line_origin, Integer(s), s) end rescue ArgumentError => e if e.message =~ /^invalid value for (Float|Integer)\(\)/ # not a number after all, see if it's an unquoted string. s.each_char do |u| if NOT_IN_UNQUOTED_TEXT.index(u) raise self.class.problem(@line_origin, u, "Reserved character '#{u}'" + "is not allowed outside quotes", true, nil) end end # no evil chars so we just decide this was a string and # not a number. Tokens.new_unquoted_text(@line_origin, s) else raise e end end end def pull_escape_sequence(sb, sb_orig) escaped = next_char_raw if escaped == -1 error_msg = "End of input but backslash in string had nothing after it" raise self.class.problem(@line_origin, "", error_msg, false, nil) end # This is needed so we return the unescaped escape characters back out when rendering # the token sb_orig << "\\" << escaped case escaped when "\"" sb << "\"" when "\\" sb << "\\" when "/" sb << "/" when "b" sb << "\b" when "f" sb << "\f" when "n" sb << "\n" when "r" sb << "\r" when "t" sb << "\t" when "u" codepoint = "" # Grab the 4 hex chars for the unicode character 4.times do c = next_char_raw if c == -1 error_msg = "End of input but expecting 4 hex digits for \\uXXXX escape" raise self.class.problem(@line_origin, c, error_msg, false, nil) end codepoint << c end sb_orig << codepoint # Convert codepoint to a unicode character packed = [codepoint.hex].pack("U") if packed == "_" raise self.class.problem(@line_origin, codepoint, "Malformed hex digits after \\u escape in string: '#{codepoint}'", false, nil) end sb << packed else error_msg = "backslash followed by '#{escaped}', this is not a valid escape sequence (quoted strings use JSON escaping, so use double-backslash \\ for literal backslash)" raise self.class.problem(Hocon::Impl::Tokenizer.as_string(escaped), "", error_msg, false, nil) end end def append_triple_quoted_string(sb, sb_orig) # we are after the opening triple quote and need to consume the # close triple consecutive_quotes = 0 while true c = next_char_raw if c == '"' consecutive_quotes += 1 elsif consecutive_quotes >= 3 # the last three quotes end the string and the other kept. sb.string = sb.string[0...-3] put_back c break else consecutive_quotes = 0 if c == -1 error_msg = "End of input but triple-quoted string was still open" raise self.class.problem(@line_origin, c, error_msg, false, nil) elsif c == "\n" # keep the line number accurate @line_number += 1 @line_origin = @origin.with_line_number(@line_number) end end sb << c sb_orig << c end end def pull_quoted_string # the open quote has already been consumed sb = StringIO.new # We need a second StringIO to keep track of escape characters. # We want to return them exactly as they appeared in the original text, # which means we will need a new StringIO to escape escape characters # so we can also keep the actual value of the string. This is gross. sb_orig = StringIO.new sb_orig << '"' c = "" while c != '"' c = next_char_raw if c == -1 raise self.class.problem(@line_origin, c, "End of input but string quote was still open", false, nil) end if c == "\\" pull_escape_sequence(sb, sb_orig) elsif c == '"' sb_orig << c # done! elsif c =~ /[[:cntrl:]]/ raise self.class.problem(@line_origin, c, "JSON does not allow unescaped #{c}" + " in quoted strings, use a backslash escape", false, nil) else sb << c sb_orig << c end end # maybe switch to triple-quoted string, sort of hacky... if sb.length == 0 third = next_char_raw if third == '"' sb_orig << third append_triple_quoted_string(sb, sb_orig) else put_back(third) end end Tokens.new_string(@line_origin, sb.string, sb_orig.string) end def pull_plus_equals # the initial '+' has already been consumed c = next_char_raw unless c == '=' error_msg = "'+' not followed by =, '#{c}' not allowed after '+'" raise self.class.problem(@line_origin, c, error_msg, true, nil) # true = suggest quotes end Tokens::PLUS_EQUALS end def pull_substitution # the initial '$' has already been consumed c = next_char_raw if c != '{' error_msg = "'$' not followed by {, '#{c}' not allowed after '$'" raise self.class.problem(@line_origin, c, error_msg, true, nil) # true = suggest quotes end optional = false c = next_char_raw if c == '?' optional = true else put_back(c) end saver = WhitespaceSaver.new expression = [] while true t = pull_next_token(saver) # note that we avoid validating the allowed tokens inside # the substitution here; we even allow nested substitutions # in the tokenizer. The parser sorts it out. if t == Tokens::CLOSE_CURLY # end the loop, done! break elsif t == Tokens::EOF raise self.class.problem(@line_origin, t, "Substitution ${ was not closed with a }", false, nil) else whitespace = saver.check(t, @line_origin, @line_number) unless whitespace.nil? expression << whitespace end expression << t end end Tokens.new_substitution(@line_origin, optional, expression) end def pull_next_token(saver) c = next_char_after_whitespace(saver) if c == -1 Tokens::EOF elsif c == "\n" # newline tokens have the just-ended line number line = Tokens.new_line(@line_origin) @line_number += 1 @line_origin = @origin.with_line_number(@line_number) line else t = nil if start_of_comment?(c) t = pull_comment(c) else t = case c when '"' then pull_quoted_string when '$' then pull_substitution when ':' then Tokens::COLON when ',' then Tokens::COMMA when '=' then Tokens::EQUALS when '{' then Tokens::OPEN_CURLY when '}' then Tokens::CLOSE_CURLY when '[' then Tokens::OPEN_SQUARE when ']' then Tokens::CLOSE_SQUARE when '+' then pull_plus_equals else nil end if t.nil? if FIRST_NUMBER_CHARS.index(c) t = pull_number(c) elsif NOT_IN_UNQUOTED_TEXT.index(c) raise self.class.problem(@line_origin, c, "Reserved character '#{c}' is not allowed outside quotes", true, nil) else put_back(c) t = pull_unquoted_text end end end if t.nil? raise ConfigBugOrBrokenError, "bug: failed to generate next token" end t end end def self.simple_value?(t) Tokens.substitution?(t) || Tokens.unquoted_text?(t) || Tokens.value?(t) end def queue_next_token t = pull_next_token(@whitespace_saver) whitespace = @whitespace_saver.check(t, @origin, @line_number) if whitespace @tokens.push(whitespace) end @tokens.push(t) end def has_next? !@tokens.empty? end def next t = @tokens.shift if (@tokens.empty?) and (t != Tokens::EOF) begin queue_next_token rescue TokenizerProblemError => e @tokens.push(e.problem) end if @tokens.empty? raise ConfigBugOrBrokenError, "bug: tokens queue should not be empty here" end end t end def remove raise ConfigBugOrBrokenError, "Does not make sense to remove items from token stream" end def each while has_next? # Have to use self.next instead of next because next is a reserved word yield self.next end end def map token_list = [] each do |token| # yield token to calling method, append whatever is returned from the # map block to token_list token_list << yield(token) end token_list end def to_list # Return array of tokens from the iterator self.map { |token| token } end end end hocon-1.2.5/lib/hocon/impl/abstract_config_node_value.rb0000644000175000017500000000063113154611745023223 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/abstract_config_node' # This essentially exists in the upstream so we can ensure only certain types of # config nodes can be passed into some methods. That's not a problem in Ruby, so this is # unnecessary, but it seems best to keep it around for consistency module Hocon::Impl::AbstractConfigNodeValue include Hocon::Impl::AbstractConfigNode endhocon-1.2.5/lib/hocon/impl/config_boolean.rb0000644000175000017500000000074613154611745020645 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/abstract_config_value' class Hocon::Impl::ConfigBoolean include Hocon::Impl::AbstractConfigValue def initialize(origin, value) super(origin) @value = value end attr_reader :value def value_type Hocon::ConfigValueType::BOOLEAN end def unwrapped @value end def transform_to_string @value.to_s end def new_copy(origin) Hocon::Impl::ConfigBoolean.new(origin, @value) end end hocon-1.2.5/lib/hocon/impl/simple_config_list.rb0000644000175000017500000002024713154611745021550 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/resolve_status' require 'hocon/config_value_type' require 'hocon/config_error' require 'hocon/impl/abstract_config_object' require 'forwardable' require 'hocon/impl/unsupported_operation_error' require 'hocon/impl/resolve_result' require 'hocon/impl/container' require 'hocon/config_list' class Hocon::Impl::SimpleConfigList include Hocon::Impl::Container include Hocon::ConfigList include Hocon::Impl::AbstractConfigValue extend Forwardable ResolveStatus = Hocon::Impl::ResolveStatus ResolveResult = Hocon::Impl::ResolveResult ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError def initialize(origin, value, status = ResolveStatus.from_values(value)) super(origin) @value = value @resolved = (status == ResolveStatus::RESOLVED) # kind of an expensive debug check (makes this constructor pointless) if status != ResolveStatus.from_values(value) raise ConfigBugOrBrokenError, "SimpleConfigList created with wrong resolve status: #{self}" end end attr_reader :value def_delegators :@value, :[], :include?, :empty?, :size, :index, :rindex, :each, :map def value_type Hocon::ConfigValueType::LIST end def unwrapped @value.map { |v| v.unwrapped } end def resolve_status ResolveStatus.from_boolean(@resolved) end def replace_child(child, replacement) new_list = replace_child_in_list(@value, child, replacement) if new_list.nil? nil else # we use the constructor flavor that will recompute the resolve status SimpleConfigList.new(origin, new_list) end end def has_descendant?(descendant) Hocon::Impl::AbstractConfigValue.has_descendant_in_list?(@value, descendant) end def modify(modifier, new_resolve_status) begin modify_may_throw(modifier, new_resolve_status) rescue Hocon::ConfigError => e raise e end end def modify_may_throw(modifier, new_resolve_status) # lazy-create for optimization changed = nil i = 0 @value.each { |v| modified = modifier.modify_child_may_throw(nil, v) # lazy-create the new list if required if changed == nil && !modified.equal?(v) changed = [] j = 0 while j < i changed << @value[j] j += 1 end end # once the new list is created, all elements # have to go in it.if modifyChild returned # null, we drop that element. if changed != nil && modified != nil changed << modified end i += 1 } if changed != nil if new_resolve_status != nil self.class.new(origin, changed, new_resolve_status) else self.class.new(origin, changed) end else self end end class ResolveModifier attr_reader :context, :source def initialize(context, source) @context = context @source = source end def modify_child_may_throw(key, v) result = @context.resolve(v, source) @context = result.context result.value end end def resolve_substitutions(context, source) if @resolved return Hocon::Impl::ResolveResult.make(context, self) end if context.is_restricted_to_child # if a list restricts to a child path, then it has no child paths, # so nothing to do. Hocon::Impl::ResolveResult.make(context, self) else begin modifier = ResolveModifier.new(context, source.push_parent(self)) value = modify_may_throw(modifier, context.options.allow_unresolved ? nil : ResolveStatus::RESOLVED) Hocon::Impl::ResolveResult.make(modifier.context, value) rescue NotPossibleToResolve => e raise e rescue RuntimeError => e raise e rescue Exception => e raise ConfigBugOrBrokenError.new("unexpected exception", e) end end end def relativized(prefix) modifier = Class.new do include Hocon::Impl::AbstractConfigValue::NoExceptionsModifier # prefix isn't in scope inside of a def, but it is in scope inside of Class.new # so manually define a method that has access to prefix # I feel dirty define_method(:modify_child) do |key, v| v.relativized(prefix) end end modify(modifier.new, resolve_status) end def can_equal(other) other.is_a?(self.class) end def ==(other) # note that "origin" is deliberately NOT part of equality if other.is_a?(self.class) # optimization to avoid unwrapped() for two ConfigList can_equal(other) && (value.equal?(other.value) || (value == other.value)) else false end end def hash # note that "origin" is deliberately NOT part of equality value.hash end def render_value_to_sb(sb, indent_size, at_root, options) if @value.empty? sb << "[]" else sb << "[" if options.formatted? sb << "\n" end @value.each do |v| if options.origin_comments? lines = v.origin.description.split("\n") lines.each do |l| Hocon::Impl::AbstractConfigValue.indent(sb, indent_size + 1, options) sb << "# " sb << l sb << "\n" end end if options.comments? v.origin.comments.each do |comment| sb << "# " sb << comment sb << "\n" end end Hocon::Impl::AbstractConfigValue.indent(sb, indent_size + 1, options) v.render_value_to_sb(sb, indent_size + 1, at_root, options) sb << "," if options.formatted? sb << "\n" end end # couldn't figure out a better way to chop characters off of the end of # the StringIO. This relies on making sure that, prior to returning the # final string, we take a substring that ends at sb.pos. sb.pos = sb.pos - 1 # chop or newline if options.formatted? sb.pos = sb.pos - 1 # also chop comma sb << "\n" Hocon::Impl::AbstractConfigValue.indent(sb, indent_size, options) end sb << "]" end end def contains?(o) value.include?(o) end def include_all?(value_list) value_list.all? { |v| @value.include?(v)} end def contains_all?(c) include_all?(c) end def get(index) value[index] end def index_of(o) value.index(o) end def is_empty empty? end # Skipping upstream definition of "iterator", because that's not really a thing # in Ruby. def last_index_of(o) value.rindex(o) end # skipping upstream definitions of "wrapListIterator", "listIterator", and # "listIterator(int)", because those don't really apply in Ruby. def sub_list(from_index, to_index) value[from_index..to_index] end def to_array value end def we_are_immutable(method) Hocon::Impl::UnsupportedOperationError.new("ConfigList is immutable, you can't call List. '#{method}'") end def add(e) raise we_are_immutable("add") end def add_at(index, element) raise we_are_immutable("add_at") end def add_all(c) raise we_are_immutable("add_all") end def add_all_at(index, c) raise we_are_immutable("add_all_at") end def clear raise we_are_immutable("clear") end def remove(o) raise we_are_immutable("remove") end def remove_at(i) raise we_are_immutable("remove_at") end def delete(o) raise we_are_immutable("delete") end def remove_all(c) raise we_are_immutable("remove_all") end def retain_all(c) raise we_are_immutable("retain_all") end def set(index, element) raise we_are_immutable("set") end def []=(index, element) raise we_are_immutable("[]=") end def push(e) raise we_are_immutable("push") end def <<(e) raise we_are_immutable("<<") end def new_copy(origin) Hocon::Impl::SimpleConfigList.new(origin, @value) end def concatenate(other) combined_origin = Hocon::Impl::SimpleConfigOrigin.merge_two_origins(origin, other.origin) combined = value + other.value Hocon::Impl::SimpleConfigList.new(combined_origin, combined) end # Skipping upstream "writeReplace" until we see that we need it for something def with_origin(origin) super(origin) end end hocon-1.2.5/lib/hocon/impl/config_node_complex_value.rb0000644000175000017500000000315313154611745023071 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/abstract_config_node_value' require 'hocon/impl/config_node_field' require 'hocon/impl/config_node_include' require 'hocon/impl/config_node_single_token' require 'hocon/impl/tokens' require 'hocon/config_error' module Hocon::Impl::ConfigNodeComplexValue include Hocon::Impl::AbstractConfigNodeValue def initialize(children) @children = children end attr_reader :children def tokens tokens = [] @children.each do |child| tokens += child.tokens end tokens end def indent_text(indentation) children_copy = @children.clone i = 0 while i < children_copy.size child = children_copy[i] if child.is_a?(Hocon::Impl::ConfigNodeSingleToken) && Hocon::Impl::Tokens.newline?(child.token) children_copy.insert(i + 1, indentation) i += 1 elsif child.is_a?(Hocon::Impl::ConfigNodeField) value = child.value if value.is_a?(Hocon::Impl::ConfigNodeComplexValue) children_copy[i] = child.replace_value(value.indent_text(indentation)) end elsif child.is_a?(Hocon::Impl::ConfigNodeComplexValue) children_copy[i] = child.indent_text(indentation) end i += 1 end new_node(children_copy) end # This method will just call into the object's constructor, but it's needed # for use in the indentText() method so we can avoid a gross if/else statement # checking the type of this def new_node(nodes) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of ConfigNodeComplexValue should override `new_node` (#{self.class})" end endhocon-1.2.5/lib/hocon/impl/config_node_path.rb0000644000175000017500000000215413154611745021162 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/tokens' require 'hocon/impl/abstract_config_node' class Hocon::Impl::ConfigNodePath include Hocon::Impl::AbstractConfigNode Tokens = Hocon::Impl::Tokens def initialize(path, tokens) @path = path @tokens = tokens end attr_reader :tokens def value @path end def sub_path(to_remove) period_count = 0 tokens_copy = tokens.clone (0..tokens_copy.size - 1).each do |i| if Tokens.unquoted_text?(tokens_copy[i]) && tokens_copy[i].token_text == "." period_count += 1 end if period_count == to_remove return self.class.new(@path.sub_path_to_end(to_remove), tokens_copy[i + 1..tokens_copy.size]) end end raise ConfigBugOrBrokenError, "Tried to remove too many elements from a Path node" end def first tokens_copy = tokens.clone (0..tokens_copy.size - 1).each do |i| if Tokens.unquoted_text?(tokens_copy[i]) && tokens_copy[i].token_text == "." return self.class.new(@path.sub_path(0, 1), tokens_copy[0, i]) end end self end end hocon-1.2.5/lib/hocon/impl/parseable.rb0000644000175000017500000004062613154611745017640 0ustar apoikosapoikos# encoding: utf-8 require 'stringio' require 'pathname' require 'hocon/impl' require 'hocon/config_error' require 'hocon/config_syntax' require 'hocon/config_value_type' require 'hocon/impl/config_impl' require 'hocon/impl/simple_include_context' require 'hocon/impl/simple_config_object' require 'hocon/impl/simple_config_origin' require 'hocon/impl/tokenizer' require 'hocon/impl/config_parser' require 'hocon/config_parseable' require 'hocon/impl/config_document_parser' require 'hocon/impl/simple_config_document' # # Internal implementation detail, not ABI stable, do not touch. # For use only by the {@link com.typesafe.config} package. # The point of this class is to avoid "propagating" each # overload on "thing which can be parsed" through multiple # interfaces. Most interfaces can have just one overload that # takes a Parseable. Also it's used as an abstract "resource # handle" in the ConfigIncluder interface. # class Hocon::Impl::Parseable include Hocon::ConfigParseable # Internal implementation detail, not ABI stable, do not touch module Relativizer def relative_to(filename) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Relativizer` must implement `relative_to` (#{self.class})" end end # Changed this to a class variable because the upstream library seems to use it # as a global way of keeping track of how many files have been included, to # avoid cycles @@parse_stack= [] MAX_INCLUDE_DEPTH = 50 def initialize end def fixup_options(base_options) syntax = base_options.syntax if !syntax syntax = guess_syntax end if !syntax syntax = Hocon::ConfigSyntax::CONF end modified = base_options.set_syntax(syntax) # make sure the app-provided includer falls back to default modified = modified.append_includer(Hocon::Impl::ConfigImpl.default_includer) # make sure the app-provided includer is complete modified = modified.set_includer(Hocon::Impl::SimpleIncluder.make_full(modified.includer)) modified end def post_construct(base_options) @initial_options = fixup_options(base_options) @include_context = Hocon::Impl::SimpleIncludeContext.new(self) if @initial_options.origin_description @initial_origin = Hocon::Impl::SimpleConfigOrigin.new_simple(@initial_options.origin_description) else @initial_origin = create_origin end end # the general idea is that any work should be in here, not in the # constructor, so that exceptions are thrown from the public parse() # function and not from the creation of the Parseable. # Essentially this is a lazy field. The parser should close the # reader when it's done with it. #{//}# ALSO, IMPORTANT: if the file or URL is not found, this must throw. #{//}# to support the "allow missing" feature. def custom_reader raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Parseable` must implement `custom_reader` (#{self.class})" end def reader(options) custom_reader end def self.trace(message) if Hocon::Impl::ConfigImpl.trace_loads_enabled Hocon::Impl::ConfigImpl.trace(message) end end def guess_syntax nil end def content_type nil end def relative_to(filename) # fall back to classpath; we treat the "filename" as absolute # (don't add a package name in front), # if it starts with "/" then remove the "/", for consistency # with ParseableResources.relativeTo resource = filename if filename.start_with?("/") resource = filename.slice(1) end self.class.new_resources(resource, options.set_origin_description(nil)) end def include_context @include_context end def self.force_parsed_to_object(value) if value.is_a? Hocon::Impl::AbstractConfigObject value else raise Hocon::ConfigError::ConfigWrongTypeError.with_expected_actual(value.origin, "", "object at file root", Hocon::ConfigValueType.value_type_name(value.value_type)) end end def parse(base_options = nil) if (base_options.nil?) base_options = options end stack = @@parse_stack if stack.length >= MAX_INCLUDE_DEPTH raise Hocon::ConfigError::ConfigParseError.new(@initial_origin, "include statements nested more than #{MAX_INCLUDE_DEPTH} times, " + "you probably have a cycle in your includes. Trace: #{stack}", nil) end # Push into beginning of stack stack.unshift(self) begin self.class.force_parsed_to_object(parse_value(base_options)) ensure # Pop from beginning of stack stack.shift end end def parse_value(base_options = nil) if base_options.nil? base_options = options end # note that we are NOT using our "initialOptions", # but using the ones from the passed-in options. The idea is that # callers can get our original options and then parse with different # ones if they want. options = fixup_options(base_options) # passed-in options can override origin origin = if options.origin_description Hocon::Impl::SimpleConfigOrigin.new_simple(options.origin_description) else @initial_origin end parse_value_from_origin(origin, options) end def parse_value_from_origin(origin, final_options) begin raw_parse_value(origin, final_options) rescue IOError => e if final_options.allow_missing? Hocon::Impl::SimpleConfigObject.empty_missing(origin) else self.class.trace("exception loading #{origin.description}: #{e.class}: #{e.message}") raise Hocon::ConfigError::ConfigIOError.new(origin, "#{e.class.name}: #{e.message}", e) end end end def parse_document(base_options = nil) if base_options.nil? base_options = options end # note that we are NOT using our "initialOptions", # but using the ones from the passed-in options. The idea is that # callers can get our original options and then parse with different # ones if they want. options = fixup_options(base_options) # passed-in option can override origin origin = nil if ! options.origin_description.nil? origin = Hocon::Impl::SimpleConfigOrigin.new_simple(options.origin_description) else origin = @initial_origin end parse_document_from_origin(origin, options) end def parse_document_from_origin(origin, final_options) begin raw_parse_document(origin, final_options) rescue IOError => e if final_options.allow_missing? Hocon::Impl::SimpleConfigDocument.new( Hocon::Impl::ConfigNodeObject.new([]), final_options) else self.class.trace("exception loading #{origin.description}: #{e.class}: #{e.message}") raise ConfigIOError.new(origin, "#{e.class.name}: #{e.message}", e) end end end # this is parseValue without post-processing the IOException or handling # options.getAllowMissing() def raw_parse_value(origin, final_options) reader = reader(final_options) # after reader() we will have loaded the Content-Type content_type = content_type() options_with_content_type = nil if !(content_type.nil?) if Hocon::Impl::ConfigImpl.trace_loads_enabled && (! final_options.get_syntax.nil?) self.class.trace("Overriding syntax #{final_options.get_syntax} with Content-Type which specified #{content-type}") end options_with_content_type = final_options.set_syntax(content_type) else options_with_content_type = final_options end reader.open { |io| raw_parse_value_from_io(io, origin, options_with_content_type) } end def raw_parse_value_from_io(io, origin, final_options) tokens = Hocon::Impl::Tokenizer.tokenize(origin, io, final_options.syntax) document = Hocon::Impl::ConfigDocumentParser.parse(tokens, origin, final_options) Hocon::Impl::ConfigParser.parse(document, origin, final_options, include_context) end def raw_parse_document(origin, final_options) reader = reader(final_options) content_type = content_type() options_with_content_type = nil if !(content_type.nil?) if Hocon::Impl::ConfigImpl.trace_loads_enabled && (! final_options.get_syntax.nil?) self.class.trace("Overriding syntax #{final_options.get_syntax} with Content-Type which specified #{content-type}") end options_with_content_type = final_options.set_syntax(content_type) else options_with_content_type = final_options end reader.open { |io| raw_parse_document_from_io(io, origin, options_with_content_type) } end def raw_parse_document_from_io(reader, origin, final_options) tokens = Hocon::Impl::Tokenizer.tokenize(origin, reader, final_options.syntax) Hocon::Impl::SimpleConfigDocument.new( Hocon::Impl::ConfigDocumentParser.parse(tokens, origin, final_options), final_options) end def parse_config_document parse_document(options) end def origin @initial_origin end def create_origin raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Parseable` must implement `create_origin` (#{self.class})" end def options @initial_options end def to_s self.class.name.split('::').last end def self.syntax_from_extension(name) if name.end_with?(".json") Hocon::ConfigSyntax::JSON elsif name.end_with?(".conf") Hocon::ConfigSyntax::CONF else # Skipping PROPERTIES because we can't really support that in ruby nil end end # NOTE: skipping `readerFromStream` and `doNotClose` because they don't seem relevant in Ruby # NOTE: skipping `relativeTo(URL, String)` because we're not supporting URLs for now def self.relative_to(file, filename) child = Pathname.new(filename) file = Pathname.new(file) if child.absolute? nil end parent = file.parent if parent.nil? nil else File.join(parent, filename) end end # this is a parseable that doesn't exist and just throws when you try to parse it class ParseableNotFound < Hocon::Impl::Parseable def initialize(what, message, options) super() @what = what @message = message post_construct(options) end def custom_reader raise Hocon::ConfigError::ConfigBugOrBrokenError, @message end def create_origin Hocon::Impl::SimpleConfigOrigin.new_simple(@what) end end def self.new_not_found(what_not_found, message, options) ParseableNotFound.new(what_not_found, message, options) end # NOTE: skipping `ParseableReader` until we know we need it (probably should # have done that with `ParseableNotFound`) class ParseableString < Hocon::Impl::Parseable def initialize(string, options) super() @input = string post_construct(options) end def custom_reader if Hocon::Impl::ConfigImpl.trace_loads_enabled self.class.trace("Loading config from a String: #{@input}") end # we return self here, which will cause `open` to be called on us, so # we can provide an implementation of that. self end def open if block_given? StringIO.open(@input) do |f| yield f end else StringIO.open(@input) end end def create_origin Hocon::Impl::SimpleConfigOrigin.new_simple("String") end def to_s "#{self.class.name.split('::').last} (#{@input})" end end def self.new_string(string, options) ParseableString.new(string, options) end # NOTE: Skipping `ParseableURL` for now as we probably won't support this right away class ParseableFile < Hocon::Impl::Parseable def initialize(input, options) super() @input = input post_construct(options) end def custom_reader if Hocon::Impl::ConfigImpl.trace_loads_enabled self.class.trace("Loading config from a String: #{@input}") end # we return self here, which will cause `open` to be called on us, so # we can provide an implementation of that. self end def open begin if block_given? File.open(@input, :encoding => 'bom|utf-8') do |f| yield f end else File.open(@input, :encoding => 'bom|utf-8') end rescue Errno::ENOENT if @initial_options.allow_missing? return Hocon::Impl::SimpleConfigObject.empty end raise Hocon::ConfigError::ConfigIOError.new(nil, "File not found. No file called #{@input}") end end def guess_syntax Hocon::Impl::Parseable.syntax_from_extension(File.basename(@input)) end def relative_to(filename) sibling = nil if Pathname.new(filename).absolute? sibling = File.new(filename) else # this may return nil sibling = Hocon::Impl::Parseable.relative_to(@input, filename) end if sibling.nil? nil elsif File.exists?(sibling) self.class.trace("#{sibling} exists, so loading it as a file") Hocon::Impl::Parseable.new_file(sibling, options.set_origin_description(nil)) else self.class.trace("#{sibling} does not exist, so trying it as a resource") super(filename) end end def create_origin Hocon::Impl::SimpleConfigOrigin.new_file(@input) end def to_s "#{self.class.name.split('::').last} (#{@input})" end end def self.new_file(file_path, options) ParseableFile.new(file_path, options) end # NOTE: skipping `ParseableResourceURL`, we probably won't support that # NOTE: this is not a faithful port of the `ParseableResources` class from the # upstream, because at least for now we're not going to try to do anything # crazy like look for files on the ruby load path. However, there is a decent # chunk of logic elsewhere in the codebase that is written with the assumption # that this class will provide the 'last resort' attempt to find a config file # before giving up, so we're basically port just enough to have it provide # that last resort behavior class ParseableResources < Hocon::Impl::Parseable include Relativizer def initialize(resource, options) super() @resource = resource post_construct(options) end def reader raise Hocon::ConfigError::ConfigBugOrBrokenError, "reader() should not be called on resources" end def raw_parse_value(origin, final_options) # this is where the upstream code would go out and look for a file on the # classpath. We're not going to do that, and instead we're just going to # raise the same exception that the upstream code would raise if it failed # to find the file. raise IOError, "resource not found: #{@resource}" end def guess_syntax Hocon::Impl::Parseable.syntax_from_extension(@resource) end def self.parent(resource) # the "resource" is not supposed to begin with a "/" # because it's supposed to be the raw resource # (ClassLoader#getResource), not the # resource "syntax" (Class#getResource) i = resource.rindex("/") if i < 0 nil else resource.slice(0..i) end end def relative_to(sibling) if sibling.start_with?("/") # if it starts with "/" then don't make it relative to the # including resource Hocon::Impl::Parseable.new_resources(sibling.slice(1), options.set_origin_description(nil)) else # here we want to build a new resource name and let # the class loader have it, rather than getting the # url with getResource() and relativizing to that url. # This is needed in case the class loader is going to # search a classpath. parent = self.class.parent(@resource) if parent.nil? Hocon::Impl::Parseable.new_resources(sibling, options.set_origin_description(nil)) else Hocon::Impl::Parseable.new_resources("#{parent}/sibling", options.set_origin_description(nil)) end end end def create_origin Hocon::Impl::SimpleConfigOrigin.new_resource(@resource) end def to_s "#{self.class.name.split('::').last}(#{@resource})" end end def self.new_resources(resource, options) ParseableResources.new(resource, options) end # NOTE: skipping `ParseableProperties`, we probably won't support that end hocon-1.2.5/lib/hocon/impl/token.rb0000644000175000017500000000160713154611745017016 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/token_type' class Hocon::Impl::Token attr_reader :token_type, :token_text def self.new_without_origin(token_type, debug_string, token_text) Hocon::Impl::Token.new(token_type, nil, token_text, debug_string) end def initialize(token_type, origin, token_text = nil, debug_string = nil) @token_type = token_type @origin = origin @token_text = token_text @debug_string = debug_string end attr_reader :origin def line_number if @origin @origin.line_number else -1 end end def to_s if !@debug_string.nil? @debug_string else Hocon::Impl::TokenType.token_type_name(@token_type) end end def ==(other) # @origin deliberately left out other.is_a?(Hocon::Impl::Token) && @token_type == other.token_type end def hash @token_type.hash end end hocon-1.2.5/lib/hocon/impl/config_node_array.rb0000644000175000017500000000034213154611745021341 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/config_node_complex_value' class Hocon::Impl::ConfigNodeArray include Hocon::Impl::ConfigNodeComplexValue def new_node(nodes) self.class.new(nodes) end endhocon-1.2.5/lib/hocon/impl/origin_type.rb0000644000175000017500000000067013154611745020225 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' module Hocon::Impl::OriginType ## for now, we only support a subset of these GENERIC = 0 FILE = 1 #URL = 2 # We don't actually support loading from the classpath / loadpath, which is # what 'RESOURCE' is about in the upstream library. However, some code paths # still flow through our simplistic implementation of `ParseableResource`, so # we need this constant. RESOURCE = 3 end hocon-1.2.5/lib/hocon/impl/config_node_field.rb0000644000175000017500000000357413154611745021320 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/config_error' require 'hocon/impl/abstract_config_node' require 'hocon/impl/abstract_config_node_value' require 'hocon/impl/config_node_comment' require 'hocon/impl/config_node_path' require 'hocon/impl/config_node_single_token' require 'hocon/impl/tokens' class Hocon::Impl::ConfigNodeField include Hocon::Impl::AbstractConfigNode Tokens = Hocon::Impl::Tokens def initialize(children) @children = children end attr_reader :children def tokens tokens = [] @children.each do |child| tokens += child.tokens end tokens end def replace_value(new_value) children_copy = @children.clone children_copy.each_with_index do |child, i| if child.is_a?(Hocon::Impl::AbstractConfigNodeValue) children_copy[i] = new_value return self.class.new(children_copy) end end raise Hocon::ConfigError::ConfigBugOrBrokenError, "Field node doesn't have a value" end def value @children.each do |child| if child.is_a?(Hocon::Impl::AbstractConfigNodeValue) return child end end raise Hocon::ConfigError::ConfigBugOrBrokenError, "Field node doesn't have a value" end def path @children.each do |child| if child.is_a?(Hocon::Impl::ConfigNodePath) return child end end raise Hocon::ConfigError::ConfigBugOrBrokenError, "Field node doesn't have a path" end def separator @children.each do |child| if child.is_a?(Hocon::Impl::ConfigNodeSingleToken) t = child.token if t == Tokens::PLUS_EQUALS or t == Tokens::COLON or t == Tokens::EQUALS return t end end end nil end def comments comments = [] @children.each do |child| if child.is_a?(Hocon::Impl::ConfigNodeComment) comments << child.comment_text end end comments end endhocon-1.2.5/lib/hocon/impl/config_impl_util.rb0000644000175000017500000000500013154611745021210 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'stringio' class Hocon::Impl::ConfigImplUtil def self.equals_handling_nil?(a, b) # This method probably doesn't make any sense in ruby... not sure if a.nil? && !b.nil? false elsif !a.nil? && b.nil? false # in ruby, the == and .equal? are the opposite of what they are in Java elsif a.equal?(b) true else a == b end end # # This is public ONLY for use by the "config" package, DO NOT USE this ABI # may change. # def self.render_json_string(s) sb = StringIO.new sb << '"' s.chars.each do |c| case c when '"' then sb << "\\\"" when "\\" then sb << "\\\\" when "\n" then sb << "\\n" when "\b" then sb << "\\b" when "\f" then sb << "\\f" when "\r" then sb << "\\r" when "\t" then sb << "\\t" else if c =~ /[[:cntrl:]]/ sb << ("\\u%04x" % c) else sb << c end end end sb << '"' sb.string end def self.render_string_unquoted_if_possible(s) # this can quote unnecessarily as long as it never fails to quote when # necessary if s.length == 0 return render_json_string(s) end # if it starts with a hyphen or number, we have to quote # to ensure we end up with a string and not a number first = s.chars.first if (first =~ /[[:digit:]]/) || (first == '-') return render_json_string(s) end # only unquote if it's pure alphanumeric s.chars.each do |c| unless (c =~ /[[:alnum:]]/) || (c == '-') return render_json_string(s) end end s end def self.join_path(*elements) Hocon::Impl::Path.from_string_list(elements).render end def self.split_path(path) p = Hocon::Impl::Path.new_path(path) elements = [] until p.nil? elements << p.first p = p.remainder end elements end def self.whitespace?(c) # this implementation is *not* a port of the java code, because it relied on # the method java.lang.Character#isWhitespace. This is probably # insanely slow (running a regex against every single character in the # file). c =~ /[[:space:]]/ end def self.unicode_trim(s) # this implementation is *not* a port of the java code. Ruby can strip # unicode whitespace much easier than Java can, and relies on a lot of # Java functions that don't really have straight equivalents in Ruby. s.gsub(/[:space]/, ' ') s.strip end end hocon-1.2.5/lib/hocon/impl/config_node_comment.rb0000644000175000017500000000100613154611745021663 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/config_error' require 'hocon/impl/config_node_single_token' require 'hocon/impl/tokens' class Hocon::Impl::ConfigNodeComment < Hocon::Impl::ConfigNodeSingleToken def initialize(comment) super(comment) unless Hocon::Impl::Tokens.comment?(@token) raise Hocon::ConfigError::ConfigBugOrBrokenError, 'Tried to create a ConfigNodeComment from a non-comment token' end end def comment_text Hocon::Impl::Tokens.comment_text(@token) end endhocon-1.2.5/lib/hocon/impl/config_delayed_merge_object.rb0000644000175000017500000002060513154611745023336 0ustar apoikosapoikosrequire 'hocon/impl' require 'hocon/impl/unmergeable' require 'hocon/impl/replaceable_merge_stack' # This is just like ConfigDelayedMerge except we know statically # that it will turn out to be an object. class Hocon::Impl::ConfigDelayedMergeObject include Hocon::Impl::Unmergeable include Hocon::Impl::ReplaceableMergeStack include Hocon::Impl::AbstractConfigObject def initialize(origin, stack) super(origin) @stack = stack if stack.empty? raise Hocon::ConfigError::ConfigBugOrBrokenError.new("creating empty delayed merge value", nil) end if !@stack[0].is_a? Hocon::Impl::AbstractConfigObject error_message = "created a delayed merge object not guaranteed to be an object" raise Hocon::ConfigError::ConfigBugOrBrokenError.new(error_message, nil) end stack.each do |v| if v.is_a?(Hocon::Impl::ConfigDelayedMergeObject) || v.is_a?(Hocon::Impl::ConfigDelayedMergeObject) error_message = "placed nested DelayedMerge in a ConfigDelayedMerge, should have consolidated stack" raise Hocon::ConfigError::ConfigBugOrBrokenError.new(error_message, nil) end end end attr_reader :stack def new_copy(status, origin) if status != resolve_status raise Hocon::ConfigError::ConfigBugOrBrokenError.new( "attempt to create resolved ConfigDelayedMergeObject") end Hocon::Impl::ConfigDelayedMergeObject.new(origin, @stack) end def resolve_substitutions(context, source) merged = Hocon::Impl::ConfigDelayedMerge.resolve_substitutions(self, @stack, context, source) merged.as_object_result end def make_replacement(context, skipping) Hocon::Impl::ConfigDelayedMerge.make_replacement(context, @stack, skipping) end def resolve_status Hocon::Impl::ResolveStatus::UNRESOLVED end def replace_child(child, replacement) new_stack = Hocon::Impl::AbstractConfigValue.replace_child_in_list(@stack, child, replacement) if new_stack == nil nil else self.class.new(origin, new_stack) end end def has_descendant?(descendant) Hocon::Impl::AbstractConfigValue.has_descendant_in_list?(@stack, descendant) end def relativized(prefix) new_stack = [] @stack.each { |o| new_stack << o.relativized(prefix) } self.class.new(origin, new_stack) end def ignores_fallbacks? Hocon::Impl::ConfigDelayedMerge.stack_ignores_fallbacks?(@stack) end def merged_with_the_unmergeable(fallback) require_not_ignoring_fallbacks merged_stack_with_the_unmergeable(@stack, fallback) end def merged_with_object(fallback) merged_with_non_object(fallback) end def merged_with_non_object(fallback) require_not_ignoring_fallbacks merged_stack_with_non_object(@stack, fallback) end # No implementation of withFallback here, # just use the implementation in the super-class def with_only_key(key) raise self.class.not_resolved end def without_key(key) raise self.class.not_resolved end def with_only_path_or_nil(key) raise self.class.not_resolved end def with_only_path(key) raise self.class.not_resolved end def without_path(key) raise self.class.not_resolved end def with_value(key_or_path, value = nil) raise self.class.not_resolved end def unmerged_values @stack end def can_equal(other) other.is_a? Hocon::Impl::ConfigDelayedMergeObject end def ==(other) # note that "origin" is deliberately NOT part of equality if other.is_a? Hocon::Impl::ConfigDelayedMergeObject can_equal(other) && (@stack == other.stack || @stack.equal?(other.stack)) else false end end def hash # note that "origin" is deliberately NOT part of equality @stack.hash end def render_to_sb(sb, indent, at_root, at_key, options) Hocon::Impl::ConfigDelayedMerge.render_value_to_sb_from_stack(@stack, sb, indent, at_root, at_key, options) end def self.not_resolved error_message = "need to Config#resolve() before using this object, see the API docs for Config#resolve()" Hocon::ConfigError::ConfigNotResolvedError.new(error_message, nil) end def unwrapped raise self.class.not_resolved end def [](key) raise self.class.not_resolved end def has_key?(key) raise self.class.not_resolved end def has_value?(value) raise self.class.not_resolved end def each raise self.class.not_resolved end def empty? raise self.class.not_resolved end def keys raise self.class.not_resolved end def values raise self.class.not_resolved end def size raise self.class.not_resolved end def self.unmergeable?(object) # Ruby note: This is the best way I could find to simulate # else if (layer instanceof Unmergeable) in java since we're including # the Unmergeable module instead of extending an Unmergeable class object.class.included_modules.include?(Hocon::Impl::Unmergeable) end def attempt_peek_with_partial_resolve(key) # a partial resolve of a ConfigDelayedMergeObject always results in a # SimpleConfigObject because all the substitutions in the stack get # resolved in order to look up the partial. # So we know here that we have not been resolved at all even # partially. # Given that, all this code is probably gratuitous, since the app code # is likely broken. But in general we only throw NotResolved if you try # to touch the exact key that isn't resolved, so this is in that # spirit. # we'll be able to return a key if we have a value that ignores # fallbacks, prior to any unmergeable values. @stack.each do |layer| if layer.is_a?(Hocon::Impl::AbstractConfigObject) v = layer.attempt_peek_with_partial_resolve(key) if !v.nil? if v.ignores_fallbacks? # we know we won't need to merge anything in to this # value return v else # we can't return this value because we know there are # unmergeable values later in the stack that may # contain values that need to be merged with this # value. we'll throw the exception when we get to those # unmergeable values, so continue here. next end elsif self.class.unmergeable?(layer) error_message = "should not be reached: unmergeable object returned null value" raise Hocon::ConfigError::ConfigBugOrBrokenError.new(error_message, nil) else # a non-unmergeable AbstractConfigObject that returned null # for the key in question is not relevant, we can keep # looking for a value. next end elsif self.class.unmergeable?(layer) error_message = "Key '#{key}' is not available at '#{origin.description}'" + "because value at '#{layer.origin.description}' has not been resolved" + " and may turn out to contain or hide '#{key}'. Be sure to Config#resolve()" + " before using a config object" raise Hocon::ConfigError::ConfigNotResolvedError.new(error_message, nil) elsif layer.resolved_status == ResolveStatus::UNRESOLVED # if the layer is not an object, and not a substitution or # merge, # then it's something that's unresolved because it _contains_ # an unresolved object... i.e. it's an array if !layer.is_a?(Hocon::Impl::ConfigList) error_message = "Expecting a list here, not #{layer}" raise Hocon::ConfigError::ConfigBugOrBrokenError.new(error_message, nil) end return nil else # non-object, but resolved, like an integer or something. # has no children so the one we're after won't be in it. # we would only have this in the stack in case something # else "looks back" to it due to a cycle. # anyway at this point we know we can't find the key anymore. if !layer.ignores_fallbacks? error_message = "resolved non-object should ignore fallbacks" raise Hocon::ConfigError::ConfigBugOrBrokenError.new(error_message, nil) end return nil end end # If we get here, then we never found anything unresolved which means # the ConfigDelayedMergeObject should not have existed. some # invariant was violated. error_message = "Delayed merge stack does not contain any unmergeable values" raise Hocon::ConfigError::ConfigBugOrBrokenError.new(error_message, nil) end end hocon-1.2.5/lib/hocon/impl/unmergeable.rb0000644000175000017500000000100613154611745020155 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/config_error' # # Interface that tags a ConfigValue that is not mergeable until after # substitutions are resolved. Basically these are special ConfigValue that # never appear in a resolved tree, like {@link ConfigSubstitution} and # {@link ConfigDelayedMerge}. # module Hocon::Impl::Unmergeable def unmerged_values raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `Unmergeable` must implement `unmerged_values` (#{self.class})" end end hocon-1.2.5/lib/hocon/impl/simple_config_document.rb0000644000175000017500000000260613154611745022412 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/parser/config_document' require 'hocon/impl/config_document_parser' require 'hocon/config_render_options' class Hocon::Impl::SimpleConfigDocument include Hocon::Parser::ConfigDocument def initialize(parsed_node, parse_options) @config_node_tree = parsed_node @parse_options = parse_options end def set_value(path, new_value) origin = Hocon::Impl::SimpleConfigOrigin.new_simple("single value parsing") reader = StringIO.new(new_value) tokens = Hocon::Impl::Tokenizer.tokenize(origin, reader, @parse_options.syntax) parsed_value = Hocon::Impl::ConfigDocumentParser.parse_value(tokens, origin, @parse_options) reader.close self.class.new(@config_node_tree.set_value(path, parsed_value, @parse_options.syntax), @parse_options) end def set_config_value(path, new_value) options = Hocon::ConfigRenderOptions.defaults options.origin_comments = false set_value(path, new_value.render(options).strip) end def remove_value(path) self.class.new(@config_node_tree.set_value(path, nil, @parse_options.syntax), @parse_options) end def has_value?(path) @config_node_tree.has_value(path) end def render @config_node_tree.render end def ==(other) other.class.ancestors.include?(Hocon::Parser::ConfigDocument) && render == other.render end def hash render.hash end end hocon-1.2.5/lib/hocon/impl/config_reference.rb0000644000175000017500000001006613154611745021160 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/impl' require 'hocon/impl/abstract_config_value' class Hocon::Impl::ConfigReference include Hocon::Impl::Unmergeable include Hocon::Impl::AbstractConfigValue # Require these lazily, to avoid circular dependencies require 'hocon/impl/resolve_source' require 'hocon/impl/resolve_result' NotPossibleToResolve = Hocon::Impl::AbstractConfigValue::NotPossibleToResolve UnresolvedSubstitutionError = Hocon::ConfigError::UnresolvedSubstitutionError attr_reader :expr, :prefix_length def initialize(origin, expr, prefix_length = 0) super(origin) @expr = expr @prefix_length = prefix_length end def unmerged_values [self] end # ConfigReference should be a firewall against NotPossibleToResolve going # further up the stack; it should convert everything to ConfigException. # This way it 's impossible for NotPossibleToResolve to "escape" since # any failure to resolve has to start with a ConfigReference. def resolve_substitutions(context, source) new_context = context.add_cycle_marker(self) begin result_with_path = source.lookup_subst(new_context, @expr, @prefix_length) new_context = result_with_path.result.context if result_with_path.result.value != nil if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( "recursively resolving #{result_with_path} which was the resolution of #{expr} against #{source}", context.depth) end recursive_resolve_source = Hocon::Impl::ResolveSource.new( result_with_path.path_from_root.last, result_with_path.path_from_root) if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("will recursively resolve against #{recursive_resolve_source}", context.depth) end result = new_context.resolve(result_with_path.result.value, recursive_resolve_source) v = result.value new_context = result.context else v = nil end rescue NotPossibleToResolve => e if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( "not possible to resolve #{expr}, cycle involved: #{e.trace_string}", new_context.depth) end if @expr.optional v = nil else raise UnresolvedSubstitutionError.new( origin, "#{@expr} was part of a cycle of substitutions involving #{e.trace_string}", e) end end if v == nil && !@expr.optional if new_context.options.allow_unresolved ResolveResult.make(new_context.remove_cycle_marker(self), self) else raise UnresolvedSubstitutionError.new(origin, @expr.to_s) end else Hocon::Impl::ResolveResult.make(new_context.remove_cycle_marker(self), v) end end def value_type raise not_resolved end def unwrapped raise not_resolved end def new_copy(new_origin) Hocon::Impl::ConfigReference.new(new_origin, @expr, @prefix_length) end def ignores_fallbacks? false end def resolve_status Hocon::Impl::ResolveStatus::UNRESOLVED end def relativized(prefix) new_expr = @expr.change_path(@expr.path.prepend(prefix)) Hocon::Impl::ConfigReference.new(origin, new_expr, @prefix_length + prefix.length) end def can_equal(other) other.is_a? Hocon::Impl::ConfigReference end def ==(other) # note that "origin" is deliberately NOT part of equality if other.is_a? Hocon::Impl::ConfigReference can_equal(other) && @expr == other.expr end end def hash # note that "origin" is deliberately NOT part of equality @expr.hash end def render_value_to_sb(sb, indent, at_root, options) sb << @expr.to_s end def expression @expr end private def not_resolved error_message = "need to Config#resolve, see the API docs for Config#resolve; substitution not resolved: #{self}" Hocon::ConfigError::ConfigNotResolvedError.new(error_message, nil) end end hocon-1.2.5/lib/hocon/impl/resolve_source.rb0000644000175000017500000002615613154611745020743 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/config_error' require 'hocon/impl' require 'hocon/impl/config_impl' require 'hocon/impl/container' class Hocon::Impl::ResolveSource ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError ConfigNotResolvedError = Hocon::ConfigError::ConfigNotResolvedError # 'path_from_root' is used for knowing the chain of parents we used to get here. # null if we should assume we are not a descendant of the root. # the root itself should be a node in this if non-null. attr_accessor :root, :path_from_root def initialize(root, path_from_root = nil) @root = root @path_from_root = path_from_root end # as a side effect, findInObject() will have to resolve all parents of the # child being peeked, but NOT the child itself.Caller has to resolve # the child itself if needed.ValueWithPath.value can be null but # the ValueWithPath instance itself should not be. def find_in_object(obj, context, path) # resolve ONLY portions of the object which are along our path if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("*** finding '#{path}' in #{obj}") end restriction = context.restrict_to_child partially_resolved = context.restrict(path).resolve(obj, self.class.new(obj)) new_context = partially_resolved.context.restrict(restriction) if partially_resolved.value.is_a?(Hocon::Impl::AbstractConfigObject) pair = self.class.find_in_object_impl(partially_resolved.value, path) ResultWithPath.new(Hocon::Impl::ResolveResult.make(new_context, pair.value), pair.path_from_root) else raise ConfigBugOrBrokenError.new("resolved object to non-object " + obj + " to " + partially_resolved) end end def lookup_subst(context, subst, prefix_length) if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("searching for #{subst}", context.depth) end if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("#{subst} - looking up relative to file it occurred in", context.depth) end # First we look up the full path, which means relative to the # included file if we were not a root file result = find_in_object(@root, context, subst.path) if result.result.value == nil # Then we want to check relative to the root file.We don 't # want the prefix we were included at to be used when looking # up env variables either. unprefixed = subst.path.sub_path_to_end(prefix_length) if prefix_length > 0 if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( unprefixed + " - looking up relative to parent file", result.result.context.depth) end result = find_in_object(@root, result.result.context, unprefixed) end if result.result.value == nil && result.result.context.options.use_system_environment if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( "#{unprefixed} - looking up in system environment", result.result.context.depth) end result = find_in_object(Hocon::Impl::ConfigImpl.env_variables_as_config_object, context, unprefixed) end end if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace( "resolved to #{result}", result.result.context.depth) end result end def push_parent(parent) unless parent raise ConfigBugOrBrokenError.new("can't push null parent") end if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("pushing parent #{parent} ==root #{(parent == root)} onto #{self}") end if @path_from_root == nil if parent.equal?(@root) return self.class.new(@root, Node.new(parent)) else if Hocon::Impl::ConfigImpl.trace_substitution_enabled # this hasDescendant check is super-expensive so it's a # trace message rather than an assertion if @root.has_descendant?(parent) Hocon::Impl::ConfigImpl.trace( "***** BUG ***** tried to push parent #{parent} without having a path to it in #{self}") end end # ignore parents if we aren't proceeding from the # root return self end else parent_parent = @path_from_root.head if Hocon::Impl::ConfigImpl.trace_substitution_enabled # this hasDescendant check is super-expensive so it's a # trace message rather than an assertion if parent_parent != nil && !parent_parent.has_descendant?(parent) Hocon::Impl::ConfigImpl.trace( "***** BUG ***** trying to push non-child of #{parent_parent}, non-child was #{parent}") end end self.class.new(@root, @path_from_root.prepend(parent)) end end def reset_parents if @path_from_root == nil this else self.class.new(@root) end end def replace_current_parent(old, replacement) if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("replaceCurrentParent old #{old}@#{old.hash} replacement " + "#{replacement}@#{old.hash} in #{self}") end if old.equal?(replacement) self elsif @path_from_root != nil new_path = self.class.replace(@path_from_root, old, replacement) if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("replaced #{old} with #{replacement} in #{self}") Hocon::Impl::ConfigImpl.trace("path was: #{@path_from_root} is now #{new_path}") end # if we end up nuking the root object itself, we replace it with an # empty root if new_path != nil return self.class.new(new_path.last, new_path) else return self.class.new(Hocon::Impl::SimpleConfigObject.empty) end else if old.equal?(@root) return self.class.new(root_must_be_obj(replacement)) else raise ConfigBugOrBrokenError.new("attempt to replace root #{root} with #{replacement}") end end end # replacement may be null to delete def replace_within_current_parent(old, replacement) if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("replaceWithinCurrentParent old #{old}@#{old.hash}" + " replacement #{replacement}@#{old.hash} in #{self}") end if old.equal?(replacement) self elsif @path_from_root != nil parent = @path_from_root.head new_parent = parent.replace_child(old, replacement) return replace_current_parent(parent, new_parent.is_a?(Hocon::Impl::Container) ? new_parent : nil) else if old.equal?(@root) && replacement.is_a?(Hocon::Impl::Container) return self.class.new(root_must_be_obj(replacement)) else raise ConfigBugOrBrokenError.new("replace in parent not possible #{old} with #{replacement}" + " in #{self}") end end end def to_s "ResolveSource(root=#{@root}, pathFromRoot=#{@path_from_root})" end # a persistent list class Node attr_reader :next_node, :value def initialize(value, next_node = nil) @value = value @next_node = next_node end def prepend(value) Node.new(value, self) end def head @value end def tail @next_node end def last i = self while i.next_node != nil i = i.next_node end i.value end def reverse if @next_node == nil self else reversed = Node.new(@value) i = @next_node while i != nil reversed = reversed.prepend(i.value) i = i.next_node end reversed end end def to_s sb = "" sb << "[" to_append_value = self.reverse while to_append_value != nil sb << to_append_value.value.to_s if to_append_value.next_node != nil sb << " <= " end to_append_value = to_append_value.next_node end sb << "]" sb end end # value is allowed to be null class ValueWithPath attr_reader :value, :path_from_root def initialize(value, path_from_root) @value = value @path_from_root = path_from_root end def to_s "ValueWithPath(value=" + @value + ", pathFromRoot=" + @path_from_root + ")" end end class ResultWithPath attr_reader :result, :path_from_root def initialize(result, path_from_root) @result = result @path_from_root = path_from_root end def to_s "ResultWithPath(result=#{@result}, pathFromRoot=#{@path_from_root})" end end private def root_must_be_obj(value) if value.is_a?(Hocon::Impl::AbstractConfigObject) value else Hocon::Impl::SimpleConfigObject.empty end end def self.find_in_object_impl(obj, path, parents = nil) begin # we 'll fail if anything along the path can' t # be looked at without resolving. find_in_object_impl_impl(obj, path, nil) rescue ConfigNotResolvedError => e raise Hocon::Impl::ConfigImpl.improve_not_resolved(path, e) end end def self.find_in_object_impl_impl(obj, path, parents) key = path.first remainder = path.remainder if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("*** looking up '#{key}' in #{obj}") end v = obj.attempt_peek_with_partial_resolve(key) new_parents = parents == nil ? Node.new(obj) : parents.prepend(obj) if remainder == nil ValueWithPath.new(v, new_parents) else if v.is_a?(Hocon::Impl::AbstractConfigObject) find_in_object_impl_impl(v, remainder, new_parents) else ValueWithPath.new(nil, new_parents) end end end # returns null if the replacement results in deleting all the nodes. def self.replace(list, old, replacement) child = list.head unless child.equal?(old) raise ConfigBugOrBrokenError.new("Can only replace() the top node we're resolving; had " + child + " on top and tried to replace " + old + " overall list was " + list) end parent = list.tail == nil ? nil : list.tail.head if replacement == nil || !replacement.is_a?(Hocon::Impl::Container) if parent == nil return nil else # we are deleting the child from the stack of containers # because it's either going away or not a container new_parent = parent.replace_child(old, nil) return replace(list.tail, parent, new_parent) end else # we replaced the container with another container if parent == nil return Node.new(replacement) else new_parent = parent.replace_child(old, replacement) new_tail = replace(list.tail, parent, new_parent) if new_tail != nil return new_tail.prepend(replacement) else return Node.new(replacement) end end end end end hocon-1.2.5/lib/hocon/impl/mergeable_value.rb0000644000175000017500000000022213154611745021005 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/config_mergeable' class Hocon::Impl::MergeableValue < Hocon::ConfigMergeable # TODO end hocon-1.2.5/lib/hocon/impl/config_node_concatenation.rb0000644000175000017500000000035213154611745023051 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/config_node_complex_value' class Hocon::Impl::ConfigNodeConcatenation include Hocon::Impl::ConfigNodeComplexValue def new_node(nodes) self.class.new(nodes) end endhocon-1.2.5/lib/hocon/impl/array_iterator.rb0000644000175000017500000000034513154611745020723 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' class Hocon::Impl::ArrayIterator def initialize(a) @a = a @index = 0 end def has_next? @index < @a.length end def next @index += 1 @a[@index - 1] end end hocon-1.2.5/lib/hocon/impl/token_type.rb0000644000175000017500000000216313154611745020055 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' class Hocon::Impl::TokenType START = 0 EOF = 1 COMMA = 2 EQUALS = 3 COLON = 4 OPEN_CURLY = 5 CLOSE_CURLY = 6 OPEN_SQUARE = 7 CLOSE_SQUARE = 8 VALUE = 9 NEWLINE = 10 UNQUOTED_TEXT = 11 SUBSTITUTION = 12 PROBLEM = 13 COMMENT = 14 PLUS_EQUALS = 15 IGNORED_WHITESPACE = 16 def self.token_type_name(token_type) case token_type when START then "START" when EOF then "EOF" when COMMA then "COMMA" when EQUALS then "EQUALS" when COLON then "COLON" when OPEN_CURLY then "OPEN_CURLY" when CLOSE_CURLY then "CLOSE_CURLY" when OPEN_SQUARE then "OPEN_SQUARE" when CLOSE_SQUARE then "CLOSE_SQUARE" when VALUE then "VALUE" when NEWLINE then "NEWLINE" when UNQUOTED_TEXT then "UNQUOTED_TEXT" when SUBSTITUTION then "SUBSTITUTION" when PROBLEM then "PROBLEM" when COMMENT then "COMMENT" when PLUS_EQUALS then "PLUS_EQUALS" when IGNORED_WHITESPACE then "IGNORED_WHITESPACE" else raise ConfigBugOrBrokenError, "Unrecognized token type #{token_type}" end end end hocon-1.2.5/lib/hocon/impl/config_int.rb0000644000175000017500000000122213154611745020006 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/config_number' require 'hocon/config_value_type' class Hocon::Impl::ConfigInt < Hocon::Impl::ConfigNumber def initialize(origin, value, original_text) super(origin, original_text) @value = value end attr_reader :value def value_type Hocon::ConfigValueType::NUMBER end def unwrapped @value end def transform_to_string s = super if s.nil? self.to_s else s end end def long_value @value end def double_value @value end def new_copy(origin) Hocon::Impl::ConfigInt.new(origin, @value, @original_text) end end hocon-1.2.5/lib/hocon/impl/config_concatenation.rb0000644000175000017500000002074613154611745022055 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/abstract_config_value' require 'hocon/impl/abstract_config_object' require 'hocon/impl/simple_config_list' require 'hocon/config_object' require 'hocon/impl/unmergeable' require 'hocon/impl/simple_config_origin' require 'hocon/impl/config_string' require 'hocon/impl/container' class Hocon::Impl::ConfigConcatenation include Hocon::Impl::Unmergeable include Hocon::Impl::Container include Hocon::Impl::AbstractConfigValue SimpleConfigList = Hocon::Impl::SimpleConfigList ConfigObject = Hocon::ConfigObject ConfigString = Hocon::Impl::ConfigString ResolveStatus = Hocon::Impl::ResolveStatus Unmergeable = Hocon::Impl::Unmergeable SimpleConfigOrigin = Hocon::Impl::SimpleConfigOrigin ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError ConfigNotResolvedError = Hocon::ConfigError::ConfigNotResolvedError ConfigWrongTypeError = Hocon::ConfigError::ConfigWrongTypeError attr_reader :pieces def initialize(origin, pieces) super(origin) @pieces = pieces if pieces.size < 2 raise ConfigBugOrBrokenError, "Created concatenation with less than 2 items: #{self}" end had_unmergeable = false pieces.each do |p| if p.is_a?(Hocon::Impl::ConfigConcatenation) raise ConfigBugOrBrokenError, "ConfigConcatenation should never be nested: #{self}" end if p.is_a?(Unmergeable) had_unmergeable = true end end unless had_unmergeable raise ConfigBugOrBrokenError, "Created concatenation without an unmergeable in it: #{self}" end end def value_type raise not_resolved end def unwrapped raise not_resolved end def new_copy(new_origin) self.class.new(new_origin, @pieces) end def ignores_fallbacks? # we can never ignore fallbacks because if a child ConfigReference # is self-referential we have to look lower in the merge stack # for its value. false end def unmerged_values [self] end # # Add left and right, or their merger, to builder # def self.join(builder, orig_right) left = builder[builder.size - 1] right = orig_right # check for an object which can be converted to a list # (this will be an object with numeric keys, like foo.0, foo.1) if (left.is_a?(ConfigObject)) && (right.is_a?(SimpleConfigList)) left = Hocon::Impl::DefaultTransformer.transform(left, Hocon::ConfigValueType::LIST) elsif (left.is_a?(SimpleConfigList)) && (right.is_a?(ConfigObject)) right = Hocon::Impl::DefaultTransformer.transform(right, Hocon::ConfigValueType::LIST) end # Since this depends on the type of two instances, I couldn't think # of much alternative to an instanceof chain. Visitors are sometimes # used for multiple dispatch but seems like overkill. joined = nil if (left.is_a?(ConfigObject)) && (right.is_a?(ConfigObject)) joined = right.with_fallback(left) elsif (left.is_a?(SimpleConfigList)) && (right.is_a?(SimpleConfigList)) joined = left.concatenate(right) elsif (left.is_a?(SimpleConfigList) || left.is_a?(ConfigObject)) && is_ignored_whitespace(right) joined = left # it should be impossible that left is whitespace and right is a list or object elsif (left.is_a?(Hocon::Impl::ConfigConcatenation)) || (right.is_a?(Hocon::Impl::ConfigConcatenation)) raise ConfigBugOrBrokenError, "unflattened ConfigConcatenation" elsif (left.is_a?(Unmergeable)) || (right.is_a?(Unmergeable)) # leave joined=null, cannot join else # handle primitive type or primitive type mixed with object or list s1 = left.transform_to_string s2 = right.transform_to_string if s1.nil? || s2.nil? raise ConfigWrongTypeError.new(left.origin, "Cannot concatenate object or list with a non-object-or-list, #{left} " + "and #{right} are not compatible", nil) else joined_origin = SimpleConfigOrigin.merge_origins([left.origin, right.origin]) joined = Hocon::Impl::ConfigString::Quoted.new(joined_origin, s1 + s2) end end if joined.nil? builder.push(right) else builder.pop builder.push(joined) end end def self.consolidate(pieces) if pieces.length < 2 pieces else flattened = [] pieces.each do |v| if v.is_a?(Hocon::Impl::ConfigConcatenation) flattened.concat(v.pieces) else flattened.push(v) end end consolidated = [] flattened.each do |v| if consolidated.empty? consolidated.push(v) else join(consolidated, v) end end consolidated end end def self.concatenate(pieces) consolidated = consolidate(pieces) if consolidated.empty? nil elsif consolidated.length == 1 consolidated[0] else merged_origin = SimpleConfigOrigin.merge_value_origins(consolidated) Hocon::Impl::ConfigConcatenation.new(merged_origin, consolidated) end end def resolve_substitutions(context, source) if Hocon::Impl::ConfigImpl.trace_substitution_enabled indent = context.depth + 2 Hocon::Impl::ConfigImpl.trace("concatenation has #{@pieces.size} pieces", indent - 1) count = 0 @pieces.each { |v| Hocon::Impl::ConfigImpl.trace("#{count}: #{v}", count) count += 1 } end # Right now there's no reason to pushParent here because the # content of ConfigConcatenation should not need to replaceChild, # but if it did we'd have to do this. source_with_parent = source new_context = context resolved = [] @pieces.each { |p| # to concat into a string we have to do a full resolve, # so unrestrict the context, then put restriction back afterward restriction = new_context.restrict_to_child result = new_context.unrestricted .resolve(p, source_with_parent) r = result.value new_context = result.context.restrict(restriction) if Hocon::Impl::ConfigImpl.trace_substitution_enabled Hocon::Impl::ConfigImpl.trace("resolved concat piece to #{r}", context.depth) end if r resolved << r end # otherwise, it was optional ... omit } # now need to concat everything joined = self.class.consolidate(resolved) # if unresolved is allowed we can just become another # ConfigConcatenation if joined.size > 1 and context.options.allow_unresolved Hocon::Impl::ResolveResult.make(new_context, Hocon::Impl::ConfigConcatenation.new(origin, joined)) elsif joined.empty? # we had just a list of optional references using ${?} Hocon::Impl::ResolveResult.make(new_context, nil) elsif joined.size == 1 Hocon::Impl::ResolveResult.make(new_context, joined[0]) else raise ConfigBugOrBrokenError.new( "Bug in the library; resolved list was joined to too many values: #{joined}") end end def resolve_status ResolveStatus::UNRESOLVED end def replace_child(child, replacement) new_pieces = replace_child_in_list(@pieces, child, replacement) if new_pieces == nil nil else self.class.new(origin, new_pieces) end end def has_descendant?(descendant) has_descendant_in_list?(@pieces, descendant) end # when you graft a substitution into another object, # you have to prefix it with the location in that object # where you grafted it; but save prefixLength so # system property and env variable lookups don 't get # broken. def relativized(prefix) new_pieces = [] @pieces.each { |p| new_pieces << p.relativized(prefix) } self.class.new(origin, new_pieces) end def can_equal(other) other.is_a? Hocon::Impl::ConfigConcatenation end def ==(other) if other.is_a? Hocon::Impl::ConfigConcatenation can_equal(other) && @pieces == other.pieces else false end end def hash # note that "origin" is deliberately NOT part of equality @pieces.hash end def render_value_to_sb(sb, indent, at_root, options) @pieces.each do |piece| piece.render_value_to_sb(sb, indent, at_root, options) end end private def not_resolved ConfigNotResolvedError.new("need to Config#resolve(), see the API docs for Config#resolve(); substitution not resolved: #{self}") end def self.is_ignored_whitespace(value) return value.is_a?(ConfigString) && !value.was_quoted? end end hocon-1.2.5/lib/hocon/impl/config_node_root.rb0000644000175000017500000000417213154611745021213 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/config_node_array' require 'hocon/impl/config_node_complex_value' require 'hocon/impl/config_node_object' class Hocon::Impl::ConfigNodeRoot include Hocon::Impl::ConfigNodeComplexValue def initialize(children, origin) super(children) @origin = origin end def new_node(nodes) raise Hocon::ConfigError::ConfigBugOrBrokenError, "Tried to indent the root object" end def value @children.each do |node| if node.is_a?(Hocon::Impl::ConfigNodeComplexValue) return node end end raise Hocon::ConfigError::ConfigBugOrBrokenError, "ConfigNodeRoot did not contain a value" end def set_value(desired_path, value, flavor) children_copy = @children.clone children_copy.each_with_index do |node, index| if node.is_a?(Hocon::Impl::ConfigNodeComplexValue) if node.is_a?(Hocon::Impl::ConfigNodeArray) raise Hocon::ConfigError::ConfigBugOrBrokenError, "The ConfigDocument had an array at the root level, and values cannot be modified inside an array." elsif node.is_a?(Hocon::Impl::ConfigNodeObject) if value.nil? children_copy[index] = node.remove_value_on_path(desired_path, flavor) else children_copy[index] = node.set_value_on_path(desired_path, value, flavor) end return self.class.new(children_copy, @origin) end end end raise Hocon::ConfigError::ConfigBugOrBrokenError, "ConfigNodeRoot did not contain a value" end def has_value(desired_path) path = Hocon::Impl::PathParser.parse_path(desired_path) @children.each do |node| if node.is_a?(Hocon::Impl::ConfigNodeComplexValue) if node.is_a?(Hocon::Impl::ConfigNodeArray) raise Hocon::ConfigError::ConfigBugOrBrokenError, "The ConfigDocument had an array at the root level, and values cannot be modified inside an array." elsif node.is_a?(Hocon::Impl::ConfigNodeObject) return node.has_value(path) end end end raise Hocon::ConfigError::ConfigBugOrBrokenError, "ConfigNodeRoot did not contain a value" end end hocon-1.2.5/lib/hocon/impl/resolve_status.rb0000644000175000017500000000051513154611745020755 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' class Hocon::Impl::ResolveStatus UNRESOLVED = 0 RESOLVED = 1 def self.from_values(values) if values.any? { |v| v.resolve_status == UNRESOLVED } UNRESOLVED else RESOLVED end end def self.from_boolean(resolved) resolved ? RESOLVED : UNRESOLVED end end hocon-1.2.5/lib/hocon/impl/config_impl.rb0000644000175000017500000002003313154611745020156 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/simple_includer' require 'hocon/config_error' require 'hocon/impl/from_map_mode' require 'hocon/impl/simple_config_origin' require 'hocon/impl/simple_config_list' require 'hocon/impl/config_boolean' require 'hocon/impl/config_null' require 'hocon/impl/parseable' class Hocon::Impl::ConfigImpl @default_includer = Hocon::Impl::SimpleIncluder.new(nil) @default_value_origin = Hocon::Impl::SimpleConfigOrigin.new_simple("hardcoded value") @default_true_value = Hocon::Impl::ConfigBoolean.new(@default_value_origin, true) @default_false_value = Hocon::Impl::ConfigBoolean.new(@default_value_origin, false) @default_null_value = Hocon::Impl::ConfigNull.new(@default_value_origin) @default_empty_list = Hocon::Impl::SimpleConfigList.new(@default_value_origin, Array.new) @default_empty_object = Hocon::Impl::SimpleConfigObject.empty(@default_value_origin) ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError ConfigNotResolvedError = Hocon::ConfigError::ConfigNotResolvedError FromMapMode = Hocon::Impl::FromMapMode def self.default_includer @default_includer end class FileNameSource < Hocon::Impl::SimpleIncluder::NameSource def name_to_parseable(name, parse_options) Hocon::Impl::Parseable.new_file(name, parse_options) end end def self.improve_not_resolved(what, original) new_message = "#{what.render} has not been resolved, you need to call Config#resolve, see API docs for Config#resolve" if new_message == original.message return original else return ConfigNotResolvedError.new(new_message, original) end end def self.value_origin(origin_description) if origin_description.nil? return @default_value_origin else return Hocon::Impl::SimpleConfigOrigin.new_simple(origin_description) end end def self.parse_file_any_syntax(basename, base_options) source = FileNameSource.new() Hocon::Impl::SimpleIncluder.from_basename(source, File.expand_path(basename), base_options) end def self.empty_list(origin) if origin.nil? || origin == @default_value_origin return @default_empty_list else return Hocon::Impl::SimpleConfigList.new(origin, Array.new) end end def self.from_any_ref(object, origin_description) origin = self.value_origin(origin_description) from_any_ref_mode(object, origin, FromMapMode::KEYS_ARE_KEYS) end def self.from_any_ref_mode(object, origin, map_mode) if origin.nil? raise ConfigBugOrBrokenError.new("origin not supposed to be nil") end if object.nil? if origin != @default_value_origin return Hocon::Impl::ConfigNull.new(origin) else return @default_null_value end elsif object.is_a?(Hocon::Impl::AbstractConfigValue) return object elsif object.is_a?(TrueClass) || object.is_a?(FalseClass) if origin != @default_value_origin return Hocon::Impl::ConfigBoolean.new(origin, object) elsif object return @default_true_value else return @default_false_value end elsif object.is_a?(String) return Hocon::Impl::ConfigString::Quoted.new(origin, object) elsif object.is_a?(Numeric) # here we always keep the same type that was passed to us, # rather than figuring out if a Long would fit in an Int # or a Double has no fractional part. i.e. deliberately # not using ConfigNumber.newNumber() when we have a # Double, Integer, or Long. if object.is_a?(Float) return Hocon::Impl::ConfigDouble.new(origin, object, nil) elsif object.is_a?(Integer) return Hocon::Impl::ConfigInt.new(origin, object, nil) else return Hocon::Impl::ConfigNumber.new_number(origin, Float(object), nil) end elsif object.is_a?(Hash) if object.empty? return self.empty_object_from_origin(origin) end if map_mode == FromMapMode::KEYS_ARE_KEYS values = Hash.new object.each do |key, entry| if not key.is_a?(String) raise ConfigBugOrBrokenError.new( "bug in method caller: not valid to create ConfigObject from map with non-String key: #{key}") end value = self.from_any_ref_mode(entry, origin, map_mode) values[key] = value end return Hocon::Impl::SimpleConfigObject.new(origin, values) else raise ConfigBugOrBrokenError, "java properties format not supported" end elsif object.is_a?(Enumerable) if object.count == 0 return self.empty_list(origin) end values = Array.new object.each do |item| v = from_any_ref_mode(item, origin, map_mode) values.push(v) end return Hocon::Impl::SimpleConfigList.new(origin, values) else raise ConfigBugOrBrokenError.new("bug in method caller: not valid to create ConfigValue from: #{object}") end end def self.env_variables_as_config_object EnvVariablesHolder.get_env_variables end # This class is a lot simpler than the Java version ... # The Java version uses system properties to toggle these settings. # We don't have system properties in MRI so it's not clear what to do here. # Initially, I ported this as more of a direct translation from the Java code, # but I ran into issues around how to translate stupid Java static # initialization crap to Ruby, so what we have here is a much simpler version # that is # equivalent. # # There's no way to toggle this logging without changing code, but it's # actually proved to be useful for debugging purposes while porting code # down from Java. class DebugHolder class << self def trace_loads_enabled TRACE_LOADS_ENABLED end def trace_substitutions_enabled TRACE_SUBSTITUTIONS_ENABLED end private TRACE_LOADS_ENABLED = false TRACE_SUBSTITUTIONS_ENABLED = false end end def self.trace_loads_enabled # Ignoring 'catch ExceptionInInitializerError' from that java version, # that is just terrible java code anyway. DebugHolder.trace_loads_enabled end def self.trace_substitution_enabled # Ignoring 'catch ExceptionInInitializerError' from that java version, # that is just terrible java code anyway. DebugHolder.trace_substitutions_enabled end def self.trace(message, indent_level = 0) while indent_level > 0 $stderr.putc(" ") indent_level -= 1 end $stderr.puts(message) end def self.empty_object_from_origin(origin) # we want null origin to go to SimpleConfigObject.empty() to get the # origin "empty config" rather than "hardcoded value" if origin == @default_value_origin @default_empty_object else Hocon::Impl::SimpleConfigObject.empty(origin) end end def self.empty_object(origin_description) if !origin_description.nil? origin = Hocon::Impl::SimpleConfigOrigin.new_simple(origin_description) else origin = nil end empty_object_from_origin(origin) end def self.empty_config(origin_description) empty_object(origin_description).to_config end def empty(origin) self.class.empty_object_from_origin(origin) end def self.default_reference resource = Hocon::Impl::Parseable.new_resources("reference.conf", Hocon::ConfigParseOptions.defaults) resource.parse.to_config end private def self.load_env_variables env = ENV m = {} env.each do |key, value| m[key] = Hocon::Impl::ConfigString::Quoted.new( Hocon::Impl::SimpleConfigOrigin.new_simple("env var #{key}"), value) end Hocon::Impl::SimpleConfigObject.new( Hocon::Impl::SimpleConfigOrigin.new_simple("env variables"), m, Hocon::Impl::ResolveStatus::RESOLVED, false) end class EnvVariablesHolder ENV_VARIABLES = Hocon::Impl::ConfigImpl.load_env_variables def self.get_env_variables ENV_VARIABLES end end end hocon-1.2.5/lib/hocon/impl/path.rb0000644000175000017500000001311113154611745016623 0ustar apoikosapoikos# encoding: utf-8 require 'hocon/impl' require 'hocon/impl/path_builder' require 'hocon/config_error' require 'stringio' class Hocon::Impl::Path ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError ConfigImplUtil = Hocon::Impl::ConfigImplUtil def initialize(first, remainder) # first: String, remainder: Path @first = first @remainder = remainder end attr_reader :first, :remainder def self.from_string_list(elements) # This method was translated from the Path constructor in the # Java hocon library that has this signature: # Path(String... elements) # # It figures out what @first and @remainder should be, then # pass those to the ruby constructor if elements.length == 0 raise Hocon::ConfigError::ConfigBugOrBrokenError.new("empty path") end new_first = elements.first if elements.length > 1 pb = Hocon::Impl::PathBuilder.new # Skip first element elements.drop(1).each do |element| pb.append_key(element) end new_remainder = pb.result else new_remainder = nil end self.new(new_first, new_remainder) end def self.from_path_list(path_list) # This method was translated from the Path constructors in the # Java hocon library that take in a list of Paths # # It just passes an iterator to self.from_path_iterator, which # will return a new Path object from_path_iterator(path_list.each) end def self.from_path_iterator(path_iterator) # This method was translated from the Path constructors in the # Java hocon library that takes in an iterator of Paths # # It figures out what @first and @remainder should be, then # pass those to the ruby constructor # Try to get first path from iterator # Ruby iterators have no .hasNext() method like java # So we try to catch the StopIteration exception begin first_path = path_iterator.next rescue StopIteration raise Hocon::ConfigError::ConfigBugOrBrokenError("empty path") end new_first = first_path.first pb = Hocon::Impl::PathBuilder.new unless first_path.remainder.nil? pb.append_path(first_path.remainder) end # Skip first path path_iterator.drop(1).each do |path| pb.append_path(path) end new_remainder = pb.result self.new(new_first, new_remainder) end def first @first end def remainder @remainder end def parent if remainder.nil? return nil end pb = Hocon::Impl::PathBuilder.new p = self while not p.remainder.nil? pb.append_key(p.first) p = p.remainder end pb.result end def last p = self while p.remainder != nil p = p.remainder end p.first end def prepend(to_prepend) pb = Hocon::Impl::PathBuilder.new pb.append_path(to_prepend) pb.append_path(self) pb.result end def length count = 1 p = remainder while p != nil do count += 1 p = p.remainder end count end def sub_path(first_index, last_index) if last_index < first_index raise ConfigBugOrBrokenError.new("bad call to sub_path") end from = sub_path_to_end(first_index) pb = Hocon::Impl::PathBuilder.new count = last_index - first_index while count > 0 do count -= 1 pb.append_key(from.first) from = from.remainder if from.nil? raise ConfigBugOrBrokenError.new("sub_path last_index out of range #{last_index}") end end pb.result end # translated from `subPath(int removeFromFront)` upstream def sub_path_to_end(remove_from_front) count = remove_from_front p = self while (not p.nil?) && count > 0 do count -= 1 p = p.remainder end p end def starts_with(other) my_remainder = self other_remainder = other if other_remainder.length <= my_remainder.length while ! other_remainder.nil? if ! (other_remainder.first == my_remainder.first) return false end my_remainder = my_remainder.remainder other_remainder = other_remainder.remainder end return true end false end def ==(other) if other.is_a? Hocon::Impl::Path that = other first == that.first && ConfigImplUtil.equals_handling_nil?(remainder, that.remainder) else false end end def hash remainder_hash = remainder.nil? ? 0 : remainder.hash 41 * (41 + first.hash) + remainder_hash end # this doesn't have a very precise meaning, just to reduce # noise from quotes in the rendered path for average cases def self.has_funky_chars?(s) length = s.length if length == 0 return false end s.chars.each do |c| unless (c =~ /[[:alnum:]]/) || (c == '-') || (c == '_') return true end end false end def append_to_string_builder(sb) if self.class.has_funky_chars?(@first) || @first.empty? sb << ConfigImplUtil.render_json_string(@first) else sb << @first end unless @remainder.nil? sb << "." @remainder.append_to_string_builder(sb) end end def to_s sb = StringIO.new sb << "Path(" append_to_string_builder(sb) sb << ")" sb.string end def inspect to_s end # # toString() is a debugging-oriented version while this is an # error-message-oriented human-readable one. # def render sb = StringIO.new append_to_string_builder(sb) sb.string end def self.new_key(key) return self.new(key, nil) end def self.new_path(path) Hocon::Impl::PathParser.parse_path(path) end end hocon-1.2.5/lib/hocon/config_value.rb0000644000175000017500000001017713154611745017400 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'hocon/config_mergeable' # # An immutable value, following the JSON type # schema. # #

# Because this object is immutable, it is safe to use from multiple threads and # there's no need for "defensive copies." # #

# Do not implement interface {@code ConfigValue}; it should only be # implemented by the config library. Arbitrary implementations will not work # because the library internals assume a specific concrete implementation. # Also, this interface is likely to grow new methods over time, so third-party # implementations will break. # module Hocon::ConfigValue include Hocon::ConfigMergeable # # The origin of the value (file, line number, etc.), for debugging and # error messages. # # @return where the value came from # def origin raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigValue` must implement `origin` (#{self.class})" end # # The {@link ConfigValueType} of the value; matches the JSON type schema. # # @return value's type # def value_type raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigValue` must implement `value_type` (#{self.class})" end # # Returns the value as a plain Java boxed value, that is, a {@code String}, # {@code Number}, {@code Boolean}, {@code Map}, # {@code List}, or {@code null}, matching the {@link #valueType()} # of this {@code ConfigValue}. If the value is a {@link ConfigObject} or # {@link ConfigList}, it is recursively unwrapped. # @return a plain Java value corresponding to this ConfigValue # def unwrapped raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigValue` must implement `unwrapped` (#{self.class})" end # # Renders the config value to a string, using the provided options. # #

# If the config value has not been resolved (see {@link Config#resolve}), # it's possible that it can't be rendered as valid HOCON. In that case the # rendering should still be useful for debugging but you might not be able # to parse it. If the value has been resolved, it will always be parseable. # #

# If the config value has been resolved and the options disable all # HOCON-specific features (such as comments), the rendering will be valid # JSON. If you enable HOCON-only features such as comments, the rendering # will not be valid JSON. # # @param options # the rendering options # @return the rendered value # def render(options) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigValue` must implement `render` (#{self.class})" end def with_fallback(other) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigValue` must implement `with_fallback` (#{self.class})" end # # Places the value inside a {@link Config} at the given path. See also # {@link ConfigValue#atKey(String)}. # # @param path # path to store this value at. # @return a {@code Config} instance containing this value at the given # path. # def at_path(path) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigValue` must implement `at_path` (#{self.class})" end # # Places the value inside a {@link Config} at the given key. See also # {@link ConfigValue#atPath(String)}. # # @param key # key to store this value at. # @return a {@code Config} instance containing this value at the given key. # def at_key(key) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigValue` must implement `at_key` (#{self.class})" end # # Returns a {@code ConfigValue} based on this one, but with the given # origin. This is useful when you are parsing a new format of file or setting # comments for a single ConfigValue. # # @since 1.3.0 # # @param origin the origin set on the returned value # @return the new ConfigValue with the given origin # def with_origin(origin) raise Hocon::ConfigError::ConfigBugOrBrokenError, "subclasses of `ConfigValue` must implement `with_origin` (#{self.class})" end end hocon-1.2.5/lib/hocon/config_render_options.rb0000644000175000017500000000226013154611745021310 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' class Hocon::ConfigRenderOptions def initialize(origin_comments, comments, formatted, json, key_value_separator=:equals) @origin_comments = origin_comments @comments = comments @formatted = formatted @json = json @key_value_separator = key_value_separator end attr_accessor :origin_comments, :comments, :formatted, :json, :key_value_separator def origin_comments? @origin_comments end def comments? @comments end def formatted? @formatted end def json? @json end # # Returns the default render options which are verbose (commented and # formatted). See {@link ConfigRenderOptions#concise} for stripped-down # options. This rendering will not be valid JSON since it has comments. # # @return the default render options # def self.defaults Hocon::ConfigRenderOptions.new(true, true, true, true) end # # Returns concise render options (no whitespace or comments). For a # resolved {@link Config}, the concise rendering will be valid JSON. # # @return the concise render options # def self.concise Hocon::ConfigRenderOptions.new(false, false, false, true) end end hocon-1.2.5/spec/0000755000175000017500000000000013154611745013462 5ustar apoikosapoikoshocon-1.2.5/spec/fixtures/0000755000175000017500000000000013154611745015333 5ustar apoikosapoikoshocon-1.2.5/spec/fixtures/parse_render/0000755000175000017500000000000013154611745020004 5ustar apoikosapoikoshocon-1.2.5/spec/fixtures/parse_render/example3/0000755000175000017500000000000013154611745021522 5ustar apoikosapoikoshocon-1.2.5/spec/fixtures/parse_render/example3/output.conf0000644000175000017500000000001513154611745023725 0ustar apoikosapoikosa=true b=truehocon-1.2.5/spec/fixtures/parse_render/example3/input.conf0000644000175000017500000000002013154611745023520 0ustar apoikosapoikosa: true b: ${a} hocon-1.2.5/spec/fixtures/parse_render/example1/0000755000175000017500000000000013154611745021520 5ustar apoikosapoikoshocon-1.2.5/spec/fixtures/parse_render/example1/output_nocomments.conf0000644000175000017500000000036613154611745026176 0ustar apoikosapoikosfoo { bar { falsy=false truthy=true yahoo=yippee baz=42 boom=[ 1, 2, { derp=duh }, 4 ] abracadabra=hi } } hocon-1.2.5/spec/fixtures/parse_render/example1/output.conf0000644000175000017500000000077413154611745023737 0ustar apoikosapoikosfoo={ # These are some opening comments # These are some additional opening comments bar={ # falsy falsy=false # truthy truthy=true # as for the yippee # it entails some things yahoo=yippee # the baz is is blah blah baz=42 boom=[ 1, 2, { derp=duh }, 4 ] empty=[] # abracadabra setting abracadabra=hi } } hocon-1.2.5/spec/fixtures/parse_render/example1/input.conf0000644000175000017500000000054713154611745023534 0ustar apoikosapoikos# These are some opening comments # These are some additional opening comments foo.bar { // the baz is is blah blah baz = 42 boom = [1, 2, {derp : duh }, 4] empty = [] # abracadabra setting abracadabra = "hi" } // as for the yippee # it entails some things foo.bar.yahoo = "yippee" # truthy foo.bar.truthy = true # falsy foo.bar.falsy = falsehocon-1.2.5/spec/fixtures/parse_render/example4/0000755000175000017500000000000013154611745021523 5ustar apoikosapoikoshocon-1.2.5/spec/fixtures/parse_render/example4/input.json0000644000175000017500000000013213154611745023551 0ustar apoikosapoikos{ "kermit": "frog", "miss": "piggy", "bert": "ernie", "janice": "guitar" }hocon-1.2.5/spec/fixtures/parse_render/example4/output.conf0000644000175000017500000000012613154611745023731 0ustar apoikosapoikos{ "kermit"="frog", "miss"="piggy", "bert"="ernie", "janice"="guitar" }hocon-1.2.5/spec/fixtures/parse_render/example2/0000755000175000017500000000000013154611745021521 5ustar apoikosapoikoshocon-1.2.5/spec/fixtures/parse_render/example2/output_nocomments.conf0000644000175000017500000000045213154611745026173 0ustar apoikosapoikosjruby-puppet { jruby-pools=[ { environment=production } ] load-path=[ "/usr/lib/ruby/site_ruby/1.8", "/usr/lib/ruby/site_ruby/1.8" ] master-conf-dir="/etc/puppet" master-var-dir="/var/lib/puppet" } webserver { host="1.2.3.4" } hocon-1.2.5/spec/fixtures/parse_render/example2/output.conf0000644000175000017500000000045213154611745023731 0ustar apoikosapoikosjruby-puppet { jruby-pools=[ { environment=production } ] load-path=[ "/usr/lib/ruby/site_ruby/1.8", "/usr/lib/ruby/site_ruby/1.8" ] master-conf-dir="/etc/puppet" master-var-dir="/var/lib/puppet" } webserver { host="1.2.3.4" } hocon-1.2.5/spec/fixtures/parse_render/example2/input.conf0000644000175000017500000000035613154611745023533 0ustar apoikosapoikosjruby-puppet: { jruby-pools: [{environment: production}] load-path: [/usr/lib/ruby/site_ruby/1.8, /usr/lib/ruby/site_ruby/1.8] master-conf-dir: /etc/puppet master-var-dir: /var/lib/puppet } webserver: { host: 1.2.3.4 } hocon-1.2.5/spec/fixtures/test_utils/0000755000175000017500000000000013154611745017532 5ustar apoikosapoikoshocon-1.2.5/spec/fixtures/test_utils/resources/0000755000175000017500000000000013154611745021544 5ustar apoikosapoikoshocon-1.2.5/spec/fixtures/test_utils/resources/file-include.conf0000644000175000017500000000021413154611745024750 0ustar apoikosapoikosbase=41 # included without file() in a subdir include "subdir/foo.conf" # included using file() in a subdir include file("subdir/baz.conf") hocon-1.2.5/spec/fixtures/test_utils/resources/cycle.conf0000644000175000017500000000002513154611745023507 0ustar apoikosapoikosinclude "cycle.conf" hocon-1.2.5/spec/fixtures/test_utils/resources/bom.conf0000644000175000017500000000001713154611745023166 0ustar apoikosapoikos# foo = bar hocon-1.2.5/spec/fixtures/test_utils/resources/test03.conf0000644000175000017500000000132613154611745023537 0ustar apoikosapoikos{ "test01" : { "ints" : 12, include "test01", "booleans" : 42 }, "test02" : { include "test02.conf" }, "equiv01" : { include "equiv01/original.json" }, # missing includes are supposed to be silently ignored nonexistent { include "nothere" include "nothere.conf" include "nothere.json" include "nothere.properties" } # make sure included file substitutions fall back to parent file, # both when the include is at the root (so doesn't need to have # substitutions adjusted) and when it is not. foo="This is in the including file" bar="This is in the including file" include "test03-included.conf" subtree { include "test03-included.conf" } } hocon-1.2.5/spec/fixtures/test_utils/resources/include-from-list.conf0000644000175000017500000000022313154611745025745 0ustar apoikosapoikos// The {} inside the [] is needed because // just [ include ] means an array with the // string "include" in it. a = [ { include "test01.conf" } ] hocon-1.2.5/spec/fixtures/test_utils/resources/test01.json0000644000175000017500000000005613154611745023560 0ustar apoikosapoikos{ "fromJson1" : 1, "fromJsonA" : "A" }hocon-1.2.5/spec/fixtures/test_utils/resources/utf16.conf0000644000175000017500000000005013154611745023353 0ustar apoikosapoikosÿþ#  Ç» = ëÒæ¦ë ±© ¢ hocon-1.2.5/spec/fixtures/test_utils/resources/utf8.conf0000644000175000017500000000005513154611745023301 0ustar apoikosapoikos# ᚠᛇᚻ = ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢ hocon-1.2.5/spec/fixtures/test_utils/resources/ᚠᛇᚻ.conf0000644000175000017500000000005513154611745025616 0ustar apoikosapoikos# ᚠᛇᚻ = ᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢ hocon-1.2.5/spec/fixtures/test_utils/resources/subdir/0000755000175000017500000000000013154611745023034 5ustar apoikosapoikoshocon-1.2.5/spec/fixtures/test_utils/resources/subdir/bar.conf0000644000175000017500000000000713154611745024444 0ustar apoikosapoikosbar=43 hocon-1.2.5/spec/fixtures/test_utils/resources/subdir/baz.conf0000644000175000017500000000000713154611745024454 0ustar apoikosapoikosbaz=45 hocon-1.2.5/spec/fixtures/test_utils/resources/subdir/foo.conf0000644000175000017500000000015213154611745024464 0ustar apoikosapoikosfoo=42 # included without file() include "bar.conf" # included using file() include file("bar-file.conf") hocon-1.2.5/spec/fixtures/test_utils/resources/test01.conf0000644000175000017500000000404713154611745023540 0ustar apoikosapoikos{ "ints" : { "fortyTwo" : 42, "fortyTwoAgain" : ${ints.fortyTwo} }, "floats" : { "fortyTwoPointOne" : 42.1, "fortyTwoPointOneAgain" : ${floats.fortyTwoPointOne} }, "strings" : { "abcd" : "abcd", "abcdAgain" : ${strings.a}${strings.b}${strings.c}${strings.d}, "a" : "a", "b" : "b", "c" : "c", "d" : "d", "concatenated" : null bar 42 baz true 3.14 hi, "double" : "3.14", "number" : "57", "null" : "null", "true" : "true", "yes" : "yes", "false" : "false", "no" : "no" }, "arrays" : { "empty" : [], "ofInt" : [1, 2, 3], "ofString" : [ ${strings.a}, ${strings.b}, ${strings.c} ], "ofDouble" : [3.14, 4.14, 5.14], "ofNull" : [null, null, null], "ofBoolean" : [true, false], "ofArray" : [${arrays.ofString}, ${arrays.ofString}, ${arrays.ofString}], "ofObject" : [${ints}, ${booleans}, ${strings}], "firstElementNotASubst" : [ "a", ${strings.b} ] }, "booleans" : { "true" : true, "trueAgain" : ${booleans.true}, "false" : false, "falseAgain" : ${booleans.false} }, "nulls" : { "null" : null, "nullAgain" : ${nulls.null} }, "durations" : { "second" : 1s, "secondsList" : [1s,2seconds,3 s, 4000], "secondAsNumber" : 1000, "halfSecond" : 0.5s, "millis" : 1 milli, "micros" : 2000 micros }, "memsizes" : { "meg" : 1M, "megsList" : [1M, 1024K, 1048576], "megAsNumber" : 1048576, "halfMeg" : 0.5M }, "system" : { "javaversion" : ${?java.version}, "userhome" : ${?user.home}, "home" : ${?HOME}, "pwd" : ${?PWD}, "shell" : ${?SHELL}, "lang" : ${?LANG}, "path" : ${?PATH}, "not_here" : ${?NOT_HERE}, "concatenated" : Your Java version is ${?system.javaversion} and your user.home is ${?system.userhome} } } hocon-1.2.5/spec/fixtures/hocon/0000755000175000017500000000000013154611745016441 5ustar apoikosapoikoshocon-1.2.5/spec/fixtures/hocon/by_extension/0000755000175000017500000000000013154611745021147 5ustar apoikosapoikoshocon-1.2.5/spec/fixtures/hocon/by_extension/cat.test-json0000644000175000017500000000002413154611745023562 0ustar apoikosapoikos{ "meow": "cats" }hocon-1.2.5/spec/fixtures/hocon/by_extension/cat.test0000644000175000017500000000003613154611745022616 0ustar apoikosapoikos# Comment { "meow": "cats" }hocon-1.2.5/spec/fixtures/hocon/by_extension/cat.conf0000644000175000017500000000003613154611745022564 0ustar apoikosapoikos# Comment { "meow": "cats" }hocon-1.2.5/spec/fixtures/hocon/with_substitution/0000755000175000017500000000000013154611745022250 5ustar apoikosapoikoshocon-1.2.5/spec/fixtures/hocon/with_substitution/subst.conf0000644000175000017500000000002013154611745024247 0ustar apoikosapoikosa: true b: ${a} hocon-1.2.5/spec/test_utils.rb0000644000175000017500000006761013154611745016220 0ustar apoikosapoikos# encoding: utf-8 require 'hocon' require 'spec_helper' require 'rspec' require 'hocon/impl/config_reference' require 'hocon/impl/substitution_expression' require 'hocon/impl/path_parser' require 'hocon/impl/config_impl_util' require 'hocon/impl/config_node_simple_value' require 'hocon/impl/config_node_single_token' require 'hocon/impl/config_node_object' require 'hocon/impl/config_node_array' require 'hocon/impl/config_node_concatenation' require 'hocon/cli' module TestUtils Tokens = Hocon::Impl::Tokens ConfigInt = Hocon::Impl::ConfigInt ConfigDouble = Hocon::Impl::ConfigDouble ConfigString = Hocon::Impl::ConfigString ConfigNull = Hocon::Impl::ConfigNull ConfigBoolean = Hocon::Impl::ConfigBoolean ConfigReference = Hocon::Impl::ConfigReference SubstitutionExpression = Hocon::Impl::SubstitutionExpression ConfigConcatenation = Hocon::Impl::ConfigConcatenation Path = Hocon::Impl::Path EOF = Hocon::Impl::TokenType::EOF include RSpec::Matchers def self.intercept(exception_type, & block) thrown = nil result = nil begin result = block.call rescue => e if e.is_a?(exception_type) thrown = e else raise "Expected exception #{exception_type} was not thrown, got #{e.class}: #{e}\n#{e.backtrace.join("\n")}" end end if thrown.nil? raise "Expected exception #{exception_type} was not thrown, no exception was thrown and got result #{result}" end thrown end class ParseTest def self.from_s(test) ParseTest.new(false, false, test) end def self.from_pair(lift_behavior_unexpected, test) ParseTest.new(lift_behavior_unexpected, false, test) end def initialize(lift_behavior_unexpected, whitespace_matters, test) @lift_behavior_unexpected = lift_behavior_unexpected @whitespace_matters = whitespace_matters @test = test end attr_reader :test def lift_behavior_unexpected? @lift_behavior_unexpected end def whitespace_matters? @whitespace_matters end end # note: it's important to put {} or [] at the root if you # want to test "invalidity reasons" other than "wrong root" InvalidJsonInvalidConf = [ ParseTest.from_s("{"), ParseTest.from_s("}"), ParseTest.from_s("["), ParseTest.from_s("]"), ParseTest.from_s(","), ParseTest.from_pair(true, "10"), # value not in array or object, lift-json now allows this ParseTest.from_pair(true, "\"foo\""), # value not in array or object, lift-json allows it ParseTest.from_s(")\""), # single quote by itself ParseTest.from_pair(true, "[,]"), # array with just a comma in it; lift is OK with this ParseTest.from_pair(true, "[,,]"), # array with just two commas in it; lift is cool with this too ParseTest.from_pair(true, "[1,2,,]"), # array with two trailing commas ParseTest.from_pair(true, "[,1,2]"), # array with initial comma ParseTest.from_pair(true, "{ , }"), # object with just a comma in it ParseTest.from_pair(true, "{ , , }"), # object with just two commas in it ParseTest.from_s("{ 1,2 }"), # object with single values not key-value pair ParseTest.from_pair(true, '{ , "foo" : 10 }'), # object starts with comma ParseTest.from_pair(true, "{ \"foo\" : 10 ,, }"), # object has two trailing commas ParseTest.from_s(") \"a\" : 10 ,, "), # two trailing commas for braceless root object ParseTest.from_s("{ \"foo\" : }"), # no value in object ParseTest.from_s("{ : 10 }"), # no key in object ParseTest.from_pair(true, " \"foo\" : "), # no value in object with no braces; lift-json thinks this is acceptable ParseTest.from_pair(true, " : 10 "), # no key in object with no braces; lift-json is cool with this too ParseTest.from_s(') "foo" : 10 } '), # close brace but no open ParseTest.from_s(") \"foo\" : 10 } "), # close brace but no open ParseTest.from_s(") \"foo\" : 10 [ "), # no-braces object with trailing gunk ParseTest.from_s("{ \"foo\" }"), # no value or colon ParseTest.from_s("{ \"a\" : [ }"), # [ is not a valid value ParseTest.from_s("{ \"foo\" : 10, true }"), # non-key after comma ParseTest.from_s("{ foo \n bar : 10 }"), # newline in the middle of the unquoted key ParseTest.from_s("[ 1, \\"), # ends with backslash # these two problems are ignored by the lift tokenizer ParseTest.from_s("[:\"foo\", \"bar\"]"), # colon in an array; lift doesn't throw (tokenizer erases it) ParseTest.from_s("[\"foo\" : \"bar\"]"), # colon in an array another way, lift ignores (tokenizer erases it) ParseTest.from_s("[ \"hello ]"), # unterminated string ParseTest.from_pair(true, "{ \"foo\" , true }"), # comma instead of colon, lift is fine with this ParseTest.from_pair(true, "{ \"foo\" : true \"bar\" : false }"), # missing comma between fields, lift fine with this ParseTest.from_s("[ 10, }]"), # array with } as an element ParseTest.from_s("[ 10, {]"), # array with { as an element ParseTest.from_s("{}x"), # trailing invalid token after the root object ParseTest.from_s("[]x"), # trailing invalid token after the root array ParseTest.from_pair(true, "{}{}"), # trailing token after the root object - lift OK with it ParseTest.from_pair(true, "{}true"), # trailing token after the root object; lift ignores the {} ParseTest.from_pair(true, "[]{}"), # trailing valid token after the root array ParseTest.from_pair(true, "[]true"), # trailing valid token after the root array, lift ignores the [] ParseTest.from_s("[${]"), # unclosed substitution ParseTest.from_s("[$]"), # '$' by itself ParseTest.from_s("[$ ]"), # '$' by itself with spaces after ParseTest.from_s("[${}]"), # empty substitution (no path) ParseTest.from_s("[${?}]"), # no path with ? substitution ParseTest.new(false, true, "[${ ?foo}]"), # space before ? not allowed ParseTest.from_s(%q|{ "a" : [1,2], "b" : y${a}z }|), # trying to interpolate an array in a string ParseTest.from_s(%q|{ "a" : { "c" : 2 }, "b" : y${a}z }|), # trying to interpolate an object in a string ParseTest.from_s(%q|{ "a" : ${a} }|), # simple cycle ParseTest.from_s(%q|[ { "a" : 2, "b" : ${${a}} } ]|), # nested substitution ParseTest.from_s("[ = ]"), # = is not a valid token in unquoted text ParseTest.from_s("[ + ]"), ParseTest.from_s("[ # ]"), ParseTest.from_s("[ ` ]"), ParseTest.from_s("[ ^ ]"), ParseTest.from_s("[ ? ]"), ParseTest.from_s("[ ! ]"), ParseTest.from_s("[ @ ]"), ParseTest.from_s("[ * ]"), ParseTest.from_s("[ & ]"), ParseTest.from_s("[ \\ ]"), ParseTest.from_s("+="), ParseTest.from_s("[ += ]"), ParseTest.from_s("+= 10"), ParseTest.from_s("10 +="), ParseTest.from_s("[ 10e+3e ]"), # "+" not allowed in unquoted strings, and not a valid number ParseTest.from_pair(true, "[ \"foo\nbar\" ]"), # unescaped newline in quoted string, lift doesn't care ParseTest.from_s("[ # comment ]"), ParseTest.from_s("${ #comment }"), ParseTest.from_s("[ // comment ]"), ParseTest.from_s("${ // comment }"), # ParseTest.from_s("{ include \"bar\" : 10 }"), # include with a value after it ParseTest.from_s("{ include foo }"), # include with unquoted string ParseTest.from_s("{ include : { \"a\" : 1 } }"), # include used as unquoted key ParseTest.from_s("a="), # no value ParseTest.from_s("a:"), # no value with colon ParseTest.from_s("a= "), # no value with whitespace after ParseTest.from_s("a.b="), # no value with path ParseTest.from_s("{ a= }"), # no value inside braces ParseTest.from_s("{ a: }") # no value with colon inside braces ] # We'll automatically try each of these with whitespace modifications # so no need to add every possible whitespace variation ValidJson = [ ParseTest.from_s("{}"), ParseTest.from_s("[]"), ParseTest.from_s(%q|{ "foo" : "bar" }|), ParseTest.from_s(%q|["foo", "bar"]|), ParseTest.from_s(%q|{ "foo" : 42 }|), ParseTest.from_s("{ \"foo\"\n : 42 }"), # newline after key ParseTest.from_s("{ \"foo\" : \n 42 }"), # newline after colon ParseTest.from_s(%q|[10, 11]|), ParseTest.from_s(%q|[10,"foo"]|), ParseTest.from_s(%q|{ "foo" : "bar", "baz" : "boo" }|), ParseTest.from_s(%q|{ "foo" : { "bar" : "baz" }, "baz" : "boo" }|), ParseTest.from_s(%q|{ "foo" : { "bar" : "baz", "woo" : "w00t" }, "baz" : "boo" }|), ParseTest.from_s(%q|{ "foo" : [10,11,12], "baz" : "boo" }|), ParseTest.from_s(%q|[{},{},{},{}]|), ParseTest.from_s(%q|[[[[[[]]]]]]|), ParseTest.from_s(%q|[[1], [1,2], [1,2,3], []]|), # nested multiple-valued array ParseTest.from_s(%q|{"a":{"a":{"a":{"a":{"a":{"a":{"a":{"a":42}}}}}}}}|), ParseTest.from_s("[ \"#comment\" ]"), # quoted # comment ParseTest.from_s("[ \"//comment\" ]"), # quoted // comment # this long one is mostly to test rendering ParseTest.from_s(%q|{ "foo" : { "bar" : "baz", "woo" : "w00t" }, "baz" : { "bar" : "baz", "woo" : [1,2,3,4], "w00t" : true, "a" : false, "b" : 3.14, "c" : null } }|), ParseTest.from_s("{}"), ParseTest.from_pair(true, "[ 10e+3 ]") # "+" in a number (lift doesn't handle)) ] ValidConfInvalidJson = [ ParseTest.from_s(""), # empty document ParseTest.from_s(" "), # empty document single space ParseTest.from_s("\n"), # empty document single newline ParseTest.from_s(" \n \n \n\n\n"), # complicated empty document ParseTest.from_s("# foo"), # just a comment ParseTest.from_s("# bar\n"), # just a comment with a newline ParseTest.from_s("# foo\n//bar"), # comment then another with no newline ParseTest.from_s(%q|{ "foo" = 42 }|), # equals rather than colon ParseTest.from_s(%q|{ foo { "bar" : 42 } }|), # omit the colon for object value ParseTest.from_s(%q|{ foo baz { "bar" : 42 } }|), # omit the colon with unquoted key with spaces ParseTest.from_s(%q| "foo" : 42 |), # omit braces on root object ParseTest.from_s(%q|{ "foo" : bar }|), # no quotes on value ParseTest.from_s(%q|{ "foo" : null bar 42 baz true 3.14 "hi" }|), # bunch of values to concat into a string ParseTest.from_s("{ foo : \"bar\" }"), # no quotes on key ParseTest.from_s("{ foo : bar }"), # no quotes on key or value ParseTest.from_s("{ foo.bar : bar }"), # path expression in key ParseTest.from_s("{ foo.\"hello world\".baz : bar }"), # partly-quoted path expression in key ParseTest.from_s("{ foo.bar \n : bar }"), # newline after path expression in key ParseTest.from_s("{ foo bar : bar }"), # whitespace in the key ParseTest.from_s("{ true : bar }"), # key is a non-string token ParseTest.from_pair(true, %q|{ "foo" : "bar", "foo" : "bar2" }|), # dup keys - lift just returns both ParseTest.from_pair(true, "[ 1, 2, 3, ]"), # single trailing comma (lift fails to throw) ParseTest.from_pair(true, "[1,2,3 , ]"), # single trailing comma with whitespace ParseTest.from_pair(true, "[1,2,3\n\n , \n]"), # single trailing comma with newlines ParseTest.from_pair(true, "[1,]"), # single trailing comma with one-element array ParseTest.from_pair(true, "{ \"foo\" : 10, }"), # extra trailing comma (lift fails to throw) ParseTest.from_pair(true, "{ \"a\" : \"b\", }"), # single trailing comma in object ParseTest.from_s("{ a : b, }"), # single trailing comma in object (unquoted strings) ParseTest.from_s("{ a : b \n , \n }"), # single trailing comma in object with newlines ParseTest.from_s("a : b, c : d,"), # single trailing comma in object with no root braces ParseTest.from_s("{ a : b\nc : d }"), # skip comma if there's a newline ParseTest.from_s("a : b\nc : d"), # skip comma if there's a newline and no root braces ParseTest.from_s("a : b\nc : d,"), # skip one comma but still have one at the end ParseTest.from_s("[ foo ]"), # not a known token in JSON ParseTest.from_s("[ t ]"), # start of "true" but ends wrong in JSON ParseTest.from_s("[ tx ]"), ParseTest.from_s("[ tr ]"), ParseTest.from_s("[ trx ]"), ParseTest.from_s("[ tru ]"), ParseTest.from_s("[ trux ]"), ParseTest.from_s("[ truex ]"), ParseTest.from_s("[ 10x ]"), # number token with trailing junk ParseTest.from_s("[ / ]"), # unquoted string "slash" ParseTest.from_s("{ include \"foo\" }"), # valid include ParseTest.from_s("{ include\n\"foo\" }"), # include with just a newline separating from string ParseTest.from_s("{ include\"foo\" }"), # include with no whitespace after it ParseTest.from_s("[ include ]"), # include can be a string value in an array ParseTest.from_s("{ foo : include }"), # include can be a field value also ParseTest.from_s("{ include \"foo\", \"a\" : \"b\" }"), # valid include followed by comma and field ParseTest.from_s("{ foo include : 42 }"), # valid to have a key not starting with include ParseTest.from_s("[ ${foo} ]"), ParseTest.from_s("[ ${?foo} ]"), ParseTest.from_s("[ ${\"foo\"} ]"), ParseTest.from_s("[ ${foo.bar} ]"), ParseTest.from_s("[ abc xyz ${foo.bar} qrs tuv ]"), # value concatenation ParseTest.from_s("[ 1, 2, 3, blah ]"), ParseTest.from_s("[ ${\"foo.bar\"} ]"), ParseTest.from_s("{} # comment"), ParseTest.from_s("{} // comment"), ParseTest.from_s(%q|{ "foo" #comment : 10 }|), ParseTest.from_s(%q|{ "foo" // comment : 10 }|), ParseTest.from_s(%q|{ "foo" : #comment 10 }|), ParseTest.from_s(%q|{ "foo" : // comment 10 }|), ParseTest.from_s(%q|{ "foo" : 10 #comment }|), ParseTest.from_s(%q|{ "foo" : 10 // comment }|), ParseTest.from_s(%q|[ 10, # comment 11]|), ParseTest.from_s(%q|[ 10, // comment 11]|), ParseTest.from_s(%q|[ 10 # comment , 11]|), ParseTest.from_s(%q|[ 10 // comment , 11]|), ParseTest.from_s(%q|{ /a/b/c : 10 }|), # key has a slash in it ParseTest.new(false, true, "[${ foo.bar}]"), # substitution with leading spaces ParseTest.new(false, true, "[${foo.bar }]"), # substitution with trailing spaces ParseTest.new(false, true, "[${ \"foo.bar\"}]"), # substitution with leading spaces and quoted ParseTest.new(false, true, "[${\"foo.bar\" }]"), # substitution with trailing spaces and quoted ParseTest.from_s(%q|[ ${"foo""bar"} ]|), # multiple strings in substitution ParseTest.from_s(%q|[ ${foo "bar" baz} ]|), # multiple strings and whitespace in substitution ParseTest.from_s("[${true}]"), # substitution with unquoted true token ParseTest.from_s("a = [], a += b"), # += operator with previous init ParseTest.from_s("{ a = [], a += 10 }"), # += in braces object with previous init ParseTest.from_s("a += b"), # += operator without previous init ParseTest.from_s("{ a += 10 }"), # += in braces object without previous init ParseTest.from_s("[ 10e3e3 ]"), # two exponents. this should parse to a number plus string "e3" ParseTest.from_s("[ 1-e3 ]"), # malformed number should end up as a string instead ParseTest.from_s("[ 1.0.0 ]"), # two decimals, should end up as a string ParseTest.from_s("[ 1.0. ]") ] InvalidConf = InvalidJsonInvalidConf # .conf is a superset of JSON so validJson just goes in here ValidConf = ValidConfInvalidJson + ValidJson def self.add_offending_json_to_exception(parser_name, s, & block) begin block.call rescue => e tokens = begin "tokens: " + TestUtils.tokenize_as_list(s).join("\n") rescue => tokenize_ex "tokenizer failed: #{tokenize_ex}\n#{tokenize_ex.backtrace.join("\n")}" end raise ArgumentError, "#{parser_name} parser did wrong thing on '#{s}', #{tokens}; error: #{e}\n#{e.backtrace.join("\n")}" end end def self.whitespace_variations(tests, valid_in_lift) variations = [ Proc.new { |s| s }, # identity Proc.new { |s| " " + s }, Proc.new { |s| s + " " }, Proc.new { |s| " " + s + " " }, Proc.new { |s| s.gsub(" ", "") }, # this would break with whitespace in a key or value Proc.new { |s| s.gsub(":", " : ") }, # could break with : in a key or value Proc.new { |s| s.gsub(",", " , ") }, # could break with , in a key or value ] tests.map { |t| if t.whitespace_matters? t else with_no_ascii = if t.test.include?(" ") [ParseTest.from_pair(valid_in_lift, t.test.gsub(" ", "\u2003"))] # 2003 = em space, to test non-ascii whitespace else [] end with_no_ascii << variations.reduce([]) { |acc, v| acc << ParseTest.from_pair(t.lift_behavior_unexpected?, v.call(t.test)) acc } end }.flatten end ################## # Tokenizer Functions ################## def self.wrap_tokens(token_list) # Wraps token_list in START and EOF tokens [Tokens::START] + token_list + [Tokens::EOF] end def self.tokenize(config_origin, input) Hocon::Impl::Tokenizer.tokenize(config_origin, input, Hocon::ConfigSyntax::CONF) end def self.tokenize_from_s(s) tokenize(Hocon::Impl::SimpleConfigOrigin.new_simple("anonymous Reader"), StringIO.new(s)) end def self.tokenize_as_list(input_string) token_iterator = tokenize_from_s(input_string) token_iterator.to_list end def self.tokenize_as_string(input_string) Hocon::Impl::Tokenizer.render(tokenize_from_s(input_string)) end def self.config_node_simple_value(value) Hocon::Impl::ConfigNodeSimpleValue.new(value) end def self.config_node_key(path) Hocon::Impl::PathParser.parse_path_node(path) end def self.config_node_single_token(value) Hocon::Impl::ConfigNodeSingleToken.new(value) end def self.config_node_object(nodes) Hocon::Impl::ConfigNodeObject.new(nodes) end def self.config_node_array(nodes) Hocon::Impl::ConfigNodeArray.new(nodes) end def self.config_node_concatenation(nodes) Hocon::Impl::ConfigNodeConcatenation.new(nodes) end def self.node_colon Hocon::Impl::ConfigNodeSingleToken.new(Tokens::COLON) end def self.node_space Hocon::Impl::ConfigNodeSingleToken.new(token_unquoted(" ")) end def self.node_open_brace Hocon::Impl::ConfigNodeSingleToken.new(Tokens::OPEN_CURLY) end def self.node_close_brace Hocon::Impl::ConfigNodeSingleToken.new(Tokens::CLOSE_CURLY) end def self.node_open_bracket Hocon::Impl::ConfigNodeSingleToken.new(Tokens::OPEN_SQUARE) end def self.node_close_bracket Hocon::Impl::ConfigNodeSingleToken.new(Tokens::CLOSE_SQUARE) end def self.node_comma Hocon::Impl::ConfigNodeSingleToken.new(Tokens::COMMA) end def self.node_line(line) Hocon::Impl::ConfigNodeSingleToken.new(token_line(line)) end def self.node_whitespace(whitespace) Hocon::Impl::ConfigNodeSingleToken.new(token_whitespace(whitespace)) end def self.node_key_value_pair(key, value) nodes = [key, node_colon, node_space, value] Hocon::Impl::ConfigNodeField.new(nodes) end def self.node_int(value) Hocon::Impl::ConfigNodeSimpleValue.new(token_int(value)) end def self.node_string(value) Hocon::Impl::ConfigNodeSimpleValue.new(token_string(value)) end def self.node_double(value) Hocon::Impl::ConfigNodeSimpleValue.new(token_double(value)) end def self.node_true Hocon::Impl::ConfigNodeSimpleValue.new(token_true) end def self.node_false Hocon::Impl::ConfigNodeSimpleValue.new(token_false) end def self.node_comment_hash(text) Hocon::Impl::ConfigNodeComment.new(token_comment_hash(text)) end def self.node_comment_double_slash(text) Hocon::Impl::ConfigNodeComment.new(token_comment_double_slash(text)) end def self.node_unquoted_text(text) Hocon::Impl::ConfigNodeSimpleValue.new(token_unquoted(text)) end def self.node_null Hocon::Impl::ConfigNodeSimpleValue.new(token_null) end def self.node_key_substitution(s) Hocon::Impl::ConfigNodeSimpleValue.new(token_key_substitution(s)) end def self.node_optional_substitution(*expression) Hocon::Impl::ConfigNodeSimpleValue.new(token_optional_substitution(*expression)) end def self.node_substitution(*expression) Hocon::Impl::ConfigNodeSimpleValue.new(token_substitution(*expression)) end def self.fake_origin Hocon::Impl::SimpleConfigOrigin.new_simple("fake origin") end def self.token_line(line_number) Tokens.new_line(fake_origin.with_line_number(line_number)) end def self.token_true Tokens.new_boolean(fake_origin, true) end def self.token_false Tokens.new_boolean(fake_origin, false) end def self.token_null Tokens.new_null(fake_origin) end def self.token_unquoted(value) Tokens.new_unquoted_text(fake_origin, value) end def self.token_comment_double_slash(value) Tokens.new_comment_double_slash(fake_origin, value) end def self.token_comment_hash(value) Tokens.new_comment_hash(fake_origin, value) end def self.token_whitespace(value) Tokens.new_ignored_whitespace(fake_origin, value) end def self.token_string(value) Tokens.new_string(fake_origin, value, "\"#{value}\"") end def self.token_double(value) Tokens.new_double(fake_origin, value, "#{value}") end def self.token_int(value) Tokens.new_int(fake_origin, value, "#{value}") end def self.token_maybe_optional_substitution(optional, token_list) Tokens.new_substitution(fake_origin, optional, token_list) end def self.token_substitution(*token_list) token_maybe_optional_substitution(false, token_list) end def self.token_optional_substitution(*token_list) token_maybe_optional_substitution(true, token_list) end def self.token_key_substitution(value) token_substitution(token_string(value)) end def self.parse_object(s) parse_config(s).root end def self.parse_config(s) options = Hocon::ConfigParseOptions.defaults. set_origin_description("test string"). set_syntax(Hocon::ConfigSyntax::CONF) Hocon::ConfigFactory.parse_string(s, options) end ################## # ConfigValue helpers ################## def self.int_value(value) ConfigInt.new(fake_origin, value, nil) end def self.double_value(value) ConfigDouble.new(fake_origin, value, nil) end def self.string_value(value) ConfigString::Quoted.new(fake_origin, value) end def self.null_value ConfigNull.new(fake_origin) end def self.bool_value(value) ConfigBoolean.new(fake_origin, value) end def self.config_map(input_map) # Turns {String: Int} maps into {String: ConfigInt} maps Hash[ input_map.map { |k, v| [k, int_value(v)] } ] end def self.subst(ref, optional = false) path = Path.new_path(ref) ConfigReference.new(fake_origin, SubstitutionExpression.new(path, optional)) end def self.subst_in_string(ref, optional = false) pieces = [string_value("start<"), subst(ref, optional), string_value(">end")] ConfigConcatenation.new(fake_origin, pieces) end ################## # Token Functions ################## class NotEqualToAnythingElse def ==(other) other.is_a? NotEqualToAnythingElse end def hash 971 end end ################## # Path Functions ################## def self.path(*elements) # this is importantly NOT using Path.newPath, which relies on # the parser; in the test suite we are often testing the parser, # so we don't want to use the parser to build the expected result. Path.from_string_list(elements) end RESOURCE_DIR = "spec/fixtures/test_utils/resources" def self.resource_file(filename) File.join(RESOURCE_DIR, filename) end def self.json_quoted_resource_file(filename) quote_json_string(resource_file(filename).to_s) end def self.quote_json_string(s) Hocon::Impl::ConfigImplUtil.render_json_string(s) end ################## # RSpec Tests ################## def self.check_equal_objects(first_object, second_object) it "should find the two objects to be equal" do not_equal_to_anything_else = TestUtils::NotEqualToAnythingElse.new # Equality expect(first_object).to eq(second_object) expect(second_object).to eq(first_object) # Hashes expect(first_object.hash).to eq(second_object.hash) # Other random object expect(first_object).not_to eq(not_equal_to_anything_else) expect(not_equal_to_anything_else).not_to eq(first_object) expect(second_object).not_to eq(not_equal_to_anything_else) expect(not_equal_to_anything_else).not_to eq(second_object) end end def self.check_not_equal_objects(first_object, second_object) it "should find the two objects to be not equal" do not_equal_to_anything_else = TestUtils::NotEqualToAnythingElse.new # Equality expect(first_object).not_to eq(second_object) expect(second_object).not_to eq(first_object) # Hashes # hashcode inequality isn't guaranteed, but # as long as it happens to work it might # detect a bug (if hashcodes are equal, # check if it's due to a bug or correct # before you remove this) expect(first_object.hash).not_to eq(second_object.hash) # Other random object expect(first_object).not_to eq(not_equal_to_anything_else) expect(not_equal_to_anything_else).not_to eq(first_object) expect(second_object).not_to eq(not_equal_to_anything_else) expect(not_equal_to_anything_else).not_to eq(second_object) end end end ################## # RSpec Shared Examples ################## # Examples for comparing an object that won't equal anything but itself # Used in the object_equality examples below shared_examples_for "not_equal_to_other_random_thing" do let(:not_equal_to_anything_else) { TestUtils::NotEqualToAnythingElse.new } it "should find the first object not equal to a random other thing" do expect(first_object).not_to eq(not_equal_to_anything_else) expect(not_equal_to_anything_else).not_to eq(first_object) end it "should find the second object not equal to a random other thing" do expect(second_object).not_to eq(not_equal_to_anything_else) expect(not_equal_to_anything_else).not_to eq(second_object) end end # Examples for making sure two objects are equal shared_examples_for "object_equality" do it "should find the first object to be equal to the second object" do expect(first_object).to eq(second_object) end it "should find the second object to be equal to the first object" do expect(second_object).to eq(first_object) end it "should find the hash codes of the two objects to be equal" do expect(first_object.hash).to eq(second_object.hash) end include_examples "not_equal_to_other_random_thing" end # Examples for making sure two objects are not equal shared_examples_for "object_inequality" do it "should find the first object to not be equal to the second object" do expect(first_object).not_to eq(second_object) end it "should find the second object to not be equal to the first object" do expect(second_object).not_to eq(first_object) end it "should find the hash codes of the two objects to not be equal" do # hashcode inequality isn't guaranteed, but # as long as it happens to work it might # detect a bug (if hashcodes are equal, # check if it's due to a bug or correct # before you remove this) expect(first_object.hash).not_to eq(second_object.hash) end include_examples "not_equal_to_other_random_thing" end shared_examples_for "path_render_test" do it "should find the expected rendered text equal to the rendered path" do expect(path.render).to eq(expected) end it "should find the path equal to the parsed expected text" do expect(Hocon::Impl::PathParser.parse_path(expected)).to eq(path) end it "should find the path equal to the parsed text that came from the rendered path" do expect(Hocon::Impl::PathParser.parse_path(path.render)).to eq(path) end end hocon-1.2.5/spec/unit/0000755000175000017500000000000013154611745014441 5ustar apoikosapoikoshocon-1.2.5/spec/unit/typesafe/0000755000175000017500000000000013154611745016261 5ustar apoikosapoikoshocon-1.2.5/spec/unit/typesafe/config/0000755000175000017500000000000013154611745017526 5ustar apoikosapoikoshocon-1.2.5/spec/unit/typesafe/config/tokenizer_spec.rb0000644000175000017500000007053613154611745023112 0ustar apoikosapoikos# encoding: utf-8 require 'spec_helper' require 'hocon' require 'test_utils' require 'pp' describe Hocon::Impl::Tokenizer do Tokens = Hocon::Impl::Tokens shared_examples_for "token_matching" do it "should match the tokenized string to the list of expected tokens" do tokenized_from_string = TestUtils.tokenize_as_list(test_string) tokenized_as_string = TestUtils.tokenize_as_string(test_string) # Add START and EOF tokens wrapped_tokens = TestUtils.wrap_tokens(expected_tokens) # Compare the two lists of tokens expect(tokenized_from_string).to eq(wrapped_tokens) expect(tokenized_as_string).to eq(test_string) end end shared_examples_for "strings_with_problems" do it "should find a problem when tokenizing" do token_list = TestUtils.tokenize_as_list(test_string) expect(token_list.map { |token| Tokens.problem?(token) }).to include(true) end end #################### # Whitespace #################### context "tokenizing whitespace" do context "tokenize empty string" do let(:test_string) { "" } let(:expected_tokens) { [] } include_examples "token_matching" end context "tokenize newlines" do let(:test_string) { "\n\n" } let(:expected_tokens) { [TestUtils.token_line(1), TestUtils.token_line(2)] } include_examples "token_matching" end context "tokenize unquoted text should keep spaces" do let(:test_string) { " foo \n" } let(:expected_tokens) { [TestUtils.token_whitespace(" "), TestUtils.token_unquoted("foo"), TestUtils.token_whitespace(" "), TestUtils.token_line(1)] } include_examples "token_matching" end context "tokenize unquoted text with internal spaces should keep spaces" do let(:test_string) { " foo bar baz \n" } let(:expected_tokens) { [TestUtils.token_whitespace(" "), TestUtils.token_unquoted("foo"), TestUtils.token_unquoted(" "), TestUtils.token_unquoted("bar"), TestUtils.token_unquoted(" "), TestUtils.token_unquoted("baz"), TestUtils.token_whitespace(" "), TestUtils.token_line(1)] } include_examples "token_matching" end end #################### # Booleans and Null #################### context "tokenizing booleans and null" do context "tokenize true and unquoted text" do let(:test_string) { "truefoo" } let(:expected_tokens) { [TestUtils.token_true, TestUtils.token_unquoted("foo")] } include_examples "token_matching" end context "tokenize false and unquoted text" do let(:test_string) { "falsefoo" } let(:expected_tokens) { [TestUtils.token_false, TestUtils.token_unquoted("foo")] } include_examples "token_matching" end context "tokenize null and unquoted text" do let(:test_string) { "nullfoo" } let(:expected_tokens) { [TestUtils.token_null, TestUtils.token_unquoted("foo")] } include_examples "token_matching" end context "tokenize unquoted text containing true" do let(:test_string) { "footrue" } let(:expected_tokens) { [TestUtils.token_unquoted("footrue")] } include_examples "token_matching" end context "tokenize unquoted text containing space and true" do let(:test_string) { "foo true" } let(:expected_tokens) { [TestUtils.token_unquoted("foo"), TestUtils.token_unquoted(" "), TestUtils.token_true] } include_examples "token_matching" end context "tokenize true and space and unquoted text" do let(:test_string) { "true foo" } let(:expected_tokens) { [TestUtils.token_true, TestUtils.token_unquoted(" "), TestUtils.token_unquoted("foo")] } include_examples "token_matching" end end #################### # Slashes #################### context "tokenizing slashes" do context "tokenize unquoted text containing slash" do let(:test_string) { "a/b/c/" } let(:expected_tokens) { [TestUtils.token_unquoted("a/b/c/")] } include_examples "token_matching" end context "tokenize slash" do let(:test_string) { "/" } let(:expected_tokens) { [TestUtils.token_unquoted("/")] } include_examples "token_matching" end context "tokenize slash space slash" do let(:test_string) { "/ /" } let(:expected_tokens) { [TestUtils.token_unquoted("/"), TestUtils.token_unquoted(" "), TestUtils.token_unquoted("/")] } include_examples "token_matching" end #################### # Quotes #################### context "tokenize mixed unquoted and quoted" do let(:test_string) { " foo\"bar\"baz \n" } let(:expected_tokens) { [TestUtils.token_whitespace(" "), TestUtils.token_unquoted("foo"), TestUtils.token_string("bar"), TestUtils.token_unquoted("baz"), TestUtils.token_whitespace(" "), TestUtils.token_line(1)] } include_examples "token_matching" end context "tokenize empty triple quoted string" do let(:test_string) { '""""""' } let(:expected_tokens) { [TestUtils.token_string("")] } include_examples "token_matching" end context "tokenize trivial triple quoted string" do let(:test_string) { '"""bar"""' } let(:expected_tokens) { [TestUtils.token_string("bar")] } include_examples "token_matching" end context "tokenize no escapes in triple quoted string" do let(:test_string) { '"""\n"""' } let(:expected_tokens) { [TestUtils.token_string('\n')] } include_examples "token_matching" end context "tokenize trailing quotes in triple quoted string" do let(:test_string) { '"""""""""' } let(:expected_tokens) { [TestUtils.token_string('"""')] } include_examples "token_matching" end context "tokenize new line in triple quoted string" do let(:test_string) { '"""foo\nbar"""' } let(:expected_tokens) { [TestUtils.token_string('foo\nbar')] } include_examples "token_matching" end end #################### # Find problems when tokenizing #################### context "finding problems when tokenizing" do context "nothing after backslash" do let(:test_string) { ' "\" ' } include_examples "strings_with_problems" end context "there is no \q escape sequence" do let(:test_string) { ' "\q" ' } include_examples "strings_with_problems" end context "unicode byte sequence missing a byte" do let(:test_string) { '"\u123"' } include_examples "strings_with_problems" end context "unicode byte sequence missing two bytes" do let(:test_string) { '"\u12"' } include_examples "strings_with_problems" end context "unicode byte sequence missing three bytes" do let(:test_string) { '"\u1"' } include_examples "strings_with_problems" end context "unicode byte missing" do let(:test_string) { '"\u"' } include_examples "strings_with_problems" end context "just a single quote" do let(:test_string) { '"' } include_examples "strings_with_problems" end context "no end quote" do let(:test_string) { ' "abcdefg' } include_examples "strings_with_problems" end context "file ends with a backslash" do let(:test_string) { '\"\\' } include_examples "strings_with_problems" end context "file ends with a $" do let(:test_string) { "$" } include_examples "strings_with_problems" end context "file ends with a ${" do let(:test_string) { "${" } include_examples "strings_with_problems" end end #################### # Numbers #################### context "tokenizing numbers" do context "parse positive float" do let(:test_string) { "1.2" } let(:expected_tokens) { [TestUtils.token_double(1.2)] } include_examples "token_matching" end context "parse negative float" do let(:test_string) { "-1.2" } let(:expected_tokens) { [TestUtils.token_double(-1.2)] } include_examples "token_matching" end context "parse exponent notation" do let(:test_string) { "1e6" } let(:expected_tokens) { [TestUtils.token_double(1e6)] } include_examples "token_matching" end context "parse negative exponent" do let(:test_string) { "1e-6" } let(:expected_tokens) { [TestUtils.token_double(1e-6)] } include_examples "token_matching" end context "parse exponent with capital E" do let(:test_string) { "1E-6" } let(:expected_tokens) { [TestUtils.token_double(1e-6)] } include_examples "token_matching" end context "parse negative int" do let(:test_string) { "-1" } let(:expected_tokens) { [TestUtils.token_int(-1)] } include_examples "token_matching" end end #################### # Comments #################### context "tokenizing comments" do context "tokenize two slashes as comment" do let(:test_string) { "//" } let(:expected_tokens) { [TestUtils.token_comment_double_slash("")] } include_examples "token_matching" end context "tokenize two slashes in string as string" do let(:test_string) { '"//bar"' } let(:expected_tokens) { [TestUtils.token_string("//bar")] } include_examples "token_matching" end context "tokenize hash in string as string" do let(:test_string) { '"#bar"' } let(:expected_tokens) { [TestUtils.token_string("#bar")] } include_examples "token_matching" end context "tokenize slash comment after unquoted text" do let(:test_string) { "bar//comment" } let(:expected_tokens) { [TestUtils.token_unquoted("bar"), TestUtils.token_comment_double_slash("comment")] } include_examples "token_matching" end context "tokenize hash comment after unquoted text" do let(:test_string) { "bar#comment" } let(:expected_tokens) { [TestUtils.token_unquoted("bar"), TestUtils.token_comment_hash("comment")] } include_examples "token_matching" end context "tokenize slash comment after int" do let(:test_string) { "10//comment" } let(:expected_tokens) { [TestUtils.token_int(10), TestUtils.token_comment_double_slash("comment")] } include_examples "token_matching" end context "tokenize hash comment after int" do let(:test_string) { "10#comment" } let(:expected_tokens) { [TestUtils.token_int(10), TestUtils.token_comment_hash("comment")] } include_examples "token_matching" end context "tokenize hash comment after int" do let(:test_string) { "10#comment" } let(:expected_tokens) { [TestUtils.token_int(10), TestUtils.token_comment_hash("comment")] } include_examples "token_matching" end context "tokenize slash comment after float" do let(:test_string) { "3.14//comment" } let(:expected_tokens) { [TestUtils.token_double(3.14), TestUtils.token_comment_double_slash("comment")] } include_examples "token_matching" end context "tokenize hash comment after float" do let(:test_string) { "3.14#comment" } let(:expected_tokens) { [TestUtils.token_double(3.14), TestUtils.token_comment_hash("comment")] } include_examples "token_matching" end context "tokenize slash comment with newline" do let(:test_string) { "10//comment\n12" } let(:expected_tokens) { [TestUtils.token_int(10), TestUtils.token_comment_double_slash("comment"), TestUtils.token_line(1), TestUtils.token_int(12)] } include_examples "token_matching" end context "tokenize hash comment with newline" do let(:test_string) { "10#comment\n12" } let(:expected_tokens) { [TestUtils.token_int(10), TestUtils.token_comment_hash("comment"), TestUtils.token_line(1), TestUtils.token_int(12)] } include_examples "token_matching" end context "tokenize slash comments on two consecutive lines" do let(:test_string) { "//comment\n//comment2" } let(:expected_tokens) { [TestUtils.token_comment_double_slash("comment"), TestUtils.token_line(1), TestUtils.token_comment_double_slash("comment2")] } include_examples "token_matching" end context "tokenize hash comments on two consecutive lines" do let(:test_string) { "#comment\n#comment2" } let(:expected_tokens) { [TestUtils.token_comment_hash("comment"), TestUtils.token_line(1), TestUtils.token_comment_hash("comment2")] } include_examples "token_matching" end context "tokenize slash comments on multiple lines with whitespace" do let(:test_string) { " //comment\r\n //comment2 \n//comment3 \n\n//comment4" } let(:expected_tokens) { [TestUtils.token_whitespace(" "), TestUtils.token_comment_double_slash("comment\r"), TestUtils.token_line(1), TestUtils.token_whitespace(" "), TestUtils.token_comment_double_slash("comment2 "), TestUtils.token_line(2), TestUtils.token_comment_double_slash("comment3 "), TestUtils.token_line(3), TestUtils.token_line(4), TestUtils.token_comment_double_slash("comment4")] } include_examples "token_matching" end context "tokenize hash comments on multiple lines with whitespace" do let(:test_string) { " #comment\r\n #comment2 \n#comment3 \n\n#comment4" } let(:expected_tokens) { [TestUtils.token_whitespace(" "), TestUtils.token_comment_hash("comment\r"), TestUtils.token_line(1), TestUtils.token_whitespace(" "), TestUtils.token_comment_hash("comment2 "), TestUtils.token_line(2), TestUtils.token_comment_hash("comment3 "), TestUtils.token_line(3), TestUtils.token_line(4), TestUtils.token_comment_hash("comment4")] } include_examples "token_matching" end end #################### # Brackets, braces #################### context "tokenizing brackets and braces" do context "tokenize open curly braces" do let(:test_string) { "{{" } let(:expected_tokens) { [Tokens::OPEN_CURLY, Tokens::OPEN_CURLY] } include_examples "token_matching" end context "tokenize close curly braces" do let(:test_string) { "}}" } let(:expected_tokens) { [Tokens::CLOSE_CURLY, Tokens::CLOSE_CURLY] } include_examples "token_matching" end context "tokenize open and close curly braces" do let(:test_string) { "{}" } let(:expected_tokens) { [Tokens::OPEN_CURLY, Tokens::CLOSE_CURLY] } include_examples "token_matching" end context "tokenize open and close curly braces" do let(:test_string) { "{}" } let(:expected_tokens) { [Tokens::OPEN_CURLY, Tokens::CLOSE_CURLY] } include_examples "token_matching" end context "tokenize open square brackets" do let(:test_string) { "[[" } let(:expected_tokens) { [Tokens::OPEN_SQUARE, Tokens::OPEN_SQUARE] } include_examples "token_matching" end context "tokenize close square brackets" do let(:test_string) { "]]" } let(:expected_tokens) { [Tokens::CLOSE_SQUARE, Tokens::CLOSE_SQUARE] } include_examples "token_matching" end context "tokenize open and close square brackets" do let(:test_string) { "[]" } let(:expected_tokens) { [Tokens::OPEN_SQUARE, Tokens::CLOSE_SQUARE] } include_examples "token_matching" end end #################### # comma, colon, equals, plus equals #################### context "tokenizing comma, colon, equals, and plus equals" do context "tokenize comma" do let(:test_string) { "," } let(:expected_tokens) { [Tokens::COMMA] } include_examples "token_matching" end context "tokenize colon" do let(:test_string) { ":" } let(:expected_tokens) { [Tokens::COLON] } include_examples "token_matching" end context "tokenize equals" do let(:test_string) { "=" } let(:expected_tokens) { [Tokens::EQUALS] } include_examples "token_matching" end context "tokenize plus equals" do let(:test_string) { "+=" } let(:expected_tokens) { [Tokens::PLUS_EQUALS] } include_examples "token_matching" end context "tokenize comma, colon, plus equals, and equals together" do let(:test_string) { "=:,+=" } let(:expected_tokens) { [Tokens::EQUALS, Tokens::COLON, Tokens::COMMA, Tokens::PLUS_EQUALS] } include_examples "token_matching" end end #################### # Substitutions #################### context "tokenizing substitutions" do context "tokenize substitution" do let(:test_string) { "${a.b}" } let(:expected_tokens) { [TestUtils.token_substitution(TestUtils.token_unquoted("a.b"))] } include_examples "token_matching" end context "tokenize optional substitution" do let(:test_string) { "${?x.y}" } let(:expected_tokens) { [TestUtils.token_optional_substitution(TestUtils.token_unquoted("x.y"))] } include_examples "token_matching" end context "tokenize key substitution" do let(:test_string) { '${"c.d"}' } let(:expected_tokens) { [TestUtils.token_key_substitution("c.d")] } include_examples "token_matching" end end #################### # Unicode and escape characters #################### context "tokenizing unicode and escape characters" do context "tokenize unicode infinity symbol" do let(:test_string) { '"\u221E"' } let(:expected_tokens) { [TestUtils.token_string("\u{221E}")] } include_examples "token_matching" end context "tokenize null byte" do let(:test_string) { ' "\u0000" ' } let(:expected_tokens) { [TestUtils.token_whitespace(" "), TestUtils.token_string("\u0000"), TestUtils.token_whitespace(" ")] } include_examples "token_matching" end context "tokenize various espace codes" do let(:test_string) { ' "\"\\\/\b\f\n\r\t" ' } let(:expected_tokens) { [TestUtils.token_whitespace(" "), TestUtils.token_string("\"\\/\b\f\n\r\t"), TestUtils.token_whitespace(" ")] } include_examples "token_matching" end context "tokenize unicode F" do let(:test_string) { ' "\u0046" ' } let(:expected_tokens) { [TestUtils.token_whitespace(" "), TestUtils.token_string("F"), TestUtils.token_whitespace(" ")] } include_examples "token_matching" end context "tokenize two unicode Fs" do let(:test_string) { ' "\u0046\u0046" ' } let(:expected_tokens) { [TestUtils.token_whitespace(" "), TestUtils.token_string("FF"), TestUtils.token_whitespace(" ")] } include_examples "token_matching" end end #################### # Reserved Characters #################### context "Finding problems with using reserved characters" do context "problem with reserved character +" do let(:test_string) { "+" } include_examples "strings_with_problems" end context "problem with reserved character `" do let(:test_string) { "`" } include_examples "strings_with_problems" end context "problem with reserved character ^" do let(:test_string) { "^" } include_examples "strings_with_problems" end context "problem with reserved character ?" do let(:test_string) { "?" } include_examples "strings_with_problems" end context "problem with reserved character !" do let(:test_string) { "!" } include_examples "strings_with_problems" end context "problem with reserved character @" do let(:test_string) { "@" } include_examples "strings_with_problems" end context "problem with reserved character *" do let(:test_string) { "*" } include_examples "strings_with_problems" end context "problem with reserved character &" do let(:test_string) { "&" } include_examples "strings_with_problems" end context "problem with reserved character \\" do let(:test_string) { "\\" } include_examples "strings_with_problems" end end #################### # Combine all types #################### context "Tokenizing all types together" do context "tokenize all types no spaces" do let(:test_string) { ',:=}{][+="foo""""bar"""true3.14false42null${a.b}${?x.y}${"c.d"}' + "\n" } let(:expected_tokens) { [Tokens::COMMA, Tokens::COLON, Tokens::EQUALS, Tokens::CLOSE_CURLY, Tokens::OPEN_CURLY, Tokens::CLOSE_SQUARE, Tokens::OPEN_SQUARE, Tokens::PLUS_EQUALS, TestUtils.token_string("foo"), TestUtils.token_string("bar"), TestUtils.token_true, TestUtils.token_double(3.14), TestUtils.token_false, TestUtils.token_int(42), TestUtils.token_null, TestUtils.token_substitution(TestUtils.token_unquoted("a.b")), TestUtils.token_optional_substitution(TestUtils.token_unquoted("x.y")), TestUtils.token_key_substitution("c.d"), TestUtils.token_line(1)] } include_examples "token_matching" end context "tokenize all types single spaces" do let(:test_string) { ' , : = } { ] [ += "foo" """bar""" 42 true 3.14 false null ${a.b} ${?x.y} ${"c.d"} ' + "\n " } let(:expected_tokens) { [TestUtils.token_whitespace(" "), Tokens::COMMA, TestUtils.token_whitespace(" "), Tokens::COLON, TestUtils.token_whitespace(" "), Tokens::EQUALS, TestUtils.token_whitespace(" "), Tokens::CLOSE_CURLY, TestUtils.token_whitespace(" "), Tokens::OPEN_CURLY, TestUtils.token_whitespace(" "), Tokens::CLOSE_SQUARE, TestUtils.token_whitespace(" "), Tokens::OPEN_SQUARE, TestUtils.token_whitespace(" "), Tokens::PLUS_EQUALS, TestUtils.token_whitespace(" "), TestUtils.token_string("foo"), TestUtils.token_unquoted(" "), TestUtils.token_string("bar"), TestUtils.token_unquoted(" "), TestUtils.token_int(42), TestUtils.token_unquoted(" "), TestUtils.token_true, TestUtils.token_unquoted(" "), TestUtils.token_double(3.14), TestUtils.token_unquoted(" "), TestUtils.token_false, TestUtils.token_unquoted(" "), TestUtils.token_null, TestUtils.token_unquoted(" "), TestUtils.token_substitution(TestUtils.token_unquoted("a.b")), TestUtils.token_unquoted(" "), TestUtils.token_optional_substitution(TestUtils.token_unquoted("x.y")), TestUtils.token_unquoted(" "), TestUtils.token_key_substitution("c.d"), TestUtils.token_whitespace(" "), TestUtils.token_line(1), TestUtils.token_whitespace(" ")] } include_examples "token_matching" end context "tokenize all types multiple spaces" do let(:test_string) { ' , : = } { ] [ += "foo" """bar""" 42 true 3.14 false null ${a.b} ${?x.y} ${"c.d"} ' + "\n " } let(:expected_tokens) { [TestUtils.token_whitespace(" "), Tokens::COMMA, TestUtils.token_whitespace(" "), Tokens::COLON, TestUtils.token_whitespace(" "), Tokens::EQUALS, TestUtils.token_whitespace(" "), Tokens::CLOSE_CURLY, TestUtils.token_whitespace(" "), Tokens::OPEN_CURLY, TestUtils.token_whitespace(" "), Tokens::CLOSE_SQUARE, TestUtils.token_whitespace(" "), Tokens::OPEN_SQUARE, TestUtils.token_whitespace(" "), Tokens::PLUS_EQUALS, TestUtils.token_whitespace(" "), TestUtils.token_string("foo"), TestUtils.token_unquoted(" "), TestUtils.token_string("bar"), TestUtils.token_unquoted(" "), TestUtils.token_int(42), TestUtils.token_unquoted(" "), TestUtils.token_true, TestUtils.token_unquoted(" "), TestUtils.token_double(3.14), TestUtils.token_unquoted(" "), TestUtils.token_false, TestUtils.token_unquoted(" "), TestUtils.token_null, TestUtils.token_unquoted(" "), TestUtils.token_substitution(TestUtils.token_unquoted("a.b")), TestUtils.token_unquoted(" "), TestUtils.token_optional_substitution(TestUtils.token_unquoted("x.y")), TestUtils.token_unquoted(" "), TestUtils.token_key_substitution("c.d"), TestUtils.token_whitespace(" "), TestUtils.token_line(1), TestUtils.token_whitespace(" ")] } include_examples "token_matching" end end end hocon-1.2.5/spec/unit/typesafe/config/concatenation_spec.rb0000644000175000017500000003616213154611745023722 0ustar apoikosapoikos# encoding: utf-8 require 'test_utils' describe "concatenation" do it "string concat, no substitutions" do conf = TestUtils.parse_config(' a : true "xyz" 123 foo ').resolve expect(conf.get_string("a")).to eq("true xyz 123 foo") end it "trivial string concat" do conf = TestUtils.parse_config(" a : ${x}foo, x = 1 ").resolve expect(conf.get_string("a")).to eq("1foo") end it "two substitutions and string concat" do conf = TestUtils.parse_config(" a : ${x}foo${x}, x = 1 ").resolve expect(conf.get_string("a")).to eq("1foo1") end it "string concat cannot span lines" do e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) { TestUtils.parse_config(" a : ${x} foo, x = 1 ") } expect(e.message).to include("not be followed") expect(e.message).to include("','") end it "no objects in string concat" do e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) { TestUtils.parse_config(" a : abc { x : y } ") } expect(e.message).to include("Cannot concatenate") expect(e.message).to include("abc") expect(e.message).to include('{"x":"y"}') end it "no object concat with nil" do e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) { TestUtils.parse_config(" a : null { x : y } ") } expect(e.message).to include("Cannot concatenate") expect(e.message).to include("null") expect(e.message).to include('{"x":"y"}') end it "no arrays in string concat" do e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) { TestUtils.parse_config(" a : abc [1, 2] ") } expect(e.message).to include("Cannot concatenate") expect(e.message).to include("abc") expect(e.message).to include("[1,2]") end it "no objects substituted in string concat" do e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) { TestUtils.parse_config(" a : abc ${x}, x : { y : z } ").resolve } expect(e.message).to include("Cannot concatenate") expect(e.message).to include("abc") end it "no arrays substituted in string concat" do e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) { TestUtils.parse_config(" a : abc ${x}, x : [1,2] ").resolve } expect(e.message).to include("Cannot concatenate") expect(e.message).to include("abc") end it "no substitutions in list concat" do conf = TestUtils.parse_config(" a : [1,2] [3,4] ") expect([1, 2, 3, 4]).to eq(conf.get_list("a").unwrapped) end it "list concat with substitutions" do conf = TestUtils.parse_config(" a : ${x} [3,4] ${y}, x : [1,2], y : [5,6] ").resolve expect([1, 2, 3, 4, 5, 6]).to eq(conf.get_list("a").unwrapped) end it "list concat self referential" do conf = TestUtils.parse_config(" a : [1, 2], a : ${a} [3,4], a : ${a} [5,6] ").resolve expect([1, 2, 3, 4, 5, 6]).to eq(conf.get_list("a").unwrapped) end it "no substitutions in list concat cannot span lines" do e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) { TestUtils.parse_config(" a : [1,2] [3,4] ") } expect(e.message).to include("expecting") expect(e.message).to include("'['") end it "list concat can span lines inside brackest" do conf = TestUtils.parse_config(" a : [1,2 ] [3,4] ") expect([1, 2, 3, 4]).to eq(conf.get_list("a").unwrapped) end it "no substitutions object concat" do conf = TestUtils.parse_config(" a : { b : c } { x : y } ") expect({"b" => "c", "x" => "y"}).to eq(conf.get_object("a").unwrapped) end it "object concat merge order" do conf = TestUtils.parse_config(" a : { b : 1 } { b : 2 } { b : 3 } { b : 4 } ") expect(4).to eq(conf.get_int("a.b")) end it "object concat with substitutions" do conf = TestUtils.parse_config(" a : ${x} { b : 1 } ${y}, x : { a : 0 }, y : { c : 2 } ").resolve expect({"a" => 0, "b" => 1, "c" => 2}).to eq(conf.get_object("a").unwrapped) end it "object concat self referential" do conf = TestUtils.parse_config(" a : { a : 0 }, a : ${a} { b : 1 }, a : ${a} { c : 2 } ").resolve expect({"a" => 0, "b" => 1, "c" => 2}).to eq(conf.get_object("a").unwrapped) end it "object concat self referential override" do conf = TestUtils.parse_config(" a : { b : 3 }, a : { b : 2 } ${a} ").resolve expect({"b" => 3}).to eq(conf.get_object("a").unwrapped) end it "no substitutions object concat cannot span lines" do e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) { TestUtils.parse_config(" a : { b : c } { x : y }") } expect(e.message).to include("expecting") expect(e.message).to include("'{'") end it "object concat can span lines inside braces" do conf = TestUtils.parse_config(" a : { b : c } { x : y } ") expect({"b" => "c", "x" => "y"}).to eq(conf.get_object("a").unwrapped) end it "string concat inside array value" do conf = TestUtils.parse_config(" a : [ foo bar 10 ] ") expect(["foo bar 10"]).to eq(conf.get_string_list("a")) end it "string non concat inside array value" do conf = TestUtils.parse_config(" a : [ foo bar 10 ] ") expect(["foo", "bar", "10"]).to eq(conf.get_string_list("a")) end it "object concat inside array value" do conf = TestUtils.parse_config(" a : [ { b : c } { x : y } ] ") expect([{"b" => "c", "x" => "y"}]).to eq(conf.get_object_list("a").map { |x| x.unwrapped }) end it "object non concat inside array value" do conf = TestUtils.parse_config(" a : [ { b : c } { x : y } ] ") expect([{"b" => "c"}, {"x" => "y"}]).to eq(conf.get_object_list("a").map { |x| x.unwrapped }) end it "list concat inside array value" do conf = TestUtils.parse_config(" a : [ [1, 2] [3, 4] ] ") expect([[1,2,3,4]]).to eq(conf.get_list("a").unwrapped) end it "list non concat inside array value" do conf = TestUtils.parse_config(" a : [ [1, 2] [3, 4] ] ") expect([[1, 2], [3, 4]]).to eq(conf.get_list("a").unwrapped) end it "string concats are keys" do conf = TestUtils.parse_config(' 123 foo : "value" ') expect("value").to eq(conf.get_string("123 foo")) end it "objects are not keys" do e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) { TestUtils.parse_config('{ { a : 1 } : "value" }') } expect(e.message).to include("expecting a close") expect(e.message).to include("'{'") end it "arrays are not keys" do e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) { TestUtils.parse_config('{ [ "a" ] : "value" }') } expect(e.message).to include("expecting a close") expect(e.message).to include("'['") end it "empty array plus equals" do conf = TestUtils.parse_config(' a = [], a += 2 ').resolve expect([2]).to eq(conf.get_int_list("a")) end it "missing array plus equals" do conf = TestUtils.parse_config(' a += 2 ').resolve expect([2]).to eq(conf.get_int_list("a")) end it "short array plus equals" do conf = TestUtils.parse_config(' a = [1], a += 2 ').resolve expect([1, 2]).to eq(conf.get_int_list("a")) end it "number plus equals" do e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) { TestUtils.parse_config(' a = 10, a += 2 ').resolve } expect(e.message).to include("Cannot concatenate") expect(e.message).to include("10") expect(e.message).to include("[2]") end it "string plus equals" do e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) { TestUtils.parse_config(' a = abc, a += 2 ').resolve } expect(e.message).to include("Cannot concatenate") expect(e.message).to include("abc") expect(e.message).to include("[2]") end it "objects plus equals" do e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) { TestUtils.parse_config(' a = { x : y }, a += 2 ').resolve } expect(e.message).to include("Cannot concatenate") expect(e.message).to include("\"x\":\"y\"") expect(e.message).to include("[2]") end it "plus equals nested path" do conf = TestUtils.parse_config(' a.b.c = [1], a.b.c += 2 ').resolve expect([1, 2]).to eq(conf.get_int_list("a.b.c")) end it "plus equals nested objects" do conf = TestUtils.parse_config(' a : { b : { c : [1] } }, a : { b : { c += 2 } }').resolve expect([1, 2]).to eq(conf.get_int_list("a.b.c")) end it "plus equals single nested object" do conf = TestUtils.parse_config(' a : { b : { c : [1], c += 2 } }').resolve expect([1, 2]).to eq(conf.get_int_list("a.b.c")) end it "substitution plus equals substitution" do conf = TestUtils.parse_config(' a = ${x}, a += ${y}, x = [1], y = 2 ').resolve expect([1, 2]).to eq(conf.get_int_list("a")) end it "plus equals multiple times" do conf = TestUtils.parse_config(' a += 1, a += 2, a += 3 ').resolve expect([1, 2, 3]).to eq(conf.get_int_list("a")) end it "plus equals multiple times nested" do conf = TestUtils.parse_config(' x { a += 1, a += 2, a += 3 } ').resolve expect([1, 2, 3]).to eq(conf.get_int_list("x.a")) end it "plus equals an object multiple times" do conf = TestUtils.parse_config(' a += { b: 1 }, a += { b: 2 }, a += { b: 3 } ').resolve expect([1, 2, 3]).to eq(conf.get_object_list("a").map { |x| x.to_config.get_int("b")}) end it "plus equals an object multiple times nested" do conf = TestUtils.parse_config(' x { a += { b: 1 }, a += { b: 2 }, a += { b: 3 } } ').resolve expect([1, 2, 3]).to eq(conf.get_object_list("x.a").map { |x| x.to_config.get_int("b") }) end # We would ideally make this case NOT throw an exception but we need to do some work # to get there, see https: // github.com/typesafehub/config/issues/160 it "plus equals multiple times nested in array" do e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) { conf = TestUtils.parse_config('x = [ { a += 1, a += 2, a += 3 } ] ').resolve expect([1, 2, 3]).to eq(conf.get_object_list("x").to_config.get_int_list("a")) } expect(e.message).to include("limitation") end # We would ideally make this case NOT throw an exception but we need to do some work # to get there, see https: // github.com/typesafehub/config/issues/160 it "plus equals multiple times nested in plus equals" do e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) { conf = TestUtils.parse_config('x += { a += 1, a += 2, a += 3 } ').resolve expect([1, 2, 3]).to eq(conf.get_object_list("x").to_config.get_int_list("a")) } expect(e.message).to include("limitation") end # from https://github.com/typesafehub/config/issues/177 it "array concatenation in double nested delayed merge" do unresolved = TestUtils.parse_config("d { x = [] }, c : ${d}, c { x += 1, x += 2 }") conf = unresolved.resolve expect([1,2]).to eq(conf.get_int_list("c.x")) end # from https://github.com/typesafehub/config/issues/177 it "array concatenation as part of delayed merge" do unresolved = TestUtils.parse_config(" c { x: [], x : ${c.x}[1], x : ${c.x}[2] }") conf = unresolved.resolve expect([1,2]).to eq(conf.get_int_list("c.x")) end # from https://github.com/typesafehub/config/issues/177 it "array concatenation in double nested delayed merge 2" do unresolved = TestUtils.parse_config("d { x = [] }, c : ${d}, c { x : ${c.x}[1], x : ${c.x}[2] }") conf = unresolved.resolve expect([1,2]).to eq(conf.get_int_list("c.x")) end # from https://github.com/typesafehub/config/issues/177 it "array concatenation in triple nested delayed merge" do unresolved = TestUtils.parse_config("{ r: { d.x=[] }, q: ${r}, q : { d { x = [] }, c : ${q.d}, c { x : ${q.c.x}[1], x : ${q.c.x}[2] } } }") conf = unresolved.resolve expect([1,2]).to eq(conf.get_int_list("q.c.x")) end it "concat undefined substitution with string" do conf = TestUtils.parse_config("a = foo${?bar}").resolve expect("foo").to eq(conf.get_string("a")) end it "concat defined optional substitution with string" do conf = TestUtils.parse_config("bar=bar, a = foo${?bar}").resolve expect("foobar").to eq(conf.get_string("a")) end it "concat defined substitution with array" do conf = TestUtils.parse_config("a = [1] ${?bar}").resolve expect([1]).to eq(conf.get_int_list("a")) end it "concat defined optional substitution with array" do conf = TestUtils.parse_config("bar=[2], a = [1] ${?bar}").resolve expect([1, 2]).to eq(conf.get_int_list("a")) end it "concat undefined substitution with object" do conf = TestUtils.parse_config('a = { x : "foo" } ${?bar}').resolve expect('foo').to eq(conf.get_string("a.x")) end it "concat defined optional substitution with object" do conf = TestUtils.parse_config('bar={ y : 42 }, a = { x : "foo" } ${?bar}').resolve expect('foo').to eq(conf.get_string("a.x")) expect(42).to eq(conf.get_int("a.y")) end it "concat two undefined substitutions" do conf = TestUtils.parse_config("a = ${?foo}${?bar}").resolve expect(conf.has_path?("a")).to be_falsey end it "concat several undefined substitutions" do conf = TestUtils.parse_config("a = ${?foo}${?bar}${?baz}${?woooo}").resolve expect(conf.has_path?("a")).to be_falsey end it "concat two undefined substitutions with a space" do conf = TestUtils.parse_config("a = ${?foo} ${?bar}").resolve expect(conf.get_string("a")).to eq(" ") end it "concat two defined substitutions with a space" do conf = TestUtils.parse_config("foo=abc, bar=def, a = ${foo} ${bar}").resolve expect(conf.get_string("a")).to eq("abc def") end it "concat two undefined substitutions with empty string" do conf = TestUtils.parse_config('a = ""${?foo}${?bar}').resolve expect(conf.get_string("a")).to eq("") end it "concat substitutions that are objects with no space" do conf = TestUtils.parse_config('foo = { a : 1}, bar = { b : 2 }, x = ${foo}${bar}').resolve expect(1).to eq(conf.get_int("x.a")) expect(2).to eq(conf.get_int("x.b")) end # whitespace is insignificant if substitutions don't turn out to be a string it "concat substitutions that are objects with space" do conf = TestUtils.parse_config('foo = { a : 1}, bar = { b : 2 }, x = ${foo} ${bar}').resolve expect(1).to eq(conf.get_int("x.a")) expect(2).to eq(conf.get_int("x.b")) end # whitespace is insignificant if substitutions don't turn out to be a string it "concat substitutions that are lists with space" do conf = TestUtils.parse_config('foo = [1], bar = [2], x = ${foo} ${bar}').resolve expect([1,2]).to eq(conf.get_int_list("x")) end # but quoted whitespace should be an error it "concat substitutions that are objects with quoted space" do e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) { conf = TestUtils.parse_config('foo = { a : 1}, bar = { b : 2 }, x = ${foo}" "${bar}').resolve } end # but quoted whitespace should be an error it "concat substitutions that are lists with quoted space" do e = TestUtils.intercept(Hocon::ConfigError::ConfigWrongTypeError) { conf = TestUtils.parse_config('foo = [1], bar = [2], x = ${foo}" "${bar}').resolve } end end hocon-1.2.5/spec/unit/typesafe/config/README.md0000644000175000017500000000016713154611745021011 0ustar apoikosapoikos## TESTS PORTED FROM UPSTREAM This directory should only contain tests that are ported from the upstream Java library.hocon-1.2.5/spec/unit/typesafe/config/config_factory_spec.rb0000644000175000017500000000667113154611745024073 0ustar apoikosapoikos# encoding: utf-8 require 'spec_helper' require 'hocon/config_factory' require 'hocon/config_render_options' require 'hocon/config_error' def get_comment_config_hash(config_string) split_config_string = config_string.split("\n") r = Regexp.new('^\s*#') previous_string_comment = false hash = {} comment_list = [] split_config_string.each do |s| if r.match(s) comment_list << s previous_string_comment = true else if previous_string_comment hash[s] = comment_list comment_list = [] end previous_string_comment = false end end return hash end describe Hocon::ConfigFactory do let(:render_options) { Hocon::ConfigRenderOptions.defaults } before do render_options.origin_comments = false render_options.json = false end shared_examples_for "config_factory_parsing" do let(:input_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/input#{extension}" } let(:output_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/output.conf" } let(:expected) { example[:hash] } let(:reparsed) { Hocon::ConfigFactory.parse_file("#{output_file}") } let(:output) { File.read("#{output_file}") } it "should make the config data available as a map" do expect(conf.root.unwrapped).to eq(expected) end it "should render the config data to a string with comments intact" do rendered_conf = conf.root.render(render_options) rendered_conf_comment_hash = get_comment_config_hash(rendered_conf) output_comment_hash = get_comment_config_hash(output) expect(rendered_conf_comment_hash).to eq(output_comment_hash) end it "should generate the same conf data via re-parsing the rendered output" do expect(reparsed.root.unwrapped).to eq(expected) end end context "example1" do let(:example) { EXAMPLE1 } let (:extension) { ".conf" } context "parsing a HOCON string" do let(:string) { File.open(input_file).read } let(:conf) { Hocon::ConfigFactory.parse_string(string) } include_examples "config_factory_parsing" end context "parsing a .conf file" do let(:conf) { Hocon::ConfigFactory.parse_file(input_file) } include_examples "config_factory_parsing" end end context "example2" do let(:example) { EXAMPLE2 } let (:extension) { ".conf" } context "parsing a HOCON string" do let(:string) { File.open(input_file).read } let(:conf) { Hocon::ConfigFactory.parse_string(string) } include_examples "config_factory_parsing" end context "parsing a .conf file" do let(:conf) { Hocon::ConfigFactory.parse_file(input_file) } include_examples "config_factory_parsing" end end context "example3" do let (:example) { EXAMPLE3 } let (:extension) { ".conf" } context "loading a HOCON file with substitutions" do let(:conf) { Hocon::ConfigFactory.load_file(input_file) } include_examples "config_factory_parsing" end end context "example4" do let(:example) { EXAMPLE4 } let (:extension) { ".json" } context "parsing a .json file" do let (:conf) { Hocon::ConfigFactory.parse_file(input_file) } include_examples "config_factory_parsing" end end context "example5" do it "should raise a ConfigParseError when given an invalid .conf file" do expect{Hocon::ConfigFactory.parse_string("abcdefg")}.to raise_error(Hocon::ConfigError::ConfigParseError) end end end hocon-1.2.5/spec/unit/typesafe/config/path_spec.rb0000644000175000017500000001726013154611745022027 0ustar apoikosapoikos# encoding: utf-8 require 'spec_helper' require 'hocon' require 'test_utils' describe Hocon::Impl::Path do Path = Hocon::Impl::Path #################### # Path Equality #################### context "Check path equality" do # note: foo.bar is a single key here let(:key_a) { Path.new_key("foo.bar") } let(:same_as_key_a) { Path.new_key("foo.bar") } let(:different_key) { Path.new_key("hello") } # Here foo.bar is two elements let(:two_elements) { Path.new_path("foo.bar") } let(:same_as_two_elements) { Path.new_path("foo.bar") } context "key_a equals a path of the same name" do let(:first_object) { key_a } let(:second_object) { TestUtils.path("foo.bar") } include_examples "object_equality" end context "two_elements equals a path with those two elements" do let(:first_object) { two_elements} let(:second_object) { TestUtils.path("foo", "bar") } include_examples "object_equality" end context "key_a equals key_a" do let(:first_object) { key_a } let(:second_object) { key_a } include_examples "object_equality" end context "key_a equals same_as_key_a" do let(:first_object) { key_a } let(:second_object) { same_as_key_a } include_examples "object_equality" end context "key_a not equal to different_key" do let(:first_object) { key_a } let(:second_object) { different_key } include_examples "object_inequality" end context "key_a not equal to the two_elements path" do let(:first_object) { key_a } let(:second_object) { two_elements } include_examples "object_inequality" end context "two_elements path equals same_as_two_elements path" do let(:first_object) { two_elements} let(:second_object) { same_as_two_elements } include_examples "object_equality" end end #################### # Testing to_s #################### context "testing to_s" do it "should find to_s returning the correct strings" do expect("Path(foo)").to eq(TestUtils.path("foo").to_s) expect("Path(foo.bar)").to eq(TestUtils.path("foo", "bar").to_s) expect('Path(foo."bar*")').to eq(TestUtils.path("foo", "bar*").to_s) expect('Path("foo.bar")').to eq(TestUtils.path("foo.bar").to_s) end end #################### # Render #################### context "testing .render" do context "rendering simple one element case" do let(:expected) { "foo" } let(:path) { TestUtils.path("foo") } include_examples "path_render_test" end context "rendering simple two element case" do let(:expected) { "foo.bar" } let(:path) { TestUtils.path("foo", "bar") } include_examples "path_render_test" end context "rendering non safe char in an element" do let(:expected) { 'foo."bar*"' } let(:path) { TestUtils.path("foo", "bar*") } include_examples "path_render_test" end context "rendering period in an element" do let(:expected) { '"foo.bar"' } let(:path) { TestUtils.path("foo.bar") } include_examples "path_render_test" end context "rendering hyphen in element" do let(:expected) { "foo-bar" } let(:path) { TestUtils.path("foo-bar") } include_examples "path_render_test" end context "rendering hyphen in element" do let(:expected) { "foo_bar" } let(:path) { TestUtils.path("foo_bar") } include_examples "path_render_test" end context "rendering element starting with a hyphen" do let(:expected) { "-foo" } let(:path) { TestUtils.path("-foo") } include_examples "path_render_test" end context "rendering element starting with a number" do let(:expected) { "10foo" } let(:path) { TestUtils.path("10foo") } include_examples "path_render_test" end context "rendering empty elements" do let(:expected) { '"".""' } let(:path) { TestUtils.path("", "") } include_examples "path_render_test" end context "rendering element with internal space" do let(:expected) { '"foo bar"' } let(:path) { TestUtils.path("foo bar") } include_examples "path_render_test" end context "rendering leading and trailing spaces" do let(:expected) { '" foo "' } let(:path) { TestUtils.path(" foo ") } include_examples "path_render_test" end context "rendering trailing space only" do let(:expected) { '"foo "' } let(:path) { TestUtils.path("foo ") } include_examples "path_render_test" end context "rendering number with decimal point" do let(:expected) { "1.2" } let(:path) { TestUtils.path("1", "2") } include_examples "path_render_test" end context "rendering number with multiple decimal points" do let(:expected) { "1.2.3.4" } let(:path) { TestUtils.path("1", "2", "3", "4") } include_examples "path_render_test" end end context "test that paths made from a list of Path objects equal paths made from a list of strings" do it "should find a path made from a list of one path equal to a path from one string" do path_from_path_list = Path.from_path_list([TestUtils.path("foo")]) expected_path = TestUtils.path("foo") expect(path_from_path_list).to eq(expected_path) end it "should find a path made from a list of multiple paths equal to that list of strings" do path_from_path_list = Path.from_path_list([TestUtils.path("foo", "bar"), TestUtils.path("baz", "boo")]) expected_path = TestUtils.path("foo", "bar", "baz", "boo") expect(path_from_path_list).to eq(expected_path) end end context "prepending paths" do it "should find prepending a single path works" do prepended_path = TestUtils.path("bar").prepend(TestUtils.path("foo")) expected_path = TestUtils.path("foo", "bar") expect(prepended_path).to eq(expected_path) end it "should find prepending multiple paths works" do prepended_path = TestUtils.path("c", "d").prepend(TestUtils.path("a", "b")) expected_path = TestUtils.path("a", "b", "c", "d") expect(prepended_path).to eq(expected_path) end end context "path length" do it "should find length of single part path to be 1" do path = TestUtils.path("food") expect(path.length).to eq(1) end it "should find length of two part path to be 2" do path = TestUtils.path("foo", "bar") expect(path.length).to eq(2) end end context "parent paths" do it "should find parent of single level path to be nil" do path = TestUtils.path("a") expect(path.parent).to be_nil end it "should find parent of a.b to be a" do path = TestUtils.path("a", "b") parent = TestUtils.path("a") expect(path.parent).to eq(parent) end it "should find parent of a.b.c to be a.b" do path = TestUtils.path("a", "b", "c") parent = TestUtils.path("a", "b") expect(path.parent).to eq(parent) end end context "path last method" do it "should find last of single level path to be itself" do path = TestUtils.path("a") expect(path.last).to eq("a") end it "should find last of a.b to be b" do path = TestUtils.path("a", "b") expect(path.last).to eq("b") end end context "invalid paths" do it "should catch exception from empty path" do bad_path = "" expect { Path.new_path(bad_path) }.to raise_error(Hocon::ConfigError::ConfigBadPathError) end it "should catch exception from path '..'" do bad_path = ".." expect { Path.new_path(bad_path) }.to raise_error(Hocon::ConfigError::ConfigBadPathError) end end end hocon-1.2.5/spec/unit/typesafe/config/simple_config_spec.rb0000644000175000017500000001036613154611745023711 0ustar apoikosapoikos# encoding: utf-8 require 'spec_helper' require 'hocon/config_factory' require 'hocon/config_render_options' require 'hocon/config_value_factory' describe Hocon::Impl::SimpleConfig do let(:render_options) { Hocon::ConfigRenderOptions.defaults } before do render_options.origin_comments = false render_options.json = false end shared_examples_for "config_value_retrieval_single_value" do let(:input_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/input.conf" } it "should allow you to get a value for a specific configuration setting" do expect(conf.get_value(setting).transform_to_string).to eq(expected_setting) end end shared_examples_for "config_value_retrieval_config_list" do let(:input_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/input.conf" } it "should allow you to get a value for a setting whose value is a data structure" do expect(conf.get_value(setting). render_value_to_sb(StringIO.new, 2, nil, Hocon::ConfigRenderOptions.new(false, false, false, false)). string).to eq(expected_setting) end end shared_examples_for "has_path_check" do let(:input_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/input.conf" } it "should return true if a path exists" do expect(conf.has_path?(setting)).to eql(true) end it "should return false if a path does not exist" do expect(conf.has_path?(false_setting)).to eq(false) end end shared_examples_for "add_value_to_config" do let(:input_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/input.conf" } it "should add desired setting with desired value" do modified_conf = conf.with_value(setting_to_add, value_to_add) expect(modified_conf.get_value(setting_to_add)).to eq(value_to_add) end end shared_examples_for "add_data_structures_to_config" do let(:input_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/input.conf" } it "should add a nested map to a config" do map = Hocon::ConfigValueFactory.from_any_ref({"a" => "b", "c" => {"d" => "e"}}, nil) modified_conf = conf.with_value(setting_to_add, map) expect(modified_conf.get_value(setting_to_add)).to eq(map) end it "should add an array to a config" do array = Hocon::ConfigValueFactory.from_any_ref([1,2,3,4,5], nil) modified_conf = conf.with_value(setting_to_add, array) expect(modified_conf.get_value(setting_to_add)).to eq(array) end end shared_examples_for "remove_value_from_config" do let(:input_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/input.conf" } it "should remove desired setting" do modified_conf = conf.without_path(setting_to_remove) expect(modified_conf.has_path?(setting_to_remove)).to be false end end context "example1" do let(:example) { EXAMPLE1 } let(:setting) { "foo.bar.yahoo" } let(:expected_setting) { "yippee" } let(:false_setting) { "non-existent" } let(:setting_to_add) { "foo.bar.test" } let(:value_to_add) { Hocon::Impl::ConfigString.new(nil, "This is a test string") } let(:setting_to_remove) { "foo.bar" } context "parsing a .conf file" do let(:conf) { Hocon::ConfigFactory.parse_file(input_file) } include_examples "config_value_retrieval_single_value" include_examples "has_path_check" include_examples "add_value_to_config" include_examples "add_data_structures_to_config" include_examples "remove_value_from_config" end end context "example2" do let(:example) { EXAMPLE2 } let(:setting) { "jruby-puppet.jruby-pools" } let(:expected_setting) { "[{environment=production}]" } let(:false_setting) { "jruby-puppet-false" } let(:setting_to_add) { "top" } let(:value_to_add) { Hocon::Impl::ConfigInt.new(nil, 12345, "12345") } let(:setting_to_remove) { "jruby-puppet.master-conf-dir" } context "parsing a .conf file" do let(:conf) { Hocon::ConfigFactory.parse_file(input_file) } include_examples "config_value_retrieval_config_list" include_examples "has_path_check" include_examples "add_value_to_config" include_examples "add_data_structures_to_config" include_examples "remove_value_from_config" end end end hocon-1.2.5/spec/unit/typesafe/config/config_value_spec.rb0000644000175000017500000007567713154611745023554 0ustar apoikosapoikosrequire 'spec_helper' require 'hocon' require 'test_utils' require 'hocon/impl/config_delayed_merge' require 'hocon/impl/config_delayed_merge_object' require 'hocon/config_error' require 'hocon/impl/unsupported_operation_error' require 'hocon/config_value_factory' require 'hocon/config_render_options' SimpleConfigOrigin = Hocon::Impl::SimpleConfigOrigin SimpleConfigObject = Hocon::Impl::SimpleConfigObject SimpleConfigList = Hocon::Impl::SimpleConfigList SubstitutionExpression = Hocon::Impl::SubstitutionExpression ConfigReference = Hocon::Impl::ConfigReference ConfigConcatenation = Hocon::Impl::ConfigConcatenation ConfigDelayedMerge = Hocon::Impl::ConfigDelayedMerge ConfigDelayedMergeObject = Hocon::Impl::ConfigDelayedMergeObject ConfigNotResolvedError = Hocon::ConfigError::ConfigNotResolvedError UnresolvedSubstitutionError = Hocon::ConfigError::UnresolvedSubstitutionError ConfigBugOrBrokenError = Hocon::ConfigError::ConfigBugOrBrokenError AbstractConfigObject = Hocon::Impl::AbstractConfigObject ConfigValueFactory = Hocon::ConfigValueFactory ConfigFactory = Hocon::ConfigFactory UnsupportedOperationError = Hocon::Impl::UnsupportedOperationError ConfigNumber = Hocon::Impl::ConfigNumber ConfigRenderOptions = Hocon::ConfigRenderOptions describe "SimpleConfigOrigin equality" do context "different origins with the same name should be equal" do let(:a) { SimpleConfigOrigin.new_simple("foo") } let(:same_as_a) { SimpleConfigOrigin.new_simple("foo") } let(:b) { SimpleConfigOrigin.new_simple("bar") } context "a equals a" do let(:first_object) { a } let(:second_object) { a } include_examples "object_equality" end context "a equals same_as_a" do let(:first_object) { a } let(:second_object) { same_as_a } include_examples "object_equality" end context "a does not equal b" do let(:first_object) { a } let(:second_object) { b } include_examples "object_inequality" end end end describe "ConfigInt equality" do context "different ConfigInts with the same value should be equal" do a = TestUtils.int_value(42) same_as_a = TestUtils.int_value(42) b = TestUtils.int_value(43) context "a equals a" do let(:first_object) { a } let(:second_object) { a } include_examples "object_equality" end context "a equals same_as_a" do let(:first_object) { a } let(:second_object) { same_as_a } include_examples "object_equality" end context "a does not equal b" do let(:first_object) { a } let(:second_object) { b } include_examples "object_inequality" end end end describe "ConfigFloat equality" do context "different ConfigFloats with the same value should be equal" do a = TestUtils.double_value(3.14) same_as_a = TestUtils.double_value(3.14) b = TestUtils.double_value(4.14) context "a equals a" do let(:first_object) { a } let(:second_object) { a } include_examples "object_equality" end context "a equals same_as_a" do let(:first_object) { a } let(:second_object) { same_as_a } include_examples "object_equality" end context "a does not equal b" do let(:first_object) { a } let(:second_object) { b } include_examples "object_inequality" end end end describe "ConfigFloat and ConfigInt equality" do context "different ConfigInts with the same value should be equal" do double_val = TestUtils.double_value(3.0) int_value = TestUtils.int_value(3) double_value_b = TestUtils.double_value(4.0) int_value_b = TestUtils.double_value(4) context "int equals double" do let(:first_object) { double_val } let(:second_object) { int_value } include_examples "object_equality" end context "ConfigFloat made from int equals double" do let(:first_object) { double_value_b } let(:second_object) { int_value_b } include_examples "object_equality" end context "3 doesn't equal 4.0" do let(:first_object) { int_value } let(:second_object) { double_value_b } include_examples "object_inequality" end context "4.0 doesn't equal 3.0" do let(:first_object) { int_value_b } let(:second_object) { double_val } include_examples "object_inequality" end end end describe "SimpleConfigObject equality" do context "SimpleConfigObjects made from hash maps" do a_map = TestUtils.config_map({a: 1, b: 2, c: 3}) same_as_a_map = TestUtils.config_map({a: 1, b: 2, c: 3}) b_map = TestUtils.config_map({a: 3, b: 4, c: 5}) # different keys is a different case in the equals implementation c_map = TestUtils.config_map({x: 3, y: 4, z: 5}) a = SimpleConfigObject.new(TestUtils.fake_origin, a_map) same_as_a = SimpleConfigObject.new(TestUtils.fake_origin, same_as_a_map) b = SimpleConfigObject.new(TestUtils.fake_origin, b_map) c = SimpleConfigObject.new(TestUtils.fake_origin, c_map) # the config for an equal object is also equal config = a.to_config context "a equals a" do let(:first_object) { a } let(:second_object) { a } include_examples "object_equality" end context "a equals same_as_a" do let(:first_object) { a } let(:second_object) { same_as_a } include_examples "object_equality" end context "b equals b" do let(:first_object) { b } let(:second_object) { b } include_examples "object_equality" end context "c equals c" do let(:first_object) { c } let(:second_object) { c } include_examples "object_equality" end context "a doesn't equal b" do let(:first_object) { a } let(:second_object) { b } include_examples "object_inequality" end context "a doesn't equal c" do let(:first_object) { a } let(:second_object) { c } include_examples "object_inequality" end context "b doesn't equal c" do let(:first_object) { b } let(:second_object) { c } include_examples "object_inequality" end context "a's config equals a's config" do let(:first_object) { config } let(:second_object) { config } include_examples "object_equality" end context "a's config equals same_as_a's config" do let(:first_object) { config } let(:second_object) { same_as_a.to_config } include_examples "object_equality" end context "a's config equals a's config computed again" do let(:first_object) { config } let(:second_object) { a.to_config } include_examples "object_equality" end context "a's config doesn't equal b's config" do let(:first_object) { config } let(:second_object) { b.to_config } include_examples "object_inequality" end context "a's config doesn't equal c's config" do let(:first_object) { config } let(:second_object) { c.to_config } include_examples "object_inequality" end context "a doesn't equal a's config" do let(:first_object) { a } let(:second_object) { config } include_examples "object_inequality" end context "b doesn't equal b's config" do let(:first_object) { b } let(:second_object) { b.to_config } include_examples "object_inequality" end end end describe "SimpleConfigList equality" do a_values = [1, 2, 3].map { |i| TestUtils.int_value(i) } a_list = SimpleConfigList.new(TestUtils.fake_origin, a_values) same_as_a_values = [1, 2, 3].map { |i| TestUtils.int_value(i) } same_as_a_list = SimpleConfigList.new(TestUtils.fake_origin, same_as_a_values) b_values = [4, 5, 6].map { |i| TestUtils.int_value(i) } b_list = SimpleConfigList.new(TestUtils.fake_origin, b_values) context "a_list equals a_list" do let(:first_object) { a_list } let(:second_object) { a_list } include_examples "object_equality" end context "a_list equals same_as_a_list" do let(:first_object) { a_list } let(:second_object) { same_as_a_list } include_examples "object_equality" end context "a_list doesn't equal b_list" do let(:first_object) { a_list } let(:second_object) { b_list } include_examples "object_inequality" end end describe "ConfigReference equality" do a = TestUtils.subst("foo") same_as_a = TestUtils.subst("foo") b = TestUtils.subst("bar") c = TestUtils.subst("foo", true) specify "testing values are of the right type" do expect(a).to be_instance_of(ConfigReference) expect(b).to be_instance_of(ConfigReference) expect(c).to be_instance_of(ConfigReference) end context "a equals a" do let(:first_object) { a } let(:second_object) { a } include_examples "object_equality" end context "a equals same_as_a" do let(:first_object) { a } let(:second_object) { same_as_a } include_examples "object_equality" end context "a doesn't equal b" do let(:first_object) { a } let(:second_object) { b } include_examples "object_inequality" end context "a doesn't equal c, an optional substitution" do let(:first_object) { a } let(:second_object) { c } include_examples "object_inequality" end end describe "ConfigConcatenation equality" do a = TestUtils.subst_in_string("foo") same_as_a = TestUtils.subst_in_string("foo") b = TestUtils.subst_in_string("bar") c = TestUtils.subst_in_string("foo", true) specify "testing values are of the right type" do expect(a).to be_instance_of(ConfigConcatenation) expect(b).to be_instance_of(ConfigConcatenation) expect(c).to be_instance_of(ConfigConcatenation) end context "a equals a" do let(:first_object) { a } let(:second_object) { a } include_examples "object_equality" end context "a equals same_as_a" do let(:first_object) { a } let(:second_object) { same_as_a } include_examples "object_equality" end context "a doesn't equal b" do let(:first_object) { a } let(:second_object) { b } include_examples "object_inequality" end context "a doesn't equal c, an optional substitution" do let(:first_object) { a } let(:second_object) { c } include_examples "object_inequality" end end describe "ConfigDelayedMerge equality" do s1 = TestUtils.subst("foo") s2 = TestUtils.subst("bar") a = ConfigDelayedMerge.new(TestUtils.fake_origin, [s1, s2]) same_as_a = ConfigDelayedMerge.new(TestUtils.fake_origin, [s1, s2]) b = ConfigDelayedMerge.new(TestUtils.fake_origin, [s2, s1]) context "a equals a" do let(:first_object) { a } let(:second_object) { a } include_examples "object_equality" end context "a equals same_as_a" do let(:first_object) { a } let(:second_object) { same_as_a } include_examples "object_equality" end context "a doesn't equal b" do let(:first_object) { a } let(:second_object) { b } include_examples "object_inequality" end end describe "ConfigDelayedMergeObject equality" do empty = SimpleConfigObject.empty s1 = TestUtils.subst("foo") s2 = TestUtils.subst("bar") a = ConfigDelayedMergeObject.new(TestUtils.fake_origin, [empty, s1, s2]) same_as_a = ConfigDelayedMergeObject.new(TestUtils.fake_origin, [empty, s1, s2]) b = ConfigDelayedMergeObject.new(TestUtils.fake_origin, [empty, s2, s1]) context "a equals a" do let(:first_object) { a } let(:second_object) { a } include_examples "object_equality" end context "a equals same_as_a" do let(:first_object) { a } let(:second_object) { same_as_a } include_examples "object_equality" end context "a doesn't equal b" do let(:first_object) { a } let(:second_object) { b } include_examples "object_inequality" end end describe "Values' to_s methods" do # just check that these don't throw, the exact output # isn't super important since it's just for debugging specify "to_s doesn't throw error" do TestUtils.int_value(10).to_s TestUtils.double_value(3.14).to_s TestUtils.string_value("hi").to_s TestUtils.null_value.to_s TestUtils.bool_value(true).to_s empty_object = SimpleConfigObject.empty empty_object.to_s SimpleConfigList.new(TestUtils.fake_origin, []).to_s TestUtils.subst("a").to_s TestUtils.subst_in_string("b").to_s dm = ConfigDelayedMerge.new(TestUtils.fake_origin, [TestUtils.subst("a"), TestUtils.subst("b")]) dm.to_s dmo = ConfigDelayedMergeObject.new(TestUtils.fake_origin, [empty_object, TestUtils.subst("a"), TestUtils.subst("b")]) dmo.to_s TestUtils.fake_origin.to_s end end describe "ConfigObject" do specify "should unwrap correctly" do m = SimpleConfigObject.new(TestUtils.fake_origin, TestUtils.config_map({a: 1, b: 2, c: 3})) expect({a: 1, b: 2, c: 3}).to eq(m.unwrapped) end specify "should implement read only map" do m = SimpleConfigObject.new(TestUtils.fake_origin, TestUtils.config_map({a: 1, b: 2, c: 3})) expect(TestUtils.int_value(1)).to eq(m[:a]) expect(TestUtils.int_value(2)).to eq(m[:b]) expect(TestUtils.int_value(3)).to eq(m[:c]) expect(m[:d]).to be_nil # [] can take a non-string expect(m[[]]).to be_nil expect(m.has_key? :a).to be_truthy expect(m.has_key? :z).to be_falsey # has_key? can take a non-string expect(m.has_key? []).to be_falsey expect(m.has_value? TestUtils.int_value(1)).to be_truthy expect(m.has_value? TestUtils.int_value(10)).to be_falsey # has_value? can take a non-string expect(m.has_value? []).to be_falsey expect(m.empty?).to be_falsey expect(m.size).to eq(3) values = [TestUtils.int_value(1), TestUtils.int_value(2), TestUtils.int_value(3)] expect(values).to eq(m.values) keys = [:a, :b, :c] expect(keys).to eq(m.keys) expect { m["hello"] = TestUtils.int_value(41) }.to raise_error(UnsupportedOperationError) expect { m.delete(:a) }.to raise_error(UnsupportedOperationError) end end describe "ConfigList" do specify "should implement read only list" do values = ["a", "b", "c"].map { |i| TestUtils.string_value(i) } l = SimpleConfigList.new(TestUtils.fake_origin, values) expect(values[0]).to eq(l[0]) expect(values[1]).to eq(l[1]) expect(values[2]).to eq(l[2]) expect(l.include? TestUtils.string_value("a")).to be_truthy expect(l.include_all?([TestUtils.string_value("a")])).to be_truthy expect(l.include_all?([TestUtils.string_value("b")])).to be_truthy expect(l.include_all?(values)).to be_truthy expect(l.index(values[1])).to eq(1) expect(l.empty?).to be_falsey expect(l.map { |v| v }).to eq(values.map { |v| v }) expect(l.rindex(values[1])).to eq(1) expect(l.size).to eq(3) expect { l.push(TestUtils.int_value(3)) }.to raise_error(UnsupportedOperationError) expect { l << TestUtils.int_value(3) }.to raise_error(UnsupportedOperationError) expect { l.clear }.to raise_error(UnsupportedOperationError) expect { l.delete(TestUtils.int_value(2)) }.to raise_error(UnsupportedOperationError) expect { l.delete(1) }.to raise_error(UnsupportedOperationError) expect { l[0] = TestUtils.int_value(42) }.to raise_error(UnsupportedOperationError) end end describe "Objects throwing ConfigNotResolvedError" do context "ConfigSubstitution" do specify "should throw ConfigNotResolvedError" do expect{ TestUtils.subst("foo").value_type }.to raise_error(ConfigNotResolvedError) expect{ TestUtils.subst("foo").unwrapped }.to raise_error(ConfigNotResolvedError) end end context "ConfigDelayedMerge" do let(:dm) { ConfigDelayedMerge.new(TestUtils.fake_origin, [TestUtils.subst("a"), TestUtils.subst("b")]) } specify "should throw ConfigNotResolvedError" do expect{ dm.value_type }.to raise_error(ConfigNotResolvedError) expect{ dm.unwrapped }.to raise_error(ConfigNotResolvedError) end end context "ConfigDelayedMergeObject" do empty_object = SimpleConfigObject.empty objects = [empty_object, TestUtils.subst("a"), TestUtils.subst("b")] let(:dmo) { ConfigDelayedMergeObject.new(TestUtils.fake_origin, objects) } specify "should have value type of OBJECT" do expect(dmo.value_type).to eq(Hocon::ConfigValueType::OBJECT) end specify "should throw ConfigNotResolvedError" do expect{ dmo.unwrapped }.to raise_error(ConfigNotResolvedError) expect{ dmo["foo"] }.to raise_error(ConfigNotResolvedError) expect{ dmo.has_key?(nil) }.to raise_error(ConfigNotResolvedError) expect{ dmo.has_value?(nil) }.to raise_error(ConfigNotResolvedError) expect{ dmo.each }.to raise_error(ConfigNotResolvedError) expect{ dmo.empty? }.to raise_error(ConfigNotResolvedError) expect{ dmo.keys }.to raise_error(ConfigNotResolvedError) expect{ dmo.size }.to raise_error(ConfigNotResolvedError) expect{ dmo.values }.to raise_error(ConfigNotResolvedError) expect{ dmo.to_config.get_int("foo") }.to raise_error(ConfigNotResolvedError) end end end describe "Round tripping numbers through parse_string" do specify "should get the same numbers back out" do # formats rounded off with E notation a = "132454454354353245.3254652656454808909932874873298473298472" # formats as 100000.0 b = "1e6" # formats as 5.0E-5 c = "0.00005" # formats as 1E100 (capital E) d = "1e100" object = TestUtils.parse_config("{ a : #{a}, b : #{b}, c : #{c}, d : #{d}}") expect([a, b, c, d]).to eq(["a", "b", "c", "d"].map { |x| object.get_string(x) }) object2 = TestUtils.parse_config("{ a : xx #{a} yy, b : xx #{b} yy, c : xx #{c} yy, d : xx #{d} yy}") expected2 = [a, b, c, d].map { |x| "xx #{x} yy"} expect(["a", "b", "c", "d"].map { |x| object2.get_string(x) }).to eq(expected2) end end describe "AbstractConfigObject#merge_origins" do def o(desc, empty) values = {} if !empty values["hello"] = TestUtils.int_value(37) end SimpleConfigObject.new(SimpleConfigOrigin.new_simple(desc), values) end def m(*values) AbstractConfigObject.merge_origins(values).description end specify "should merge origins correctly" do # simplest case expect(m(o("a", false), o("b", false))).to eq("merge of a,b") # combine duplicate "merge of" expect(m(o("a", false), o("merge of x,y", false))).to eq("merge of a,x,y") expect(m(o("merge of a,b", false), o("merge of x,y", false))).to eq("merge of a,b,x,y") # ignore empty objects expect(m(o("foo", true), o("a", false))).to eq("a") # unless they are all empty, pick the first one expect(m(o("foo", true), o("a", true))).to eq("foo") # merge just one expect(m(o("foo", false))).to eq("foo") # merge three expect(m(o("a", false), o("b", false), o("c", false))).to eq("merge of a,b,c") end end describe "SimpleConfig#has_path?" do specify "should work in various contexts" do empty = TestUtils.parse_config("{}") expect(empty.has_path?("foo")).to be_falsey object = TestUtils.parse_config("a=null, b.c.d=11, foo=bar") # returns true for the non-null values expect(object.has_path?("foo")).to be_truthy expect(object.has_path?("b.c.d")).to be_truthy expect(object.has_path?("b.c")).to be_truthy expect(object.has_path?("b")).to be_truthy # has_path is false for null values but contains_key is true expect(object.root["a"]).to eq(TestUtils.null_value) expect(object.root.has_key?("a")).to be_truthy expect(object.has_path?("a")).to be_falsey # false for totally absent values expect(object.root.has_key?("notinhere")).to be_falsey expect(object.has_path?("notinhere")).to be_falsey # throws proper exceptions expect { empty.has_path?("a.") }.to raise_error(Hocon::ConfigError::ConfigBadPathError) expect { empty.has_path?("..") }.to raise_error(Hocon::ConfigError::ConfigBadPathError) end end describe "ConfigNumber::new_number" do specify "should create new objects correctly" do def n(v) ConfigNumber.new_number(TestUtils.fake_origin, v, nil) end expect(n(3.14).unwrapped).to eq(3.14) expect(n(1).unwrapped).to eq(1) expect(n(1).unwrapped).to eq(1.0) end end describe "Boolean conversions" do specify "true, yes, and on all convert to true" do trues = TestUtils.parse_object("{ a=true, b=yes, c=on }").to_config ["a", "b", "c"].map { |x| expect(trues.get_boolean(x)).to be true } falses = TestUtils.parse_object("{ a=false, b=no, c=off }").to_config ["a", "b", "c"].map { |x| expect(falses.get_boolean(x)).to be false } end end describe "SimpleConfigOrigin" do let(:has_filename) { SimpleConfigOrigin.new_file("foo") } let(:no_filename) { SimpleConfigOrigin.new_simple("bar") } let(:filename_with_line) { has_filename.with_line_number(3) } let(:no_filename_with_line) { no_filename.with_line_number(4) } specify "filename matches what was specified" do expect(has_filename.filename).to eq("foo") expect(filename_with_line.filename).to eq("foo") expect(no_filename.filename).to be nil expect(no_filename_with_line.filename).to be nil end specify "description matches correctly" do expect(has_filename.description).to eq("foo") expect(no_filename.description).to eq("bar") expect(filename_with_line.description).to eq("foo: 3") expect(no_filename_with_line.description).to eq("bar: 4") end specify "origins with no line number should have line number of -1" do expect(has_filename.line_number).to eq(-1) expect(no_filename.line_number).to eq(-1) end specify "line_number returns the right line number" do expect(filename_with_line.line_number).to eq(3) expect(no_filename_with_line.line_number).to eq(4) end # Note: skipping tests related to URLs since we aren't implementing that end describe "Config#with_only_key and with_only_path" do context "should keep the correct data" do object = TestUtils.parse_object("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }") it "should keep only a" do expect(object.with_only_key("a")).to eq(TestUtils.parse_object("{ a=1 }")) end it "should keep only e" do expect(object.with_only_key("e")).to eq(TestUtils.parse_object("{ e.f.g=4 }")) end it "should keep only c.d" do expect(object.to_config.with_only_path("c.d").root).to eq(TestUtils.parse_object("{ c.d.y=3, c.d.z=5 }")) end it "should keep only c.d.z" do expect(object.to_config.with_only_path("c.d.z").root).to eq(TestUtils.parse_object("{ c.d.z=5 }")) end it "should keep nonexistent key" do expect(object.with_only_key("nope")).to eq(TestUtils.parse_object("{ }")) end it "should keep nonexistent path" do expect(object.to_config.with_only_path("q.w.e.r.t.y").root).to eq(TestUtils.parse_object("{ }")) end it "should keep only nonexistent underneath non-object" do expect(object.to_config.with_only_path("a.nonextistent").root).to eq(TestUtils.parse_object("{ }")) end it "should keep only nonexistent underneath nested non-object" do expect(object.to_config.with_only_path("c.d.z.nonexistent").root).to eq(TestUtils.parse_object("{ }")) end end specify "should handle unresolved correctly" do object = TestUtils.parse_object("{ a = {}, a=${x}, b=${y}, b=${z}, x={asf:1}, y=2, z=3 }") expect(object.to_config.resolve.with_only_path("a.asf").root).to eq(TestUtils.parse_object("{ a={asf:1} }")) TestUtils.intercept(UnresolvedSubstitutionError) do object.with_only_key("a").to_config.resolve end TestUtils.intercept(UnresolvedSubstitutionError) do object.with_only_key("b").to_config.resolve end expect(object.resolve_status).to eq(Hocon::Impl::ResolveStatus::UNRESOLVED) expect(object.with_only_key("z").resolve_status).to eq(Hocon::Impl::ResolveStatus::RESOLVED) end end describe "Config#without_key/path" do context "should remove keys correctly" do object = TestUtils.parse_object("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }") it "should not have a" do expect(object.without_key("a")).to eq(TestUtils.parse_object("{ b=2, c.d.y=3, e.f.g=4, c.d.z=5 }")) end it "should not have c" do expect(object.without_key("c")).to eq(TestUtils.parse_object("{ a=1, b=2, e.f.g=4 }")) end it "should not have c.d" do expect(object.to_config.without_path("c.d").root).to eq(TestUtils.parse_object("{ a=1, b=2, e.f.g=4, c={} }")) end it "should not have c.d.z" do expect(object.to_config.without_path("c.d.z").root).to eq(TestUtils.parse_object("{ a=1, b=2, c.d.y=3, e.f.g=4 }")) end it "should not change without nonexistent key" do expect(object.without_key("nonexistent")).to eq(TestUtils.parse_object("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }")) end it "should not change without nonexistent path" do expect(object.to_config.without_path("q.w.e.r.t.y").root).to eq(TestUtils.parse_object("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }")) end it "should not change without nonexistent path with existing prefix" do expect(object.to_config.without_path("a.foo").root).to eq(TestUtils.parse_object("{ a=1, b=2, c.d.y=3, e.f.g=4, c.d.z=5 }")) end end end describe "Config#without_key/path involving unresolved" do specify "should handle unresolved correctly" do object = TestUtils.parse_object("{ a = {}, a=${x}, b=${y}, b=${z}, x={asf:1}, y=2, z=3 }") expect(object.to_config.resolve.without_path("a.asf").root).to eq(TestUtils.parse_object("{ a={}, b=3, x={asf:1}, y=2, z=3 }")) TestUtils.intercept(UnresolvedSubstitutionError) do object.without_key("x").to_config.resolve end TestUtils.intercept(UnresolvedSubstitutionError) do object.without_key("z").to_config.resolve end expect(object.resolve_status).to eq(Hocon::Impl::ResolveStatus::UNRESOLVED) expect(object.without_key("a").resolve_status).to eq(Hocon::Impl::ResolveStatus::UNRESOLVED) expect(object.without_key("a").without_key("b").resolve_status).to eq(Hocon::Impl::ResolveStatus::RESOLVED) end end describe "Config#at_path" do specify "works with one element" do v = ConfigValueFactory.from_any_ref(42) config = v.at_path("a") expect(config).to eq(TestUtils.parse_config("a=42")) expect(v).to eq(config.get_value("a")) expect(config.origin.description).to include("at_path") end specify "works with two elements" do v = ConfigValueFactory.from_any_ref(42) config = v.at_path("a.b") expect(config).to eq(TestUtils.parse_config("a.b=42")) expect(v).to eq(config.get_value("a.b")) expect(config.origin.description).to include("at_path") end specify "works with four elements" do v = ConfigValueFactory.from_any_ref(42) config = v.at_path("a.b.c.d") expect(config).to eq(TestUtils.parse_config("a.b.c.d=42")) expect(v).to eq(config.get_value("a.b.c.d")) expect(config.origin.description).to include("at_path") end end describe "Config#at_key" do specify "at_key works" do v = ConfigValueFactory.from_any_ref(42) config = v.at_key("a") expect(config).to eq(TestUtils.parse_config("a=42")) expect(v).to eq(config.get_value("a")) expect(config.origin.description).to include("at_key") end specify "works with value depth 1 from empty" do v = ConfigValueFactory.from_any_ref(42) config = ConfigFactory.empty.with_value("a", v) expect(config).to eq(TestUtils.parse_config("a=42")) expect(v).to eq(config.get_value("a")) end specify "works with value depth 2 from empty" do v = ConfigValueFactory.from_any_ref(42) config = ConfigFactory.empty.with_value("a.b", v) expect(config).to eq(TestUtils.parse_config("a.b=42")) expect(v).to eq(config.get_value("a.b")) end specify "works with value depth 3 from empty" do v = ConfigValueFactory.from_any_ref(42) config = ConfigFactory.empty.with_value("a.b.c", v) expect(config).to eq(TestUtils.parse_config("a.b.c=42")) expect(v).to eq(config.get_value("a.b.c")) end specify "with value depth 1 overwrites existing" do v = ConfigValueFactory.from_any_ref(47) old = v.at_path("a") config = old.with_value("a", ConfigValueFactory.from_any_ref(42)) expect(config).to eq(TestUtils.parse_config("a=42")) expect(config.get_int("a")).to eq(42) end specify "with value depth 2 overwrites existing" do v = ConfigValueFactory.from_any_ref(47) old = v.at_path("a.b") config = old.with_value("a.b", ConfigValueFactory.from_any_ref(42)) expect(config).to eq(TestUtils.parse_config("a.b=42")) expect(config.get_int("a.b")).to eq(42) end specify "with value inside existing object" do v = ConfigValueFactory.from_any_ref(47) old = v.at_path("a.c") config = old.with_value("a.b", ConfigValueFactory.from_any_ref(42)) expect(config).to eq(TestUtils.parse_config("a.b=42,a.c=47")) expect(config.get_int("a.b")).to eq(42) expect(config.get_int("a.c")).to eq(47) end specify "with value build complex config" do v1 = ConfigValueFactory.from_any_ref(1) v2 = ConfigValueFactory.from_any_ref(2) v3 = ConfigValueFactory.from_any_ref(3) v4 = ConfigValueFactory.from_any_ref(4) config = ConfigFactory.empty.with_value("a", v1) .with_value("b.c", v2) .with_value("b.d", v3) .with_value("x.y.z", v4) expect(config).to eq(TestUtils.parse_config("a=1,b.c=2,b.d=3,x.y.z=4")) end end describe "#render" do context "has newlines in description" do v = ConfigValueFactory.from_any_ref(89, "this is a description\nwith some\nnewlines") list = SimpleConfigList.new(SimpleConfigOrigin.new_simple("\n5\n6\n7\n"), [v]) conf = ConfigFactory.empty.with_value("bar", list) rendered = conf.root.render specify "rendered config should have all the lines that were added, with newlines" do expect(rendered).to include("is a description\n") expect(rendered).to include("with some\n") expect(rendered).to include("newlines\n") expect(rendered).to include("#\n") expect(rendered).to include("5\n") expect(rendered).to include("6\n") expect(rendered).to include("7\n") end specify "the rendered config should give back the original config" do parsed = ConfigFactory.parse_string(rendered) expect(parsed).to eq(conf) end end specify "should sort properly" do config = TestUtils.parse_config('0=a,1=b,2=c,3=d,10=e,20=f,30=g') rendered = config.root.render(ConfigRenderOptions.concise) expect(rendered).to eq('{"0":"a","1":"b","2":"c","3":"d","10":"e","20":"f","30":"g"}') end context "RenderOptions.key_value_separator" do specify "should use colons when set to :colon" do conf = Hocon::ConfigValueFactory.from_any_ref({foo: {bar: 'baz'}}) expected = "foo: {\n bar: baz\n}\n" render_options = ConfigRenderOptions.defaults render_options.json = false render_options.key_value_separator = :colon render_options.origin_comments = false expect(conf.render(render_options)).to eq(expected) end specify "should use equals signs when set to :equals" do conf = Hocon::ConfigValueFactory.from_any_ref({foo: {bar: 'baz'}}) expected = "foo={\n bar=baz\n}\n" render_options = ConfigRenderOptions.defaults render_options.json = false render_options.origin_comments = false render_options.key_value_separator = :equals expect(conf.render(render_options)).to eq(expected) end end end hocon-1.2.5/spec/unit/typesafe/config/config_document_parser_spec.rb0000644000175000017500000004165213154611745025614 0ustar apoikosapoikos# encoding: utf-8 require 'spec_helper' require 'hocon' require 'hocon/impl/config_document_parser' require 'test_utils' describe "ConfigDocumentParser" do ConfigDocumentParser = Hocon::Impl::ConfigDocumentParser ConfigParseOptions = Hocon::ConfigParseOptions ConfigSyntax = Hocon::ConfigSyntax shared_examples_for "parse test" do it "should correctly render the parsed node" do node = ConfigDocumentParser.parse(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults) expect(node.render).to eq(orig_text) end end shared_examples_for "parse JSON failures test" do it "should thrown an exception when parsing invalid JSON" do e = TestUtils.intercept(Hocon::ConfigError) { ConfigDocumentParser.parse(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON)) } expect(e.message).to include(contains_message) end end shared_examples_for "parse simple value test" do it "should correctly parse and render the original text as CONF" do expected_rendered_text = final_text.nil? ? orig_text : final_text node = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults) expect(node.render).to eq(expected_rendered_text) expect(node).to be_a(Hocon::Impl::ConfigNodeSimpleValue) end it "should correctly parse and render the original text as JSON" do expected_rendered_text = final_text.nil? ? orig_text : final_text nodeJSON = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON)) expect(nodeJSON.render).to eq(expected_rendered_text) expect(nodeJSON).to be_a(Hocon::Impl::ConfigNodeSimpleValue) end end shared_examples_for "parse complex value test" do it "should correctly parse and render the original text as CONF" do node = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults) expect(node.render).to eq(orig_text) expect(node).to be_a(Hocon::Impl::ConfigNodeComplexValue) end it "should correctly parse and render the original text as JSON" do nodeJSON = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON)) expect(nodeJSON.render).to eq(orig_text) expect(nodeJSON).to be_a(Hocon::Impl::ConfigNodeComplexValue) end end shared_examples_for "parse single value invalid JSON test" do it "should correctly parse and render the original text as CONF" do node = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults) expect(node.render).to eq(orig_text) end it "should throw an exception when parsing the original text as JSON" do e = TestUtils.intercept(Hocon::ConfigError) { ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON)) } expect(e.message).to include(contains_message) end end shared_examples_for "parse leading trailing failure" do it "should throw an exception when parsing an invalid single value" do e = TestUtils.intercept(Hocon::ConfigError) { ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults) } expect(e.message).to include("The value from setValue cannot have leading or trailing newlines, whitespace, or comments") end end context "parse_success" do context "simple map with no braces" do let (:orig_text) { "foo:bar" } include_examples "parse test" end context "simple map with no braces and whitespace" do let (:orig_text) { " foo : bar " } include_examples "parse test" end context "include with no braces" do let (:orig_text) { 'include "foo.conf" ' } include_examples "parse test" end context "simple map with no braces and newlines" do let (:orig_text) { " \nfoo:bar\n " } include_examples "parse test" end context "map with no braces and all simple types" do let (:orig_text) { ' aUnquoted : bar aString = "qux" aNum:123 aDouble=123.456 aTrue=true aFalse=false aNull=null aSub = ${a.b} include "foo.conf" ' } include_examples "parse test" end context "empty map" do let (:orig_text) { "{}" } include_examples "parse test" end context "simple map with braces" do let (:orig_text) { "{foo:bar}" } include_examples "parse test" end context "simple map with braces and whitespace" do let (:orig_text) { "{ foo : bar }" } include_examples "parse test" end context "simple map with braces and trailing whitespace" do let (:orig_text) { "{foo:bar} " } include_examples "parse test" end context "simple map with braces and include" do let (:orig_text) { '{include "foo.conf"}' } include_examples "parse test" end context "simple map with braces and leading/trailing newlines" do let (:orig_text) { "\n{foo:bar}\n" } include_examples "parse test" end context "map with braces and all simple types" do let (:orig_text) { '{ aUnquoted : bar aString = "qux" aNum:123 aDouble=123.456 aTrue=true aFalse=false aNull=null aSub = ${a.b} include "foo.conf" }' } include_examples "parse test" end context "maps can be nested within other maps" do let(:orig_text) { ' foo.bar.baz : { qux : "abcdefg" "abc".def."ghi" : 123 abc = { foo:bar } } qux = 123.456 '} include_examples "parse test" end context "comments can be parsed in maps" do let(:orig_text) { '{ foo: bar // This is a comment baz:qux // This is another comment }'} include_examples "parse test" end context "empty array" do let (:orig_text) { "[]" } include_examples "parse test" end context "single-element array" do let (:orig_text) { "[foo]" } include_examples "parse test" end context "trailing comment" do let (:orig_text) { "[foo,]" } include_examples "parse test" end context "trailing comment and whitespace" do let (:orig_text) { "[foo,] " } include_examples "parse test" end context "leading and trailing whitespace" do let (:orig_text) { " \n[]\n " } include_examples "parse test" end context "array with all simple types" do let (:orig_text) { '[foo, bar,"qux", 123,123.456, true,false, null, ${a.b}]' } include_examples "parse test" end context "array with all simple types and weird whitespace" do let (:orig_text) { '[foo, bar,"qux" , 123 , 123.456, true,false, null, ${a.b} ]' } include_examples "parse test" end context "basic concatenation inside an array" do let (:orig_text) { "[foo bar baz qux]" } include_examples "parse test" end context "basic concatenation inside a map" do let (:orig_text) { "{foo: foo bar baz qux}" } include_examples "parse test" end context "complex concatenation in an array with multiple elements" do let (:orig_text) { "[abc 123 123.456 null true false [1, 2, 3] {a:b}, 2]" } include_examples "parse test" end context "complex node with all types" do let (:orig_text) { '{ foo: bar baz qux ernie // The above was a concatenation baz = [ abc 123, {a:12 b: { c: 13 d: { a: 22 b: "abcdefg" # this is a comment c: [1, 2, 3] } } }, # this was an object in an array //The above value is a map containing a map containing a map, all in an array 22, // The below value is an array contained in another array [1,2,3]] // This is a map with some nested maps and arrays within it, as well as some concatenations qux { baz: abc 123 bar: { baz: abcdefg bar: { a: null b: true c: [true false 123, null, [1, 2, 3]] } } } // Did I cover everything? }' } include_examples "parse test" end context "can correctly parse a JSON string" do it "should correctly parse and render a JSON string" do orig_text = '{ "foo": "bar", "baz": 123, "qux": true, "array": [ {"a": true, "c": false}, 12 ] } ' node = ConfigDocumentParser.parse(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON)) expect(node.render).to eq(orig_text) end end end context "parse JSON failures" do context "JSON does not support concatenations" do let (:orig_text) { '{ "foo": 123 456 789 } ' } let (:contains_message) { "Expecting close brace } or a comma" } include_examples "parse JSON failures test" end context "JSON must begin with { or [" do let (:orig_text) { '"a": 123, "b": 456' } let (:contains_message) { "Document must have an object or array at root" } include_examples "parse JSON failures test" end context "JSON does not support unquoted text" do let (:orig_text) { '{"foo": unquotedtext}' } let (:contains_message) { "Token not allowed in valid JSON" } include_examples "parse JSON failures test" end context "JSON does not support substitutions" do let (:orig_text) { '{"foo": ${"a.b"}}' } let (:contains_message) { "Substitutions (${} syntax) not allowed in JSON" } include_examples "parse JSON failures test" end context "JSON does not support multi-element paths" do let (:orig_text) { '{"foo"."bar": 123}' } let (:contains_message) { "Token not allowed in valid JSON" } include_examples "parse JSON failures test" end context "JSON does not support =" do let (:orig_text) { '{"foo"=123}' } let (:contains_message) { "Key '\"foo\"' may not be followed by token: '='" } include_examples "parse JSON failures test" end context "JSON does not support +=" do let (:orig_text) { '{"foo" += "bar"}' } let (:contains_message) { "Key '\"foo\"' may not be followed by token: '+='" } include_examples "parse JSON failures test" end context "JSON does not support duplicate keys" do let (:orig_text) { '{"foo" : 123, "foo": 456}' } let (:contains_message) { "JSON does not allow duplicate fields" } include_examples "parse JSON failures test" end context "JSON does not support trailing commas" do let (:orig_text) { '{"foo" : 123,}' } let (:contains_message) { "expecting a field name after a comma, got a close brace } instead" } include_examples "parse JSON failures test" end context "JSON does not support empty documents" do let (:orig_text) { '' } let (:contains_message) { "Empty document" } include_examples "parse JSON failures test" end end context "parse single values" do let (:final_text) { nil } context "parse a single integer" do let (:orig_text) { "123" } include_examples "parse simple value test" end context "parse a single double" do let (:orig_text) { "123.456" } include_examples "parse simple value test" end context "parse a single string" do let (:orig_text) { '"a string"' } include_examples "parse simple value test" end context "parse true" do let (:orig_text) { "true" } include_examples "parse simple value test" end context "parse false" do let (:orig_text) { "false" } include_examples "parse simple value test" end context "parse null" do let (:orig_text) { "null" } include_examples "parse simple value test" end context "parse a map" do let (:orig_text) { '{"a": "b"}' } include_examples "parse complex value test" end context "parse an array" do let (:orig_text) { '{"a": "b"}' } include_examples "parse complex value test" end it "should parse concatenations when using CONF syntax" do orig_text = "123 456 \"abc\"" node = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults) expect(node.render).to eq(orig_text) end it "should parse keys with no separators and object values with CONF parsing" do orig_text = '{"foo" { "bar" : 12 } }' node = ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults) expect(node.render).to eq(orig_text) end end context "parse single values failures" do context "throws on leading whitespace" do let (:orig_text) { " 123" } include_examples "parse leading trailing failure" end context "throws on trailing whitespace" do let (:orig_text) { "123 " } include_examples "parse leading trailing failure" end context "throws on leading and trailing whitespace" do let (:orig_text) { " 123 " } include_examples "parse leading trailing failure" end context "throws on leading newline" do let (:orig_text) { "\n123" } include_examples "parse leading trailing failure" end context "throws on trailing newline" do let (:orig_text) { "123\n" } include_examples "parse leading trailing failure" end context "throws on leading and trailing newline" do let (:orig_text) { "\n123\n" } include_examples "parse leading trailing failure" end context "throws on leading and trailing comments" do let (:orig_text) { "#thisisacomment\n123#comment" } include_examples "parse leading trailing failure" end context "throws on whitespace after a concatenation" do let (:orig_text) { "123 456 789 " } include_examples "parse leading trailing failure" end context "throws on unquoted text in JSON" do let (:orig_text) { "unquotedtext" } let (:contains_message) { "Token not allowed in valid JSON" } include_examples("parse single value invalid JSON test") end context "throws on substitutions in JSON" do let (:orig_text) { "${a.b}" } let (:contains_message) { "Substitutions (${} syntax) not allowed in JSON" } include_examples("parse single value invalid JSON test") end it "should throw an error when parsing concatenations in JSON" do orig_text = "123 456 \"abc\"" e = TestUtils.intercept(Hocon::ConfigError) { ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON)) } expect(e.message).to include("Parsing JSON and the value set in setValue was either a concatenation or had trailing whitespace, newlines, or comments") end it "should throw an error when parsing keys with no separators in JSON" do orig_text = '{"foo" { "bar" : 12 } }' e = TestUtils.intercept(Hocon::ConfigError) { ConfigDocumentParser.parse_value(TestUtils.tokenize_from_s(orig_text), TestUtils.fake_origin, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON)) } expect(e.message).to include("Key '\"foo\"' may not be followed by token: '{'") end end context "parse empty document" do it "should parse an empty document with CONF syntax" do node = ConfigDocumentParser.parse(TestUtils.tokenize_from_s(""), TestUtils.fake_origin, ConfigParseOptions.defaults) expect(node.value).to be_a(Hocon::Impl::ConfigNodeObject) expect(node.value.children.empty?).to be_truthy end it "should parse a document with only comments and whitespace with CONF syntax" do node = ConfigDocumentParser.parse(TestUtils.tokenize_from_s("#comment\n#comment\n\n"), TestUtils.fake_origin, ConfigParseOptions.defaults) expect(node.value).to be_a(Hocon::Impl::ConfigNodeObject) end end endhocon-1.2.5/spec/unit/typesafe/config/token_spec.rb0000644000175000017500000001214613154611745022211 0ustar apoikosapoikos# encoding: utf-8 require 'spec_helper' require 'hocon' require 'test_utils' require 'pp' describe Hocon::Impl::Token do Tokens = Hocon::Impl::Tokens #################### # Equality #################### context "check token equality" do context "syntax tokens" do let(:first_object) { Tokens::START } let(:second_object) { Tokens::START } include_examples "object_equality" end context "integer tokens" do let(:first_object) { TestUtils.token_int(42) } let(:second_object) { TestUtils.token_int(42) } include_examples "object_equality" end context "truth tokens" do let(:first_object) { TestUtils.token_true } let(:second_object) { TestUtils.token_true } include_examples "object_equality" end context "int and double of the same value" do let(:first_object) { TestUtils.token_int(10) } let(:second_object) { TestUtils.token_double(10.0) } include_examples "object_equality" end context "double tokens" do let(:first_object) { TestUtils.token_int(3.14) } let(:second_object) { TestUtils.token_int(3.14) } include_examples "object_equality" end context "quoted string tokens" do let(:first_object) { TestUtils.token_string("foo") } let(:second_object) { TestUtils.token_string("foo") } include_examples "object_equality" end context "unquoted string tokens" do let(:first_object) { TestUtils.token_unquoted("foo") } let(:second_object) { TestUtils.token_unquoted("foo") } include_examples "object_equality" end context "key substitution tokens" do let(:first_object) { TestUtils.token_key_substitution("foo") } let(:second_object) { TestUtils.token_key_substitution("foo") } include_examples "object_equality" end context "null tokens" do let(:first_object) { TestUtils.token_null } let(:second_object) { TestUtils.token_null } include_examples "object_equality" end context "newline tokens" do let(:first_object) { TestUtils.token_line(10) } let(:second_object) { TestUtils.token_line(10) } include_examples "object_equality" end end #################### # Inequality #################### context "check token inequality" do context "syntax tokens" do let(:first_object) { Tokens::START } let(:second_object) { Tokens::OPEN_CURLY } include_examples "object_inequality" end context "integer tokens" do let(:first_object) { TestUtils.token_int(42) } let(:second_object) { TestUtils.token_int(43) } include_examples "object_inequality" end context "double tokens" do let(:first_object) { TestUtils.token_int(3.14) } let(:second_object) { TestUtils.token_int(4.14) } include_examples "object_inequality" end context "truth tokens" do let(:first_object) { TestUtils.token_true } let(:second_object) { TestUtils.token_false } include_examples "object_inequality" end context "quoted string tokens" do let(:first_object) { TestUtils.token_string("foo") } let(:second_object) { TestUtils.token_string("bar") } include_examples "object_inequality" end context "unquoted string tokens" do let(:first_object) { TestUtils.token_unquoted("foo") } let(:second_object) { TestUtils.token_unquoted("bar") } include_examples "object_inequality" end context "key substitution tokens" do let(:first_object) { TestUtils.token_key_substitution("foo") } let(:second_object) { TestUtils.token_key_substitution("bar") } include_examples "object_inequality" end context "newline tokens" do let(:first_object) { TestUtils.token_line(10) } let(:second_object) { TestUtils.token_line(11) } include_examples "object_inequality" end context "true and int tokens" do let(:first_object) { TestUtils.token_true } let(:second_object) { TestUtils.token_int(1) } include_examples "object_inequality" end context "string 'true' and true tokens" do let(:first_object) { TestUtils.token_true } let(:second_object) { TestUtils.token_string("true") } include_examples "object_inequality" end context "int and double of slightly different values" do let(:first_object) { TestUtils.token_int(10) } let(:second_object) { TestUtils.token_double(10.000001) } include_examples "object_inequality" end end context "Check that to_s doesn't throw exception" do it "shouldn't throw an exception" do # just be sure to_s doesn't throw an exception. It's for debugging # so its exact output doesn't matter a lot TestUtils.token_true.to_s TestUtils.token_false.to_s TestUtils.token_int(42).to_s TestUtils.token_double(3.14).to_s TestUtils.token_null.to_s TestUtils.token_unquoted("foo").to_s TestUtils.token_string("bar").to_s TestUtils.token_key_substitution("a").to_s TestUtils.token_line(10).to_s Tokens::START.to_s Tokens::EOF.to_s Tokens::COLON.to_s end end end hocon-1.2.5/spec/unit/typesafe/config/config_value_factory_spec.rb0000644000175000017500000000623613154611745025264 0ustar apoikosapoikos# encoding: utf-8 require 'spec_helper' require 'hocon/config_value_factory' require 'hocon/config_render_options' require 'hocon/config_error' describe Hocon::ConfigValueFactory do let(:render_options) { Hocon::ConfigRenderOptions.defaults } before do render_options.origin_comments = false render_options.json = false end context "converting objects to ConfigValue using ConfigValueFactory" do it "should convert true into a ConfigBoolean" do value = Hocon::ConfigValueFactory.from_any_ref(true, nil) expect(value).to be_instance_of(Hocon::Impl::ConfigBoolean) expect(value.unwrapped).to eql(true) end it "should convert false into a ConfigBoolean" do value = Hocon::ConfigValueFactory.from_any_ref(false, nil) expect(value).to be_instance_of(Hocon::Impl::ConfigBoolean) expect(value.unwrapped).to eql(false) end it "should convert nil into a ConfigNull object" do value = Hocon::ConfigValueFactory.from_any_ref(nil, nil) expect(value).to be_instance_of(Hocon::Impl::ConfigNull) expect(value.unwrapped).to be_nil end it "should convert an string into a ConfigString object" do value = Hocon::ConfigValueFactory.from_any_ref("Hello, World!", nil) expect(value).to be_a(Hocon::Impl::ConfigString) expect(value.unwrapped).to eq("Hello, World!") end it "should convert an integer into a ConfigInt object" do value = Hocon::ConfigValueFactory.from_any_ref(123, nil) expect(value).to be_instance_of(Hocon::Impl::ConfigInt) expect(value.unwrapped).to eq(123) end it "should convert a double into a ConfigDouble object" do value = Hocon::ConfigValueFactory.from_any_ref(123.456, nil) expect(value).to be_instance_of(Hocon::Impl::ConfigDouble) expect(value.unwrapped).to eq(123.456) end it "should convert a map into a SimpleConfigObject" do map = {"a" => 1, "b" => 2, "c" => 3} value = Hocon::ConfigValueFactory.from_any_ref(map, nil) expect(value).to be_instance_of(Hocon::Impl::SimpleConfigObject) expect(value.unwrapped).to eq(map) end it "should convert symbol keys in a map to string keys" do orig_map = {a: 1, b: 2, c: {a: 1, b: 2, c: {a: 1}}} map = {"a" => 1, "b" => 2, "c"=>{"a"=>1, "b"=>2, "c"=>{"a"=>1}}} value = Hocon::ConfigValueFactory.from_any_ref(orig_map, nil) expect(value).to be_instance_of(Hocon::Impl::SimpleConfigObject) expect(value.unwrapped).to eq(map) value = Hocon::ConfigValueFactory.from_map(orig_map, nil) expect(value).to be_instance_of(Hocon::Impl::SimpleConfigObject) expect(value.unwrapped).to eq(map) end it "should not parse maps with non-string and non-symbol keys" do map = {1 => "a", 2 => "b"} expect{ Hocon::ConfigValueFactory.from_any_ref(map, nil) }.to raise_error(Hocon::ConfigError::ConfigBugOrBrokenError) end it "should convert an Enumerable into a SimpleConfigList" do list = [1, 2, 3, 4, 5] value = Hocon::ConfigValueFactory.from_any_ref(list, nil) expect(value).to be_instance_of(Hocon::Impl::SimpleConfigList) expect(value.unwrapped).to eq(list) end end end hocon-1.2.5/spec/unit/typesafe/config/conf_parser_spec.rb0000644000175000017500000006524513154611745023402 0ustar apoikosapoikos# encoding: utf-8 require 'spec_helper' require 'test_utils' require 'hocon/config_parse_options' require 'hocon/config_syntax' require 'hocon/impl/abstract_config_object' require 'hocon/impl/resolve_context' require 'hocon/config_resolve_options' require 'hocon/config_error' require 'hocon/impl/simple_config_origin' require 'hocon/config_list' require 'hocon/impl/config_reference' require 'hocon/impl/path_parser' require 'hocon/impl/parseable' require 'hocon/config_factory' def parse_without_resolving(s) options = Hocon::ConfigParseOptions.defaults. set_origin_description("test conf string"). set_syntax(Hocon::ConfigSyntax::CONF) Hocon::Impl::Parseable.new_string(s, options).parse_value end def parse(s) tree = parse_without_resolving(s) if tree.is_a?(Hocon::Impl::AbstractConfigObject) Hocon::Impl::ResolveContext.resolve(tree, tree, Hocon::ConfigResolveOptions.no_system) else tree end end describe "Config Parser" do context "invalid_conf_throws" do TestUtils.whitespace_variations(TestUtils::InvalidConf, false).each do |invalid| it "should raise an error for invalid config string '#{invalid.test}'" do TestUtils.add_offending_json_to_exception("config", invalid.test) { TestUtils.intercept(Hocon::ConfigError) { parse(invalid.test) } } end end end context "valid_conf_works" do TestUtils.whitespace_variations(TestUtils::ValidConf, true).each do |valid| it "should successfully parse config string '#{valid.test}'" do our_ast = TestUtils.add_offending_json_to_exception("config-conf", valid.test) { parse(valid.test) } # let's also check round-trip rendering rendered = our_ast.render reparsed = TestUtils.add_offending_json_to_exception("config-conf-reparsed", rendered) { parse(rendered) } expect(our_ast).to eq(reparsed) end end end end def parse_path(s) first_exception = nil second_exception = nil # parser first by wrapping into a whole document and using the regular parser result = begin tree = parse_without_resolving("[${#{s}}]") if tree.is_a?(Hocon::ConfigList) ref = tree[0] if ref.is_a?(Hocon::Impl::ConfigReference) ref.expression.path end end rescue Hocon::ConfigError => e first_exception = e nil end # also parse with the standalone path parser and be sure the outcome is the same begin should_be_same = Hocon::Impl::PathParser.parse_path(s) unless result == should_be_same raise "expected '#{result}' to equal '#{should_be_same}'" end rescue Hocon::ConfigError => e second_exception = e end if first_exception.nil? && (!second_exception.nil?) raise "only the standalone path parser threw: #{second_exception}" end if (!first_exception.nil?) && second_exception.nil? raise "only the whole-document parser threw: #{first_exception}" end if !first_exception.nil? raise first_exception end if !second_exception.nil? raise "wtf, should have thrown because not equal" end result end def test_path_parsing(first, second) it "'#{first}' should parse to same path as '#{second}'" do expect(TestUtils.path(*first)).to eq(parse_path(second)) end end describe "Config Parser" do context "path_parsing" do test_path_parsing(["a"], "a") test_path_parsing(["a", "b"], "a.b") test_path_parsing(["a.b"], "\"a.b\"") test_path_parsing(["a."], "\"a.\"") test_path_parsing([".b"], "\".b\"") test_path_parsing(["true"], "true") test_path_parsing(["a"], " a ") test_path_parsing(["a ", "b"], " a .b") test_path_parsing(["a ", " b"], " a . b") test_path_parsing(["a b"], " a b") test_path_parsing(["a", "b.c", "d"], "a.\"b.c\".d") test_path_parsing(["3", "14"], "3.14") test_path_parsing(["3", "14", "159"], "3.14.159") test_path_parsing(["a3", "14"], "a3.14") test_path_parsing([""], "\"\"") test_path_parsing(["a", "", "b"], "a.\"\".b") test_path_parsing(["a", ""], "a.\"\"") test_path_parsing(["", "b"], "\"\".b") test_path_parsing(["", "", ""], ' "".""."" ') test_path_parsing(["a-c"], "a-c") test_path_parsing(["a_c"], "a_c") test_path_parsing(["-"], "\"-\"") test_path_parsing(["-"], "-") test_path_parsing(["-foo"], "-foo") test_path_parsing(["-10"], "-10") # here 10.0 is part of an unquoted string test_path_parsing(["foo10", "0"], "foo10.0") # here 10.0 is a number that gets value-concatenated test_path_parsing(["10", "0foo"], "10.0foo") # just a number test_path_parsing(["10", "0"], "10.0") # multiple-decimal number test_path_parsing(["1", "2", "3", "4"], "1.2.3.4") ["", " ", " \n \n ", "a.", ".b", "a..b", "a${b}c", "\"\".", ".\"\""].each do |invalid| begin it "should raise a ConfigBadPathError for '#{invalid}'" do TestUtils.intercept(Hocon::ConfigError::ConfigBadPathError) { parse_path(invalid) } end rescue => e $stderr.puts("failed on '#{invalid}'") raise e end end end it "should allow the last instance to win when duplicate keys are found" do obj = TestUtils.parse_config('{ "a" : 10, "a" : 11 } ') expect(obj.root.size).to eq(1) expect(obj.get_int("a")).to eq(11) end it "should merge maps when duplicate keys are found" do obj = TestUtils.parse_config('{ "a" : { "x" : 1, "y" : 2 }, "a" : { "x" : 42, "z" : 100 } }') expect(obj.root.size).to eq(1) expect(obj.get_object("a").size).to eq(3) expect(obj.get_int("a.x")).to eq(42) expect(obj.get_int("a.y")).to eq(2) expect(obj.get_int("a.z")).to eq(100) end it "should merge maps recursively when duplicate keys are found" do obj = TestUtils.parse_config('{ "a" : { "b" : { "x" : 1, "y" : 2 } }, "a" : { "b" : { "x" : 42, "z" : 100 } } }') expect(obj.root.size).to eq(1) expect(obj.get_object("a").size).to eq(1) expect(obj.get_object("a.b").size).to eq(3) expect(obj.get_int("a.b.x")).to eq(42) expect(obj.get_int("a.b.y")).to eq(2) expect(obj.get_int("a.b.z")).to eq(100) end it "should merge maps recursively when three levels of duplicate keys are found" do obj = TestUtils.parse_config('{ "a" : { "b" : { "c" : { "x" : 1, "y" : 2 } } }, "a" : { "b" : { "c" : { "x" : 42, "z" : 100 } } } }') expect(obj.root.size).to eq(1) expect(obj.get_object("a").size).to eq(1) expect(obj.get_object("a.b").size).to eq(1) expect(obj.get_object("a.b.c").size).to eq(3) expect(obj.get_int("a.b.c.x")).to eq(42) expect(obj.get_int("a.b.c.y")).to eq(2) expect(obj.get_int("a.b.c.z")).to eq(100) end it "should 'reset' a key when a null is found" do obj = TestUtils.parse_config('{ a : { b : 1 }, a : null, a : { c : 2 } }') expect(obj.root.size).to eq(1) expect(obj.get_object("a").size).to eq(1) expect(obj.get_int("a.c")).to eq(2) end it "should 'reset' a map key when a scalar is found" do obj = TestUtils.parse_config('{ a : { b : 1 }, a : 42, a : { c : 2 } }') expect(obj.root.size).to eq(1) expect(obj.get_object("a").size).to eq(1) expect(obj.get_int("a.c")).to eq(2) end end def drop_curlies(s) # drop the outside curly braces first = s.index('{') last = s.rindex('}') "#{s.slice(0..first)}#{s.slice(first+1..last)}#{s.slice(last + 1)}" end describe "Config Parser" do context "implied_comma_handling" do valids = [' // one line { a : y, b : z, c : [ 1, 2, 3 ] }', ' // multiline but with all commas { a : y, b : z, c : [ 1, 2, 3, ], } ', ' // multiline with no commas { a : y b : z c : [ 1 2 3 ] } '] changes = [ Proc.new { |s| s }, Proc.new { |s| s.gsub("\n", "\n\n") }, Proc.new { |s| s.gsub("\n", "\n\n\n") }, Proc.new { |s| s.gsub(",\n", "\n,\n")}, Proc.new { |s| s.gsub(",\n", "\n\n,\n\n") }, Proc.new { |s| s.gsub("\n", " \n ") }, Proc.new { |s| s.gsub(",\n", " \n \n , \n \n ") }, Proc.new { |s| drop_curlies(s) } ] tested = 0 changes.each do |change| valids.each do |v| tested += 1 s = change.call(v) it "should handle commas and whitespaces properly for string '#{s}'" do obj = TestUtils.parse_config(s) expect(obj.root.size).to eq(3) expect(obj.get_string("a")).to eq("y") expect(obj.get_string("b")).to eq("z") expect(obj.get_int_list("c")).to eq([1,2,3]) end end end it "should have run one test per change per valid string" do expect(tested).to eq(changes.length * valids.length) end context "should concatenate values when there is no newline or comma" do it "with no newline in array" do expect(TestUtils.parse_config(" { c : [ 1 2 3 ] } "). get_string_list("c")).to eq (["1 2 3"]) end it "with no newline in array with quoted strings" do expect(TestUtils.parse_config(' { c : [ "4" "5" "6" ] } '). get_string_list("c")).to eq (["4 5 6"]) end it "with no newline in object" do expect(TestUtils.parse_config(' { a : b c } '). get_string("a")).to eq ("b c") end it "with no newline at end" do expect(TestUtils.parse_config('a: b'). get_string("a")).to eq ("b") end it "errors when no newline between keys" do TestUtils.intercept(Hocon::ConfigError) { TestUtils.parse_config('{ a : y b : z }') } end it "errors when no newline between quoted keys" do TestUtils.intercept(Hocon::ConfigError) { TestUtils.parse_config('{ "a" : "y" "b" : "z" }') } end end end it "should support keys with slashes" do obj = TestUtils.parse_config('/a/b/c=42, x/y/z : 32') expect(obj.get_int("/a/b/c")).to eq(42) expect(obj.get_int("x/y/z")).to eq(32) end end def line_number_test(num, text) it "should include the line number #{num} in the error message for invalid string '#{text}'" do e = TestUtils.intercept(Hocon::ConfigError) { TestUtils.parse_config(text) } if ! (e.message.include?("#{num}:")) raise "error message did not contain line '#{num}' '#{text.gsub("\n", "\\n")}' (#{e})" end end end describe "Config Parser" do context "line_numbers_in_errors" do # error is at the last char line_number_test(1, "}") line_number_test(2, "\n}") line_number_test(3, "\n\n}") # error is before a final newline line_number_test(1, "}\n") line_number_test(2, "\n}\n") line_number_test(3, "\n\n}\n") # with unquoted string line_number_test(1, "foo") line_number_test(2, "\nfoo") line_number_test(3, "\n\nfoo") # with quoted string line_number_test(1, "\"foo\"") line_number_test(2, "\n\"foo\"") line_number_test(3, "\n\n\"foo\"") # newlines in triple-quoted string should not hose up the numbering line_number_test(1, "a : \"\"\"foo\"\"\"}") line_number_test(2, "a : \"\"\"foo\n\"\"\"}") line_number_test(3, "a : \"\"\"foo\nbar\nbaz\"\"\"}") # newlines after the triple quoted string line_number_test(5, "a : \"\"\"foo\nbar\nbaz\"\"\"\n\n}") # triple quoted string ends in a newline line_number_test(6, "a : \"\"\"foo\nbar\nbaz\n\"\"\"\n\n}") # end in the middle of triple-quoted string line_number_test(5, "a : \"\"\"foo\n\n\nbar\n") end context "to_string_for_parseables" do # just to be sure the to_string don't throw, to get test coverage options = Hocon::ConfigParseOptions.defaults it "should allow to_s on File Parseable" do Hocon::Impl::Parseable.new_file("foo", options).to_s end it "should allow to_s on Resources Parseable" do Hocon::Impl::Parseable.new_resources("foo", options).to_s end it "should allow to_s on Resources Parseable" do Hocon::Impl::Parseable.new_string("foo", options).to_s end # NOTE: Skipping 'newURL', 'newProperties', 'newReader' tests here # because we don't implement them end end def assert_comments(comments, conf) it "should have comments #{comments} at root" do expect(conf.root.origin.comments).to eq(comments) end end def assert_comments_at_path(comments, conf, path) it "should have comments #{comments} at path #{path}" do expect(conf.get_value(path).origin.comments).to eq(comments) end end def assert_comments_at_path_index(comments, conf, path, index) it "should have comments #{comments} at path #{path} and index #{index}" do expect(conf.get_list(path).get(index).origin.comments).to eq(comments) end end describe "Config Parser" do context "track_comments_for_single_field" do # no comments conf0 = TestUtils.parse_config(' { foo=10 } ') assert_comments_at_path([], conf0, "foo") # comment in front of a field is used conf1 = TestUtils.parse_config(' { # Before foo=10 } ') assert_comments_at_path([" Before"], conf1, "foo") # comment with a blank line after is dropped conf2 = TestUtils.parse_config(' { # BlankAfter foo=10 } ') assert_comments_at_path([], conf2, "foo") # comment in front of a field is used with no root {} conf3 = TestUtils.parse_config(' # BeforeNoBraces foo=10 ') assert_comments_at_path([" BeforeNoBraces"], conf3, "foo") # comment with a blank line after is dropped with no root {} conf4 = TestUtils.parse_config(' # BlankAfterNoBraces foo=10 ') assert_comments_at_path([], conf4, "foo") # comment same line after field is used conf5 = TestUtils.parse_config(' { foo=10 # SameLine } ') assert_comments_at_path([" SameLine"], conf5, "foo") # comment before field separator is used conf6 = TestUtils.parse_config(' { foo # BeforeSep =10 } ') assert_comments_at_path([" BeforeSep"], conf6, "foo") # comment after field separator is used conf7 = TestUtils.parse_config(' { foo= # AfterSep 10 } ') assert_comments_at_path([" AfterSep"], conf7, "foo") # comment on next line is NOT used conf8 = TestUtils.parse_config(' { foo=10 # NextLine } ') assert_comments_at_path([], conf8, "foo") # comment before field separator on new line conf9 = TestUtils.parse_config(' { foo # BeforeSepOwnLine =10 } ') assert_comments_at_path([" BeforeSepOwnLine"], conf9, "foo") # comment after field separator on its own line conf10 = TestUtils.parse_config(' { foo= # AfterSepOwnLine 10 } ') assert_comments_at_path([" AfterSepOwnLine"], conf10, "foo") # comments comments everywhere conf11 = TestUtils.parse_config(' {# Before foo # BeforeSep = # AfterSepSameLine # AfterSepNextLine 10 # AfterValue # AfterValueNewLine (should NOT be used) } ') assert_comments_at_path([" Before", " BeforeSep", " AfterSepSameLine", " AfterSepNextLine", " AfterValue"], conf11, "foo") # empty object conf12 = TestUtils.parse_config('# BeforeEmpty {} #AfterEmpty # NewLine ') assert_comments([" BeforeEmpty", "AfterEmpty"], conf12) # empty array conf13 = TestUtils.parse_config(' foo= # BeforeEmptyArray [] #AfterEmptyArray # NewLine ') assert_comments_at_path([" BeforeEmptyArray", "AfterEmptyArray"], conf13, "foo") # array element conf14 = TestUtils.parse_config(' foo=[ # BeforeElement 10 # AfterElement ] ') assert_comments_at_path_index( [" BeforeElement", " AfterElement"], conf14, "foo", 0) # field with comma after it conf15 = TestUtils.parse_config(' foo=10, # AfterCommaField ') assert_comments_at_path([" AfterCommaField"], conf15, "foo") # element with comma after it conf16 = TestUtils.parse_config(' foo=[10, # AfterCommaElement ] ') assert_comments_at_path_index([" AfterCommaElement"], conf16, "foo", 0) # field with comma after it but comment isn't on the field's line, so not used conf17 = TestUtils.parse_config(' foo=10 , # AfterCommaFieldNotUsed ') assert_comments_at_path([], conf17, "foo") # element with comma after it but comment isn't on the field's line, so not used conf18 = TestUtils.parse_config(' foo=[10 , # AfterCommaElementNotUsed ] ') assert_comments_at_path_index([], conf18, "foo", 0) # comment on new line, before comma, should not be used conf19 = TestUtils.parse_config(' foo=10 # BeforeCommaFieldNotUsed , ') assert_comments_at_path([], conf19, "foo") # comment on new line, before comma, should not be used conf20 = TestUtils.parse_config(' foo=[10 # BeforeCommaElementNotUsed , ] ') assert_comments_at_path_index([], conf20, "foo", 0) # comment on same line before comma conf21 = TestUtils.parse_config(' foo=10 # BeforeCommaFieldSameLine , ') assert_comments_at_path([" BeforeCommaFieldSameLine"], conf21, "foo") # comment on same line before comma conf22 = TestUtils.parse_config(' foo=[10 # BeforeCommaElementSameLine , ] ') assert_comments_at_path_index([" BeforeCommaElementSameLine"], conf22, "foo", 0) end context "track_comments_for_multiple_fields" do # nested objects conf5 = TestUtils.parse_config(' # Outside bar { # Ignore me # Middle # two lines baz { # Inner foo=10 # AfterInner # This should be ignored } # AfterMiddle # ignored } # AfterOutside # ignored! ') assert_comments_at_path([" Inner", " AfterInner"], conf5, "bar.baz.foo") assert_comments_at_path([" Middle", " two lines", " AfterMiddle"], conf5, "bar.baz") assert_comments_at_path([" Outside", " AfterOutside"], conf5, "bar") # multiple fields conf6 = TestUtils.parse_config('{ # this is not with a field # this is field A a : 10, # this is field B b : 12 # goes with field B which has no comma # this is field C c : 14, # goes with field C after comma # not used # this is not used # nor is this # multi-line block # this is with field D # this is with field D also d : 16 # this is after the fields }') assert_comments_at_path([" this is field A"], conf6, "a") assert_comments_at_path([" this is field B", " goes with field B which has no comma"], conf6, "b") assert_comments_at_path([" this is field C", " goes with field C after comma"], conf6, "c") assert_comments_at_path([" this is with field D", " this is with field D also"], conf6, "d") # array conf7 = TestUtils.parse_config(' # before entire array array = [ # goes with 0 0, # goes with 1 1, # with 1 after comma # goes with 2 2 # no comma after 2 # not with anything ] # after entire array ') assert_comments_at_path_index([" goes with 0"], conf7, "array", 0) assert_comments_at_path_index([" goes with 1", " with 1 after comma"], conf7, "array", 1) assert_comments_at_path_index([" goes with 2", " no comma after 2"], conf7, "array", 2) assert_comments_at_path([" before entire array", " after entire array"], conf7, "array") # properties-like syntax conf8 = TestUtils.parse_config(' # ignored comment # x.y comment x.y = 10 # x.z comment x.z = 11 # x.a comment x.a = 12 # a.b comment a.b = 14 a.c = 15 a.d = 16 # a.d comment # ignored comment ') assert_comments_at_path([" x.y comment"], conf8, "x.y") assert_comments_at_path([" x.z comment"], conf8, "x.z") assert_comments_at_path([" x.a comment"], conf8, "x.a") assert_comments_at_path([" a.b comment"], conf8, "a.b") assert_comments_at_path([], conf8, "a.c") assert_comments_at_path([" a.d comment"], conf8, "a.d") # here we're concerned that comments apply only to leaf # nodes, not to parent objects. assert_comments_at_path([], conf8, "x") assert_comments_at_path([], conf8, "a") end context "loading unicode file paths" do it "should be able to parse files with unicode file paths" do expect(Hocon.load("#{FIXTURE_DIR}/test_utils/resources/ᚠᛇᚻ.conf")).to eq({'ᚠᛇᚻ' => '᛫ᛒᛦᚦ᛫ᚠᚱᚩᚠᚢ'}) end end it "includeFile" do conf = Hocon::ConfigFactory.parse_string("include file(" + TestUtils.json_quoted_resource_file("test01") + ")") # should have loaded conf, json... skipping properties expect(conf.get_int("ints.fortyTwo")).to eq(42) expect(conf.get_int("fromJson1")).to eq(1) end it "includeFileWithExtension" do conf = Hocon::ConfigFactory.parse_string("include file(" + TestUtils.json_quoted_resource_file("test01.conf") + ")") expect(conf.get_int("ints.fortyTwo")).to eq(42) expect(conf.has_path?("fromJson1")).to eq(false) expect(conf.has_path?("fromProps.abc")).to eq(false) end it "includeFileWhitespaceInsideParens" do conf = Hocon::ConfigFactory.parse_string("include file( \n " + TestUtils.json_quoted_resource_file("test01") + " \n )") # should have loaded conf, json... NOT properties expect(conf.get_int("ints.fortyTwo")).to eq(42) expect(conf.get_int("fromJson1")).to eq(1) end it "includeFileNoWhitespaceOutsideParens" do e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) { Hocon::ConfigFactory.parse_string("include file (" + TestUtils.json_quoted_resource_file("test01") + ")") } expect(e.message.include?("expecting include parameter")).to eq(true) end it "includeFileNotQuoted" do # this test cannot work on Windows f = TestUtils.resource_file("test01") if (f.to_s.include?("\\")) $stderr.puts("includeFileNotQuoted test skipped on Windows") else e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) { Hocon::ConfigFactory.parse_string("include file(" + f + ")") } expect(e.message.include?("expecting include parameter")).to eq(true) end end it "includeFileNotQuotedAndSpecialChar" do f = TestUtils.resource_file("test01") if (f.to_s.include?("\\")) $stderr.puts("includeFileNotQuoted test skipped on Windows") else e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) { Hocon::ConfigFactory.parse_string("include file(:" + f + ")") } expect(e.message.include?("expecting a quoted string")).to eq(true) end end it "includeFileUnclosedParens" do e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) { Hocon::ConfigFactory.parse_string("include file(" + TestUtils.json_quoted_resource_file("test01") + " something") } expect(e.message.include?("expecting a close paren")).to eq(true) end # Skipping 'includeURLBasename' because we don't support URLs # Skipping 'includeURLWithExtension' because we don't support URLs # Skipping 'includeURLInvalid' because we don't support URLs # Skipping 'includeResources' because we don't support classpath resources # Skipping 'includeURLHeuristically' because we don't support URLs # Skipping 'includeURLBasenameHeuristically' because we don't support URLs it "acceptsUTF8FileContents" do # utf8.conf is UTF-8 with no BOM rune_utf8 = "\u16EB\u16D2\u16E6\u16A6\u16EB\u16A0\u16B1\u16A9\u16A0\u16A2" conf = Hocon::ConfigFactory.parse_file(TestUtils.resource_file("utf8.conf")) expect(conf.get_string("\u16A0\u16C7\u16BB")).to eq(rune_utf8) end it "shouldacceptUTF16FileContents" do skip('supporting UTF-16 requires appropriate BOM detection during parsing') do # utf16.conf is UTF-16LE with a BOM expect { Hocon::ConfigFactory.parse_file(TestUtils.resource_file("utf16.conf")) }.to raise_error end end it "acceptBOMStartingFile" do # BOM at start of file should be ignored conf = Hocon::ConfigFactory.parse_file(TestUtils.resource_file("bom.conf")) expect(conf.get_string("foo")).to eq("bar") end it "acceptBOMInStringValue" do # BOM inside quotes should be preserved, just as other whitespace would be conf = Hocon::ConfigFactory.parse_string("foo=\"\uFEFF\uFEFF\"") expect(conf.get_string("foo")).to eq("\uFEFF\uFEFF") end it "acceptBOMWhitespace" do skip("BOM not parsing properly yet; not fixing this now because it most likely only affects windows") do # BOM here should be treated like other whitespace (ignored, since no quotes) conf = Hocon::ConfigFactory.parse_string("foo= \uFEFFbar\uFEFF") expect(conf.get_string("foo")).to eq("bar") end end it "acceptMultiPeriodNumericPath" do conf1 = Hocon::ConfigFactory.parse_string("0.1.2.3=foobar1") expect(conf1.get_string("0.1.2.3")).to eq("foobar1") conf2 = Hocon::ConfigFactory.parse_string("0.1.2.3.ABC=foobar2") expect(conf2.get_string("0.1.2.3.ABC")).to eq("foobar2") conf3 = Hocon::ConfigFactory.parse_string("ABC.0.1.2.3=foobar3") expect(conf3.get_string("ABC.0.1.2.3")).to eq("foobar3") end end hocon-1.2.5/spec/unit/typesafe/config/config_node_spec.rb0000644000175000017500000005340213154611745023343 0ustar apoikosapoikos# encoding: utf-8 require 'spec_helper' require 'hocon' require 'test_utils' describe Hocon::Parser::ConfigNode do Tokens = Hocon::Impl::Tokens shared_examples_for "single token node test" do it "should render the node with the text of the token" do node = TestUtils.config_node_single_token(token) expect(node.render).to eq(token.token_text) end end shared_examples_for "key node test" do it "should render the node with the text of the path" do node = TestUtils.config_node_key(path) expect(path).to eq(node.render) end end shared_examples_for "simple value node test" do it "should render the original token text" do node = TestUtils.config_node_simple_value(token) expect(node.render).to eq(token.token_text) end end shared_examples_for "field node test" do it "should properly replace the value of a field node" do key_val_node = TestUtils.node_key_value_pair(key, value) expect(key_val_node.render).to eq("#{key.render}: #{value.render}") expect(key_val_node.path.render).to eq(key.render) expect(key_val_node.value.render).to eq(value.render) new_key_val_node = key_val_node.replace_value(new_value) expect(new_key_val_node.render).to eq("#{key.render}: #{new_value.render}") expect(new_key_val_node.value.render).to eq(new_value.render) end end shared_examples_for "top level value replace test" do it "should replace a value in a ConfigNodeObject" do complex_node_children = [TestUtils.node_open_brace, TestUtils.node_key_value_pair(TestUtils.config_node_key(key), value), TestUtils.node_close_brace] complex_node = TestUtils.config_node_object(complex_node_children) new_node = complex_node.set_value_on_path(key, new_value) orig_text = "{#{key}: #{value.render}}" final_text = "{#{key}: #{new_value.render}}" expect(complex_node.render).to eq(orig_text) expect(new_node.render).to eq(final_text) end end shared_examples_for "replace duplicates test" do it "should remove duplicates of a key when setting a value" do key = TestUtils.config_node_key('foo') key_val_pair_1 = TestUtils.node_key_value_pair(key, value1) key_val_pair_2 = TestUtils.node_key_value_pair(key, value2) key_val_pair_3 = TestUtils.node_key_value_pair(key, value3) complex_node = TestUtils.config_node_object([key_val_pair_1, key_val_pair_2, key_val_pair_3]) orig_text = "#{key_val_pair_1.render}#{key_val_pair_2.render}#{key_val_pair_3.render}" final_text = "#{key.render}: 15" expect(complex_node.render).to eq(orig_text) expect(complex_node.set_value_on_path("foo", TestUtils.node_int(15)).render).to eq(final_text) end end shared_examples_for "non existent path test" do it "should properly add a key/value pair if the key does not exist in the object" do node = TestUtils.config_node_object([TestUtils.node_key_value_pair(TestUtils.config_node_key("bar"), TestUtils.node_int(15))]) expect(node.render).to eq('bar: 15') new_node = node.set_value_on_path('foo', value) final_text = "bar: 15, foo: #{value.render}" expect(new_node.render).to eq(final_text) end end ######################## # ConfigNodeSingleToken ######################## context "create basic config node" do # Ensure a ConfigNodeSingleToken can handle all its required token types context "start of file" do let(:token) { Tokens::START } include_examples "single token node test" end context "end of file" do let(:token) { Tokens::EOF } include_examples "single token node test" end context "{" do let (:token) { Tokens::OPEN_CURLY } include_examples "single token node test" end context "}" do let (:token) { Tokens::CLOSE_CURLY } include_examples "single token node test" end context "[" do let (:token) { Tokens::OPEN_SQUARE } include_examples "single token node test" end context "]" do let (:token) { Tokens::CLOSE_SQUARE } include_examples "single token node test" end context "," do let (:token) { Tokens::COMMA } include_examples "single token node test" end context "=" do let (:token) { Tokens::EQUALS } include_examples "single token node test" end context ":" do let (:token) { Tokens::COLON } include_examples "single token node test" end context "+=" do let (:token) { Tokens::PLUS_EQUALS } include_examples "single token node test" end context "unquoted text" do let (:token) { TestUtils.token_unquoted(' ') } include_examples "single token node test" end context "ignored whitespace" do let (:token) { TestUtils.token_whitespace(' ') } include_examples "single token node test" end context '\n' do let (:token) { TestUtils.token_line(1) } include_examples "single token node test" end context "double slash comment" do let (:token) { TestUtils.token_comment_double_slash(" this is a double slash comment ") } include_examples "single token node test" end context "hash comment" do let (:token) { TestUtils.token_comment_hash(" this is a hash comment ") } include_examples "single token node test" end end #################### # ConfigNodeSetting #################### context "create config node setting" do # Ensure a ConfigNodeSetting can handle the normal key types context "unquoted key" do let (:path) { "foo" } include_examples "key node test" end context "quoted_key" do let (:path) { "\"Hello I am a key how are you today\"" } include_examples "key node test" end end context "path node subpath" do it "should produce correct subpaths of path nodes with subpath method" do orig_path = 'a.b.c."@$%#@!@#$"."".1234.5678' path_node = TestUtils.config_node_key(orig_path) expect(path_node.render).to eq(orig_path) expect(path_node.sub_path(2).render).to eq('c."@$%#@!@#$"."".1234.5678') expect(path_node.sub_path(6).render).to eq('5678') end end ######################## # ConfigNodeSimpleValue ######################## context "create config node simple value" do context "integer" do let (:token) { TestUtils.token_int(10) } include_examples "simple value node test" end context "double" do let (:token) { TestUtils.token_double(3.14159) } include_examples "simple value node test" end context "false" do let (:token) { TestUtils.token_false } include_examples "simple value node test" end context "true" do let (:token) { TestUtils.token_true } include_examples "simple value node test" end context "null" do let (:token) { TestUtils.token_null } include_examples "simple value node test" end context "quoted text" do let (:token) { TestUtils.token_string("Hello my name is string") } include_examples "simple value node test" end context "unquoted text" do let (:token) { TestUtils.token_unquoted("mynameisunquotedstring") } include_examples "simple value node test" end context "key substitution" do let (:token) { TestUtils.token_key_substitution("c.d") } include_examples "simple value node test" end context "optional substitution" do let (:token) { TestUtils.token_optional_substitution(TestUtils.token_unquoted("x.y")) } include_examples "simple value node test" end context "substitution" do let (:token) { TestUtils.token_substitution(TestUtils.token_unquoted("a.b")) } include_examples "simple value node test" end end #################### # ConfigNodeField #################### context "create ConfigNodeField" do let (:key) { TestUtils.config_node_key('"abc"') } let (:value) { TestUtils.node_int(123) } context "supports quoted keys" do let (:new_value) { TestUtils.node_int(245) } include_examples "field node test" end context "supports unquoted keys" do let (:key) { TestUtils.config_node_key('abc') } let (:new_value) { TestUtils.node_int(245) } include_examples "field node test" end context "can replace a simple value with a different type of simple value" do let (:new_value) { TestUtils.node_string('I am a string') } include_examples "field node test" end context "can replace a simple value with a complex value" do let (:new_value) { TestUtils.config_node_object([TestUtils.node_open_brace, TestUtils.node_close_brace]) } include_examples "field node test" end end #################### # Node Replacement #################### context "replace nodes" do let (:key) { "foo" } array = TestUtils.config_node_array([TestUtils.node_open_bracket, TestUtils.node_int(10), TestUtils.node_space, TestUtils.node_comma, TestUtils.node_space, TestUtils.node_int(15), TestUtils.node_close_bracket]) nested_map = TestUtils.config_node_object([TestUtils.node_open_brace, TestUtils.node_key_value_pair(TestUtils.config_node_key("abc"), TestUtils.config_node_simple_value(TestUtils.token_string("a string"))), TestUtils.node_close_brace]) context "replace an integer with an integer" do let (:value) { TestUtils.node_int(10) } let (:new_value) { TestUtils.node_int(15) } include_examples "top level value replace test" end context "replace a double with an integer" do let (:value) { TestUtils.node_double(3.14159) } let (:new_value) { TestUtils.node_int(10000) } include_examples "top level value replace test" end context "replace false with true" do let (:value) { TestUtils.node_false } let (:new_value) { TestUtils.node_true } include_examples "top level value replace test" end context "replace true with null" do let (:value) { TestUtils.node_true } let (:new_value) { TestUtils.node_null } include_examples "top level value replace test" end context "replace null with a string" do let (:value) { TestUtils.node_null } let (:new_value) { TestUtils.node_string("Hello my name is string") } include_examples "top level value replace test" end context "replace a string with unquoted text" do let (:value) { TestUtils.node_string("Hello my name is string") } let (:new_value) { TestUtils.node_unquoted_text("mynameisunquotedstring") } include_examples "top level value replace test" end context "replace unquoted text with a key substitution" do let (:value) { TestUtils.node_unquoted_text("mynameisunquotedstring") } let (:new_value) { TestUtils.node_key_substitution("c.d") } include_examples "top level value replace test" end context "replace int with an optional substitution" do let (:value) { TestUtils.node_int(10) } let (:new_value) { TestUtils.node_optional_substitution(TestUtils.token_unquoted("x.y")) } include_examples "top level value replace test" end context "replace int with a substitution" do let (:value) { TestUtils.node_int(10) } let (:new_value) { TestUtils.node_substitution(TestUtils.token_unquoted("a.b")) } include_examples "top level value replace test" end context "replace substitution with an int" do let (:value) { TestUtils.node_substitution(TestUtils.token_unquoted("a.b")) } let (:new_value) { TestUtils.node_int(10) } include_examples "top level value replace test" end context "ensure arrays can be replaced" do context "can replace a simple value with an array" do let (:value) { TestUtils.node_int(10) } let (:new_value) { array } include_examples "top level value replace test" end context "can replace an array with a simple value" do let (:value) { array } let (:new_value) { TestUtils.node_int(10) } include_examples "top level value replace test" end context "can replace an array with another complex value" do let (:value) { array } let (:new_value) { TestUtils.config_node_object([TestUtils.node_open_brace, TestUtils.node_close_brace])} include_examples "top level value replace test" end end context "ensure objects can be replaced" do context "can replace an object with a simple value" do let (:value) { nested_map } let (:new_value) { TestUtils.node_int(10) } include_examples "top level value replace test" end context "can replace a simple value with an object" do let (:value) { TestUtils.node_int(10) } let (:new_value) { nested_map } include_examples "top level value replace test" end context "can replace an array with an object" do let (:value) { array } let (:new_value) { nested_map } include_examples "top level value replace test" end context "can replace an object with an array" do let (:value) { nested_map } let (:new_value) { array } include_examples "top level value replace test" end context "can replace an object with an empty object" do let (:value) { nested_map } let (:new_value) { TestUtils.config_node_object([TestUtils.node_open_brace, TestUtils.node_close_brace]) } include_examples "top level value replace test" end end context "ensure concatenations can be replaced" do concatenation = TestUtils.config_node_concatenation([TestUtils.node_int(10), TestUtils.node_space, TestUtils.node_string("Hello")]) context "can replace a concatenation with a simple value" do let (:value) { concatenation } let (:new_value) { TestUtils.node_int(12) } include_examples "top level value replace test" end context "can replace a simple value with a concatenation" do let (:value) { TestUtils.node_int(12) } let (:new_value) { concatenation } include_examples "top level value replace test" end context "can replace an object with a concatenation" do let (:value) { nested_map } let (:new_value) { concatenation } include_examples "top level value replace test" end context "can replace a concatenation with an object" do let (:value) { concatenation } let (:new_value) { nested_map } include_examples "top level value replace test" end context "can replace an array with a concatenation" do let (:value) { array } let (:new_value) { concatenation } include_examples "top level value replace test" end context "can replace a concatenation with an array" do let (:value) { concatenation } let (:new_value) { array } include_examples "top level value replace test" end end context 'ensure a key with format "a.b" will be properly replaced' do let (:key) { 'foo.bar' } let (:value) { TestUtils.node_int(10) } let (:new_value) { nested_map } include_examples "top level value replace test" end end #################### # Duplicate Removal #################### context "remove duplicates" do empty_map_node = TestUtils.config_node_object([TestUtils.node_open_brace, TestUtils.node_close_brace]) empty_array_node = TestUtils.config_node_array([TestUtils.node_open_bracket, TestUtils.node_close_bracket]) context "duplicates containing simple values will all be removed" do let (:value1) { TestUtils.node_int(10) } let (:value2) { TestUtils.node_true } let (:value3) { TestUtils.node_null } include_examples "replace duplicates test" end context "duplicates containing objects will be removed" do let (:value1) { empty_map_node } let (:value2) { empty_map_node } let (:value3) { empty_map_node } include_examples "replace duplicates test" end context "duplicates containing arrays will be removed" do let (:value1) { empty_array_node } let (:value2) { empty_array_node } let (:value3) { empty_array_node } include_examples "replace duplicates test" end context "duplicates containing a mix of value types will be removed" do let (:value1) { TestUtils.node_int(10) } let (:value2) { empty_map_node } let (:value3) { empty_array_node } include_examples "replace duplicates test" end end ################################# # Addition of non-existent paths ################################# context "add non existent paths" do context "adding an integer" do let (:value) { TestUtils.node_int(10) } include_examples "non existent path test" end context "adding an array" do let (:value) { TestUtils.config_node_array([TestUtils.node_open_bracket, TestUtils.node_int(15), TestUtils.node_close_bracket]) } include_examples "non existent path test" end context "adding an object" do let (:value) { TestUtils.config_node_object([TestUtils.node_open_brace, TestUtils.node_key_value_pair(TestUtils.config_node_key('foo'), TestUtils.node_double(3.14)), TestUtils.node_close_brace]) } include_examples "non existent path test" end end ################################# # Replacement of nested nodes ################################# context "replace nested nodes" do orig_text = "foo: bar\nbaz: {\n\t\"abc.def\": 123\n\t//This is a comment about the below setting\n\n\tabc: {\n\t\t" + "def: \"this is a string\"\n\t\tghi: ${\"a.b\"}\n\t}\n}\nbaz.abc.ghi: 52\nbaz.abc.ghi: 53\n}" lowest_level_map = TestUtils.config_node_object([TestUtils.node_open_brace, TestUtils.node_line(6), TestUtils.node_whitespace("\t\t"), TestUtils.node_key_value_pair(TestUtils.config_node_key("def"), TestUtils.config_node_simple_value(TestUtils.token_string("this is a string"))), TestUtils.node_line(7), TestUtils.node_whitespace("\t\t"), TestUtils.node_key_value_pair(TestUtils.config_node_key("ghi"), TestUtils.config_node_simple_value(TestUtils.token_key_substitution("a.b"))), TestUtils.node_line(8), TestUtils.node_whitespace("\t"), TestUtils.node_close_brace]) higher_level_map = TestUtils.config_node_object([TestUtils.node_open_brace, TestUtils.node_line(2), TestUtils.node_whitespace("\t"), TestUtils.node_key_value_pair(TestUtils.config_node_key('"abc.def"'), TestUtils.config_node_simple_value(TestUtils.token_int(123))), TestUtils.node_line(3), TestUtils.node_whitespace("\t"), TestUtils.node_comment_double_slash("This is a comment about the below setting"), TestUtils.node_line(4), TestUtils.node_line(5), TestUtils.node_whitespace("\t"), TestUtils.node_key_value_pair(TestUtils.config_node_key("abc"), lowest_level_map), TestUtils.node_line(9), TestUtils.node_close_brace]) orig_node = TestUtils.config_node_object([TestUtils.node_key_value_pair(TestUtils.config_node_key("foo"), TestUtils.config_node_simple_value(TestUtils.token_unquoted("bar"))), TestUtils.node_line(1), TestUtils.node_key_value_pair(TestUtils.config_node_key('baz'), higher_level_map), TestUtils.node_line(10), TestUtils.node_key_value_pair(TestUtils.config_node_key('baz.abc.ghi'), TestUtils.config_node_simple_value(TestUtils.token_int(52))), TestUtils.node_line(11), TestUtils.node_key_value_pair(TestUtils.config_node_key('baz.abc.ghi'), TestUtils.config_node_simple_value(TestUtils.token_int(53))), TestUtils.node_line(12), TestUtils.node_close_brace]) it "should properly render the original node" do expect(orig_node.render).to eq(orig_text) end it "should properly replae values in the original node" do final_text = "foo: bar\nbaz: {\n\t\"abc.def\": true\n\t//This is a comment about the below setting\n\n\tabc: {\n\t\t" + "def: false\n\t\t\n\t\t\"this.does.not.exist@@@+$#\": {\n\t\t end: doesnotexist\n\t\t}\n\t}\n}\n\nbaz.abc.ghi: randomunquotedString\n}" # Paths with quotes in the name are treated as a single Path, rather than multiple sub-paths new_node = orig_node.set_value_on_path('baz."abc.def"', TestUtils.config_node_simple_value(TestUtils.token_true)) new_node = new_node.set_value_on_path('baz.abc.def', TestUtils.config_node_simple_value(TestUtils.token_false)) # Repeats are removed from nested maps new_node = new_node.set_value_on_path('baz.abc.ghi', TestUtils.config_node_simple_value(TestUtils.token_unquoted('randomunquotedString'))) # Missing paths are added to the top level if they don't appear anywhere, including in nested maps new_node = new_node.set_value_on_path('baz.abc."this.does.not.exist@@@+$#".end', TestUtils.config_node_simple_value(TestUtils.token_unquoted('doesnotexist'))) # The above operations cause the resultant map to be rendered properly expect(new_node.render).to eq(final_text) end end end hocon-1.2.5/spec/unit/typesafe/config/config_document_spec.rb0000644000175000017500000005624013154611745024237 0ustar apoikosapoikos# encoding: utf-8 require 'spec_helper' require 'hocon' require 'hocon/parser/config_document_factory' require 'hocon/config_value_factory' require 'test_utils' describe "ConfigDocument" do ConfigDocumentFactory = Hocon::Parser::ConfigDocumentFactory ConfigParseOptions = Hocon::ConfigParseOptions ConfigSyntax = Hocon::ConfigSyntax SimpleConfigDocument = Hocon::Impl::SimpleConfigDocument ConfigValueFactory = Hocon::ConfigValueFactory shared_examples_for "config document replace JSON test" do let (:config_document) { ConfigDocumentFactory.parse_string(orig_text, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON)) } it "should correctly render the parsed JSON document" do expect(config_document.render).to eq(orig_text) end it "should perform a successful replacement on the parsed JSON document" do new_document = config_document.set_value(replace_path, new_value) #expect(new_document).to be_a(SimpleConfigDocument) expect(new_document.render).to eq(final_text) end end shared_examples_for "config document replace CONF test" do let (:config_document) { ConfigDocumentFactory.parse_string(orig_text) } it "should correctly render the parsed CONF document" do expect(config_document.render).to eq(orig_text) end it "should perform a successful replacement on the parsed CONF document" do new_document = config_document.set_value(replace_path, new_value) #expect(new_document).to be_a(SimpleConfigDocument) expect(new_document.render).to eq(final_text) end end context "ConfigDocument replace" do let (:orig_text) { '{ "a":123, "b": 123.456, "c": true, "d": false, "e": null, "f": "a string", "g": [1,2,3,4,5], "h": { "a": 123, "b": { "a": 12 }, "c": [1, 2, 3, {"a": "b"}, [1,2,3]] } }' } context "parsing/replacement with a very simple map" do let(:orig_text) { '{"a":1}' } let(:final_text) { '{"a":2}' } let (:new_value) { "2" } let (:replace_path) { "a" } include_examples "config document replace CONF test" include_examples "config document replace JSON test" end context "parsing/replacement with a map without surrounding braces" do let (:orig_text) { "a: b\nc = d" } let (:final_text) { "a: b\nc = 12" } let (:new_value) { "12" } let (:replace_path) { "c" } include_examples "config document replace CONF test" end context "parsing/replacement with a complicated map" do let (:final_text) { '{ "a":123, "b": 123.456, "c": true, "d": false, "e": null, "f": "a string", "g": [1,2,3,4,5], "h": { "a": 123, "b": { "a": "i am now a string" }, "c": [1, 2, 3, {"a": "b"}, [1,2,3]] } }' } let (:new_value) { '"i am now a string"' } let (:replace_path) { "h.b.a" } include_examples "config document replace CONF test" include_examples "config document replace JSON test" end context "replacing values with maps" do let (:final_text) { '{ "a":123, "b": 123.456, "c": true, "d": false, "e": null, "f": "a string", "g": [1,2,3,4,5], "h": { "a": 123, "b": { "a": {"a":"b", "c":"d"} }, "c": [1, 2, 3, {"a": "b"}, [1,2,3]] } }' } let (:new_value) { '{"a":"b", "c":"d"}' } let (:replace_path) { "h.b.a" } include_examples "config document replace CONF test" include_examples "config document replace JSON test" end context "replacing values with arrays" do let (:final_text) { '{ "a":123, "b": 123.456, "c": true, "d": false, "e": null, "f": "a string", "g": [1,2,3,4,5], "h": { "a": 123, "b": { "a": [1,2,3,4,5] }, "c": [1, 2, 3, {"a": "b"}, [1,2,3]] } }' } let (:new_value) { "[1,2,3,4,5]" } let (:replace_path) { "h.b.a" } include_examples "config document replace CONF test" include_examples "config document replace JSON test" end context "replacing values with concatenations" do let (:final_text) { '{ "a":123, "b": 123.456, "c": true, "d": false, "e": null, "f": "a string", "g": [1,2,3,4,5], "h": { "a": 123, "b": { "a": this is a concatenation 123 456 {a:b} [1,2,3] {a: this is another 123 concatenation null true} }, "c": [1, 2, 3, {"a": "b"}, [1,2,3]] } }' } let (:new_value) { "this is a concatenation 123 456 {a:b} [1,2,3] {a: this is another 123 concatenation null true}" } let (:replace_path) { "h.b.a" } include_examples "config document replace CONF test" end end context "config document multi element duplicates removed" do it "should remove all duplicates when setting a value" do orig_text = "{a: b, a.b.c: d, a: e}" config_doc = ConfigDocumentFactory.parse_string(orig_text) expect(config_doc.set_value("a", "2").render).to eq("{a: 2}") end it "should keep a trailing comma if succeeding elements were removed in CONF" do orig_text = "{a: b, a: e, a.b.c: d}" config_doc = ConfigDocumentFactory.parse_string(orig_text) expect(config_doc.set_value("a", "2").render).to eq("{a: 2, }") end it "should add the setting if only a multi-element duplicate exists" do orig_text = "{a.b.c: d}" config_doc = ConfigDocumentFactory.parse_string(orig_text) expect(config_doc.set_value("a", "2").render).to eq("{ a: 2}") end end context "config document set new value brace root" do let (:orig_text) { "{\n\t\"a\":\"b\",\n\t\"c\":\"d\"\n}" } let (:new_value) { "\"f\"" } let (:replace_path) { "\"e\"" } context "set a new value in CONF" do let (:final_text) { "{\n\t\"a\":\"b\",\n\t\"c\":\"d\"\n\t\"e\": \"f\"\n}" } include_examples "config document replace CONF test" end context "set a new value in JSON" do let (:final_text) { "{\n\t\"a\":\"b\",\n\t\"c\":\"d\",\n\t\"e\": \"f\"\n}" } include_examples "config document replace JSON test" end end context "config document set new value no braces" do let (:orig_text) { "\"a\":\"b\",\n\"c\":\"d\"\n" } let (:final_text) { "\"a\":\"b\",\n\"c\":\"d\"\n\"e\": \"f\"\n" } let (:new_value) { "\"f\"" } let (:replace_path) { "\"e\"" } include_examples "config document replace CONF test" end context "config document set new value multi level CONF" do let (:orig_text) { "a:b\nc:d" } let (:final_text) { "a:b\nc:d\ne: {\n f: {\n g: 12\n }\n}" } let (:new_value) { "12" } let (:replace_path) { "e.f.g" } include_examples "config document replace CONF test" end context "config document set new value multi level JSON" do let (:orig_text) { "{\"a\":\"b\",\n\"c\":\"d\"}" } let (:final_text) { "{\"a\":\"b\",\n\"c\":\"d\",\n \"e\": {\n \"f\": {\n \"g\": 12\n }\n }}" } let (:new_value) { "12" } let (:replace_path) { "e.f.g" } include_examples "config document replace JSON test" end context "config document set new config value" do let (:orig_text) { "{\"a\": \"b\"}" } let (:final_text) { "{\"a\": 12}" } let (:config_doc_hocon) { ConfigDocumentFactory.parse_string(orig_text) } let (:config_doc_json) { ConfigDocumentFactory.parse_string(orig_text, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON)) } let (:new_value) { ConfigValueFactory.from_any_ref(12) } it "should successfuly render the original text from both documents" do expect(config_doc_hocon.render).to eq(orig_text) expect(config_doc_json.render).to eq(orig_text) end it "should succesfully set a new value on both documents" do expect(config_doc_hocon.set_config_value("a", new_value).render).to eq(final_text) expect(config_doc_json.set_config_value("a", new_value).render).to eq(final_text) end end context "config document has value" do let (:orig_text) { "{a: b, a.b.c.d: e, c: {a: {b: c}}}" } let (:config_doc) { ConfigDocumentFactory.parse_string(orig_text) } it "should return true on paths that exist in the document" do expect(config_doc.has_value?("a")).to be_truthy expect(config_doc.has_value?("a.b.c")).to be_truthy expect(config_doc.has_value?("c.a.b")).to be_truthy end it "should return false on paths that don't exist in the document" do expect(config_doc.has_value?("c.a.b.c")).to be_falsey expect(config_doc.has_value?("a.b.c.d.e")).to be_falsey expect(config_doc.has_value?("this.does.not.exist")).to be_falsey end end context "config document remove value" do let (:orig_text) { "{a: b, a.b.c.d: e, c: {a: {b: c}}}" } let (:config_doc) { ConfigDocumentFactory.parse_string(orig_text) } it "should remove a top-level setting with a simple value" do expect(config_doc.remove_value("a").render).to eq("{c: {a: {b: c}}}") end it "should remove a top-level setting with a complex value" do expect(config_doc.remove_value("c").render).to eq("{a: b, a.b.c.d: e, }") end it "should do nothing if the setting does not exist" do expect(config_doc.remove_value("this.does.not.exist")).to eq(config_doc) end end context "config document remove value JSON" do it "should not leave a trailing comma when removing a value in JSON" do orig_text = '{"a": "b", "c": "d"}' config_doc = ConfigDocumentFactory.parse_string(orig_text, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON)) expect(config_doc.remove_value("c").render).to eq('{"a": "b" }') end end context "config document remove multiple" do it "should remove duplicate nested keys" do orig_text = "a { b: 42 }, a.b = 43, a { b: { c: 44 } }" config_doc = ConfigDocumentFactory.parse_string(orig_text) removed = config_doc.remove_value("a.b") expect(removed.render).to eq("a { }, a { }") end end context "config document remove overridden" do it "should remove all instances of keys even if overridden by a top-level key/value pair" do orig_text = "a { b: 42 }, a.b = 43, a { b: { c: 44 } }, a: 57 " config_doc = ConfigDocumentFactory.parse_string(orig_text) removed = config_doc.remove_value("a.b") expect(removed.render).to eq("a { }, a { }, a: 57 ") end end context "config document remove nested" do it "should remove nested keys if specified" do orig_text = "a { b: 42 }, a.b = 43, a { b: { c: 44 } }" config_doc = ConfigDocumentFactory.parse_string(orig_text) removed = config_doc.remove_value("a.b.c") expect(removed.render).to eq("a { b: 42 }, a.b = 43, a { b: { } }") end end context "config document array failures" do let (:orig_text) { "[1, 2, 3, 4, 5]" } let (:document) { ConfigDocumentFactory.parse_string(orig_text) } it "should throw when set_value is called and there is an array at the root" do e = TestUtils.intercept(Hocon::ConfigError) { document.set_value("a", "1") } expect(e.message).to include("ConfigDocument had an array at the root level") end it "should throw when has_value is called and there is an array at the root" do e = TestUtils.intercept(Hocon::ConfigError) { document.has_value?("a") } expect(e.message).to include("ConfigDocument had an array at the root level") end it "should throw when remove_value is called and there is an array at the root" do e = TestUtils.intercept(Hocon::ConfigError) { document.remove_value("a") } expect(e.message).to include("ConfigDocument had an array at the root level") end end context "config document JSON replace failure" do it "should fail when trying to replace with a value using HOCON syntax in JSON" do orig_text = "{\"foo\": \"bar\", \"baz\": \"qux\"}" document = ConfigDocumentFactory.parse_string(orig_text, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON)) e = TestUtils.intercept(Hocon::ConfigError) { document.set_value("foo", "unquoted") } expect(e.message).to include("Token not allowed in valid JSON") end end context "config document JSON replace with concatenation failure" do it "should fail when trying to add a concatenation into a JSON document" do orig_text = "{\"foo\": \"bar\", \"baz\": \"qux\"}" document = ConfigDocumentFactory.parse_string(orig_text, ConfigParseOptions.defaults.set_syntax(ConfigSyntax::JSON)) e = TestUtils.intercept(Hocon::ConfigError) { document.set_value("foo", "1 2 3 concatenation") } expect(e.message).to include("Parsing JSON and the value set in setValue was either a concatenation or had trailing whitespace, newlines, or comments") end end context "config document file parse" do let (:config_document) { ConfigDocumentFactory.parse_file(TestUtils.resource_file("test01.conf")) } let (:file_text) { file = File.open(TestUtils.resource_file("test01.conf"), "rb") contents = file.read file.close contents } it "should correctly parse from a file" do expect(config_document.render).to eq(file_text) end end # skipping reader parsing, since we don't support that in ruby hocon context "config document indentation single line object" do it "should properly indent a value in a single-line map" do orig_text = "a { b: c }" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("a.d", "e").render).to eq("a { b: c, d: e }") end it "should properly indent a value in the top-level when it is on a single line" do orig_text = "a { b: c }, d: e" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("f", "g").render).to eq("a { b: c }, d: e, f: g") end it "should not preserve trailing commas" do orig_text = "a { b: c }, d: e," config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("f", "g").render).to eq("a { b: c }, d: e, f: g") end it "should add necessary keys along the path to the value and properly space them" do orig_text = "a { b: c }, d: e," config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("f.g.h", "i").render).to eq("a { b: c }, d: e, f: { g: { h: i } }") end it "should properly indent keys added to the top-level with curly braces" do orig_text = "{a { b: c }, d: e}" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("f", "g").render).to eq("{a { b: c }, d: e, f: g}") end it "should add necessary keys along the path to the value and properly space them when the root has braces" do orig_text = "{a { b: c }, d: e}" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("f.g.h", "i").render).to eq("{a { b: c }, d: e, f: { g: { h: i } }}") end end context "config document indentation multi line object" do context "document with no trailing newlines" do let (:orig_text) { "a {\n b: c\n}" } let (:config_document) { ConfigDocumentFactory.parse_string(orig_text) } it "should properly indent a value in a multi-line map" do expect(config_document.set_value("a.e", "f").render).to eq("a {\n b: c\n e: f\n}") end it "should properly add/indent any necessary objects along the way to the value" do expect(config_document.set_value("a.d.e.f", "g").render).to eq("a {\n b: c\n d: {\n e: {\n f: g\n }\n }\n}") end end context "document with multi-line root" do let (:orig_text) { "a {\n b: c\n}\n" } let (:config_document) { ConfigDocumentFactory.parse_string(orig_text) } it "should properly indent a value at the root with multiple lines" do expect(config_document.set_value("d", "e").render).to eq("a {\n b: c\n}\nd: e\n") end it "should properly add/indent any necessary objects along the way to the value" do expect(config_document.set_value("d.e.f", "g").render).to eq("a {\n b: c\n}\nd: {\n e: {\n f: g\n }\n}\n") end end end context "config document indentation nested" do it "should properly space a new key/value pair in a nested map in a single-line document" do orig_text = "a { b { c { d: e } } }" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("a.b.c.f", "g").render).to eq("a { b { c { d: e, f: g } } }") end it "should properly space a new key/value pair in a nested map in a multi-line document" do orig_text = "a {\n b {\n c {\n d: e\n }\n }\n}" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("a.b.c.f", "g").render).to eq("a {\n b {\n c {\n d: e\n f: g\n }\n }\n}") end end context "config document indentation empty object" do it "should properly space a new key/value pair in a single-line empty object" do orig_text = "a { }" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("a.b", "c").render).to eq("a { b: c }") end it "should properly indent a new key/value pair in a multi-line empty object" do orig_text = "a {\n b {\n }\n}" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("a.b.c", "d").render).to eq("a {\n b {\n c: d\n }\n}") end end context "config document indentation multi line value" do let (:orig_text) { "a {\n b {\n c {\n d: e\n }\n }\n}" } let (:config_document) { ConfigDocumentFactory.parse_string(orig_text) } it "should successfully insert and indent a multi-line object" do expect(config_document.set_value("a.b.c.f", "{\n g: h\n i: j\n k: {\n l: m\n }\n}").render ).to eq("a {\n b {\n c {\n d: e\n f: {\n g: h\n i: j\n k: {\n l: m\n }\n }\n }\n }\n}") end it "should successfully insert a concatenation with a multi-line array" do expect(config_document.set_value("a.b.c.f", "12 13 [1,\n2,\n3,\n{\n a:b\n}]").render ).to eq("a {\n b {\n c {\n d: e\n f: 12 13 [1,\n 2,\n 3,\n {\n a:b\n }]\n }\n }\n}") end end context "config document indentation multi line value single line object" do it "should get weird indentation when adding a multi-line value to a single-line object" do orig_text = "a { b { } }" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("a.b.c", "{\n c:d\n}").render).to eq("a { b { c: {\n c:d\n } } }") end end context "config document indentation single line object containing multi line value" do it "should treat an object with no new-lines outside of its values as a single-line object" do orig_text = "a { b {\n c: d\n} }" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("a.e", "f").render).to eq("a { b {\n c: d\n}, e: f }") end end context "config document indentation replacing with multi line value" do it "should properly indent a multi-line value when replacing a single-line value" do orig_text = "a {\n b {\n c: 22\n }\n}" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("a.b.c", "{\n d:e\n}").render).to eq("a {\n b {\n c: {\n d:e\n }\n }\n}") end it "should properly indent a multi-line value when replacing a single-line value in an object with multiple keys" do orig_text = "a {\n b {\n f: 10\n c: 22\n }\n}" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("a.b.c", "{\n d:e\n}").render).to eq("a {\n b {\n f: 10\n c: {\n d:e\n }\n }\n}") end end context "config document indentation value with include" do it "should indent an include node" do orig_text = "a {\n b {\n c: 22\n }\n}" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("a.b.d", "{\n include \"foo\"\n e:f\n}").render ).to eq("a {\n b {\n c: 22\n d: {\n include \"foo\"\n e:f\n }\n }\n}") end end context "config document indentation based on include node" do it "should indent properly when only an include node is present in the object in which the value is inserted" do orig_text = "a: b\n include \"foo\"\n" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("c", "d").render).to eq("a: b\n include \"foo\"\n c: d\n") end end context "insertion into an empty document" do it "should successfully insert a value into an empty document" do orig_text = "" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("a", "1").render).to eq("a: 1") end it "should successfully insert a multi-line object into an empty document" do orig_text = "" config_document = ConfigDocumentFactory.parse_string(orig_text) expect(config_document.set_value("a.b", "1").render).to eq("a: {\n b: 1\n}") end it "should successfully insert a hash into an empty document" do orig_text = "" config_document = ConfigDocumentFactory.parse_string(orig_text) map_val = ConfigValueFactory.from_any_ref({"a" => 1, "b" => 2}) expect(config_document.set_config_value("a", map_val).render).to eq("a: {\n \"a\": 1,\n \"b\": 2\n}") end it "should successfully insert an array into an empty document" do orig_text = "" config_document = ConfigDocumentFactory.parse_string(orig_text) array_val = ConfigValueFactory.from_any_ref([1,2]) expect(config_document.set_config_value("a", array_val).render).to eq("a: [\n 1,\n 2\n]") end end context "can insert a map parsed with ConfigValueFactory" do it "should successfully insert a map into a document" do orig_text = "{ a: b }" config_document = ConfigDocumentFactory.parse_string(orig_text) map = ConfigValueFactory.from_any_ref({"a" => 1, "b" => 2}) expect(config_document.set_config_value("a", map).render).to eq("{ a: {\n \"a\": 1,\n \"b\": 2\n } }") end end end hocon-1.2.5/spec/unit/typesafe/config/public_api_spec.rb0000644000175000017500000004116113154611745023177 0ustar apoikosapoikos# encoding: utf-8 require 'spec_helper' require 'test_utils' require 'hocon' require 'hocon/config_factory' require 'hocon/config_value_factory' require 'hocon/impl/config_delayed_merge_object' require 'hocon/impl/replaceable_merge_stack' require 'hocon/config_util' # Note: Skipping many tests that rely on java's System.getProperties functionality, # which lets you access things like "os.name", "java.vendor", and "user.home" # Also skipping ConfigFactory = Hocon::ConfigFactory ConfigValueFactory = Hocon::ConfigValueFactory SimpleConfigObject = Hocon::Impl::SimpleConfigObject SimpleConfigList = Hocon::Impl::SimpleConfigList ConfigUtil = Hocon::ConfigUtil shared_examples_for "test_from_value" do default_value_description = "hardcoded value" specify "create_from made into a config value should equal the expected value" do expect(Hocon::ConfigValueFactory.from_any_ref(create_from)).to eq(expected_value) end specify "create_from made into a config value with origin description should equal the expected value" do expect(Hocon::ConfigValueFactory.from_any_ref(create_from, "foo")).to eq(expected_value) end specify "descriptions match" do if create_from.is_a?(Hocon::ConfigValue) # description is ignored for createFrom that is already a ConfigValue expect(Hocon::ConfigValueFactory.from_any_ref(create_from).origin.description).to eq(create_from.origin.description) else expect(Hocon::ConfigValueFactory.from_any_ref(create_from).origin.description).to eq(default_value_description) expect(Hocon::ConfigValueFactory.from_any_ref(create_from, "foo").origin.description).to eq("foo") end end end describe "basic load and get" do conf = ConfigFactory.load_file(TestUtils.resource_file("test01")) specify "should be able to see some values in the config object" do expect(conf.get_int("ints.fortyTwo")).to eq(42) child = conf.get_config("ints") expect(child.get_int("fortyTwo")).to eq(42) end end describe "loading JSON only" do options = Hocon::ConfigParseOptions.defaults.set_syntax(Hocon::ConfigSyntax::JSON) conf = ConfigFactory.load_file_with_parse_options(TestUtils.resource_file("test01"), options) specify "should be missing value specific to CONF files" do TestUtils.intercept(Hocon::ConfigError::ConfigMissingError) do conf.get_int("ints.fortyTwo") end end specify "should find value specific to the JSON file" do expect(conf.get_int("fromJson1")).to eq(1) end end describe "loading CONF only" do options = Hocon::ConfigParseOptions.defaults.set_syntax(Hocon::ConfigSyntax::CONF) conf = ConfigFactory.load_file_with_parse_options(TestUtils.resource_file("test01"), options) specify "should be missing value specific to JSON files" do TestUtils.intercept(Hocon::ConfigError::ConfigMissingError) do conf.get_int("fromJson1") end TestUtils.intercept(Hocon::ConfigError::ConfigMissingError) do conf.get_int("fromProps.one") end end specify "should find value specific to the CONF file" do expect(conf.get_int("ints.fortyTwo")).to eq(42) end end describe "ConfigFactory#load_file_with_resolve_options" do options = Hocon::ConfigResolveOptions.defaults conf = ConfigFactory.load_file_with_resolve_options(TestUtils.resource_file("test01"), options) specify "sanity check to make sure load_file_with_resolve_options act strange" do expect(conf.get_int("ints.fortyTwo")).to eq(42) end end describe "empty configs" do empty = ConfigFactory.empty empty_foo = ConfigFactory.empty("foo") specify "empty config is empty" do expect(empty.empty?).to be true end specify "empty config's origin should be 'empty config'" do expect(empty.origin.description).to eq("empty config") end specify "empty config with origin description is empty" do expect(empty_foo.empty?).to be true end specify "empty config with origin description 'foo' is having it's description set" do expect(empty_foo.origin.description).to eq("foo") end end describe "Creating objects with ConfigValueFactory" do context "from true" do let(:expected_value) { TestUtils.bool_value(true) } let(:create_from) { true } include_examples "test_from_value" end context "from false" do let(:expected_value) { TestUtils.bool_value(false) } let(:create_from) { false } include_examples "test_from_value" end context "from nil" do let(:expected_value) { TestUtils.null_value } let(:create_from) { nil } include_examples "test_from_value" end context "from int" do let(:expected_value) { TestUtils.int_value(5) } let(:create_from) { 5 } include_examples "test_from_value" end context "from float" do let(:expected_value) { TestUtils.double_value(3.14) } let(:create_from) { 3.14 } include_examples "test_from_value" end context "from string" do let(:expected_value) { TestUtils.string_value("hello world") } let(:create_from) { "hello world" } include_examples "test_from_value" end context "from empty hash" do let(:expected_value) { SimpleConfigObject.new(TestUtils.fake_origin, {}) } let(:create_from) { {} } include_examples "test_from_value" end context "from populated hash" do value_hash = TestUtils.config_map({"a" => 1, "b" => 2, "c" => 3}) let(:expected_value) { SimpleConfigObject.new(TestUtils.fake_origin, value_hash) } let(:create_from) { {"a" => 1, "b" => 2, "c" => 3} } include_examples "test_from_value" specify "from_map should also work" do # from_map is just a wrapper around from_any_ref expect(ConfigValueFactory.from_map({"a" => 1, "b" => 2, "c" => 3}).origin.description).to eq("hardcoded value") expect(ConfigValueFactory.from_map({"a" => 1, "b" => 2, "c" => 3}, "foo").origin.description).to eq("foo") end end context "from empty array" do let(:expected_value) { SimpleConfigList.new(TestUtils.fake_origin, []) } let(:create_from) { [] } include_examples "test_from_value" end context "from populated array" do value_array = [1, 2, 3].map { |v| TestUtils.int_value(v) } let(:expected_value) { SimpleConfigList.new(TestUtils.fake_origin, value_array) } let(:create_from) { [1, 2, 3] } include_examples "test_from_value" end # Omitting tests that involve trees and iterators # Omitting tests using units (memory size, duration, etc) context "from existing Config values" do context "from int" do let(:expected_value) { TestUtils.int_value(1000) } let(:create_from) { TestUtils.int_value(1000) } include_examples "test_from_value" end context "from string" do let(:expected_value) { TestUtils.string_value("foo") } let(:create_from) { TestUtils.string_value("foo") } include_examples "test_from_value" end context "from hash" do int_map = {"a" => 1, "b" => 2, "c" => 3} let(:expected_value) { SimpleConfigObject.new(TestUtils.fake_origin, TestUtils.config_map(int_map)) } let(:create_from) { SimpleConfigObject.new(TestUtils.fake_origin, TestUtils.config_map(int_map)) } include_examples "test_from_value" end end context "from existing list of Config values" do int_list = [1, 2, 3].map { |v| TestUtils.int_value(v) } let(:expected_value) { SimpleConfigList.new(TestUtils.fake_origin, int_list) } let(:create_from) { int_list } include_examples "test_from_value" end end describe "round tripping unwrap" do conf = ConfigFactory.load_file(TestUtils.resource_file("test01")) unwrapped = conf.root.unwrapped rewrapped = ConfigValueFactory.from_map(unwrapped, conf.origin.description) reunwrapped = rewrapped.unwrapped specify "conf has a lot of stuff in it" do expect(conf.root.size).to be > 4 end specify "rewrapped conf equals conf" do expect(rewrapped).to eq(conf.root) end specify "reunwrapped conf equals unwrapped conf" do expect(unwrapped).to eq(reunwrapped) end end # Omitting Tests (and functionality) for ConfigFactory.parse_map until I know if it's # a priority describe "default parse options" do def check_not_found(e) ["No such", "not found", "were found"].any? { |string| e.message.include?(string)} end let(:defaults) { Hocon::ConfigParseOptions::defaults } specify "allow missing == true" do expect(defaults.allow_missing?).to be true end specify "includer == nil" do expect(defaults.includer).to be_nil end specify "origin description == nil" do expect(defaults.origin_description).to be_nil end specify "syntax == nil" do expect(defaults.syntax).to be_nil end context "allow missing with ConfigFactory#parse_file" do specify "nonexistant conf throws error when allow_missing? == false" do allow_missing_false = Hocon::ConfigParseOptions::defaults.set_allow_missing(false) e = TestUtils.intercept(Hocon::ConfigError::ConfigIOError) do ConfigFactory.parse_file(TestUtils.resource_file("nonexistant.conf"), allow_missing_false) end expect(check_not_found(e)).to be true end specify "nonexistant conf returns empty conf when allow_missing? == false" do allow_missing_true = Hocon::ConfigParseOptions::defaults.set_allow_missing(true) conf = ConfigFactory.parse_file(TestUtils.resource_file("nonexistant.conf"), allow_missing_true) expect(conf.empty?).to be true end end context "allow missing with ConfigFactory#parse_file_any_syntax" do specify "nonexistant conf throws error when allow_missing? == false" do allow_missing_false = Hocon::ConfigParseOptions::defaults.set_allow_missing(false) e = TestUtils.intercept(Hocon::ConfigError::ConfigIOError) do ConfigFactory.parse_file_any_syntax(TestUtils.resource_file("nonexistant"), allow_missing_false) end expect(check_not_found(e)).to be true end specify "nonexistant conf returns empty conf when allow_missing? == false" do allow_missing_true = Hocon::ConfigParseOptions::defaults.set_allow_missing(true) conf = ConfigFactory.parse_file_any_syntax(TestUtils.resource_file("nonexistant"), allow_missing_true) expect(conf.empty?).to be true end end # Omitting ConfigFactory.prase_resources_any_syntax since we're not supporting it context "allow missing shouldn't mess up includes" do # test03.conf contains some nonexistent includes. check that # setAllowMissing on the file (which is not missing) doesn't # change that the includes are allowed to be missing. # This can break because some options might "propagate" through # to includes, but we don't want them all to do so. allow_missing_true = Hocon::ConfigParseOptions::defaults.set_allow_missing(true) allow_missing_false = Hocon::ConfigParseOptions::defaults.set_allow_missing(false) conf = ConfigFactory.parse_file(TestUtils.resource_file("test03.conf"), allow_missing_false) conf2 = ConfigFactory.parse_file(TestUtils.resource_file("test03.conf"), allow_missing_true) specify "conf should have stuff from test01.conf" do expect(conf.get_int("test01.booleans")).to eq(42) end specify "both confs should be equal regardless of allow_missing being true or false" do expect(conf).to eq(conf2) end end end # Omitting test that creates a subclass of ConfigIncluder to record everything that's # included by a .conf file. It's complex and we've decided the functionality is well # tested elsewhere and right now it isn't worth the effort. describe "string parsing" do specify "should parse correctly" do conf = ConfigFactory.parse_string("{ a : b }", Hocon::ConfigParseOptions.defaults) expect(conf.get_string("a")).to eq("b") end end # Omitting tests for parse_file_any_syntax in the interests of time since this has already # been tested above # Omitting classpath tests describe "config_utils" do # This is to test the public wrappers around ConfigImplUtils specify "can join and split paths" do expect(ConfigUtil.join_path("", "a", "b", "$")).to eq("\"\".a.b.\"$\"") expect(ConfigUtil.join_path_from_list(["", "a", "b", "$"])).to eq("\"\".a.b.\"$\"") expect(ConfigUtil.split_path("\"\".a.b.\"$\"")).to eq(["", "a", "b", "$"]) end specify "should throw errors on invalid paths" do TestUtils.intercept(Hocon::ConfigError) do ConfigUtil.split_path("$") end TestUtils.intercept(Hocon::ConfigError) do # no args ConfigUtil.join_path end TestUtils.intercept(Hocon::ConfigError) do # empty list ConfigUtil.join_path_from_list([]) end end specify "should quote strings correctly" do expect(ConfigUtil.quote_string("")).to eq("\"\"") expect(ConfigUtil.quote_string("a")).to eq("\"a\"") expect(ConfigUtil.quote_string("\n")).to eq("\"\\n\"") end end # Omitting tests that use class loaders describe "detecting cycles" do specify "should detect a cycle" do e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) do ConfigFactory.load_file(TestUtils.resource_file("cycle.conf")) end # Message mentioning cycle expect(e.message).to include("include statements nested") end end describe "including from list" do # We would ideally make this case NOT throw an exception but we need to do some work # to get there, see https://github.com/typesafehub/config/issues/160 specify "should throw error when trying to include from list" do e = TestUtils.intercept(Hocon::ConfigError::ConfigParseError) do ConfigFactory.load_file(TestUtils.resource_file("include-from-list.conf")) end # Message mentioning current implementation limitations expect(e.message).to include("limitation") end end # Omitting tests using System.getProperty since it's java specific # Omitting serialization tests since we aren't supporting it describe "using some values without resolving" do conf = ConfigFactory.parse_string("a=42,b=${NOPE}") specify "should be able to use some values without resolving" do expect(conf.get_int("a")).to eq(42) end specify "unresolved value should throw error" do TestUtils.intercept(Hocon::ConfigError::ConfigNotResolvedError) do conf.get_int("b") end end end describe "include file statements" do conf = ConfigFactory.parse_file(TestUtils.resource_file("file-include.conf")) specify "should find values from each included file" do expect(conf.get_int("base")).to eq(41) expect(conf.get_int("foo")).to eq(42) expect(conf.get_int("bar")).to eq(43) # these two do not work right now, because we do not # treat the filename as relative to the including file # if file() is specified, so `include file("bar-file.conf")` # fails. #assertEquals("got bar-file.conf", 44, conf.getInt("bar-file")) #assertEquals("got subdir/baz.conf", 45, conf.getInt("baz")) end specify "should not find certain paths" do expect(conf.has_path?("bar-file")).to be false expect(conf.has_path?("baz")).to be false end end describe "Config#has_path_or_null" do conf = ConfigFactory.parse_string("x.a=null,x.b=42") specify "has_path_or_null returns correctly" do # hasPath says false for null expect(conf.has_path?("x.a")).to be false # hasPathOrNull says true for null expect(conf.has_path_or_null?("x.a")).to be true # hasPath says true for non-null expect(conf.has_path?("x.b")).to be true # hasPathOrNull says true for non-null expect(conf.has_path_or_null?("x.b")).to be true # hasPath says false for missing expect(conf.has_path?("x.c")).to be false # hasPathOrNull says false for missing expect(conf.has_path_or_null?("x.c")).to be false # hasPath says false for missing under null expect(conf.has_path?("x.a.y")).to be false # hasPathOrNull says false for missing under null expect(conf.has_path_or_null?("x.a.y")).to be false # hasPath says false for missing under missing expect(conf.has_path?("x.c.y")).to be false # hasPathOrNull says false for missing under missing expect(conf.has_path_or_null?("x.c.y")).to be false end end describe "Config#get_is_null" do conf = ConfigFactory.parse_string("x.a=null,x.b=42") specify "should return whether or not values are null correctly" do expect(conf.is_null?("x.a")).to be true expect(conf.is_null?("x.b")).to be false end specify "should throw error for missing values" do TestUtils.intercept(Hocon::ConfigError::ConfigMissingError) do conf.is_null?("x.c") end end specify "should throw error for missing underneal null" do TestUtils.intercept(Hocon::ConfigError::ConfigMissingError) do conf.is_null?("x.a.y") end end specify "should throw error for missing underneath missing" do TestUtils.intercept(Hocon::ConfigError::ConfigMissingError) do conf.is_null?("x.c.y") end end end hocon-1.2.5/spec/unit/cli/0000755000175000017500000000000013154611745015210 5ustar apoikosapoikoshocon-1.2.5/spec/unit/cli/cli_spec.rb0000644000175000017500000001225313154611745017321 0ustar apoikosapoikos# encoding: utf-8 require 'spec_helper' require 'test_utils' describe Hocon::CLI do #################### # Argument Parsing #################### context 'argument parsing' do it 'should find all the flags and arguments' do args = %w(-i foo -o bar set some.path some_value --json) expected_options = { in_file: 'foo', out_file: 'bar', subcommand: 'set', path: 'some.path', new_value: 'some_value', json: true } expect(Hocon::CLI.parse_args(args)).to eq(expected_options) end it 'should set -i and -o to -f if given' do args = %w(-f foo set some.path some_value) expected_options = { file: 'foo', in_file: 'foo', out_file: 'foo', subcommand: 'set', path: 'some.path', new_value: 'some_value' } expect(Hocon::CLI.parse_args(args)).to eq(expected_options) end end context 'subcommands' do hocon_text = 'foo.bar { baz = 42 array = [1, 2, 3] hash: {key: value} }' context 'do_get()' do it 'should get simple values' do options = {path: 'foo.bar.baz'} expect(Hocon::CLI.do_get(options, hocon_text)).to eq('42') end it 'should work with arrays' do options = {path: 'foo.bar.array'} expected = "[\n 1,\n 2,\n 3\n]" expect(Hocon::CLI.do_get(options, hocon_text)).to eq(expected) end it 'should work with hashes' do options = {path: 'foo.bar.hash'} expected = "key: value\n" expect(Hocon::CLI.do_get(options, hocon_text)).to eq(expected) end it 'should output json if specified' do options = {path: 'foo.bar.hash', json: true} # Note that this is valid json, while the test above is not expected = "{\n \"key\": \"value\"\n}\n" expect(Hocon::CLI.do_get(options, hocon_text)).to eq(expected) end it 'should throw a MissingPathError if the path does not exist' do options = {path: 'not.a.path'} expect {Hocon::CLI.do_get(options, hocon_text)} .to raise_error(Hocon::CLI::MissingPathError) end it 'should throw a MissingPathError if the path leads into an array' do options = {path: 'foo.array.1'} expect {Hocon::CLI.do_get(options, hocon_text)} .to raise_error(Hocon::CLI::MissingPathError) end it 'should throw a MissingPathError if the path leads into a string' do options = {path: 'foo.hash.key.value'} expect {Hocon::CLI.do_get(options, hocon_text)} .to raise_error(Hocon::CLI::MissingPathError) end end context 'do_set()' do it 'should overwrite values' do options = {path: 'foo.bar.baz', new_value: 'pi'} expected = hocon_text.sub(/42/, 'pi') expect(Hocon::CLI.do_set(options, hocon_text)).to eq(expected) end it 'should create new nested values' do options = {path: 'new.nested.path', new_value: 'hello'} expected = "new: {\n nested: {\n path: hello\n }\n}" # No config is supplied, so it will need to add new nested hashes expect(Hocon::CLI.do_set(options, '')).to eq(expected) end it 'should allow arrays to be set' do options = {path: 'my_array', new_value: '[1, 2, 3]'} expected = 'my_array: [1, 2, 3]' expect(Hocon::CLI.do_set(options, '')).to eq(expected) end it 'should allow arrays in strings to be set as strings' do options = {path: 'my_array', new_value: '"[1, 2, 3]"'} expected = 'my_array: "[1, 2, 3]"' expect(Hocon::CLI.do_set(options, '')).to eq(expected) end it 'should allow hashes to be set' do do_set_options = {path: 'my_hash', new_value: '{key: value}'} do_set_expected = 'my_hash: {key: value}' do_set_result = Hocon::CLI.do_set(do_set_options, '') expect(do_set_result).to eq(do_set_expected) # Make sure it can be parsed again and be seen as a real hash do_get_options = {path: 'my_hash.key'} do_get_expected = 'value' expect(Hocon::CLI.do_get(do_get_options, do_set_result)).to eq(do_get_expected) end it 'should allow hashes to be set as strings' do do_set_options = {path: 'my_hash', new_value: '"{key: value}"'} do_set_expected = 'my_hash: "{key: value}"' do_set_result = Hocon::CLI.do_set(do_set_options, '') expect(do_set_result).to eq(do_set_expected) # Make sure it can't be parsed again and be seen as a real hash do_get_options = {path: 'my_hash.key'} expect{Hocon::CLI.do_get(do_get_options, do_set_result)} .to raise_error(Hocon::CLI::MissingPathError) end end context 'do_unset()' do it 'should remove values' do options = {path: 'foo.bar.baz'} expected = hocon_text.sub(/baz = 42/, '') expect(Hocon::CLI.do_unset(options, hocon_text)).to eq(expected) end it 'should throw a MissingPathError if the path does not exist' do options = {path: 'fake.path'} expect{Hocon::CLI.do_unset(options, hocon_text)} .to raise_error(Hocon::CLI::MissingPathError) end end end end hocon-1.2.5/spec/unit/hocon/0000755000175000017500000000000013154611745015547 5ustar apoikosapoikoshocon-1.2.5/spec/unit/hocon/README.md0000644000175000017500000000043713154611745017032 0ustar apoikosapoikos## RUBY-SPECIFIC TESTS This directory should only contain tests that are specific to the Ruby library/API. Tests ported from the upstream Java library should live in spec/typesafe/config. Where possible it would be good to avoid sharing fixtures between the two types of tests as well.hocon-1.2.5/spec/unit/hocon/hocon_spec.rb0000644000175000017500000000762113154611745020222 0ustar apoikosapoikos# encoding: utf-8 require 'spec_helper' require 'hocon' require 'hocon/config_render_options' require 'hocon/config_error' require 'hocon/config_syntax' ConfigParseError = Hocon::ConfigError::ConfigParseError ConfigWrongTypeError = Hocon::ConfigError::ConfigWrongTypeError describe Hocon do let(:render_options) { Hocon::ConfigRenderOptions.defaults } before do render_options.origin_comments = false render_options.json = false end RSpec.shared_examples "hocon_parsing" do it "should make the config data available as a map" do expect(conf).to eq(expected) end end [EXAMPLE1, EXAMPLE2].each do |example| let(:input_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/input.conf" } let(:output_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/output.conf" } let(:output) { File.read("#{output_file}") } let(:output_nocomments_file) { "#{FIXTURE_DIR}/parse_render/#{example[:name]}/output_nocomments.conf" } let(:output_nocomments) { File.read("#{output_nocomments_file}") } let(:expected) { example[:hash] } # TODO 'reparsed' appears to be unused let(:reparsed) { Hocon::ConfigFactory.parse_file("#{FIXTURE_DIR}/parse_render/#{example[:name]}/output.conf") } context "loading a HOCON file" do let(:conf) { Hocon.load(input_file) } include_examples "hocon_parsing" end context "parsing a HOCON string" do let(:string) { File.open(input_file).read } let(:conf) { Hocon.parse(string) } include_examples "hocon_parsing" end end it "should fail to parse an array" do puts expect{(Hocon.parse('[1,2,3]'))}. to raise_error(ConfigWrongTypeError) end it "should fail to parse an array" do expect{(Hocon.parse('["one", "two" "three"]'))}. to raise_error(ConfigWrongTypeError) end context "loading a HOCON file with a substitution" do conf = Hocon.load("#{FIXTURE_DIR}/parse_render/#{EXAMPLE3[:name]}/input.conf") expected = EXAMPLE3[:hash] it "should successfully resolve the substitution" do expect(conf).to eq(expected) end end context "loading a file with an unknown extension" do context "without specifying the config format" do it "should raise an error" do expect { Hocon.load("#{FIXTURE_DIR}/hocon/by_extension/cat.test") }.to raise_error(ConfigParseError, /Unrecognized file extension '.test'/) end end context "while specifying the config format" do it "should parse properly if the config format is correct" do expect(Hocon.load("#{FIXTURE_DIR}/hocon/by_extension/cat.test", {:syntax => Hocon::ConfigSyntax::HOCON})). to eq({"meow" => "cats"}) expect(Hocon.load("#{FIXTURE_DIR}/hocon/by_extension/cat.test-json", {:syntax => Hocon::ConfigSyntax::HOCON})). to eq({"meow" => "cats"}) end it "should parse properly if the config format is compatible" do expect(Hocon.load("#{FIXTURE_DIR}/hocon/by_extension/cat.test-json", {:syntax => Hocon::ConfigSyntax::JSON})). to eq({"meow" => "cats"}) end it "should raise an error if the config format is incompatible" do expect { Hocon.load("#{FIXTURE_DIR}/hocon/by_extension/cat.test", {:syntax => Hocon::ConfigSyntax::JSON}) }.to raise_error(ConfigParseError, /Document must have an object or array at root/) end end end context "loading config that includes substitutions" do it "should be able to `load` from a file" do expect(Hocon.load("#{FIXTURE_DIR}/hocon/with_substitution/subst.conf")). to eq({"a" => true, "b" => true}) end it "should be able to `parse` from a string" do expect(Hocon.parse(File.read("#{FIXTURE_DIR}/hocon/with_substitution/subst.conf"))). to eq({"a" => true, "b" => true}) end end end hocon-1.2.5/spec/spec_helper.rb0000644000175000017500000000213113154611745016275 0ustar apoikosapoikos# encoding: utf-8 FIXTURE_DIR = File.join(dir = File.expand_path(File.dirname(__FILE__)), "fixtures") EXAMPLE1 = { :hash => {"foo" => { "bar" => { "baz" => 42, "abracadabra" => "hi", "yahoo" => "yippee", "boom" => [1, 2, {"derp" => "duh"}, 4], "empty" => [], "truthy" => true, "falsy" => false }}}, :name => "example1", } EXAMPLE2 = { :hash => {"jruby-puppet"=> { "jruby-pools" => [{"environment" => "production"}], "load-path" => ["/usr/lib/ruby/site_ruby/1.8", "/usr/lib/ruby/site_ruby/1.8"], "master-conf-dir" => "/etc/puppet", "master-var-dir" => "/var/lib/puppet", }, "webserver" => {"host" => "1.2.3.4"}}, :name => "example2", } EXAMPLE3 = { :hash => {"a" => true, "b" => true}, :name => "example3", } EXAMPLE4 = { :hash => {"kermit" => "frog", "miss" => "piggy", "bert" => "ernie", "janice" => "guitar"}, :name => "example4", } hocon-1.2.5/HISTORY.md0000644000175000017500000015344013154611745014222 0ustar apoikosapoikos# enterprise_ruby-hocon_bump_and_tag_master - History ## Tags * [LATEST - 4 Apr, 2017 (b42a72f0)](#LATEST) * [1.2.4 - 3 Nov, 2016 (5157cc60)](#1.2.4) * [1.2.3 - 3 Nov, 2016 (cd9a5c8d)](#1.2.3) * [1.2.2 - 1 Nov, 2016 (4a29c034)](#1.2.2) * [1.2.1 - 27 Oct, 2016 (b6edea48)](#1.2.1) * [1.2.0 - 27 Oct, 2016 (1060d251)](#1.2.0) * [1.1.3 - 12 Oct, 2016 (bf4a7d4b)](#1.1.3) * [1.1.2 - 15 Jul, 2016 (6041a5c4)](#1.1.2) * [1.1.1 - 6 Jul, 2016 (5b2c8baa)](#1.1.1) * [1.1.0 - 1 Jul, 2016 (99b3145e)](#1.1.0) * [1.0.1 - 16 Mar, 2016 (aa36b692)](#1.0.1) * [1.0.0 - 16 Feb, 2016 (dc385fe2)](#1.0.0) * [0.9.3 - 14 Jul, 2015 (7defef59)](#0.9.3) * [0.9.2 - 30 Jun, 2015 (6b402bc2)](#0.9.2) * [0.9.1 - 30 Jun, 2015 (e8c2f405)](#0.9.1) * [0.9.0 - 10 Apr, 2015 (aeab6ab2)](#0.9.0) * [0.1.0 - 9 Apr, 2015 (bfdb7255)](#0.1.0) * [0.0.5 - 1 Oct, 2014 (67d264f4)](#0.0.5) * [0.0.3 - 24 Jul, 2014 (6cd552c3)](#0.0.3) * [0.0.2 - 24 Jul, 2014 (95dffaea)](#0.0.2) * [0.0.1 - 16 Mar, 2014 (f7dbca52)](#0.0.1) ## Details ### LATEST - 4 Apr, 2017 (b42a72f0) * (GEM) update hocon version to 1.2.5 (b42a72f0) * Merge pull request #108 from jpinsonault/maint-prepare-for-1.2.5 (50b0087b) ``` Merge pull request #108 from jpinsonault/maint-prepare-for-1.2.5 (MAINT) Change version back 1.2.5.SNAPSHOT ``` * (MAINT) Change version back 1.2.5.SNAPSHOT (40d45c77) ``` (MAINT) Change version back 1.2.5.SNAPSHOT CI needs the current to be less than the next version to be released ``` * Merge pull request #107 from jpinsonault/PE-18165-support-for-utf8-file-paths (c65941f2) ``` Merge pull request #107 from jpinsonault/PE-18165-support-for-utf8-file-paths (PE-18165) Support for utf-8 file paths ``` * (MAINT) Prepare for 1.2.5 release (8edf0841) ``` (MAINT) Prepare for 1.2.5 release Update changelog and version ``` * (PE-18165) Support for utf-8 file paths (a54c93b5) ``` (PE-18165) Support for utf-8 file paths This commit removes the dependency on Addressable and adds some comments regarding some screwy areas in the code where we half heartedly tried to support loading of URIs It maintains utf-8 file support ``` * Merge pull request #105 from mwbutcher/maint/master/PE-18165_encode_file_URIs_in_order_to_handle_utf-8_chars (e2725955) ``` Merge pull request #105 from mwbutcher/maint/master/PE-18165_encode_file_URIs_in_order_to_handle_utf-8_chars (PE-18165) encode file URIs to handle utf8 chars ``` * (PE-18165) encode file URIs to handle utf8 chars (51525f43) ``` (PE-18165) encode file URIs to handle utf8 chars Prior to this change, the hocon parser would error when give file names like ᚠᛇᚻ.conf or /tmp/旗本/pe.conf. This commit URI encodes the filenames to avoid that issue. ``` * Merge pull request #104 from puppetlabs/rm_cprice404 (ddb4afb2) ``` Merge pull request #104 from puppetlabs/rm_cprice404 remove cprice404 ``` * remove cprice404 (f74fb2ca) * Merge pull request #98 from puppetlabs/theshanx-patch-1 (c532a69e) ``` Merge pull request #98 from puppetlabs/theshanx-patch-1 (maint) Add internal_list key to MAINTAINERS ``` * Merge pull request #102 from jpinsonault/maint-fix-typo-in-readme (ecd2de47) ``` Merge pull request #102 from jpinsonault/maint-fix-typo-in-readme (MAINT) Fix typo in readme ``` * (MAINT) Fix typo in readme (10961a98) * Merge pull request #101 from jpinsonault/maint-update-changelog-after-1.2.4-release (c8d543ad) ``` Merge pull request #101 from jpinsonault/maint-update-changelog-after-1.2.4-release (MAINT) Update changelog for 1.2.4 ``` * (MAINT) Update changelog for 1.2.4 (c7a5edf1) ``` (MAINT) Update changelog for 1.2.4 And explain missing version numbers ``` * (maint) Add internal_list key to MAINTAINERS (e327d214) ``` (maint) Add internal_list key to MAINTAINERS This change adds a reference to the Google group the maintainers are associated with. ``` ### 1.2.4 - 3 Nov, 2016 (5157cc60) * (HISTORY) update ruby-hocon history for gem release 1.2.4 (5157cc60) * (GEM) update hocon version to 1.2.4 (67ff0795) * Merge pull request #100 from jpinsonault/maint-update-version-to-1.2.4 (3a493130) ``` Merge pull request #100 from jpinsonault/maint-update-version-to-1.2.4 (MAINT) Update version to 1.2.4 ``` * (MAINT) Update version to 1.2.4 (958326d4) ### 1.2.3 - 3 Nov, 2016 (cd9a5c8d) * (HISTORY) update ruby-hocon history for gem release 1.2.3 (cd9a5c8d) * (GEM) update hocon version to 1.2.3 (f2f3e235) * Merge pull request #99 from jpinsonault/maint-update-version-to-1.2.3 (18324c6d) ``` Merge pull request #99 from jpinsonault/maint-update-version-to-1.2.3 (MAINT) Update version 1.2.3 ``` * (MAINT) Update version 1.2.3 (e7be1d78) ### 1.2.2 - 1 Nov, 2016 (4a29c034) * (HISTORY) update ruby-hocon history for gem release 1.2.2 (4a29c034) * (GEM) update hocon version to 1.2.2 (5cf6b037) * Merge pull request #97 from jpinsonault/maint-update-version-for-release (e973ee34) ``` Merge pull request #97 from jpinsonault/maint-update-version-for-release (MAINT) Update version for release ``` * (MAINT) Update version for release (e81fecf9) ### 1.2.1 - 27 Oct, 2016 (b6edea48) * (HISTORY) update ruby-hocon history for gem release 1.2.1 (b6edea48) * (GEM) update hocon version to 1.2.1 (0e06af2f) * Merge pull request #96 from jpinsonault/maint-update-version-to-1.2.1.SNAPSHOT (418d5e24) ``` Merge pull request #96 from jpinsonault/maint-update-version-to-1.2.1.SNAPSHOT (MAINT) Update version to 1.2.1.SNAPSHOT ``` * (MAINT) Update version to 1.2.1.SNAPSHOT (20d34a33) ### 1.2.0 - 27 Oct, 2016 (1060d251) * (HISTORY) update ruby-hocon history for gem release 1.2.0 (1060d251) * (GEM) update hocon version to 1.2.0 (33a9edef) * Merge pull request #95 from jpinsonault/maint-fix-pre-release-version-string (dba994b3) ``` Merge pull request #95 from jpinsonault/maint-fix-pre-release-version-string (MAINT) Fix version string ``` * (MAINT) Fix version string (622fb2ab) * Merge pull request #94 from jpinsonault/maint-update-release-date (41673be4) ``` Merge pull request #94 from jpinsonault/maint-update-release-date (MAINT) Update date in gemfile for 1.2.0 release ``` * (MAINT) Update date in gemfile for 1.2.0 release (25e834fc) * Merge pull request #93 from jpinsonault/maint-prepare-for-1.2.0-release (c0ab30b1) ``` Merge pull request #93 from jpinsonault/maint-prepare-for-1.2.0-release (MAINT) Update version and changelog for 1.2.0 release ``` * (MAINT) Update version and changelog for 1.2.0 release (d4ac81ac) * Merge pull request #92 from jpinsonault/maint-revert-moving-version.rb (bd5a065e) ``` Merge pull request #92 from jpinsonault/maint-revert-moving-version.rb Revert "(MAINT) Move version.rb to work with ci" ``` * Revert "(MAINT) Move version.rb to work with ci" (a17015fc) ``` Revert "(MAINT) Move version.rb to work with ci" This reverts commit 5be440a433141e7dab5534d2309282d0865adca7. ``` * Merge pull request #90 from puppetlabs/add-issue-tracker-link (b046a116) ``` Merge pull request #90 from puppetlabs/add-issue-tracker-link Include link to Jira issue tracker in README ``` * Merge pull request #91 from jpinsonault/maint-move-version.rb-for-ci (dd9753d2) ``` Merge pull request #91 from jpinsonault/maint-move-version.rb-for-ci (MAINT) Move version.rb to work with ci ``` * (MAINT) Move version.rb to work with ci (5be440a4) ``` (MAINT) Move version.rb to work with ci Jenkins expects the version.rb file to be under lib//version.rb ``` * Include link to Jira issue tracker in README (75609de4) * Merge pull request #86 from jpinsonault/hc-92-add-cli-tool-for-hocon (ea0ddcae) ``` Merge pull request #86 from jpinsonault/hc-92-add-cli-tool-for-hocon [WIP] (HC-92) Add cli tool for hocon ``` * (HC-92) Have unset throw an error on missing paths (2577e8fe) ``` (HC-92) Have unset throw an error on missing paths Refactor the way errors are handled. Rather than catching the hocon parser errors, we now raise our own error to make things clearer Add tests for new exception ``` * (HC-92) Remove flock calls (14c548b5) * (MAINT) Update CHANGELOG for 1.1.3 (f02b161a) * (HC-92) Fix version require for ruby 1.9 (d21fb360) * (HC-92) Move version string to Hocon::Version module (ca9de704) * (HC-92) Add -f option, update docs (afe75db5) * (HC-92) Lock files while reading/writing (6271c25e) * (HC-92) Update readme with CLI docs (1afa6c2c) * (HC-92) Update optparse banner with more info (2a047009) * (HC-92) Make new render option optional (06429cac) * (HC-92) Add tests for new render option (2762f01f) ``` (HC-92) Add tests for new render option Adds tests for key_value_separator render option ``` * (HC-92) Add cli tests for setting complex types (3d548f33) * (HC-92) Add key_value_separator render option (b12a822c) ``` (HC-92) Add key_value_separator render option Also updates the CLI tool to use the colon separator in the set subcommand ``` * (HC-92) Update version to 1.2.0 (4746078e) * (HC-92) Whitespace (b9b65fd5) * (HC-92) Remove default space before colons in maps (12a31ca6) * (HC-92) Add spec tests for cli functions (8b06d660) * (HC-92) Improve modularity (846040d0) * (HC-92) Add json output support (6a203bf3) * (HC-92) Better error handling (88a6abca) * (HC-92) Add --out-file support (07bdef54) * (HC-92) Move cli code to lib dir (fb0483fc) * (HC-92) Add STDIN support (61b872c5) * (HC-92) Add CLI tool for hocon (21d5f01d) ### 1.1.3 - 12 Oct, 2016 (bf4a7d4b) * Merge pull request #88 from cprice404/bug/master/parse-with-substitutions (bf4a7d4b) ``` Merge pull request #88 from cprice404/bug/master/parse-with-substitutions Fix bug in `Hocon.parse` with substitutions ``` * Fix bug in `Hocon.parse` with substitutions (07b265b9) ``` Fix bug in `Hocon.parse` with substitutions Currently, if you call `Hocon.parse` with a string that contains substitutions, you will get a `ConfigNotResolvedError`. We had this issue with `Hocon.load` earlier, and modified it to include the code necessary to resolve the config object before returning it. However, we didn't make the same changes for `parse`, so the behavior actually diverged between the two. This commit fixes up `parse` in the same way that we previously fixed up `load`. ``` * Merge pull request #85 from cprice404/maint/master/200-add-maintainers (d98ad200) ``` Merge pull request #85 from cprice404/maint/master/200-add-maintainers (200) Add MAINTAINERS ``` * (200) Add MAINTAINERS (8119f366) * Merge pull request #84 from jpinsonault/maint-add-ruby-gems-widget (a8318435) ``` Merge pull request #84 from jpinsonault/maint-add-ruby-gems-widget (MAINT) Add rubygems version widget ``` * (MAINT) Add rubygems version widget (3b9bb37e) ### 1.1.2 - 15 Jul, 2016 (6041a5c4) * Merge pull request #83 from jpinsonault/maint-update-changelog-for-1.1.2 (6041a5c4) ``` Merge pull request #83 from jpinsonault/maint-update-changelog-for-1.1.2 (MAINT) Update changelog/version for 1.1.2 release ``` * (MAINT) Update changelog/version for 1.1.2 release (947e6aa4) * Merge pull request #82 from Iristyle/ticket/master/HC-82-parse-files-as-utf8-with-boms (a58adc87) ``` Merge pull request #82 from Iristyle/ticket/master/HC-82-parse-files-as-utf8-with-boms (HC-82) Enable UTF-8 with BOM parsing ``` * (HC-82) Add spec for UTF-8 filenames (b0d702c1) ``` (HC-82) Add spec for UTF-8 filenames - Hocon does not currently handle UTF-8 filenames properly ``` * (HC-82) Remove invalid BOM spec (9a5cabea) ``` (HC-82) Remove invalid BOM spec - A skipped test exists for validating a string can be passed to parse_string that starts with a UTF8 BOM \uFEFF - However, when comparing this to the Ruby JSON parser, that parser also doesn't handle this seemingly edge case. Arguably, by the time a string read from a file is passed to a parsing engine, it will be in the correct encoding, and will have leading BOMs trimmed off. For reference, Ruby JSON behavior: [1] pry(main)> require 'json' => true [2] pry(main)> json = "\uFEFF{ \"foo\": \"bar\" }" => "{ \"foo\": \"bar\" }" [3] pry(main)> JSON.parse(json) JSON::ParserError: 757: unexpected token at '{ "foo": "bar" }' from /usr/local/opt/rbenv/versions/2.1.9/lib/ruby/2.1.0/json/common.rb:155:in `parse' ``` * (HC-82) Add additional file encoding specs (9e04970e) ``` (HC-82) Add additional file encoding specs - Show that UTF-8 content is properly handled - Show that UTF-16 content is not yet supported ``` * (HC-82) Enable UTF-8 with BOM parsing (8ccf6625) ``` (HC-82) Enable UTF-8 with BOM parsing - Previously Hocon::ConfigFactory.parse_file did not specify an encoding, and didn't allow for files with UTF-8 BOMs on Windows. In reality, HOCON config files should be detected by their BOM and treated as UTF-8, UTF-16LE, UTF-16BE, UTF-32LE or UTF-32BE based on the presence of the BOM, with a fallback to UTF-8 when one is not present, based on RFC 4627 at https://www.ietf.org/rfc/rfc4627.txt This fix is a bit naive as it may improperly load HOCON config files on Windows which are UCS-2 (a precursor to UTF-16LE). Its recommended that this be addressed later in a better File parsing scheme that peeks at the first few bytes of the file to determine the encoding correctly. ``` ### 1.1.1 - 6 Jul, 2016 (5b2c8baa) * Merge pull request #81 from janelu2/master (5b2c8baa) ``` Merge pull request #81 from janelu2/master (MAINT) update CHANGELOG.md and version number for z release of 1.1.1 ``` * (MAINT) update CHANGELOG.md and version number for z release of 1.1.1 (582cd7e2) * Merge pull request #80 from janelu2/master (17ecc8d4) ``` Merge pull request #80 from janelu2/master (HC-81) Fix undefined method `value_type_name' error ``` * (HC-81) Add tests and fix value_type_name calls (466ca82a) ``` (HC-81) Add tests and fix value_type_name calls (MAINT) fix test to correctly call the wrong error (MAINT) add require to config_value_type and use alias ``` ### 1.1.0 - 1 Jul, 2016 (99b3145e) * Merge pull request #78 from jpinsonault/maint-update-changelog-for-release (99b3145e) ``` Merge pull request #78 from jpinsonault/maint-update-changelog-for-release (MAINT) Update changelog and version for release ``` * (MAINT) Fix changelog version (aa3fef17) * Merge pull request #79 from janelu2/master (17d96e4b) ``` Merge pull request #79 from janelu2/master (MAINT) update readme ``` * (MAINT) update readme (3123b670) * (MAINT) Update changelog and version for release (213e10f8) ``` (MAINT) Update changelog and version for release Update changelog Bump version to 1.1.0 Update gitignore with Gemfile.lock ``` * Merge pull request #77 from cprice404/bug/master/HC-80-dont-shadow-ruby-class-module-name (61194207) ``` Merge pull request #77 from cprice404/bug/master/HC-80-dont-shadow-ruby-class-module-name (HC-80) Don't shadow ruby Class/Module#name method ``` * Merge pull request #76 from cprice404/maint/master/HC-79-support-format-arg-in-load (c0c88698) ``` Merge pull request #76 from cprice404/maint/master/HC-79-support-format-arg-in-load (HC-79) support :syntax arg in load ``` * (MAINT) Change variable name for readability (cd3505ed) * (HC-80) Don't shadow ruby Class/Module#name method (4f7c8ccc) ``` (HC-80) Don't shadow ruby Class/Module#name method Prior to this commit, there were a few places in the code where we'd ported over a class-or-module-level method named `name` from the upstream library. This isn't a good idea in Ruby because it results in shadowing of the built in Ruby methods Class#name and Module#name. This was causing problems for some users, e.g. in cases where reflection is being used to examine classes. In this commit we rename all such methods to something more specific, and replace the calls to the old names with calls to the new names. ``` * (HC-79) support `:syntax` option in simple `load` (a5663ca6) ``` (HC-79) support `:syntax` option in simple `load` This commit adds support for an optional `opts` map to be passed in to the simple `load` method. If provided, this map may contain a `:syntax` key that explicitly specifies which config format/syntax the user expects the file to be in. This provides a way for users to load files whose file extension doesn't match the built-in expectations for which file extensions use which syntaxes. The commit also provides some error checking for the case where an explicit `:syntax` is not passed in, and the file extension isn't recognized. In this case, we will throw an error now, rather than silently returning an empty map like we did in the past. Finally, this commit adds some notes to the docs/example usage, indicating how to pass in an explicit syntax. ``` * (MAINT) separate ruby tests from upstream tests (3cd0f049) * Merge pull request #74 from karenvdv/server-1300-add-maintainers (ffc0e143) ``` Merge pull request #74 from karenvdv/server-1300-add-maintainers Add maintainers section ``` * Add maintainers section (6fec1cdc) ### 1.0.1 - 16 Mar, 2016 (aa36b692) * Update for 1.0.1 release (aa36b692) * Update date for 1.0.0 release (9cbb6175) ### 1.0.0 - 16 Feb, 2016 (dc385fe2) * Merge pull request #72 from jpinsonault/maint-bump-version-to-1.0.0 (dc385fe2) ``` Merge pull request #72 from jpinsonault/maint-bump-version-to-1.0.0 (MAINT) bump and relabel version 0.9.4 to 1.0.0 ``` * (MAINT) Add link to readme (d6312759) * (MAINT) Update gemfile.lock (a9b721b6) * (MAINT) Whitespace - Cleanup README (920ffafb) * (MAINT) Bump and relabel 0.9.4 to 1.0.0 (e129b2ee) ``` (MAINT) Bump and relabel 0.9.4 to 1.0.0 0.9.4 introduced changes that require some users to modify their require statements due to bugfixes. In addition the API is stable enough to consider this a 1.0.0 release 1.0.0 Changlog: This is a bugfix release. The API is stable enough and the code is being used in production, so the version is also being bumped to 1.0.0 * Fixed a bug wherein calling "Hocon.load" would not resolve substitutions. * Fixed a circular dependency between the Hocon and Hocon::ConfigFactory namespaces. Using the Hocon::ConfigFactory class now requires you to use a `require 'hocon/config_factory'` instead of `require hocon` * Add support for hashes with keyword keys ``` * Merge pull request #71 from fpringvaldsen/maint/changelog (2b55e3b7) ``` Merge pull request #71 from fpringvaldsen/maint/changelog Fix changelog for 0.9.4 ``` * Fix changelog for 0.9.4 (7e417107) ``` Fix changelog for 0.9.4 Add changes that went into the 0.9.4 release that weren't listed in the changelog. ``` * (MAINT) Update Gemfile.lock (d9f1d4c8) * (MAINT) Update for 0.9.4 release (ec909659) * Merge pull request #70 from fpringvaldsen/maint/load-issue (1ffa268a) ``` Merge pull request #70 from fpringvaldsen/maint/load-issue (MAINT) Fix Hocon.load substitution issue ``` * (MAINT) Fix Hocon.load substitution issue (31321f0b) ``` (MAINT) Fix Hocon.load substitution issue Previously, Hocon.load was calling into the ConfigFactory.parse_file method. However, the `parse` methods in ConfigFactory do not resolve substitutions, as that is the intent of the `load` methods. This commit updates the Hocon.load method to call into ConfigFactory.load_file. It also updates the readme to explain the usage of ConfigDocuments, and adds a comment to explain that the `load` methods in ConfigFactory should be used if substitutions are present ``` * Merge pull request #68 from krjackso/master (f9f29a3f) ``` Merge pull request #68 from krjackso/master Fix typo when referencing GENERIC OriginType ``` * Fix typo when referencing GENERIC OriginType (0d1a65fc) * Merge pull request #66 from traylenator/addspec (c9f56235) ``` Merge pull request #66 from traylenator/addspec Add spec tests to gem file. Fixes #65 ``` * Add spec tests to gem file. Fixes #65 (573da794) * Merge pull request #64 from cprice404/maint/master/fix-circular-deps (618592a1) ``` Merge pull request #64 from cprice404/maint/master/fix-circular-deps (HC-24) Use simple API in README, fix circular deps ``` * (HC-24) Use simple API in README, fix circular deps (3783f305) ``` (HC-24) Use simple API in README, fix circular deps This commit updates the README to show the simpler version of the API for basic read operations. It also fixes some circular dependencies that were causing the example code for the ConfigDocumentFactory not to work properly. ``` * Merge pull request #62 from fpringvaldsen/improvement/TK-251/keyword-keys (0e7216c4) ``` Merge pull request #62 from fpringvaldsen/improvement/TK-251/keyword-keys (TK-251) Convert symbol keys to strings ``` * (TK-251) Process nested hashes (724f792d) ``` (TK-251) Process nested hashes When converting a Hashes symbol keys to strings, also process any nested hashes that are present. ``` * (TK-251) Convert symbol keys to strings (89f57cc1) ``` (TK-251) Convert symbol keys to strings When parsing a Hash in ConfigValueFactory, automatically convert all symbol keys to strings. ``` ### 0.9.3 - 14 Jul, 2015 (7defef59) * Update Changelog and Gemspec for 0.9.3 (7defef59) * Merge pull request #61 from fpringvaldsen/bug/TK-249/bad-comments (be17f2a4) ``` Merge pull request #61 from fpringvaldsen/bug/TK-249/bad-comments (TK-249) Remove unnecessary comments in output ``` * (TK-249) Remove unnecessary comments in output (e893d6fc) ``` (TK-249) Remove unnecessary comments in output Remove unnecessary "# hardcoded value" comments that were being generated when inserting a hash or an array into a ConfigDocument. ``` ### 0.9.2 - 30 Jun, 2015 (6b402bc2) * Update CHANGELOG and gemspec for 0.9.2 (6b402bc2) * Merge pull request #59 from fpringvaldsen/maint/undefined-method-fix (96499050) ``` Merge pull request #59 from fpringvaldsen/maint/undefined-method-fix (MAINT) Fix undefined method bug ``` * (MAINT) Fix undefined method bug (a47ee9b0) ``` (MAINT) Fix undefined method bug Fix an undefined method bug that was occurring when attempting to add a complex value into an empty root object. ``` ### 0.9.1 - 30 Jun, 2015 (e8c2f405) * Update CHANGELOG and gemspec for 0.9.1 (e8c2f405) * Merge pull request #58 from fpringvaldsen/bug/TK-246/single-line-config (c4bfc3c0) ``` Merge pull request #58 from fpringvaldsen/bug/TK-246/single-line-config (TK-246) Fix single-line config bug ``` * (TK-246) Fix single-line config bug (9305707b) ``` (TK-246) Fix single-line config bug Previously there was a bug wherein building out a config starting from an empty ConfigDocument would cause the entire config to exist on a single line. Fix this bug by modifying the addition of new maps along a path to add multi-line maps instead of single-line maps if the object being added to is an empty root or a multi-line object. ``` * Merge pull request #57 from cprice404/maint/master/improve-error-messages-for-problem-tokens (afeed2a0) ``` Merge pull request #57 from cprice404/maint/master/improve-error-messages-for-problem-tokens (MAINT) Improve error messages for Problem tokens ``` * (MAINT) Improve error messages for Problem tokens (be4a9320) ``` (MAINT) Improve error messages for Problem tokens Prior to this commit, the `Problem` token type called `to_s` on an internal `StringIO` object when building up an error string to return to the user. Calling `to_s` on a `StringIO` just causes it to print out, basically, `#`, so you don't get the useful error message. This patch changes the code to call `string` instead, which returns a much more useful error message. ``` ### 0.9.0 - 10 Apr, 2015 (aeab6ab2) * Merge pull request #56 from jpinsonault/maint-update-for-0.9.0-release (aeab6ab2) ``` Merge pull request #56 from jpinsonault/maint-update-for-0.9.0-release (MAINT) Update for 0.9.0 release ``` * (MAINT) Update for 0.9.0 release (a139e789) * Merge pull request #55 from fpringvaldsen/maint/empty-doc-test (5b7b7d8f) ``` Merge pull request #55 from fpringvaldsen/maint/empty-doc-test (MAINT) Add empty document insertion test ``` * (MAINT) Add additional ConfigValue insertion test (9d307477) ``` (MAINT) Add additional ConfigValue insertion test Add an additional test for inserting a ConfigValue into a ConfigDocument. Fix an issue wherein this would fail as the rendered result of ConfigValue was not having whitespace trimmed. ``` * (MAINT) Add empty document insertion test (62394f9c) ``` (MAINT) Add empty document insertion test Add a test for insertion into an empty ConfigDocument. ``` ### 0.1.0 - 9 Apr, 2015 (bfdb7255) * (MAINT) Update gemspec for 0.1.0 release (bfdb7255) * (MAINT) Remove SimpleConfigDocument require (bea56c92) ``` (MAINT) Remove SimpleConfigDocument require Remove the SimpleConfigDocument require from the ConfigDocument spec, as this was causing an issue wherein Parseable would work properly even though it needed to require SimpleConfigDocument. ``` * (MAINT) Fix uninitialized constant error (fd2abd12) * Merge pull request #53 from fpringvaldsen/task/TK-188/refactor-parser (30c5feee) ``` Merge pull request #53 from fpringvaldsen/task/TK-188/refactor-parser (TK-188) Refactor Parser ``` * Merge pull request #54 from jpinsonault/tk-161-port-public-api-tests (0a4fcbe0) ``` Merge pull request #54 from jpinsonault/tk-161-port-public-api-tests (TK-161) port public api tests ``` * Added test for load_file_with_resolve_options (7afc6053) * Addressed PR feedback (029db783) * Merge pull request #52 from fpringvaldsen/task/TK-187/port-ConfigDocument (73471f1b) ``` Merge pull request #52 from fpringvaldsen/task/TK-187/port-ConfigDocument (TK-187) Port ConfigDocument and tests ``` * (MAINT) Fix typos (f3102ef8) ``` (MAINT) Fix typos Fix typos in comment and test string. ``` * (MAINT) Fix failing ConfigValue test (9ddbc290) ``` (MAINT) Fix failing ConfigValue test Fix bug with the rendering of SimpleConfigList that was causing a skipped ConfigValue test to fail. ``` * Refactor Parser (fdc74366) ``` Refactor Parser Refactor the Parser class into ConfigParser, and change it to parse ConfigNodes rather than Tokens. Change Parseable to first parse a ConfigDocument, then use that to parse a Config. ``` * (MAINT) Clean-up loops (5d10c6fc) ``` (MAINT) Clean-up loops Clean up certain loops in ConfigNode and ConfigDocument implementations to be more ruby-esque. ``` * (TK-187) Port ConfigDocument tests (df22b9f7) ``` (TK-187) Port ConfigDocument tests Port all ConfigDocument tests down to ruby-hocon and get them passing. ``` * Merge pull request #51 from fpringvaldsen/task/TK-186/port-ConfigNode (63c8907a) ``` Merge pull request #51 from fpringvaldsen/task/TK-186/port-ConfigNode (TK-186) Port ConfigNode tests ``` * (TK-187) Port ConfigDocument classes/interfaces (1b8f5eea) ``` (TK-187) Port ConfigDocument classes/interfaces Port the ConfigDocument classes and interfaces from the upstream library, sans tests. ``` * (TK-186) Update comment on AbstractConfigNodeValue (391d1c89) ``` (TK-186) Update comment on AbstractConfigNodeValue Update the comment on the AbstractConfigNodeValue to reflect that the module is unnecessary in Ruby and is being preserved solely for consistency. ``` * (TK-186) Make abstract classes into modules (00a7d5dd) ``` (TK-186) Make abstract classes into modules Change all ConfigNode classes that are abstract in the upstream library into modules. Change the ConfigNode class to a module. ``` * (TK-186) Fix typo in comment_text method name (73da7db5) ``` (TK-186) Fix typo in comment_text method name Change the commentText method to comment_text. ``` * (TK-187) Port ConfigDocumentParser tests (19134ddd) ``` (TK-187) Port ConfigDocumentParser tests Port all tests for ConfigDocumentParser and ensure they are passing. ``` * (TK-187) Port ConfigDocumentParser (18f55f46) ``` (TK-187) Port ConfigDocumentParser Port the ConfigDocumentParser class (sans tests) from the upstream library. ``` * (TK-161) Port Public API tests to ruby-hocon (03bc79a1) * (MAINT) Fix issue with concatenation tests (14615490) * (TK-186) Port ConfigNode tests (f5197a2a) ``` (TK-186) Port ConfigNode tests Port all the ConfigNode tests in the upstream library. Make various bugfixes to get the tests passing. ``` * Merge pull request #50 from KevinCorcoran/errmagerhd (798ab05a) ``` Merge pull request #50 from KevinCorcoran/errmagerhd (TK-162) enable concatenation test cases + fixes ``` * (TK-186) Implement ConfigNode classes (3600a2be) ``` (TK-186) Implement ConfigNode classes Implement all the various ConfigNode classes from the upstream library. ``` * Merge pull request #48 from jpinsonault/tk-159-round-three-config-value-tests (5e95a622) ``` Merge pull request #48 from jpinsonault/tk-159-round-three-config-value-tests (TK-159) Final round of config value tests ``` * Addressed PR feedback (d5c04edc) * Merge pull request #49 from cprice404/maint/master/excepton-typo (7d31e86f) ``` Merge pull request #49 from cprice404/maint/master/excepton-typo (MAINT) fix 'excepton' typo ``` * (MAINT) fix 'excepton' typo (2f705314) * Merge pull request #47 from cprice404/feature/master/TK-160-more-conf-parser-tests (e73f00fb) ``` Merge pull request #47 from cprice404/feature/master/TK-160-more-conf-parser-tests (TK-160) More config parser tests ``` * Addressed PR feedback (a5e314da) * (TK-160) Improve comments re: BOM tests (a054355b) * Merge pull request #46 from KevinCorcoran/delayed-merge (c2a58a41) ``` Merge pull request #46 from KevinCorcoran/delayed-merge implement rest of CDMO + other bugfixes ``` * (TK-160) Finished implementing conf parser tests (d19ea3bc) * (TK-160) Port multi-field comment tests (c959e2c9) * (TK-160) Port comment tests (dbea64ae) * (TK-160) Port more conf parser tests (07a3f335) * Merge pull request #45 from cprice404/feature/master/TK-160-more-include-parser-tests (c3b622e0) ``` Merge pull request #45 from cprice404/feature/master/TK-160-more-include-parser-tests (TK-160) fix remaining "valid conf" parser tests ``` * (TK-160) Fix typos (ac6f9c44) * (TK-162) enable concatenation test cases + fixes (de643885) ``` (TK-162) enable concatenation test cases + fixes Un-comment the remaining concatenation test cases that were still commented-out and fix bugs. Also added 'inspect' implementations and use short class names to make trace output match upstream, and re-wrote various bits of code to correspond more closely to upstream. ``` * (maint) port rest of MemoKey and fix a couple bugs (7374d543) * (maint) sync SimpleConfigObject.== with upstream (3f4d7fe0) * (maint) sync ConfigDelayedMergeObject w/ upstream (df402953) * (TK-159) Final round of config value tests (cfdaa58d) ``` (TK-159) Final round of config value tests Implemented AbstractConfigValue#at_path/at_key ``` * Merge pull request #44 from KevinCorcoran/finish-concat-test-2 (a01c2214) ``` Merge pull request #44 from KevinCorcoran/finish-concat-test-2 (TK-162) concat tests ``` * (maint) add comment about Ruby vs. Java integers (34fca36e) * (TK-160) More config parser tests (b3b48fd0) * (MAINT) Remove code related to '.properties' files (69367ad0) * (TK-160) Get `include` "valid conf" parser tests passing (bea8a481) * (TK-162) comment-out failing concat test cases (2fdb2621) * Merge pull request #43 from cprice404/feature/master/TK-160-more-valid-conf-parser-tests (1da67e76) ``` Merge pull request #43 from cprice404/feature/master/TK-160-more-valid-conf-parser-tests (TK-160) more valid conf parser tests ``` * (TK-160) re-enable += tests, they are passing now (fd646c39) * Merge pull request #40 from cprice404/maint/master/re-sync-parser-and-tokenizer (903c9bdd) ``` Merge pull request #40 from cprice404/maint/master/re-sync-parser-and-tokenizer (MAINT) Update Parser to match latest upstream ``` * (MAINT) Fix bugs and port ConfigNode interface (efcbacea) * Merge pull request #42 from KevinCorcoran/config-string (710b3f80) ``` Merge pull request #42 from KevinCorcoran/config-string (maint) sync ConfigString with upstream ``` * (MAINT) Sync ConfigDelayedMerge (4e389d3b) * (MAINT) re-sync `Path` class (853b447a) * Merge pull request #41 from KevinCorcoran/fix-null-in-concat (146f22bf) ``` Merge pull request #41 from KevinCorcoran/fix-null-in-concat Fix null in concat ``` * (maint) sync ConfigString with upstream (e13ea5a7) ``` (maint) sync ConfigString with upstream Also, replace calls to ConfigString.new with Quoted/Unquoted. ``` * (TK-162) finish porting concatenation tests (7a6f8d75) * Merge pull request #39 from KevinCorcoran/resolve-source-and-concat-test (b1874e34) ``` Merge pull request #39 from KevinCorcoran/resolve-source-and-concat-test port ResolveSource and concat test case ``` * (maint) port concatenation tests and fix bugs (f62a49c8) * (maint) small refactor to match upstream (6d2474df) ``` (maint) small refactor to match upstream Re-write a few bits of SimpleConfigObject.render_value_to_sb to make it more closely match the upstream version. ``` * (maint) fix bad reference to self.class (f77a983d) * Merge pull request #36 from KevinCorcoran/sync-up-config-concat (6ac66904) ``` Merge pull request #36 from KevinCorcoran/sync-up-config-concat (maint) sync ConfigConcatenation with upstream ``` * (maint) fix typo and log message (f33e5b31) * Merge pull request #37 from jpinsonault/tk-159-round-two-config-value-tests (9c6b2333) ``` Merge pull request #37 from jpinsonault/tk-159-round-two-config-value-tests (TK-159) Round Two of ConfigValue tests ``` * (maint) use self.class instead of class name (d5c8046b) * (maint) fix method name to match upstream (8a5b1b12) * Merge pull request #35 from cprice404/maint/master/flesh-out-simple-config-list (517ac3a3) ``` Merge pull request #35 from cprice404/maint/master/flesh-out-simple-config-list (MAINT) Flesh out SimpleConfigList ``` * Addressed PR feedback (a142e824) * (MAINT) Fix immutable exception type, bugs in SCOrigin (c1696790) ``` (MAINT) Fix immutable exception type, bugs in SCOrigin This commit does the following: * Changes the exception type for the `we_are_immutable` cases to use a new `UnsupportedOperationError`, to make the behavior model the Java version more closely. * Fix a couple of bugs in the ==/hash methods of SimpleConfigOrigin ``` * (MAINT) Update Parser to match latest upstream (f4b4ee62) * (TK-162) additional concatenation test case (2650e4b2) ``` (TK-162) additional concatenation test case ... and the changes to the production code required for it to pass. ``` * (maint) sync ResolveSource with upstream version (153c91e3) * (MAINT) Flesh out SimpleConfigOrigin (8179f4e5) * Merge pull request #34 from cprice404/maint/master/flesh-out-simple-config-object (1b4976c4) ``` Merge pull request #34 from cprice404/maint/master/flesh-out-simple-config-object (MAINT) flesh out simple config object ``` * (MAINT) Flesh out SimpleConfigList (35ec6306) * (maint) sync ConfigConcatenation with upstream (ef2dbdff) * (MAINT) Change `RuntimeError` to `ConfigError`. (e805e83c) * (TK-160) Get most 'valid conf' parser tests passing (7c93a5b3) ``` (TK-160) Get most 'valid conf' parser tests passing This commit fixes a ton of bugs and syncs some classes necessary to get most of the 'valid conf' parser tests passing. ``` * (MAINT) Another fix to a bad line in SCO (3b638f5c) * (MAINT) fix bad line of port of SimpleConfigObject (c24850e3) * (TK-160) Add `it` block for test counts (af7cfc2e) ``` (TK-160) Add `it` block for test counts Adding this `it` block causes rspec to correctly update the test counts based on these invalid configuration parsing tests. ``` * (MAINT) Finish porting / clean up AbstractConfigObject (bad5d034) * Merge pull request #33 from cprice404/feature/master/TK-160-port-conf-parser-tests (f980ff46) ``` Merge pull request #33 from cprice404/feature/master/TK-160-port-conf-parser-tests (TK-160) Port minimal `include` functionality ``` * (TK-160) fix whitespace, add cause to MalformedUrlError (0d9afb51) * (MAINT) Finish porting / clean up AbstractConfigValue (ee244518) * (MAINT) Finish porting / clean up SimpleConfigObject (659bbcda) * (TK-160) Fix config parse tests related to config_reference (d7f35022) * Merge pull request #32 from jpinsonault/tk-159-partial-set-of-config-value-tests (82648f63) ``` Merge pull request #32 from jpinsonault/tk-159-partial-set-of-config-value-tests (TK-159) Partial set of ConfigValue tests implemented ``` * Added another include_all? test, fixed description typo (60c5f83a) * (TK-160) Got most of the `reference` tests passing. (4ce36feb) * (TK-160) Port minimal `include` functionality (04989412) ``` (TK-160) Port minimal `include` functionality This commit re-enables some disabled config parser tests that had been failing due to missing functionality around HOCON's `include` capabilities. It also includes a minimal port of all of the `include` functionality that was required to get the tests passing. ``` * Addressed PR comments (16ccac67) ``` Addressed PR comments Implemented and added test for SimpleConfigList#include_all? Made ConfigReference#not_resolved private and not static Fixed typo in ConfigReference#relativized ConfigDelayedMergeObject#unwrapped now throws not_resolved Made various methods private to match Java version SimpleConfigObject#map_equals: got rid of confusing ugly lambda, uses sorted keys now No longer flay the ConfigDelayedMerge objects ``` * Using self in test_utils (efa3151b) * (TK-159) More ConfigValue tests (5d60b6d0) ``` (TK-159) More ConfigValue tests Another set of tests for config_value_spec. There will be at least one more after this one. Added a few methods to AbstractConfigObject Made the definitions of merge_origins static to match the java Implemented render/render_to_sb methods for ConfigDelayedMerge Fixed DefaultTransformer::transform method to actually compare the value type Made SimpleConfigObject::indent static SimpleConfigOrigin::merge_origins handles merging more than two tokens correctly Implemented SimpleConfigOrigin#filename to use Chris's Url class A couple methods in tokens.rb get_substitution_path_expression get_substitution_optional Commented out some failing tests in conf_parser_spec until some missing functionality is implmented ``` * Merge pull request #31 from cprice404/feature/master/TK-160-port-conf-parser-tests (f28f78ea) ``` Merge pull request #31 from cprice404/feature/master/TK-160-port-conf-parser-tests (TK-160) Initial scaffolding for conf parser tests ``` * (MAINT) add missing newline at end of file (ee375afb) * (TK-160) Change `t` to `invalid` to match upstream (012dac77) * (TK-160) Initial scaffolding for conf parser tests (04d56c9e) ``` (TK-160) Initial scaffolding for conf parser tests This commit ports over the first few ConfParser tests, and fixes a few bugs to get them passing. ``` * Merge pull request #29 from KevinCorcoran/with--vs-set (08ca846a) ``` Merge pull request #29 from KevinCorcoran/with--vs-set (maint) rename methods to match upstream ``` * Merge pull request #30 from cprice404/maint/master/add-utf8-encoding-pragma (0f3ceb58) ``` Merge pull request #30 from cprice404/maint/master/add-utf8-encoding-pragma (MAINT) Add utf-8 encoding pragma to all source files ``` * (MAINT) Add utf-8 encoding pragma to all source files (1e3b1a84) ``` (MAINT) Add utf-8 encoding pragma to all source files Because ruby-- ``` * (maint) rename methods to match upstream (7ed85816) ``` (maint) rename methods to match upstream Rename methods whose names start with "set_" or "with_" to match the names of these methods in the upstream Java project. ``` * Merge pull request #28 from KevinCorcoran/concatenation (212f6b7c) ``` Merge pull request #28 from KevinCorcoran/concatenation (TK-162) implement concatenation and substitution ``` * (maint) fix bugs identified during PR review (d608f4a5) * (TK-162) implement concatenation and substitution (bc71ba0f) ``` (TK-162) implement concatenation and substitution Initial implementation of concatenation and substitution. Ported the first test case in ConcatenationTest and as much of the production code as it took to get it to pass. ``` * Merge pull request #27 from KevinCorcoran/add-test-util (65e3ab85) ``` Merge pull request #27 from KevinCorcoran/add-test-util (maint) add TestUtils.parse_config ``` * (maint) add TestUtils.parse_config (1b47f6b4) * Merge pull request #24 from jpinsonault/tk-169-setup-travis (6e5e8698) ``` Merge pull request #24 from jpinsonault/tk-169-setup-travis (TK-169) Add travis support ``` * Updated Gemfile.lock (d8bd873e) * Removed rake dependency and Rakefile, changed .travis.yml to run rspec instead of rake (9c36b479) * Merge pull request #26 from jmccure/port-lossless-tokens (a7018fd9) ``` Merge pull request #26 from jmccure/port-lossless-tokens Add lossless comment tokens ``` * Amend lossless token test name after feedback (9b94c6a9) * Add lossless comment tokens (f14161f3) * Merge pull request #22 from jpinsonault/tk-158-port-path-tests (005a8b6f) ``` Merge pull request #22 from jpinsonault/tk-158-port-path-tests (TK-158) Port Path tests to ruby hocon ``` * Merge pull request #25 from KevinCorcoran/TK-128/fix-for-IP-addresses (1feaba32) ``` Merge pull request #25 from KevinCorcoran/TK-128/fix-for-IP-addresses (TK-128) fix tokenization of unquoted strings ``` * Merge pull request #23 from KevinCorcoran/update-gemspec (c4da445a) ``` Merge pull request #23 from KevinCorcoran/update-gemspec (maint) update gemspec with new URL and authors ``` * Changed command to use rake spec (5ae8b396) * Addressed PR feedback (bd80a3ef) ``` Addressed PR feedback Implemented Path#from_path_iterator as a constructor Fixed typos Implemented TokenIterator#to_list Fleshed out BadPath exception message handling logic ``` * (TK-128) fix tokenization of unquoted strings (224c4dfd) ``` (TK-128) fix tokenization of unquoted strings This commit fixes the tokenization of unquoted strings which might be numbers. In particular, this affects IP addresses. The tokenizer aggressively attempts to parse such strings as either a Float or Integer and relies on an error being raised to indicate when the string is not actually a valid number. However, `to_i` and `to_f` never raise execptions, so the constructors must be used instead. ``` * Changed script command (06fc855a) * Changed rspec command (37d581cc) * (TK-169) Add travis support (3fc140da) * (maint) update gemspec with new URL and authors (d6561e42) * (TK-159) Partial set of ConfigValue tests implemented (c4abaf2e) ``` (TK-159) Partial set of ConfigValue tests implemented This is a partial set of the tests for ConfigValueTest.scala We're going to get whatever functionality I've implemented in this PR so others can use it and avoid creating merge conflicts. The rest of the PRs will come as much smaller chunks of effort This branch was rebased on top of Kevin's tk-162 PR, and hopefully I merged my changes into it without breaking the tests didn't catch. Implemented parts of: ConfigDelayedMerge ConfigDelayedMergeObject ConfigReference ReplaceableMergeStack module SimpleConfigList/Object now behave like arrays/hashes by delegating required functions to their @value attribute Implemented ==() and hash() for a bunch of classes Many other small changes ``` * Merge pull request #21 from jpinsonault/tk-157-port-token-tests (45414478) ``` Merge pull request #21 from jpinsonault/tk-157-port-token-tests Tk 157 port token tests ``` * Moved shared examples into test_utils.rb and extracted out the random object examples (95a0db9b) * Extracted shared examples into separate file for reuse in other tests (f9fe3386) * (TK-158) Port Path tests to ruby hocon (bd832af6) * Removed unused TestUtils method (8d7af9ec) * (TK-157) Port Token tests to ruby hocon (921f9883) ``` (TK-157) Port Token tests to ruby hocon Ported the Token tests This involved implementing various == and hash functions for Token subclasses and Config types ``` * Merge pull request #20 from jpinsonault/tk-155-port-tokenizer-tests (5ebd16eb) ``` Merge pull request #20 from jpinsonault/tk-155-port-tokenizer-tests (TK-155) Port tokenizer tests to ruby hocon ``` * Lots of fixes for PR (039d6f42) ``` Lots of fixes for PR Used single quotes where appropriate Extracted tokenize function Changed TokenIterator.problem occurances to self.class.problem ``` * Implemented == method for token subtypes (e6a4b56b) * (TK-155) Port tokenizer tests to ruby hocon (6a2e3f83) ``` (TK-155) Port tokenizer tests to ruby hocon Ported all the java tests from the hocon library to ruby-hocon Added a few more here and there Implemented misc missing functions from the java library to get the tests passing Fixed bug in tokenizer that ignored whitespace between tokens ``` * Update gemspec for 0.0.7 release (71f475fe) * Merge pull request #18 from fpringvaldsen/json-patch (74e6ed8d) ``` Merge pull request #18 from fpringvaldsen/json-patch Allow gem to parse JSON files ``` * Merge pull request #16 from fpringvaldsen/readme-disclaimer (df334bd8) ``` Merge pull request #16 from fpringvaldsen/readme-disclaimer Add disclaimer to README ``` * Merge pull request #17 from fpringvaldsen/implement-end-token (b28b2909) ``` Merge pull request #17 from fpringvaldsen/implement-end-token Fix NameError when parsing {\n} ``` * Allow gem to parse JSON files (b72cae0b) ``` Allow gem to parse JSON files Allow the ruby hocon gem to parse JSON files. Previously, attempting to parse a JSON file would lead to an uninitialized constant error. ``` * Fix NameError when parsing {\n} (b1781fee) ``` Fix NameError when parsing {\n} Fix a NameError that would occur when a string containing {\n} was parsed. ``` * Add disclaimer to README (9f4b8df1) ``` Add disclaimer to README Add a disclaimer to the README explaining that this library is in an experimental state and some features may not work properly. ``` * Update gemspec for 0.0.6 release (1613e233) * Merge pull request #15 from waynr/maint (b25f64f7) ``` Merge pull request #15 from waynr/maint (MAINT) Fix spec tests such that they work on ruby-1.8.7-p352 ``` * Fix unecessarily strict test case. (3ed91425) ``` Fix unecessarily strict test case. As it turns out, hocon does not actually require that the output be rendered in the same order by every implementation so when running with ruby-1.9.x vs ruby-1.8.7-p352 for instance the rendered string may not have variables specified in the same order. However, hocon does require that comments be matched to their variables. This patch validates that behavior by creating a hash of config-lines mapped to lists of preceding comments and verifies that this hash is the same before and after rendering regardless of which hocon implementation created the "original" output file. Also, I find the input vs output semantics and the way variables with these names are used just a little confusing but whatever. Signed-off-by: Wayne ``` * Remove unnecessary brackets in regex. (f0a72925) ``` Remove unnecessary brackets in regex. Signed-off-by: Wayne ``` * Minor rspec testcase code cleanup. (a95cff41) ``` Minor rspec testcase code cleanup. Signed-off-by: Wayne ``` * Fix re-parsed output test cases. (e5c4d884) ``` Fix re-parsed output test cases. This testcase should not care about the rendered form of the re-parsed output since A) the hocon spec does not guarantee exact output similarity and B) testing that comments remain above the variables they describe is taken care of in the previous test case. Signed-off-by: Wayne ``` * Fix uninitialized constant error in ruby-1.8.7-p352 (53151934) ``` Fix uninitialized constant error in ruby-1.8.7-p352 Signed-off-by: Wayne ``` * Fix tokenizer for ruby-1.8.7-p352 (00fe96e2) ``` Fix tokenizer for ruby-1.8.7-p352 Signed-off-by: Wayne ``` * Fix default Rake task for ruby-1.8.7-p352 (f3bb2240) ``` Fix default Rake task for ruby-1.8.7-p352 Signed-off-by: Wayne ``` * Merge pull request #13 from cprice404/maint/master/clean-up-requires (9868fd6f) ``` Merge pull request #13 from cprice404/maint/master/clean-up-requires Clean up require statements ``` * Merge pull request #14 from fpringvaldsen/error-handling (8be042e6) ``` Merge pull request #14 from fpringvaldsen/error-handling Improve Error Handling with invalid config ``` * Improve Error Handling with invalid config (64d6166f) ``` Improve Error Handling with invalid config Implement Error Handling when an invalid config is parsed. ``` * Clean up require statements (f866d790) ### 0.0.5 - 1 Oct, 2014 (67d264f4) * Update gemspec for 0.0.5 release (67d264f4) * Merge pull request #12 from fpringvaldsen/add-methods-for-puppet (78c7afb9) ``` Merge pull request #12 from fpringvaldsen/add-methods-for-puppet Add methods required for puppet .conf module ``` * Remove commented line (d704cda2) ``` Remove commented line Delete commented constant in the Path class that was no longer needed. ``` * Move requires into Hocon module (935b2cc5) ``` Move requires into Hocon module Move requires for all files in the Hocon module into the module itself to eliminate uninitialized constant errors. ``` * Fix ConfigImpl bug and add more tests (c892c0f4) ``` Fix ConfigImpl bug and add more tests Fix bug in ConfigImpl wherein a boolean would be converted into a ConfigBoolean with value true even if the boolean is false. Increase test coverage for ConfigValueFactory tests by adding a test to ensure this bug is no longer happening, and increase test coverage of SimpleConfig spec tests by ensuring that data structures can be added to a config. ``` * Add without_path method (d23fdcde) ``` Add without_path method Port without_path method from the Java HOCON library into the SimpleConfig class. ``` * Add at_key and at_path methods (8323c2a6) ``` Add at_key and at_path methods Port the at_key and at_path methods from the Java HOCON library into the AbstractConfigValue class. ``` * Add "add" method to TokenWithComments (3430c3b7) ``` Add "add" method to TokenWithComments Port the "add" method to the TokenWithComments class. ``` * Fix requires in ConfigValueFactory (a4074d71) ``` Fix requires in ConfigValueFactory Fix the require statements so that ConfigValueFactory can be required without requiring other files. ``` * Add ConfigValueFactory (ae88610e) ``` Add ConfigValueFactory This commit adds a basic implementation of the ConfigValueFactory class. This class contains only one method, with_any_ref, which takes an object and transforms it into a ConfigObject. ``` * Add with_value method to SimpleConfig (760f7743) ``` Add with_value method to SimpleConfig Port the with_value method in the SimpleConfig class from the Java HOCON library. ``` * Add has_path method (655c1beb) ``` Add has_path method Port the has_path method in the SimpleConfig class from the Java HOCON library. ``` * Put get_value tests into their own file (9339f606) ``` Put get_value tests into their own file Move the tests of the SimpleConfig get_value method into a new file, simple_config_spec.rb ``` * Add get_value method to SimpleConfig (bfb63a35) ``` Add get_value method to SimpleConfig Add the get_value method to SimpleConfig, which allows the user to get a value from a configuration file. Add tests for this method. ``` * Merge pull request #8 from dakatsuka/add-bundler-and-rake (d204b344) ``` Merge pull request #8 from dakatsuka/add-bundler-and-rake Add bundler and rake ``` * Merge pull request #9 from dakatsuka/support-boolean (f584904f) ``` Merge pull request #9 from dakatsuka/support-boolean Support boolean ``` * Add tests for Hocon::Impl::ConfigBoolean (31fd73b6) * Implement Hocon::Impl::ConfigBoolean (927d64b7) * Add bundler and rake (c0f77be8) ### 0.0.3 - 24 Jul, 2014 (6cd552c3) * Merge pull request #6 from waynr/maint (6cd552c3) ``` Merge pull request #6 from waynr/maint Maint ``` * spec_helper: Fix EXAMPLE1 w/ empty list. (775098d9) ``` spec_helper: Fix EXAMPLE1 w/ empty list. Signed-off-by: Wayne ``` * Hocon: Fix spec tests by adding `load` and `parse` (702146cc) ``` Hocon: Fix spec tests by adding `load` and `parse` Signed-off-by: Wayne ``` * spec: Add spec tests or Hocon module. (5b65bbef) ``` spec: Add spec tests or Hocon module. The goal here is to provide an interface similar to what the JSON ruby module provides, even though this doesn't include a dump method yet. Signed-off-by: Wayne ``` * hocon.gemspec: Update gemspec for new gem release. (95d46911) ``` hocon.gemspec: Update gemspec for new gem release. Signed-off-by: Wayne ``` * parser: Don't convert anything to symbols. (0ca29caf) ``` parser: Don't convert anything to symbols. Signed-off-by: Wayne ``` * Fix Hocon::Impl::SimpleConfigList (3f77504f) ``` Fix Hocon::Impl::SimpleConfigList Chokes without this `new_copy` method. Signed-off-by: Wayne ``` * spec: Add new examples, reorganize specs. (4a9cfb1b) ``` spec: Add new examples, reorganize specs. Reorganize specs to allow for the addition of more examples. Signed-off-by: Wayne ``` * Merge pull request #5 from waynr/fix-settings-in-hocon-gemspec (14e4ed32) ``` Merge pull request #5 from waynr/fix-settings-in-hocon-gemspec gemspec: Fix settings in hocon.gemspec. ``` * Merge pull request #3 from jmccure/fix_issue_2 (38fdfc37) ``` Merge pull request #3 from jmccure/fix_issue_2 Fixed error when conf file had empty array. Issue #2 ``` * Merge pull request #4 from waynr/implement-hocon-configfactory-parsestring (464d5704) ``` Merge pull request #4 from waynr/implement-hocon-configfactory-parsestring Implement hocon configfactory parsestring ``` * Fixed error when conf file had empty array. Issue #2 (fb1248eb) ### 0.0.2 - 24 Jul, 2014 (95dffaea) * gemspec: Fix settings in hocon.gemspec. (95dffaea) ``` gemspec: Fix settings in hocon.gemspec. Signed-off-by: Wayne ``` * Implement Hocon.ConfigFactory.parse_string (430442e4) ``` Implement Hocon.ConfigFactory.parse_string Also fixes a number of typos an previously untested code paths. Signed-off-by: Wayne ``` * spec: Add Hocon::ConfigFactory.parse_string test. (1cec69f2) ``` spec: Add Hocon::ConfigFactory.parse_string test. Signed-off-by: Wayne ``` * Update README.md (9ce283b1) ### 0.0.1 - 16 Mar, 2014 (f7dbca52) * Initial release.