├── .git-blame-ignore-revs ├── .github ├── dependabot.yml └── workflows │ ├── libyaml.yml │ ├── push_gem.yml │ └── test.yml ├── .gitignore ├── CONTRIBUTING.md ├── Gemfile ├── LICENSE ├── Mavenfile ├── README.md ├── Rakefile ├── bin ├── console └── setup ├── ext ├── java │ └── org │ │ └── jruby │ │ └── ext │ │ └── psych │ │ ├── PsychEmitter.java │ │ ├── PsychLibrary.java │ │ ├── PsychParser.java │ │ └── PsychToRuby.java └── psych │ ├── depend │ ├── extconf.rb │ ├── psych.c │ ├── psych.h │ ├── psych_emitter.c │ ├── psych_emitter.h │ ├── psych_parser.c │ ├── psych_parser.h │ ├── psych_to_ruby.c │ ├── psych_to_ruby.h │ ├── psych_yaml_tree.c │ └── psych_yaml_tree.h ├── lib ├── psych.rb ├── psych │ ├── class_loader.rb │ ├── coder.rb │ ├── core_ext.rb │ ├── exception.rb │ ├── handler.rb │ ├── handlers │ │ ├── document_stream.rb │ │ └── recorder.rb │ ├── json │ │ ├── ruby_events.rb │ │ ├── stream.rb │ │ ├── tree_builder.rb │ │ └── yaml_events.rb │ ├── nodes.rb │ ├── nodes │ │ ├── alias.rb │ │ ├── document.rb │ │ ├── mapping.rb │ │ ├── node.rb │ │ ├── scalar.rb │ │ ├── sequence.rb │ │ └── stream.rb │ ├── omap.rb │ ├── parser.rb │ ├── scalar_scanner.rb │ ├── set.rb │ ├── stream.rb │ ├── streaming.rb │ ├── syntax_error.rb │ ├── tree_builder.rb │ ├── versions.rb │ ├── visitors.rb │ ├── visitors │ │ ├── depth_first.rb │ │ ├── emitter.rb │ │ ├── json_tree.rb │ │ ├── to_ruby.rb │ │ ├── visitor.rb │ │ └── yaml_tree.rb │ └── y.rb └── psych_jars.rb ├── psych.gemspec └── test ├── lib └── helper.rb └── psych ├── handlers └── test_recorder.rb ├── helper.rb ├── json └── test_stream.rb ├── nodes └── test_enumerable.rb ├── test_alias_and_anchor.rb ├── test_array.rb ├── test_boolean.rb ├── test_class.rb ├── test_coder.rb ├── test_data.rb ├── test_date_time.rb ├── test_deprecated.rb ├── test_document.rb ├── test_emitter.rb ├── test_encoding.rb ├── test_exception.rb ├── test_hash.rb ├── test_json_tree.rb ├── test_marshalable.rb ├── test_merge_keys.rb ├── test_nil.rb ├── test_null.rb ├── test_numeric.rb ├── test_object.rb ├── test_object_references.rb ├── test_omap.rb ├── test_parser.rb ├── test_psych.rb ├── test_psych_set.rb ├── test_ractor.rb ├── test_safe_load.rb ├── test_scalar.rb ├── test_scalar_scanner.rb ├── test_serialize_subclasses.rb ├── test_set.rb ├── test_stream.rb ├── test_string.rb ├── test_stringio.rb ├── test_struct.rb ├── test_symbol.rb ├── test_tree_builder.rb ├── test_yaml.rb ├── test_yaml_special_cases.rb ├── test_yamldbm.rb ├── test_yamlstore.rb └── visitors ├── test_depth_first.rb ├── test_emitter.rb ├── test_to_ruby.rb └── test_yaml_tree.rb /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Expand tabs 2 | e7d64c9848e76d848cbf316bc19674ffe169b1e7 3 | 74a6b4d226e9127e55526eb7459e96143aa84e78 4 | 64bfc308f8f785ea7c6be701eaff2836cf6cf755 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: 'github-actions' 4 | directory: '/' 5 | schedule: 6 | interval: 'weekly' 7 | -------------------------------------------------------------------------------- /.github/workflows/libyaml.yml: -------------------------------------------------------------------------------- 1 | name: libyaml 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '21 11 * * 0' 8 | workflow_dispatch: 9 | 10 | jobs: 11 | ruby-versions: 12 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 13 | with: 14 | engine: cruby 15 | min_version: 2.5 16 | 17 | build: 18 | needs: ruby-versions 19 | runs-on: ubuntu-latest 20 | strategy: 21 | fail-fast: false 22 | matrix: 23 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 24 | libyaml: [0.1.7, 0.2.5] 25 | libyaml-prefix: [/tmp/local, ''] 26 | 27 | steps: 28 | - name: Install libraries 29 | run: sudo apt install haveged 30 | - uses: actions/checkout@v4 31 | - name: Set up Ruby 32 | uses: ruby/setup-ruby@v1 33 | with: 34 | ruby-version: ${{ matrix.ruby }} 35 | - name: Set up libyaml ${{ matrix.libyaml }} 36 | run: | 37 | wget https://pyyaml.org/download/libyaml/yaml-${{ matrix.libyaml }}.tar.gz 38 | tar xzf yaml-${{ matrix.libyaml }}.tar.gz 39 | - name: Compile libyaml ${{ matrix.libyaml }} 40 | run: | 41 | cd yaml-${{ matrix.libyaml }} 42 | ./configure --prefix=${{ matrix.libyaml-prefix }} 43 | make 44 | make install 45 | if: ${{ matrix.libyaml-prefix != '' }} 46 | - name: Install dependencies 47 | run: bundle install 48 | - name: Compile with libyaml 49 | run: rake compile -- --with-libyaml-dir=${{ matrix.libyaml-prefix }} 50 | if: ${{ matrix.libyaml-prefix != '' }} 51 | - name: Compile with libyaml source 52 | run: rake compile -- --with-libyaml-source-dir=$(pwd)/yaml-${{ matrix.libyaml }} 53 | if: ${{ matrix.libyaml-prefix == '' }} 54 | - name: Run test 55 | run: rake 56 | - name: Install gem 57 | run: | 58 | rake build 59 | gem install pkg/psych-*.gem -- --with-libyaml-dir=${{ matrix.libyaml-prefix }} 60 | if: ${{ matrix.ruby != 'head' && matrix.ruby != '3.1' && matrix.libyaml-prefix != '' }} 61 | -------------------------------------------------------------------------------- /.github/workflows/push_gem.yml: -------------------------------------------------------------------------------- 1 | name: Publish gem to rubygems.org 2 | 3 | on: 4 | push: 5 | tags: 6 | - 'v*' 7 | 8 | permissions: 9 | contents: read 10 | 11 | jobs: 12 | push: 13 | if: github.repository == 'ruby/psych' 14 | runs-on: ubuntu-latest 15 | 16 | environment: 17 | name: rubygems.org 18 | url: https://rubygems.org/gems/psych 19 | 20 | permissions: 21 | contents: write 22 | id-token: write 23 | 24 | strategy: 25 | matrix: 26 | ruby: ["ruby", "jruby"] 27 | 28 | steps: 29 | - name: Harden Runner 30 | uses: step-security/harden-runner@0634a2670c59f64b4a01f0f96f84700a4088b9f0 # v2.12.0 31 | with: 32 | egress-policy: audit 33 | 34 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 35 | 36 | - name: Set up Ruby 37 | uses: ruby/setup-ruby@eaecf785f6a34567a6d97f686bbb7bccc1ac1e5c # v1.237.0 38 | with: 39 | ruby-version: ${{ matrix.ruby }} 40 | 41 | # https://github.com/rubygems/rubygems/issues/5882 42 | - name: Install dependencies and build for JRuby 43 | run: | 44 | sudo apt install default-jdk maven 45 | gem update --system 46 | gem install ruby-maven rake-compiler --no-document 47 | rake compile 48 | if: matrix.ruby == 'jruby' 49 | 50 | - name: Install dependencies 51 | run: bundle install --jobs 4 --retry 3 52 | 53 | - name: Publish to RubyGems 54 | uses: rubygems/release-gem@a25424ba2ba8b387abc8ef40807c2c85b96cbe32 # v1.1.1 55 | 56 | - name: Create GitHub release 57 | run: | 58 | tag_name="$(git describe --tags --abbrev=0)" 59 | gh release create "${tag_name}" --verify-tag --generate-notes 60 | env: 61 | GITHUB_TOKEN: ${{ secrets.MATZBOT_GITHUB_WORKFLOW_TOKEN }} 62 | if: matrix.ruby != 'jruby' 63 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | schedule: 7 | - cron: '15 11 * * 0' 8 | workflow_dispatch: 9 | 10 | env: 11 | JAVA_OPTS: '-Xms60M -Xmx1G' 12 | 13 | jobs: 14 | ruby-versions: 15 | uses: ruby/actions/.github/workflows/ruby_versions.yml@master 16 | with: 17 | engine: cruby 18 | min_version: 2.5 19 | 20 | test: 21 | needs: ruby-versions 22 | runs-on: ${{ matrix.os }} 23 | strategy: 24 | fail-fast: false 25 | matrix: 26 | ruby: ${{ fromJson(needs.ruby-versions.outputs.versions) }} 27 | os: [ ubuntu-latest, macos-latest, windows-latest ] 28 | include: 29 | # jruby is broken with "undefined method 'init_struct'" 30 | # https://github.com/ruby/psych/actions/runs/15434465445/job/43438083198?pr=734 31 | - { os: windows-latest, ruby: jruby-head } 32 | - { os: macos-latest, ruby: jruby-head } 33 | - { os: ubuntu-latest, ruby: jruby-head } 34 | - { os: windows-latest, ruby: ucrt } 35 | - { os: windows-latest, ruby: mingw } 36 | - { os: windows-latest, ruby: mswin } 37 | - { os: macos-13, ruby: 2.5 } 38 | exclude: 39 | # CRuby < 2.6 does not support macos-arm64 40 | - { os: macos-latest, ruby: 2.5 } 41 | steps: 42 | - uses: actions/checkout@v4 43 | - name: Set up Ruby 44 | uses: ruby/setup-ruby-pkgs@v1 45 | with: 46 | ruby-version: ${{ matrix.ruby }} 47 | apt-get: "haveged libyaml-dev" 48 | brew: libyaml 49 | vcpkg: libyaml 50 | - name: Set JRuby ENV vars 51 | run: | 52 | echo 'JAVA_OPTS=-Xmx1g' >> $GITHUB_ENV 53 | if: ${{ ! startsWith(matrix.ruby, 'jruby') }} 54 | - name: Install dependencies 55 | run: bundle install --jobs 1 56 | - name: Run test 57 | id: test 58 | run: rake 59 | continue-on-error: ${{ matrix.ruby == 'jruby-head' }} 60 | - name: Install gem 61 | run: rake install 62 | if: ${{ steps.test.outcome == 'success' }} 63 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.bundle 3 | *.so 4 | *.jar 5 | *.class 6 | .mvn 7 | /.bundle/ 8 | /.yardoc 9 | /Gemfile.lock 10 | /_yardoc/ 11 | /coverage/ 12 | /doc/ 13 | /pkg/ 14 | /spec/reports/ 15 | /tmp/ 16 | /vendor 17 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## How to contribute to Psych 2 | 3 | Full details [here](https://bugs.ruby-lang.org/projects/ruby/wiki/HowToContribute) 4 | 5 | #### **Did you find a bug?** 6 | 7 | * **Do not open an issue if the bug is a security vulnerability 8 | in Psych**, instead refer to our [security policy](https://www.ruby-lang.org/en/security/) and email [here](security@ruby-lang.org). 9 | 10 | * **Ensure the bug was not already reported** by searching on ruby-core, the ruby github repo and on Psych's github repo. More info [here](https://bugs.ruby-lang.org/projects/ruby/wiki/HowToReport) 11 | 12 | * If you're unable to find an open issue addressing the problem, [open a new one](https://bugs.ruby-lang.org/). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **executable test case** demonstrating the expected behavior that is not occurring. 13 | 14 | #### **Did you write a patch that fixes a bug?** 15 | 16 | * Open a new GitHub pull request with the patch for small fixes. Anything larger look [here](https://bugs.ruby-lang.org/projects/ruby/wiki/HowToContribute); submit a Feature. 17 | 18 | * Ensure you clearly describe the problem and solution. Include the relevant ticket/issue number if applicable. 19 | 20 | Psych is a volunteer effort. We encourage you to pitch in and [join the team](https://github.com/ruby/psych/contributors)! 21 | 22 | Thanks! :heart: :heart: :heart: 23 | 24 | The Psych Team 25 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'https://rubygems.org' 2 | 3 | gemspec 4 | 5 | group :development do 6 | gem 'rake-compiler', ">= 0.4.1" 7 | gem 'ruby-maven', :platforms => :jruby 8 | gem 'test-unit' 9 | gem 'test-unit-ruby-core', ">= 1.0.7" 10 | end 11 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2009 Aaron Patterson, et al. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Mavenfile: -------------------------------------------------------------------------------- 1 | #-*- mode: ruby -*- 2 | 3 | jar 'org.snakeyaml:snakeyaml-engine:${snakeyaml.version}' 4 | 5 | plugin :dependency, '2.8', :outputFile => 'pkg/classpath' 6 | 7 | # vim: syntax=Ruby 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Psych 2 | 3 | * https://github.com/ruby/psych 4 | * https://docs.ruby-lang.org/en/master/Psych.html 5 | 6 | ## Description 7 | 8 | Psych is a YAML parser and emitter. Psych leverages 9 | [libyaml](https://pyyaml.org/wiki/LibYAML) for its YAML parsing and emitting 10 | capabilities. In addition to wrapping libyaml, Psych also knows how to 11 | serialize and de-serialize most Ruby objects to and from the YAML format. 12 | 13 | ## Examples 14 | 15 | ```ruby 16 | # Safely load YAML in to a Ruby object 17 | Psych.safe_load('--- foo') # => 'foo' 18 | 19 | # Emit YAML from a Ruby object 20 | Psych.dump("foo") # => "--- foo\n...\n" 21 | ``` 22 | 23 | ## Dependencies 24 | 25 | * libyaml 26 | 27 | ## Installation 28 | 29 | Psych has been included with MRI since 1.9.2, and is the default YAML parser 30 | in 1.9.3. 31 | 32 | If you want a newer gem release of Psych, you can use RubyGems: 33 | 34 | ```bash 35 | gem install psych 36 | ``` 37 | 38 | Psych supported the static build with specific version of libyaml sources. You can build psych with libyaml-0.2.5 like this. 39 | 40 | ```bash 41 | gem install psych -- --with-libyaml-source-dir=/path/to/libyaml-0.2.5 42 | ``` 43 | 44 | In order to use the gem release in your app, and not the stdlib version, 45 | you'll need the following: 46 | 47 | ```ruby 48 | gem 'psych' 49 | require 'psych' 50 | ``` 51 | 52 | Or if you use Bundler add this to your `Gemfile`: 53 | 54 | ```ruby 55 | gem 'psych' 56 | ``` 57 | 58 | JRuby ships with a pure Java implementation of Psych. 59 | 60 | ## License 61 | 62 | Copyright 2009 Aaron Patterson, et al. 63 | 64 | Permission is hereby granted, free of charge, to any person obtaining a copy 65 | of this software and associated documentation files (the 'Software'), to deal 66 | in the Software without restriction, including without limitation the rights 67 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 68 | copies of the Software, and to permit persons to whom the Software is 69 | furnished to do so, subject to the following conditions: 70 | 71 | The above copyright notice and this permission notice shall be included in all 72 | copies or substantial portions of the Software. 73 | 74 | THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 75 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 76 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 77 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 78 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 79 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 80 | SOFTWARE. 81 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler" 2 | Bundler::GemHelper.install_tasks 3 | 4 | require "rake/testtask" 5 | Rake::TestTask.new(:test) do |t| 6 | t.libs << "test/lib" << "test" 7 | t.ruby_opts << "-rhelper" 8 | t.test_files = FileList['test/**/test_*.rb'] 9 | t.verbose = true 10 | t.warning = true 11 | end 12 | 13 | if RUBY_PLATFORM =~ /java/ 14 | require 'rake/javaextensiontask' 15 | Rake::JavaExtensionTask.new("psych") do |ext| 16 | require 'maven/ruby/maven' 17 | # force load of versions to overwrite constants with values from repo. 18 | load './lib/psych/versions.rb' 19 | # uses Mavenfile to write classpath into pkg/classpath 20 | # and tell maven via system properties the snakeyaml version 21 | # this is basically the same as running from the commandline: 22 | # rmvn dependency:build-classpath -Dsnakeyaml.version='use version from Psych::DEFAULT_SNAKEYAML_VERSION here' 23 | Maven::Ruby::Maven.new.exec('dependency:build-classpath', "-Dsnakeyaml.version=#{Psych::DEFAULT_SNAKEYAML_VERSION}", '-Dverbose=true') 24 | ext.source_version = '1.8' 25 | ext.target_version = '1.8' 26 | ext.classpath = File.read('pkg/classpath') 27 | ext.ext_dir = 'ext/java' 28 | end 29 | else 30 | require 'rake/extensiontask' 31 | Rake::ExtensionTask.new("psych") 32 | end 33 | 34 | task :default => [:compile, :test] 35 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | 3 | require "bundler/setup" 4 | require "psych" 5 | 6 | require "irb" 7 | IRB.start 8 | -------------------------------------------------------------------------------- /bin/setup: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | set -euo pipefail 3 | IFS=$'\n\t' 4 | set -vx 5 | 6 | bundle install 7 | -------------------------------------------------------------------------------- /ext/java/org/jruby/ext/psych/PsychLibrary.java: -------------------------------------------------------------------------------- 1 | /***** BEGIN LICENSE BLOCK ***** 2 | * Version: EPL 1.0/GPL 2.0/LGPL 2.1 3 | * 4 | * The contents of this file are subject to the Eclipse Public 5 | * License Version 1.0 (the "License"); you may not use this file 6 | * except in compliance with the License. You may obtain a copy of 7 | * the License at http://www.eclipse.org/legal/epl-v10.html 8 | * 9 | * Software distributed under the License is distributed on an "AS 10 | * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or 11 | * implied. See the License for the specific language governing 12 | * rights and limitations under the License. 13 | * 14 | * Copyright (C) 2010 Charles O Nutter 15 | * 16 | * Alternatively, the contents of this file may be used under the terms of 17 | * either of the GNU General Public License Version 2 or later (the "GPL"), 18 | * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 19 | * in which case the provisions of the GPL or the LGPL are applicable instead 20 | * of those above. If you wish to allow use of your version of this file only 21 | * under the terms of either the GPL or the LGPL, and not to allow others to 22 | * use your version of this file under the terms of the EPL, indicate your 23 | * decision by deleting the provisions above and replace them with the notice 24 | * and other provisions required by the GPL or the LGPL. If you do not delete 25 | * the provisions above, a recipient may use your version of this file under 26 | * the terms of any one of the EPL, the GPL or the LGPL. 27 | ***** END LICENSE BLOCK *****/ 28 | package org.jruby.ext.psych; 29 | 30 | import org.jcodings.Encoding; 31 | import org.jcodings.specific.UTF16BEEncoding; 32 | import org.jcodings.specific.UTF16LEEncoding; 33 | import org.jcodings.specific.UTF8Encoding; 34 | import org.jruby.Ruby; 35 | import org.jruby.RubyArray; 36 | import org.jruby.RubyModule; 37 | import org.jruby.RubyString; 38 | import org.jruby.internal.runtime.methods.JavaMethod.JavaMethodZero; 39 | import org.jruby.runtime.ThreadContext; 40 | import org.jruby.runtime.Visibility; 41 | import org.jruby.runtime.builtin.IRubyObject; 42 | import org.jruby.runtime.load.Library; 43 | 44 | import org.snakeyaml.engine.v2.common.SpecVersion; 45 | 46 | import java.io.IOException; 47 | import java.io.InputStream; 48 | import java.util.Properties; 49 | 50 | public class PsychLibrary implements Library { 51 | 52 | private static final String POM_PROPERTIES = "META-INF/maven/org.snakeyaml/snakeyaml-engine/pom.properties"; 53 | private static final String DUMMY_VERSION = "0.0"; 54 | 55 | public void load(final Ruby runtime, boolean wrap) { 56 | RubyModule psych = runtime.defineModule("Psych"); 57 | 58 | // load version from properties packed with the jar 59 | Properties props = new Properties(); 60 | try( InputStream is = SpecVersion.class.getResourceAsStream(POM_PROPERTIES) ) { 61 | if (is != null) props.load(is); 62 | } 63 | catch( IOException e ) { 64 | // ignored 65 | } 66 | String snakeyamlVersion = props.getProperty("version", DUMMY_VERSION); 67 | 68 | RubyString version = runtime.newString(snakeyamlVersion); 69 | version.setFrozen(true); 70 | psych.setConstant("SNAKEYAML_VERSION", version); // e.g. 2.10-SNAPSHOT 71 | 72 | if (snakeyamlVersion.endsWith("-SNAPSHOT")) { 73 | snakeyamlVersion = snakeyamlVersion.substring(0, snakeyamlVersion.length() - "-SNAPSHOT".length()); 74 | } 75 | 76 | String[] versionParts = (snakeyamlVersion + ".0").split("\\."); // 2.10-SNAPSHOT -> 2.10.0 77 | final RubyArray versionElements = runtime.newArray(runtime.newFixnum(Integer.parseInt(versionParts[0])), runtime.newFixnum(Integer.parseInt(versionParts[1])), runtime.newFixnum(Integer.parseInt(versionParts[2]))); 78 | versionElements.setFrozen(true); 79 | 80 | psych.getSingletonClass().addMethod("libyaml_version", new JavaMethodZero(psych, Visibility.PUBLIC) { 81 | @Override 82 | public IRubyObject call(ThreadContext context, IRubyObject self, RubyModule clazz, String name) { 83 | return versionElements; 84 | } 85 | }); 86 | 87 | PsychParser.initPsychParser(runtime, psych); 88 | PsychEmitter.initPsychEmitter(runtime, psych); 89 | PsychToRuby.initPsychToRuby(runtime, psych); 90 | } 91 | 92 | public enum YAMLEncoding { 93 | YAML_ANY_ENCODING(UTF8Encoding.INSTANCE), 94 | YAML_UTF8_ENCODING(UTF8Encoding.INSTANCE), 95 | YAML_UTF16LE_ENCODING(UTF16LEEncoding.INSTANCE), 96 | YAML_UTF16BE_ENCODING(UTF16BEEncoding.INSTANCE); 97 | 98 | YAMLEncoding(Encoding encoding) { 99 | this.encoding = encoding; 100 | } 101 | 102 | public final Encoding encoding; 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /ext/java/org/jruby/ext/psych/PsychToRuby.java: -------------------------------------------------------------------------------- 1 | /***** BEGIN LICENSE BLOCK ***** 2 | * Version: EPL 1.0/GPL 2.0/LGPL 2.1 3 | * 4 | * The contents of this file are subject to the Eclipse Public 5 | * License Version 1.0 (the "License"); you may not use this file 6 | * except in compliance with the License. You may obtain a copy of 7 | * the License at http://www.eclipse.org/legal/epl-v10.html 8 | * 9 | * Software distributed under the License is distributed on an "AS 10 | * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or 11 | * implied. See the License for the specific language governing 12 | * rights and limitations under the License. 13 | * 14 | * Copyright (C) 2010 Charles O Nutter 15 | * 16 | * Alternatively, the contents of this file may be used under the terms of 17 | * either of the GNU General Public License Version 2 or later (the "GPL"), 18 | * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), 19 | * in which case the provisions of the GPL or the LGPL are applicable instead 20 | * of those above. If you wish to allow use of your version of this file only 21 | * under the terms of either the GPL or the LGPL, and not to allow others to 22 | * use your version of this file under the terms of the EPL, indicate your 23 | * decision by deleting the provisions above and replace them with the notice 24 | * and other provisions required by the GPL or the LGPL. If you do not delete 25 | * the provisions above, a recipient may use your version of this file under 26 | * the terms of any one of the EPL, the GPL or the LGPL. 27 | ***** END LICENSE BLOCK *****/ 28 | package org.jruby.ext.psych; 29 | 30 | import org.jruby.Ruby; 31 | import org.jruby.RubyClass; 32 | import org.jruby.RubyException; 33 | import org.jruby.RubyModule; 34 | import org.jruby.RubyObject; 35 | import org.jruby.anno.JRubyMethod; 36 | import org.jruby.exceptions.RaiseException; 37 | import org.jruby.runtime.ThreadContext; 38 | import org.jruby.runtime.builtin.IRubyObject; 39 | 40 | import static org.jruby.runtime.Visibility.PRIVATE; 41 | 42 | public class PsychToRuby { 43 | public static void initPsychToRuby(Ruby runtime, RubyModule psych) { 44 | RubyClass classLoader = runtime.defineClassUnder("ClassLoader", runtime.getObject(), RubyObject.OBJECT_ALLOCATOR, psych); 45 | 46 | RubyModule visitors = runtime.defineModuleUnder("Visitors", psych); 47 | RubyClass visitor = runtime.defineClassUnder("Visitor", runtime.getObject(), runtime.getObject().getAllocator(), visitors); 48 | RubyClass psychToRuby = runtime.defineClassUnder("ToRuby", visitor, RubyObject.OBJECT_ALLOCATOR, visitors); 49 | 50 | psychToRuby.defineAnnotatedMethods(ToRuby.class); 51 | classLoader.defineAnnotatedMethods(ClassLoader.class); 52 | } 53 | 54 | public static class ToRuby { 55 | @JRubyMethod(visibility = PRIVATE) 56 | public static IRubyObject build_exception(ThreadContext context, IRubyObject self, IRubyObject klass, IRubyObject message) { 57 | if (klass instanceof RubyClass) { 58 | IRubyObject exception = ((RubyClass)klass).allocate(); 59 | ((RubyException)exception).setMessage(message); 60 | return exception; 61 | } else { 62 | throw context.runtime.newTypeError(klass, context.runtime.getClassClass()); 63 | } 64 | } 65 | } 66 | 67 | public static class ClassLoader { 68 | @JRubyMethod(visibility = PRIVATE) 69 | public static IRubyObject path2class(ThreadContext context, IRubyObject self, IRubyObject path) { 70 | try { 71 | return context.runtime.getClassFromPath(path.asJavaString()); 72 | } catch (RaiseException re) { 73 | if (re.getException().getMetaClass() == context.runtime.getNameError()) { 74 | throw context.runtime.newArgumentError("undefined class/module " + path); 75 | } 76 | throw re; 77 | } 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /ext/psych/depend: -------------------------------------------------------------------------------- 1 | $(TARGET_SO): $(LIBYAML) 2 | 3 | libyaml $(LIBYAML): 4 | cd libyaml && $(MAKE) 5 | $(AR) $(ARFLAGS) $(LIBYAML) $(LIBYAML_OBJDIR)/*.$(OBJEXT) 6 | $(RANLIB) $(LIBYAML) 7 | 8 | clean-so:: 9 | -cd libyaml && $(MAKE) clean 10 | 11 | distclean-so:: 12 | -cd libyaml && $(MAKE) distclean 13 | -$(Q)$(RMDIRS) libyaml/* libyaml 14 | 15 | $(OBJS): $(HDRS) $(ruby_headers) \ 16 | $(hdrdir)/ruby/encoding.h \ 17 | $(hdrdir)/ruby/oniguruma.h 18 | -------------------------------------------------------------------------------- /ext/psych/extconf.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: us-ascii -*- 2 | # frozen_string_literal: true 3 | require 'mkmf' 4 | 5 | if $mswin or $mingw or $cygwin 6 | $CPPFLAGS << " -DYAML_DECLARE_STATIC" 7 | end 8 | 9 | yaml_source = with_config("libyaml-source-dir") 10 | if yaml_source 11 | yaml_source = yaml_source.gsub(/\$\((\w+)\)|\$\{(\w+)\}/) {ENV[$1||$2]} 12 | yaml_source = yaml_source.chomp("/") 13 | yaml_configure = "#{File.expand_path(yaml_source)}/configure" 14 | unless File.exist?(yaml_configure) 15 | raise "Configure script not found in #{yaml_source.quote}" 16 | end 17 | 18 | puts("Configuring libyaml source in #{yaml_source.quote}") 19 | yaml = "libyaml" 20 | Dir.mkdir(yaml) unless File.directory?(yaml) 21 | shared = $enable_shared || !$static 22 | args = [ 23 | yaml_configure, 24 | "--enable-#{shared ? 'shared' : 'static'}", 25 | "--host=#{RbConfig::CONFIG['host'].sub(/-unknown-/, '-').sub(/arm64/, 'arm')}", 26 | "CC=#{RbConfig::CONFIG['CC']}", 27 | *(["CFLAGS=-w"] if RbConfig::CONFIG["GCC"] == "yes"), 28 | ] 29 | puts(args.quote.join(' ')) 30 | unless system(*args, chdir: yaml) 31 | raise "failed to configure libyaml" 32 | end 33 | inc = yaml_source.start_with?("#$srcdir/") ? "$(srcdir)#{yaml_source[$srcdir.size..-1]}" : yaml_source 34 | $INCFLAGS << " -I#{yaml}/include -I#{inc}/include" 35 | puts("INCFLAGS=#$INCFLAGS") 36 | libyaml = "libyaml.#$LIBEXT" 37 | $cleanfiles << libyaml 38 | $LOCAL_LIBS.prepend("$(LIBYAML) ") 39 | else # default to pre-installed libyaml 40 | pkg_config('yaml-0.1') 41 | dir_config('libyaml') 42 | find_header('yaml.h') or abort "yaml.h not found" 43 | find_library('yaml', 'yaml_get_version') or abort "libyaml not found" 44 | end 45 | 46 | create_makefile 'psych' do |mk| 47 | mk << "LIBYAML = #{libyaml}".strip << "\n" 48 | mk << "LIBYAML_OBJDIR = libyaml/src#{shared ? '/.libs' : ''}\n" 49 | mk << "OBJEXT = #$OBJEXT" 50 | mk << "RANLIB = #{config_string('RANLIB') || config_string('NULLCMD')}\n" 51 | end 52 | 53 | # :startdoc: 54 | -------------------------------------------------------------------------------- /ext/psych/psych.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* call-seq: Psych.libyaml_version 4 | * 5 | * Returns the version of libyaml being used 6 | */ 7 | static VALUE libyaml_version(VALUE module) 8 | { 9 | int major, minor, patch; 10 | VALUE list[3]; 11 | 12 | yaml_get_version(&major, &minor, &patch); 13 | 14 | list[0] = INT2NUM(major); 15 | list[1] = INT2NUM(minor); 16 | list[2] = INT2NUM(patch); 17 | 18 | return rb_ary_new4((long)3, list); 19 | } 20 | 21 | VALUE mPsych; 22 | 23 | void Init_psych(void) 24 | { 25 | #ifdef HAVE_RB_EXT_RACTOR_SAFE 26 | RB_EXT_RACTOR_SAFE(true); 27 | #endif 28 | mPsych = rb_define_module("Psych"); 29 | 30 | rb_define_singleton_method(mPsych, "libyaml_version", libyaml_version, 0); 31 | 32 | Init_psych_parser(); 33 | Init_psych_emitter(); 34 | Init_psych_to_ruby(); 35 | Init_psych_yaml_tree(); 36 | } 37 | -------------------------------------------------------------------------------- /ext/psych/psych.h: -------------------------------------------------------------------------------- 1 | #ifndef PSYCH_H 2 | #define PSYCH_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | extern VALUE mPsych; 15 | 16 | 17 | #endif 18 | -------------------------------------------------------------------------------- /ext/psych/psych_emitter.h: -------------------------------------------------------------------------------- 1 | #ifndef PSYCH_EMITTER_H 2 | #define PSYCH_EMITTER_H 3 | 4 | #include 5 | 6 | void Init_psych_emitter(void); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /ext/psych/psych_parser.h: -------------------------------------------------------------------------------- 1 | #ifndef PSYCH_PARSER_H 2 | #define PSYCH_PARSER_H 3 | 4 | void Init_psych_parser(void); 5 | 6 | #endif 7 | -------------------------------------------------------------------------------- /ext/psych/psych_to_ruby.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | VALUE cPsychVisitorsToRuby; 4 | 5 | /* call-seq: vis.build_exception(klass, message) 6 | * 7 | * Create an exception with class +klass+ and +message+ 8 | */ 9 | static VALUE build_exception(VALUE self, VALUE klass, VALUE mesg) 10 | { 11 | VALUE e = rb_obj_alloc(klass); 12 | 13 | rb_iv_set(e, "mesg", mesg); 14 | 15 | return e; 16 | } 17 | 18 | /* call-seq: vis.path2class(path) 19 | * 20 | * Convert +path+ string to a class 21 | */ 22 | static VALUE path2class(VALUE self, VALUE path) 23 | { 24 | return rb_path_to_class(path); 25 | } 26 | 27 | static VALUE init_struct(VALUE self, VALUE data, VALUE attrs) 28 | { 29 | VALUE args = rb_ary_new2(1); 30 | rb_ary_push(args, attrs); 31 | rb_struct_initialize(data, args); 32 | 33 | return data; 34 | } 35 | 36 | void Init_psych_to_ruby(void) 37 | { 38 | VALUE psych = rb_define_module("Psych"); 39 | VALUE class_loader = rb_define_class_under(psych, "ClassLoader", rb_cObject); 40 | 41 | VALUE visitors = rb_define_module_under(psych, "Visitors"); 42 | VALUE visitor = rb_define_class_under(visitors, "Visitor", rb_cObject); 43 | cPsychVisitorsToRuby = rb_define_class_under(visitors, "ToRuby", visitor); 44 | 45 | rb_define_private_method(cPsychVisitorsToRuby, "init_struct", init_struct, 2); 46 | rb_define_private_method(cPsychVisitorsToRuby, "build_exception", build_exception, 2); 47 | rb_define_private_method(class_loader, "path2class", path2class, 1); 48 | } 49 | -------------------------------------------------------------------------------- /ext/psych/psych_to_ruby.h: -------------------------------------------------------------------------------- 1 | #ifndef PSYCH_TO_RUBY_H 2 | #define PSYCH_TO_RUBY_H 3 | 4 | #include 5 | 6 | void Init_psych_to_ruby(void); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /ext/psych/psych_yaml_tree.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | VALUE cPsychVisitorsYamlTree; 4 | 5 | void Init_psych_yaml_tree(void) 6 | { 7 | VALUE psych = rb_define_module("Psych"); 8 | VALUE visitors = rb_define_module_under(psych, "Visitors"); 9 | VALUE visitor = rb_define_class_under(visitors, "Visitor", rb_cObject); 10 | cPsychVisitorsYamlTree = rb_define_class_under(visitors, "YAMLTree", visitor); 11 | } 12 | -------------------------------------------------------------------------------- /ext/psych/psych_yaml_tree.h: -------------------------------------------------------------------------------- 1 | #ifndef PSYCH_YAML_TREE_H 2 | #define PSYCH_YAML_TREE_H 3 | 4 | #include 5 | 6 | void Init_psych_yaml_tree(void); 7 | 8 | #endif 9 | -------------------------------------------------------------------------------- /lib/psych/class_loader.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'omap' 3 | require_relative 'set' 4 | 5 | module Psych 6 | class ClassLoader # :nodoc: 7 | BIG_DECIMAL = 'BigDecimal' 8 | COMPLEX = 'Complex' 9 | DATA = 'Data' unless RUBY_VERSION < "3.2" 10 | DATE = 'Date' 11 | DATE_TIME = 'DateTime' 12 | EXCEPTION = 'Exception' 13 | OBJECT = 'Object' 14 | PSYCH_OMAP = 'Psych::Omap' 15 | PSYCH_SET = 'Psych::Set' 16 | RANGE = 'Range' 17 | RATIONAL = 'Rational' 18 | REGEXP = 'Regexp' 19 | STRUCT = 'Struct' 20 | SYMBOL = 'Symbol' 21 | 22 | def initialize 23 | @cache = CACHE.dup 24 | end 25 | 26 | def load klassname 27 | return nil if !klassname || klassname.empty? 28 | 29 | find klassname 30 | end 31 | 32 | def symbolize sym 33 | symbol 34 | sym.to_sym 35 | end 36 | 37 | constants.each do |const| 38 | konst = const_get const 39 | class_eval <<~RUBY, __FILE__, __LINE__ + 1 40 | def #{const.to_s.downcase} 41 | load #{konst.inspect} 42 | end 43 | RUBY 44 | end 45 | 46 | private 47 | 48 | def find klassname 49 | @cache[klassname] ||= resolve(klassname) 50 | end 51 | 52 | def resolve klassname 53 | name = klassname 54 | retried = false 55 | 56 | begin 57 | path2class(name) 58 | rescue ArgumentError, NameError => ex 59 | unless retried 60 | name = "Struct::#{name}" 61 | retried = ex 62 | retry 63 | end 64 | raise retried 65 | end 66 | end 67 | 68 | CACHE = Hash[constants.map { |const| 69 | val = const_get const 70 | begin 71 | [val, ::Object.const_get(val)] 72 | rescue 73 | nil 74 | end 75 | }.compact].freeze 76 | 77 | class Restricted < ClassLoader 78 | def initialize classes, symbols 79 | @classes = classes 80 | @symbols = symbols 81 | super() 82 | end 83 | 84 | def symbolize sym 85 | return super if @symbols.empty? 86 | 87 | if @symbols.include? sym 88 | super 89 | else 90 | raise DisallowedClass.new('load', 'Symbol') 91 | end 92 | end 93 | 94 | private 95 | 96 | def find klassname 97 | if @classes.include? klassname 98 | super 99 | else 100 | raise DisallowedClass.new('load', klassname) 101 | end 102 | end 103 | end 104 | end 105 | end 106 | -------------------------------------------------------------------------------- /lib/psych/coder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | ### 4 | # If an object defines +encode_with+, then an instance of Psych::Coder will 5 | # be passed to the method when the object is being serialized. The Coder 6 | # automatically assumes a Psych::Nodes::Mapping is being emitted. Other 7 | # objects like Sequence and Scalar may be emitted if +seq=+ or +scalar=+ are 8 | # called, respectively. 9 | class Coder 10 | attr_accessor :tag, :style, :implicit, :object 11 | attr_reader :type, :seq 12 | 13 | def initialize tag 14 | @map = {} 15 | @seq = [] 16 | @implicit = false 17 | @type = :map 18 | @tag = tag 19 | @style = Psych::Nodes::Mapping::BLOCK 20 | @scalar = nil 21 | @object = nil 22 | end 23 | 24 | def scalar *args 25 | if args.length > 0 26 | warn "#{caller[0]}: Coder#scalar(a,b,c) is deprecated" if $VERBOSE 27 | @tag, @scalar, _ = args 28 | @type = :scalar 29 | end 30 | @scalar 31 | end 32 | 33 | # Emit a map. The coder will be yielded to the block. 34 | def map tag = @tag, style = @style 35 | @tag = tag 36 | @style = style 37 | yield self if block_given? 38 | @map 39 | end 40 | 41 | # Emit a scalar with +value+ and +tag+ 42 | def represent_scalar tag, value 43 | self.tag = tag 44 | self.scalar = value 45 | end 46 | 47 | # Emit a sequence with +list+ and +tag+ 48 | def represent_seq tag, list 49 | @tag = tag 50 | self.seq = list 51 | end 52 | 53 | # Emit a sequence with +map+ and +tag+ 54 | def represent_map tag, map 55 | @tag = tag 56 | self.map = map 57 | end 58 | 59 | # Emit an arbitrary object +obj+ and +tag+ 60 | def represent_object tag, obj 61 | @tag = tag 62 | @type = :object 63 | @object = obj 64 | end 65 | 66 | # Emit a scalar with +value+ 67 | def scalar= value 68 | @type = :scalar 69 | @scalar = value 70 | end 71 | 72 | # Emit a map with +value+ 73 | def map= map 74 | @type = :map 75 | @map = map 76 | end 77 | 78 | def []= k, v 79 | @type = :map 80 | @map[k] = v 81 | end 82 | alias :add :[]= 83 | 84 | def [] k 85 | @type = :map 86 | @map[k] 87 | end 88 | 89 | # Emit a sequence of +list+ 90 | def seq= list 91 | @type = :seq 92 | @seq = list 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /lib/psych/core_ext.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | class Object 3 | def self.yaml_tag url 4 | Psych.add_tag(url, self) 5 | end 6 | 7 | ### 8 | # call-seq: to_yaml(options = {}) 9 | # 10 | # Convert an object to YAML. See Psych.dump for more information on the 11 | # available +options+. 12 | def to_yaml options = {} 13 | Psych.dump self, options 14 | end 15 | end 16 | 17 | if defined?(::IRB) 18 | require_relative 'y' 19 | end 20 | 21 | # Up to Ruby 3.4, Set was a regular object and was dumped as such 22 | # by Pysch. 23 | # Starting from Ruby 3.5 it's a core class written in C, so we have to implement 24 | # #encode_with / #init_with to preserve backward compatibility. 25 | if defined?(::Set) && Set.new.instance_variables.empty? 26 | class Set 27 | def encode_with(coder) 28 | hash = {} 29 | each do |m| 30 | hash[m] = true 31 | end 32 | coder["hash"] = hash 33 | coder 34 | end 35 | 36 | def init_with(coder) 37 | replace(coder["hash"].keys) 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/psych/exception.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | class Exception < RuntimeError 4 | end 5 | 6 | class BadAlias < Exception 7 | end 8 | 9 | # Subclasses `BadAlias` for backwards compatibility 10 | class AliasesNotEnabled < BadAlias 11 | def initialize 12 | super "Alias parsing was not enabled. To enable it, pass `aliases: true` to `Psych::load` or `Psych::safe_load`." 13 | end 14 | end 15 | 16 | # Subclasses `BadAlias` for backwards compatibility 17 | class AnchorNotDefined < BadAlias 18 | def initialize anchor_name 19 | super "An alias referenced an unknown anchor: #{anchor_name}" 20 | end 21 | end 22 | 23 | class DisallowedClass < Exception 24 | def initialize action, klass_name 25 | super "Tried to #{action} unspecified class: #{klass_name}" 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/psych/handler.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | ### 4 | # Psych::Handler is an abstract base class that defines the events used 5 | # when dealing with Psych::Parser. Clients who want to use Psych::Parser 6 | # should implement a class that inherits from Psych::Handler and define 7 | # events that they can handle. 8 | # 9 | # Psych::Handler defines all events that Psych::Parser can possibly send to 10 | # event handlers. 11 | # 12 | # See Psych::Parser for more details 13 | class Handler 14 | ### 15 | # Configuration options for dumping YAML. 16 | class DumperOptions 17 | attr_accessor :line_width, :indentation, :canonical 18 | 19 | def initialize 20 | @line_width = 0 21 | @indentation = 2 22 | @canonical = false 23 | end 24 | end 25 | 26 | # Default dumping options 27 | OPTIONS = DumperOptions.new 28 | 29 | # Events that a Handler should respond to. 30 | EVENTS = [ :alias, 31 | :empty, 32 | :end_document, 33 | :end_mapping, 34 | :end_sequence, 35 | :end_stream, 36 | :scalar, 37 | :start_document, 38 | :start_mapping, 39 | :start_sequence, 40 | :start_stream ] 41 | 42 | ### 43 | # Called with +encoding+ when the YAML stream starts. This method is 44 | # called once per stream. A stream may contain multiple documents. 45 | # 46 | # See the constants in Psych::Parser for the possible values of +encoding+. 47 | def start_stream encoding 48 | end 49 | 50 | ### 51 | # Called when the document starts with the declared +version+, 52 | # +tag_directives+, if the document is +implicit+. 53 | # 54 | # +version+ will be an array of integers indicating the YAML version being 55 | # dealt with, +tag_directives+ is a list of tuples indicating the prefix 56 | # and suffix of each tag, and +implicit+ is a boolean indicating whether 57 | # the document is started implicitly. 58 | # 59 | # === Example 60 | # 61 | # Given the following YAML: 62 | # 63 | # %YAML 1.1 64 | # %TAG ! tag:tenderlovemaking.com,2009: 65 | # --- !squee 66 | # 67 | # The parameters for start_document must be this: 68 | # 69 | # version # => [1, 1] 70 | # tag_directives # => [["!", "tag:tenderlovemaking.com,2009:"]] 71 | # implicit # => false 72 | def start_document version, tag_directives, implicit 73 | end 74 | 75 | ### 76 | # Called with the document ends. +implicit+ is a boolean value indicating 77 | # whether or not the document has an implicit ending. 78 | # 79 | # === Example 80 | # 81 | # Given the following YAML: 82 | # 83 | # --- 84 | # hello world 85 | # 86 | # +implicit+ will be true. Given this YAML: 87 | # 88 | # --- 89 | # hello world 90 | # ... 91 | # 92 | # +implicit+ will be false. 93 | def end_document implicit 94 | end 95 | 96 | ### 97 | # Called when an alias is found to +anchor+. +anchor+ will be the name 98 | # of the anchor found. 99 | # 100 | # === Example 101 | # 102 | # Here we have an example of an array that references itself in YAML: 103 | # 104 | # --- &ponies 105 | # - first element 106 | # - *ponies 107 | # 108 | # &ponies is the anchor, *ponies is the alias. In this case, alias is 109 | # called with "ponies". 110 | def alias anchor 111 | end 112 | 113 | ### 114 | # Called when a scalar +value+ is found. The scalar may have an 115 | # +anchor+, a +tag+, be implicitly +plain+ or implicitly +quoted+ 116 | # 117 | # +value+ is the string value of the scalar 118 | # +anchor+ is an associated anchor or nil 119 | # +tag+ is an associated tag or nil 120 | # +plain+ is a boolean value 121 | # +quoted+ is a boolean value 122 | # +style+ is an integer indicating the string style 123 | # 124 | # See the constants in Psych::Nodes::Scalar for the possible values of 125 | # +style+ 126 | # 127 | # === Example 128 | # 129 | # Here is a YAML document that exercises most of the possible ways this 130 | # method can be called: 131 | # 132 | # --- 133 | # - !str "foo" 134 | # - &anchor fun 135 | # - many 136 | # lines 137 | # - | 138 | # many 139 | # newlines 140 | # 141 | # The above YAML document contains a list with four strings. Here are 142 | # the parameters sent to this method in the same order: 143 | # 144 | # # value anchor tag plain quoted style 145 | # ["foo", nil, "!str", false, false, 3 ] 146 | # ["fun", "anchor", nil, true, false, 1 ] 147 | # ["many lines", nil, nil, true, false, 1 ] 148 | # ["many\nnewlines\n", nil, nil, false, true, 4 ] 149 | # 150 | def scalar value, anchor, tag, plain, quoted, style 151 | end 152 | 153 | ### 154 | # Called when a sequence is started. 155 | # 156 | # +anchor+ is the anchor associated with the sequence or nil. 157 | # +tag+ is the tag associated with the sequence or nil. 158 | # +implicit+ a boolean indicating whether or not the sequence was implicitly 159 | # started. 160 | # +style+ is an integer indicating the list style. 161 | # 162 | # See the constants in Psych::Nodes::Sequence for the possible values of 163 | # +style+. 164 | # 165 | # === Example 166 | # 167 | # Here is a YAML document that exercises most of the possible ways this 168 | # method can be called: 169 | # 170 | # --- 171 | # - !!seq [ 172 | # a 173 | # ] 174 | # - &pewpew 175 | # - b 176 | # 177 | # The above YAML document consists of three lists, an outer list that 178 | # contains two inner lists. Here is a matrix of the parameters sent 179 | # to represent these lists: 180 | # 181 | # # anchor tag implicit style 182 | # [nil, nil, true, 1 ] 183 | # [nil, "tag:yaml.org,2002:seq", false, 2 ] 184 | # ["pewpew", nil, true, 1 ] 185 | 186 | def start_sequence anchor, tag, implicit, style 187 | end 188 | 189 | ### 190 | # Called when a sequence ends. 191 | def end_sequence 192 | end 193 | 194 | ### 195 | # Called when a map starts. 196 | # 197 | # +anchor+ is the anchor associated with the map or +nil+. 198 | # +tag+ is the tag associated with the map or +nil+. 199 | # +implicit+ is a boolean indicating whether or not the map was implicitly 200 | # started. 201 | # +style+ is an integer indicating the mapping style. 202 | # 203 | # See the constants in Psych::Nodes::Mapping for the possible values of 204 | # +style+. 205 | # 206 | # === Example 207 | # 208 | # Here is a YAML document that exercises most of the possible ways this 209 | # method can be called: 210 | # 211 | # --- 212 | # k: !!map { hello: world } 213 | # v: &pewpew 214 | # hello: world 215 | # 216 | # The above YAML document consists of three maps, an outer map that contains 217 | # two inner maps. Below is a matrix of the parameters sent in order to 218 | # represent these three maps: 219 | # 220 | # # anchor tag implicit style 221 | # [nil, nil, true, 1 ] 222 | # [nil, "tag:yaml.org,2002:map", false, 2 ] 223 | # ["pewpew", nil, true, 1 ] 224 | 225 | def start_mapping anchor, tag, implicit, style 226 | end 227 | 228 | ### 229 | # Called when a map ends 230 | def end_mapping 231 | end 232 | 233 | ### 234 | # Called when an empty event happens. (Which, as far as I can tell, is 235 | # never). 236 | def empty 237 | end 238 | 239 | ### 240 | # Called when the YAML stream ends 241 | def end_stream 242 | end 243 | 244 | ### 245 | # Called before each event with line/column information. 246 | def event_location(start_line, start_column, end_line, end_column) 247 | end 248 | 249 | ### 250 | # Is this handler a streaming handler? 251 | def streaming? 252 | false 253 | end 254 | end 255 | end 256 | -------------------------------------------------------------------------------- /lib/psych/handlers/document_stream.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative '../tree_builder' 3 | 4 | module Psych 5 | module Handlers 6 | class DocumentStream < Psych::TreeBuilder # :nodoc: 7 | def initialize &block 8 | super 9 | @block = block 10 | end 11 | 12 | def start_document version, tag_directives, implicit 13 | n = Nodes::Document.new version, tag_directives, implicit 14 | push n 15 | end 16 | 17 | def end_document implicit_end = !streaming? 18 | @last.implicit_end = implicit_end 19 | @block.call pop 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/psych/handlers/recorder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative '../handler' 3 | 4 | module Psych 5 | module Handlers 6 | ### 7 | # This handler will capture an event and record the event. Recorder events 8 | # are available vial Psych::Handlers::Recorder#events. 9 | # 10 | # For example: 11 | # 12 | # recorder = Psych::Handlers::Recorder.new 13 | # parser = Psych::Parser.new recorder 14 | # parser.parse '--- foo' 15 | # 16 | # recorder.events # => [list of events] 17 | # 18 | # # Replay the events 19 | # 20 | # emitter = Psych::Emitter.new $stdout 21 | # recorder.events.each do |m, args| 22 | # emitter.send m, *args 23 | # end 24 | 25 | class Recorder < Psych::Handler 26 | attr_reader :events 27 | 28 | def initialize 29 | @events = [] 30 | super 31 | end 32 | 33 | EVENTS.each do |event| 34 | define_method event do |*args| 35 | @events << [event, args] 36 | end 37 | end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/psych/json/ruby_events.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | module JSON 4 | module RubyEvents # :nodoc: 5 | def visit_Time o 6 | formatted = format_time o 7 | @emitter.scalar formatted, nil, nil, false, true, Nodes::Scalar::DOUBLE_QUOTED 8 | end 9 | 10 | def visit_DateTime o 11 | visit_Time o.to_time 12 | end 13 | 14 | def visit_String o 15 | @emitter.scalar o.to_s, nil, nil, false, true, Nodes::Scalar::DOUBLE_QUOTED 16 | end 17 | alias :visit_Symbol :visit_String 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /lib/psych/json/stream.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'ruby_events' 3 | require_relative 'yaml_events' 4 | 5 | module Psych 6 | module JSON 7 | class Stream < Psych::Visitors::JSONTree 8 | include Psych::JSON::RubyEvents 9 | include Psych::Streaming 10 | extend Psych::Streaming::ClassMethods 11 | 12 | class Emitter < Psych::Stream::Emitter # :nodoc: 13 | include Psych::JSON::YAMLEvents 14 | end 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /lib/psych/json/tree_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'yaml_events' 3 | 4 | module Psych 5 | module JSON 6 | ### 7 | # Psych::JSON::TreeBuilder is an event based AST builder. Events are sent 8 | # to an instance of Psych::JSON::TreeBuilder and a JSON AST is constructed. 9 | class TreeBuilder < Psych::TreeBuilder 10 | include Psych::JSON::YAMLEvents 11 | end 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /lib/psych/json/yaml_events.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | module JSON 4 | module YAMLEvents # :nodoc: 5 | def start_document version, tag_directives, implicit 6 | super(version, tag_directives, !streaming?) 7 | end 8 | 9 | def end_document implicit_end = !streaming? 10 | super(implicit_end) 11 | end 12 | 13 | def start_mapping anchor, tag, implicit, style 14 | super(anchor, nil, true, Nodes::Mapping::FLOW) 15 | end 16 | 17 | def start_sequence anchor, tag, implicit, style 18 | super(anchor, nil, true, Nodes::Sequence::FLOW) 19 | end 20 | 21 | def scalar value, anchor, tag, plain, quoted, style 22 | if "tag:yaml.org,2002:null" == tag 23 | super('null', nil, nil, true, false, Nodes::Scalar::PLAIN) 24 | else 25 | super 26 | end 27 | end 28 | end 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/psych/nodes.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'nodes/node' 3 | require_relative 'nodes/stream' 4 | require_relative 'nodes/document' 5 | require_relative 'nodes/sequence' 6 | require_relative 'nodes/scalar' 7 | require_relative 'nodes/mapping' 8 | require_relative 'nodes/alias' 9 | 10 | module Psych 11 | ### 12 | # = Overview 13 | # 14 | # When using Psych.load to deserialize a YAML document, the document is 15 | # translated to an intermediary AST. That intermediary AST is then 16 | # translated in to a Ruby object graph. 17 | # 18 | # In the opposite direction, when using Psych.dump, the Ruby object graph is 19 | # translated to an intermediary AST which is then converted to a YAML 20 | # document. 21 | # 22 | # Psych::Nodes contains all of the classes that make up the nodes of a YAML 23 | # AST. You can manually build an AST and use one of the visitors (see 24 | # Psych::Visitors) to convert that AST to either a YAML document or to a 25 | # Ruby object graph. 26 | # 27 | # Here is an example of building an AST that represents a list with one 28 | # scalar: 29 | # 30 | # # Create our nodes 31 | # stream = Psych::Nodes::Stream.new 32 | # doc = Psych::Nodes::Document.new 33 | # seq = Psych::Nodes::Sequence.new 34 | # scalar = Psych::Nodes::Scalar.new('foo') 35 | # 36 | # # Build up our tree 37 | # stream.children << doc 38 | # doc.children << seq 39 | # seq.children << scalar 40 | # 41 | # The stream is the root of the tree. We can then convert the tree to YAML: 42 | # 43 | # stream.to_yaml => "---\n- foo\n" 44 | # 45 | # Or convert it to Ruby: 46 | # 47 | # stream.to_ruby => [["foo"]] 48 | # 49 | # == YAML AST Requirements 50 | # 51 | # A valid YAML AST *must* have one Psych::Nodes::Stream at the root. A 52 | # Psych::Nodes::Stream node must have 1 or more Psych::Nodes::Document nodes 53 | # as children. 54 | # 55 | # Psych::Nodes::Document nodes must have one and *only* one child. That child 56 | # may be one of: 57 | # 58 | # * Psych::Nodes::Sequence 59 | # * Psych::Nodes::Mapping 60 | # * Psych::Nodes::Scalar 61 | # 62 | # Psych::Nodes::Sequence and Psych::Nodes::Mapping nodes may have many 63 | # children, but Psych::Nodes::Mapping nodes should have an even number of 64 | # children. 65 | # 66 | # All of these are valid children for Psych::Nodes::Sequence and 67 | # Psych::Nodes::Mapping nodes: 68 | # 69 | # * Psych::Nodes::Sequence 70 | # * Psych::Nodes::Mapping 71 | # * Psych::Nodes::Scalar 72 | # * Psych::Nodes::Alias 73 | # 74 | # Psych::Nodes::Scalar and Psych::Nodes::Alias are both terminal nodes and 75 | # should not have any children. 76 | module Nodes 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /lib/psych/nodes/alias.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | module Nodes 4 | ### 5 | # This class represents a {YAML Alias}[http://yaml.org/spec/1.1/#alias]. 6 | # It points to an +anchor+. 7 | # 8 | # A Psych::Nodes::Alias is a terminal node and may have no children. 9 | class Alias < Psych::Nodes::Node 10 | # The anchor this alias links to 11 | attr_accessor :anchor 12 | 13 | # Create a new Alias that points to an +anchor+ 14 | def initialize anchor 15 | @anchor = anchor 16 | end 17 | 18 | def alias?; true; end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/psych/nodes/document.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | module Nodes 4 | ### 5 | # This represents a YAML Document. This node must be a child of 6 | # Psych::Nodes::Stream. A Psych::Nodes::Document must have one child, 7 | # and that child may be one of the following: 8 | # 9 | # * Psych::Nodes::Sequence 10 | # * Psych::Nodes::Mapping 11 | # * Psych::Nodes::Scalar 12 | class Document < Psych::Nodes::Node 13 | # The version of the YAML document 14 | attr_accessor :version 15 | 16 | # A list of tag directives for this document 17 | attr_accessor :tag_directives 18 | 19 | # Was this document implicitly created? 20 | attr_accessor :implicit 21 | 22 | # Is the end of the document implicit? 23 | attr_accessor :implicit_end 24 | 25 | ### 26 | # Create a new Psych::Nodes::Document object. 27 | # 28 | # +version+ is a list indicating the YAML version. 29 | # +tags_directives+ is a list of tag directive declarations 30 | # +implicit+ is a flag indicating whether the document will be implicitly 31 | # started. 32 | # 33 | # == Example: 34 | # This creates a YAML document object that represents a YAML 1.1 document 35 | # with one tag directive, and has an implicit start: 36 | # 37 | # Psych::Nodes::Document.new( 38 | # [1,1], 39 | # [["!", "tag:tenderlovemaking.com,2009:"]], 40 | # true 41 | # ) 42 | # 43 | # == See Also 44 | # See also Psych::Handler#start_document 45 | def initialize version = [], tag_directives = [], implicit = false 46 | super() 47 | @version = version 48 | @tag_directives = tag_directives 49 | @implicit = implicit 50 | @implicit_end = true 51 | end 52 | 53 | ### 54 | # Returns the root node. A Document may only have one root node: 55 | # http://yaml.org/spec/1.1/#id898031 56 | def root 57 | children.first 58 | end 59 | 60 | def document?; true; end 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/psych/nodes/mapping.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | module Nodes 4 | ### 5 | # This class represents a {YAML Mapping}[http://yaml.org/spec/1.1/#mapping]. 6 | # 7 | # A Psych::Nodes::Mapping node may have 0 or more children, but must have 8 | # an even number of children. Here are the valid children a 9 | # Psych::Nodes::Mapping node may have: 10 | # 11 | # * Psych::Nodes::Sequence 12 | # * Psych::Nodes::Mapping 13 | # * Psych::Nodes::Scalar 14 | # * Psych::Nodes::Alias 15 | class Mapping < Psych::Nodes::Node 16 | # Any Map Style 17 | ANY = 0 18 | 19 | # Block Map Style 20 | BLOCK = 1 21 | 22 | # Flow Map Style 23 | FLOW = 2 24 | 25 | # The optional anchor for this mapping 26 | attr_accessor :anchor 27 | 28 | # The optional tag for this mapping 29 | attr_accessor :tag 30 | 31 | # Is this an implicit mapping? 32 | attr_accessor :implicit 33 | 34 | # The style of this mapping 35 | attr_accessor :style 36 | 37 | ### 38 | # Create a new Psych::Nodes::Mapping object. 39 | # 40 | # +anchor+ is the anchor associated with the map or +nil+. 41 | # +tag+ is the tag associated with the map or +nil+. 42 | # +implicit+ is a boolean indicating whether or not the map was implicitly 43 | # started. 44 | # +style+ is an integer indicating the mapping style. 45 | # 46 | # == See Also 47 | # See also Psych::Handler#start_mapping 48 | def initialize anchor = nil, tag = nil, implicit = true, style = BLOCK 49 | super() 50 | @anchor = anchor 51 | @tag = tag 52 | @implicit = implicit 53 | @style = style 54 | end 55 | 56 | def mapping?; true; end 57 | end 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /lib/psych/nodes/node.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative '../class_loader' 3 | require_relative '../scalar_scanner' 4 | 5 | module Psych 6 | module Nodes 7 | ### 8 | # The base class for any Node in a YAML parse tree. This class should 9 | # never be instantiated. 10 | class Node 11 | include Enumerable 12 | 13 | # The children of this node 14 | attr_reader :children 15 | 16 | # An associated tag 17 | attr_reader :tag 18 | 19 | # The line number where this node start 20 | attr_accessor :start_line 21 | 22 | # The column number where this node start 23 | attr_accessor :start_column 24 | 25 | # The line number where this node ends 26 | attr_accessor :end_line 27 | 28 | # The column number where this node ends 29 | attr_accessor :end_column 30 | 31 | # Create a new Psych::Nodes::Node 32 | def initialize 33 | @children = [] 34 | end 35 | 36 | ### 37 | # Iterate over each node in the tree. Yields each node to +block+ depth 38 | # first. 39 | def each &block 40 | return enum_for :each unless block_given? 41 | Visitors::DepthFirst.new(block).accept self 42 | end 43 | 44 | ### 45 | # Convert this node to Ruby. 46 | # 47 | # See also Psych::Visitors::ToRuby 48 | def to_ruby(symbolize_names: false, freeze: false, strict_integer: false) 49 | Visitors::ToRuby.create(symbolize_names: symbolize_names, freeze: freeze, strict_integer: strict_integer).accept(self) 50 | end 51 | alias :transform :to_ruby 52 | 53 | ### 54 | # Convert this node to YAML. 55 | # 56 | # See also Psych::Visitors::Emitter 57 | def yaml io = nil, options = {} 58 | require "stringio" unless defined?(StringIO) 59 | 60 | real_io = io || StringIO.new(''.encode('utf-8')) 61 | 62 | Visitors::Emitter.new(real_io, options).accept self 63 | return real_io.string unless io 64 | io 65 | end 66 | alias :to_yaml :yaml 67 | 68 | def alias?; false; end 69 | def document?; false; end 70 | def mapping?; false; end 71 | def scalar?; false; end 72 | def sequence?; false; end 73 | def stream?; false; end 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /lib/psych/nodes/scalar.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | module Nodes 4 | ### 5 | # This class represents a {YAML Scalar}[http://yaml.org/spec/1.1/#id858081]. 6 | # 7 | # This node type is a terminal node and should not have any children. 8 | class Scalar < Psych::Nodes::Node 9 | # Any style scalar, the emitter chooses 10 | ANY = 0 11 | 12 | # Plain scalar style 13 | PLAIN = 1 14 | 15 | # Single quoted style 16 | SINGLE_QUOTED = 2 17 | 18 | # Double quoted style 19 | DOUBLE_QUOTED = 3 20 | 21 | # Literal style 22 | LITERAL = 4 23 | 24 | # Folded style 25 | FOLDED = 5 26 | 27 | # The scalar value 28 | attr_accessor :value 29 | 30 | # The anchor value (if there is one) 31 | attr_accessor :anchor 32 | 33 | # The tag value (if there is one) 34 | attr_accessor :tag 35 | 36 | # Is this a plain scalar? 37 | attr_accessor :plain 38 | 39 | # Is this scalar quoted? 40 | attr_accessor :quoted 41 | 42 | # The style of this scalar 43 | attr_accessor :style 44 | 45 | ### 46 | # Create a new Psych::Nodes::Scalar object. 47 | # 48 | # +value+ is the string value of the scalar 49 | # +anchor+ is an associated anchor or nil 50 | # +tag+ is an associated tag or nil 51 | # +plain+ is a boolean value 52 | # +quoted+ is a boolean value 53 | # +style+ is an integer indicating the string style 54 | # 55 | # == See Also 56 | # 57 | # See also Psych::Handler#scalar 58 | def initialize value, anchor = nil, tag = nil, plain = true, quoted = false, style = ANY 59 | @value = value 60 | @anchor = anchor 61 | @tag = tag 62 | @plain = plain 63 | @quoted = quoted 64 | @style = style 65 | end 66 | 67 | def scalar?; true; end 68 | end 69 | end 70 | end 71 | -------------------------------------------------------------------------------- /lib/psych/nodes/sequence.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | module Nodes 4 | ### 5 | # This class represents a 6 | # {YAML sequence}[http://yaml.org/spec/1.1/#sequence/syntax]. 7 | # 8 | # A YAML sequence is basically a list, and looks like this: 9 | # 10 | # %YAML 1.1 11 | # --- 12 | # - I am 13 | # - a Sequence 14 | # 15 | # A YAML sequence may have an anchor like this: 16 | # 17 | # %YAML 1.1 18 | # --- 19 | # &A [ 20 | # "This sequence", 21 | # "has an anchor" 22 | # ] 23 | # 24 | # A YAML sequence may also have a tag like this: 25 | # 26 | # %YAML 1.1 27 | # --- 28 | # !!seq [ 29 | # "This sequence", 30 | # "has a tag" 31 | # ] 32 | # 33 | # This class represents a sequence in a YAML document. A 34 | # Psych::Nodes::Sequence node may have 0 or more children. Valid children 35 | # for this node are: 36 | # 37 | # * Psych::Nodes::Sequence 38 | # * Psych::Nodes::Mapping 39 | # * Psych::Nodes::Scalar 40 | # * Psych::Nodes::Alias 41 | class Sequence < Psych::Nodes::Node 42 | # Any Styles, emitter chooses 43 | ANY = 0 44 | 45 | # Block style sequence 46 | BLOCK = 1 47 | 48 | # Flow style sequence 49 | FLOW = 2 50 | 51 | # The anchor for this sequence (if any) 52 | attr_accessor :anchor 53 | 54 | # The tag name for this sequence (if any) 55 | attr_accessor :tag 56 | 57 | # Is this sequence started implicitly? 58 | attr_accessor :implicit 59 | 60 | # The sequence style used 61 | attr_accessor :style 62 | 63 | ### 64 | # Create a new object representing a YAML sequence. 65 | # 66 | # +anchor+ is the anchor associated with the sequence or nil. 67 | # +tag+ is the tag associated with the sequence or nil. 68 | # +implicit+ a boolean indicating whether or not the sequence was 69 | # implicitly started. 70 | # +style+ is an integer indicating the list style. 71 | # 72 | # See Psych::Handler#start_sequence 73 | def initialize anchor = nil, tag = nil, implicit = true, style = BLOCK 74 | super() 75 | @anchor = anchor 76 | @tag = tag 77 | @implicit = implicit 78 | @style = style 79 | end 80 | 81 | def sequence?; true; end 82 | end 83 | end 84 | end 85 | -------------------------------------------------------------------------------- /lib/psych/nodes/stream.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | module Nodes 4 | ### 5 | # Represents a YAML stream. This is the root node for any YAML parse 6 | # tree. This node must have one or more child nodes. The only valid 7 | # child node for a Psych::Nodes::Stream node is Psych::Nodes::Document. 8 | class Stream < Psych::Nodes::Node 9 | 10 | # Encodings supported by Psych (and libyaml) 11 | 12 | # Any encoding 13 | ANY = Psych::Parser::ANY 14 | 15 | # UTF-8 encoding 16 | UTF8 = Psych::Parser::UTF8 17 | 18 | # UTF-16LE encoding 19 | UTF16LE = Psych::Parser::UTF16LE 20 | 21 | # UTF-16BE encoding 22 | UTF16BE = Psych::Parser::UTF16BE 23 | 24 | # The encoding used for this stream 25 | attr_accessor :encoding 26 | 27 | ### 28 | # Create a new Psych::Nodes::Stream node with an +encoding+ that 29 | # defaults to Psych::Nodes::Stream::UTF8. 30 | # 31 | # See also Psych::Handler#start_stream 32 | def initialize encoding = UTF8 33 | super() 34 | @encoding = encoding 35 | end 36 | 37 | def stream?; true; end 38 | end 39 | end 40 | end 41 | -------------------------------------------------------------------------------- /lib/psych/omap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | class Omap < ::Hash 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/psych/parser.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | ### 4 | # YAML event parser class. This class parses a YAML document and calls 5 | # events on the handler that is passed to the constructor. The events can 6 | # be used for things such as constructing a YAML AST or deserializing YAML 7 | # documents. It can even be fed back to Psych::Emitter to emit the same 8 | # document that was parsed. 9 | # 10 | # See Psych::Handler for documentation on the events that Psych::Parser emits. 11 | # 12 | # Here is an example that prints out ever scalar found in a YAML document: 13 | # 14 | # # Handler for detecting scalar values 15 | # class ScalarHandler < Psych::Handler 16 | # def scalar value, anchor, tag, plain, quoted, style 17 | # puts value 18 | # end 19 | # end 20 | # 21 | # parser = Psych::Parser.new(ScalarHandler.new) 22 | # parser.parse(yaml_document) 23 | # 24 | # Here is an example that feeds the parser back in to Psych::Emitter. The 25 | # YAML document is read from STDIN and written back out to STDERR: 26 | # 27 | # parser = Psych::Parser.new(Psych::Emitter.new($stderr)) 28 | # parser.parse($stdin) 29 | # 30 | # Psych uses Psych::Parser in combination with Psych::TreeBuilder to 31 | # construct an AST of the parsed YAML document. 32 | 33 | class Parser 34 | class Mark < Struct.new(:index, :line, :column) 35 | end 36 | 37 | # The handler on which events will be called 38 | attr_accessor :handler 39 | 40 | # Set the encoding for this parser to +encoding+ 41 | attr_writer :external_encoding 42 | 43 | ### 44 | # Creates a new Psych::Parser instance with +handler+. YAML events will 45 | # be called on +handler+. See Psych::Parser for more details. 46 | 47 | def initialize handler = Handler.new 48 | @handler = handler 49 | @external_encoding = ANY 50 | end 51 | 52 | ### 53 | # call-seq: 54 | # parser.parse(yaml) 55 | # 56 | # Parse the YAML document contained in +yaml+. Events will be called on 57 | # the handler set on the parser instance. 58 | # 59 | # See Psych::Parser and Psych::Parser#handler 60 | 61 | def parse yaml, path = yaml.respond_to?(:path) ? yaml.path : "" 62 | _native_parse @handler, yaml, path 63 | end 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/psych/scalar_scanner.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Psych 4 | ### 5 | # Scan scalars for built in types 6 | class ScalarScanner 7 | # Taken from http://yaml.org/type/timestamp.html 8 | TIME = /^-?\d{4}-\d{1,2}-\d{1,2}(?:[Tt]|\s+)\d{1,2}:\d\d:\d\d(?:\.\d*)?(?:\s*(?:Z|[-+]\d{1,2}:?(?:\d\d)?))?$/ 9 | 10 | # Taken from http://yaml.org/type/float.html 11 | # Base 60, [-+]inf and NaN are handled separately 12 | FLOAT = /^(?:[-+]?([0-9][0-9_,]*)?\.[0-9]*([eE][-+][0-9]+)?(?# base 10))$/x 13 | 14 | # Taken from http://yaml.org/type/int.html and modified to ensure at least one numerical symbol exists 15 | INTEGER_STRICT = /^(?:[-+]?0b[_]*[0-1][0-1_]* (?# base 2) 16 | |[-+]?0[_]*[0-7][0-7_]* (?# base 8) 17 | |[-+]?(0|[1-9][0-9_]*) (?# base 10) 18 | |[-+]?0x[_]*[0-9a-fA-F][0-9a-fA-F_]* (?# base 16))$/x 19 | 20 | # Same as above, but allows commas. 21 | # Not to YML spec, but kept for backwards compatibility 22 | INTEGER_LEGACY = /^(?:[-+]?0b[_,]*[0-1][0-1_,]* (?# base 2) 23 | |[-+]?0[_,]*[0-7][0-7_,]* (?# base 8) 24 | |[-+]?(?:0|[1-9](?:[0-9]|,[0-9]|_[0-9])*) (?# base 10) 25 | |[-+]?0x[_,]*[0-9a-fA-F][0-9a-fA-F_,]* (?# base 16))$/x 26 | 27 | attr_reader :class_loader 28 | 29 | # Create a new scanner 30 | def initialize class_loader, strict_integer: false 31 | @symbol_cache = {} 32 | @class_loader = class_loader 33 | @strict_integer = strict_integer 34 | end 35 | 36 | # Tokenize +string+ returning the Ruby object 37 | def tokenize string 38 | return nil if string.empty? 39 | return @symbol_cache[string] if @symbol_cache.key?(string) 40 | integer_regex = @strict_integer ? INTEGER_STRICT : INTEGER_LEGACY 41 | # Check for a String type, being careful not to get caught by hash keys, hex values, and 42 | # special floats (e.g., -.inf). 43 | if string.match?(%r{^[^\d.:-]?[[:alpha:]_\s!@#$%\^&*(){}<>|/\\~;=]+}) || string.match?(/\n/) 44 | return string if string.length > 5 45 | 46 | if string.match?(/^[^ytonf~]/i) 47 | string 48 | elsif string == '~' || string.match?(/^null$/i) 49 | nil 50 | elsif string.match?(/^(yes|true|on)$/i) 51 | true 52 | elsif string.match?(/^(no|false|off)$/i) 53 | false 54 | else 55 | string 56 | end 57 | elsif string.match?(TIME) 58 | begin 59 | parse_time string 60 | rescue ArgumentError 61 | string 62 | end 63 | elsif string.match?(/^\d{4}-(?:1[012]|0\d|\d)-(?:[12]\d|3[01]|0\d|\d)$/) 64 | begin 65 | class_loader.date.strptime(string, '%F', Date::GREGORIAN) 66 | rescue ArgumentError 67 | string 68 | end 69 | elsif string.match?(/^\+?\.inf$/i) 70 | Float::INFINITY 71 | elsif string.match?(/^-\.inf$/i) 72 | -Float::INFINITY 73 | elsif string.match?(/^\.nan$/i) 74 | Float::NAN 75 | elsif string.match?(/^:./) 76 | if string =~ /^:(["'])(.*)\1/ 77 | @symbol_cache[string] = class_loader.symbolize($2.sub(/^:/, '')) 78 | else 79 | @symbol_cache[string] = class_loader.symbolize(string.sub(/^:/, '')) 80 | end 81 | elsif string.match?(/^[-+]?[0-9][0-9_]*(:[0-5]?[0-9]){1,2}$/) 82 | i = 0 83 | string.split(':').each_with_index do |n,e| 84 | i += (n.to_i * 60 ** (e - 2).abs) 85 | end 86 | i 87 | elsif string.match?(/^[-+]?[0-9][0-9_]*(:[0-5]?[0-9]){1,2}\.[0-9_]*$/) 88 | i = 0 89 | string.split(':').each_with_index do |n,e| 90 | i += (n.to_f * 60 ** (e - 2).abs) 91 | end 92 | i 93 | elsif string.match?(FLOAT) 94 | if string.match?(/\A[-+]?\.\Z/) 95 | string 96 | else 97 | Float(string.delete(',_').gsub(/\.([Ee]|$)/, '\1')) 98 | end 99 | elsif string.match?(integer_regex) 100 | parse_int string 101 | else 102 | string 103 | end 104 | end 105 | 106 | ### 107 | # Parse and return an int from +string+ 108 | def parse_int string 109 | Integer(string.delete(',_')) 110 | end 111 | 112 | ### 113 | # Parse and return a Time from +string+ 114 | def parse_time string 115 | klass = class_loader.load 'Time' 116 | 117 | date, time = *(string.split(/[ tT]/, 2)) 118 | (yy, m, dd) = date.match(/^(-?\d{4})-(\d{1,2})-(\d{1,2})/).captures.map { |x| x.to_i } 119 | md = time.match(/(\d+:\d+:\d+)(?:\.(\d*))?\s*(Z|[-+]\d+(:\d\d)?)?/) 120 | 121 | (hh, mm, ss) = md[1].split(':').map { |x| x.to_i } 122 | us = (md[2] ? Rational("0.#{md[2]}") : 0) * 1000000 123 | 124 | time = klass.utc(yy, m, dd, hh, mm, ss, us) 125 | 126 | return time if 'Z' == md[3] 127 | return klass.at(time.to_i, us) unless md[3] 128 | 129 | tz = md[3].match(/^([+\-]?\d{1,2})\:?(\d{1,2})?$/)[1..-1].compact.map { |digit| Integer(digit, 10) } 130 | offset = tz.first * 3600 131 | 132 | if offset < 0 133 | offset -= ((tz[1] || 0) * 60) 134 | else 135 | offset += ((tz[1] || 0) * 60) 136 | end 137 | 138 | klass.new(yy, m, dd, hh, mm, ss+us/(1_000_000r), offset) 139 | end 140 | end 141 | end 142 | -------------------------------------------------------------------------------- /lib/psych/set.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | class Set < ::Hash 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /lib/psych/stream.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | ### 4 | # Psych::Stream is a streaming YAML emitter. It will not buffer your YAML, 5 | # but send it straight to an IO. 6 | # 7 | # Here is an example use: 8 | # 9 | # stream = Psych::Stream.new($stdout) 10 | # stream.start 11 | # stream.push({:foo => 'bar'}) 12 | # stream.finish 13 | # 14 | # YAML will be immediately emitted to $stdout with no buffering. 15 | # 16 | # Psych::Stream#start will take a block and ensure that Psych::Stream#finish 17 | # is called, so you can do this form: 18 | # 19 | # stream = Psych::Stream.new($stdout) 20 | # stream.start do |em| 21 | # em.push(:foo => 'bar') 22 | # end 23 | # 24 | class Stream < Psych::Visitors::YAMLTree 25 | class Emitter < Psych::Emitter # :nodoc: 26 | def end_document implicit_end = !streaming? 27 | super 28 | end 29 | 30 | def streaming? 31 | true 32 | end 33 | end 34 | 35 | include Psych::Streaming 36 | extend Psych::Streaming::ClassMethods 37 | end 38 | end 39 | -------------------------------------------------------------------------------- /lib/psych/streaming.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | module Streaming 4 | module ClassMethods 5 | ### 6 | # Create a new streaming emitter. Emitter will print to +io+. See 7 | # Psych::Stream for an example. 8 | def new io 9 | emitter = const_get(:Emitter).new(io) 10 | class_loader = ClassLoader.new 11 | ss = ScalarScanner.new class_loader 12 | super(emitter, ss, {}) 13 | end 14 | end 15 | 16 | ### 17 | # Start streaming using +encoding+ 18 | def start encoding = Nodes::Stream::UTF8 19 | super.tap { yield self if block_given? } 20 | ensure 21 | finish if block_given? 22 | end 23 | 24 | private 25 | def register target, obj 26 | end 27 | end 28 | end 29 | -------------------------------------------------------------------------------- /lib/psych/syntax_error.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'exception' 3 | 4 | module Psych 5 | class SyntaxError < Psych::Exception 6 | attr_reader :file, :line, :column, :offset, :problem, :context 7 | 8 | def initialize file, line, col, offset, problem, context 9 | err = [problem, context].compact.join ' ' 10 | filename = file || '' 11 | message = "(%s): %s at line %d column %d" % [filename, err, line, col] 12 | 13 | @file = file 14 | @line = line 15 | @column = col 16 | @offset = offset 17 | @problem = problem 18 | @context = context 19 | super(message) 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/psych/tree_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'handler' 3 | 4 | module Psych 5 | ### 6 | # This class works in conjunction with Psych::Parser to build an in-memory 7 | # parse tree that represents a YAML document. 8 | # 9 | # == Example 10 | # 11 | # parser = Psych::Parser.new Psych::TreeBuilder.new 12 | # parser.parse('--- foo') 13 | # tree = parser.handler.root 14 | # 15 | # See Psych::Handler for documentation on the event methods used in this 16 | # class. 17 | class TreeBuilder < Psych::Handler 18 | # Returns the root node for the built tree 19 | attr_reader :root 20 | 21 | # Create a new TreeBuilder instance 22 | def initialize 23 | @stack = [] 24 | @last = nil 25 | @root = nil 26 | 27 | @start_line = nil 28 | @start_column = nil 29 | @end_line = nil 30 | @end_column = nil 31 | end 32 | 33 | def event_location(start_line, start_column, end_line, end_column) 34 | @start_line = start_line 35 | @start_column = start_column 36 | @end_line = end_line 37 | @end_column = end_column 38 | end 39 | 40 | %w{ 41 | Sequence 42 | Mapping 43 | }.each do |node| 44 | class_eval <<~RUBY, __FILE__, __LINE__ + 1 45 | def start_#{node.downcase}(anchor, tag, implicit, style) 46 | n = Nodes::#{node}.new(anchor, tag, implicit, style) 47 | set_start_location(n) 48 | @last.children << n 49 | push n 50 | end 51 | 52 | def end_#{node.downcase} 53 | n = pop 54 | set_end_location(n) 55 | n 56 | end 57 | RUBY 58 | end 59 | 60 | ### 61 | # Handles start_document events with +version+, +tag_directives+, 62 | # and +implicit+ styling. 63 | # 64 | # See Psych::Handler#start_document 65 | def start_document version, tag_directives, implicit 66 | n = Nodes::Document.new version, tag_directives, implicit 67 | set_start_location(n) 68 | @last.children << n 69 | push n 70 | end 71 | 72 | ### 73 | # Handles end_document events with +version+, +tag_directives+, 74 | # and +implicit+ styling. 75 | # 76 | # See Psych::Handler#start_document 77 | def end_document implicit_end = !streaming? 78 | @last.implicit_end = implicit_end 79 | n = pop 80 | set_end_location(n) 81 | n 82 | end 83 | 84 | def start_stream encoding 85 | @root = Nodes::Stream.new(encoding) 86 | set_start_location(@root) 87 | push @root 88 | end 89 | 90 | def end_stream 91 | n = pop 92 | set_end_location(n) 93 | n 94 | end 95 | 96 | def scalar value, anchor, tag, plain, quoted, style 97 | s = Nodes::Scalar.new(value,anchor,tag,plain,quoted,style) 98 | set_location(s) 99 | @last.children << s 100 | s 101 | end 102 | 103 | def alias anchor 104 | a = Nodes::Alias.new(anchor) 105 | set_location(a) 106 | @last.children << a 107 | a 108 | end 109 | 110 | private 111 | def push value 112 | @stack.push value 113 | @last = value 114 | end 115 | 116 | def pop 117 | x = @stack.pop 118 | @last = @stack.last 119 | x 120 | end 121 | 122 | def set_location(node) 123 | set_start_location(node) 124 | set_end_location(node) 125 | end 126 | 127 | def set_start_location(node) 128 | node.start_line = @start_line 129 | node.start_column = @start_column 130 | end 131 | 132 | def set_end_location(node) 133 | node.end_line = @end_line 134 | node.end_column = @end_column 135 | end 136 | end 137 | end 138 | -------------------------------------------------------------------------------- /lib/psych/versions.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | module Psych 4 | # The version of Psych you are using 5 | VERSION = '5.2.6' 6 | 7 | if RUBY_ENGINE == 'jruby' 8 | DEFAULT_SNAKEYAML_VERSION = '2.9'.freeze 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /lib/psych/visitors.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'visitors/visitor' 3 | require_relative 'visitors/to_ruby' 4 | require_relative 'visitors/emitter' 5 | require_relative 'visitors/yaml_tree' 6 | require_relative 'visitors/json_tree' 7 | require_relative 'visitors/depth_first' 8 | -------------------------------------------------------------------------------- /lib/psych/visitors/depth_first.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | module Visitors 4 | class DepthFirst < Psych::Visitors::Visitor 5 | def initialize block 6 | @block = block 7 | end 8 | 9 | private 10 | 11 | def nary o 12 | o.children.each { |x| visit x } 13 | @block.call o 14 | end 15 | alias :visit_Psych_Nodes_Stream :nary 16 | alias :visit_Psych_Nodes_Document :nary 17 | alias :visit_Psych_Nodes_Sequence :nary 18 | alias :visit_Psych_Nodes_Mapping :nary 19 | 20 | def terminal o 21 | @block.call o 22 | end 23 | alias :visit_Psych_Nodes_Scalar :terminal 24 | alias :visit_Psych_Nodes_Alias :terminal 25 | end 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /lib/psych/visitors/emitter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | module Visitors 4 | class Emitter < Psych::Visitors::Visitor 5 | def initialize io, options = {} 6 | opts = [:indentation, :canonical, :line_width].find_all { |opt| 7 | options.key?(opt) 8 | } 9 | 10 | if opts.empty? 11 | @handler = Psych::Emitter.new io 12 | else 13 | du = Handler::DumperOptions.new 14 | opts.each { |option| du.send :"#{option}=", options[option] } 15 | @handler = Psych::Emitter.new io, du 16 | end 17 | end 18 | 19 | def visit_Psych_Nodes_Stream o 20 | @handler.start_stream o.encoding 21 | o.children.each { |c| accept c } 22 | @handler.end_stream 23 | end 24 | 25 | def visit_Psych_Nodes_Document o 26 | @handler.start_document o.version, o.tag_directives, o.implicit 27 | o.children.each { |c| accept c } 28 | @handler.end_document o.implicit_end 29 | end 30 | 31 | def visit_Psych_Nodes_Scalar o 32 | @handler.scalar o.value, o.anchor, o.tag, o.plain, o.quoted, o.style 33 | end 34 | 35 | def visit_Psych_Nodes_Sequence o 36 | @handler.start_sequence o.anchor, o.tag, o.implicit, o.style 37 | o.children.each { |c| accept c } 38 | @handler.end_sequence 39 | end 40 | 41 | def visit_Psych_Nodes_Mapping o 42 | @handler.start_mapping o.anchor, o.tag, o.implicit, o.style 43 | o.children.each { |c| accept c } 44 | @handler.end_mapping 45 | end 46 | 47 | def visit_Psych_Nodes_Alias o 48 | @handler.alias o.anchor 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/psych/visitors/json_tree.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative '../json/ruby_events' 3 | 4 | module Psych 5 | module Visitors 6 | class JSONTree < YAMLTree 7 | include Psych::JSON::RubyEvents 8 | 9 | def self.create options = {} 10 | emitter = Psych::JSON::TreeBuilder.new 11 | class_loader = ClassLoader.new 12 | ss = ScalarScanner.new class_loader 13 | new(emitter, ss, options) 14 | end 15 | 16 | def accept target 17 | if target.respond_to?(:encode_with) 18 | dump_coder target 19 | else 20 | send(@dispatch_cache[target.class], target) 21 | end 22 | end 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/psych/visitors/visitor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Psych 3 | module Visitors 4 | class Visitor 5 | def accept target 6 | visit target 7 | end 8 | 9 | private 10 | 11 | # @api private 12 | def self.dispatch_cache 13 | Hash.new do |hash, klass| 14 | hash[klass] = :"visit_#{klass.name.gsub('::', '_')}" 15 | end.compare_by_identity 16 | end 17 | 18 | if defined?(Ractor) 19 | def dispatch 20 | @dispatch_cache ||= (Ractor.current[:Psych_Visitors_Visitor] ||= Visitor.dispatch_cache) 21 | end 22 | else 23 | DISPATCH = dispatch_cache 24 | def dispatch 25 | DISPATCH 26 | end 27 | end 28 | 29 | def visit target 30 | send dispatch[target.class], target 31 | end 32 | end 33 | end 34 | end 35 | -------------------------------------------------------------------------------- /lib/psych/y.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | module Kernel 3 | ### 4 | # An alias for Psych.dump_stream meant to be used with IRB. 5 | def y *objects 6 | puts Psych.dump_stream(*objects) 7 | end 8 | private :y 9 | end 10 | 11 | -------------------------------------------------------------------------------- /lib/psych_jars.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'psych.jar' 3 | 4 | require 'jar-dependencies' 5 | require_jar('org.snakeyaml', 'snakeyaml-engine', Psych::DEFAULT_SNAKEYAML_VERSION) 6 | -------------------------------------------------------------------------------- /psych.gemspec: -------------------------------------------------------------------------------- 1 | # -*- encoding: utf-8 -*- 2 | # frozen_string_literal: true 3 | 4 | version_module = Module.new do 5 | version_rb = File.join(__dir__, "lib/psych/versions.rb") 6 | module_eval(File.read(version_rb), version_rb) 7 | end 8 | 9 | Gem::Specification.new do |s| 10 | s.name = "psych" 11 | s.version = version_module::Psych::VERSION 12 | s.authors = ["Aaron Patterson", "SHIBATA Hiroshi", "Charles Oliver Nutter"] 13 | s.email = ["aaron@tenderlovemaking.com", "hsbt@ruby-lang.org", "headius@headius.com"] 14 | s.summary = "Psych is a YAML parser and emitter" 15 | s.description = <<-DESCRIPTION 16 | Psych is a YAML parser and emitter. Psych leverages libyaml[https://pyyaml.org/wiki/LibYAML] 17 | for its YAML parsing and emitting capabilities. In addition to wrapping libyaml, 18 | Psych also knows how to serialize and de-serialize most Ruby objects to and from the YAML format. 19 | DESCRIPTION 20 | s.homepage = "https://github.com/ruby/psych" 21 | s.licenses = ["MIT"] 22 | s.require_paths = ["lib"] 23 | 24 | # for ruby core repository. 25 | # It was generated by 26 | # `git ls-files -z`.split("\x0").reject { |f| 27 | # f.match(%r{^\.git|^(test|spec|features|bin|tool)/|^[A-Z]\w+file$|/extlibs$|\.(gemspec|java)$|jar}) 28 | # } 29 | s.files = [ 30 | "CONTRIBUTING.md", "LICENSE", "README.md", "ext/psych/depend", 31 | "ext/psych/extconf.rb", "ext/psych/psych.c", "ext/psych/psych.h", 32 | "ext/psych/psych_emitter.c", "ext/psych/psych_emitter.h", 33 | "ext/psych/psych_parser.c", "ext/psych/psych_parser.h", 34 | "ext/psych/psych_to_ruby.c", "ext/psych/psych_to_ruby.h", 35 | "ext/psych/psych_yaml_tree.c", "ext/psych/psych_yaml_tree.h", 36 | "lib/psych.rb", "lib/psych/class_loader.rb", "lib/psych/coder.rb", 37 | "lib/psych/core_ext.rb", "lib/psych/exception.rb", "lib/psych/handler.rb", 38 | "lib/psych/handlers/document_stream.rb", "lib/psych/handlers/recorder.rb", 39 | "lib/psych/json/ruby_events.rb", "lib/psych/json/stream.rb", 40 | "lib/psych/json/tree_builder.rb", "lib/psych/json/yaml_events.rb", 41 | "lib/psych/nodes.rb", "lib/psych/nodes/alias.rb", 42 | "lib/psych/nodes/document.rb", "lib/psych/nodes/mapping.rb", 43 | "lib/psych/nodes/node.rb", "lib/psych/nodes/scalar.rb", 44 | "lib/psych/nodes/sequence.rb", "lib/psych/nodes/stream.rb", 45 | "lib/psych/omap.rb", "lib/psych/parser.rb", "lib/psych/scalar_scanner.rb", 46 | "lib/psych/set.rb", "lib/psych/stream.rb", "lib/psych/streaming.rb", 47 | "lib/psych/syntax_error.rb", "lib/psych/tree_builder.rb", 48 | "lib/psych/versions.rb", "lib/psych/visitors.rb", 49 | "lib/psych/visitors/depth_first.rb", "lib/psych/visitors/emitter.rb", 50 | "lib/psych/visitors/json_tree.rb", "lib/psych/visitors/to_ruby.rb", 51 | "lib/psych/visitors/visitor.rb", "lib/psych/visitors/yaml_tree.rb", 52 | "lib/psych/y.rb" 53 | ] 54 | 55 | s.rdoc_options = ["--main", "README.md"] 56 | s.extra_rdoc_files = ["README.md"] 57 | 58 | s.required_ruby_version = Gem::Requirement.new(">= 2.5.0") 59 | s.required_rubygems_version = Gem::Requirement.new(">= 0") 60 | 61 | if RUBY_ENGINE == 'jruby' 62 | s.platform = 'java' 63 | s.files.concat [ 64 | "ext/java/org/jruby/ext/psych/PsychEmitter.java", 65 | "ext/java/org/jruby/ext/psych/PsychLibrary.java", 66 | "ext/java/org/jruby/ext/psych/PsychParser.java", 67 | "ext/java/org/jruby/ext/psych/PsychToRuby.java", 68 | "lib/psych_jars.rb", 69 | "lib/psych.jar" 70 | ] 71 | s.requirements = "jar org.snakeyaml:snakeyaml-engine, #{version_module::Psych::DEFAULT_SNAKEYAML_VERSION}" 72 | s.add_dependency 'jar-dependencies', '>= 0.1.7' 73 | else 74 | s.extensions = ["ext/psych/extconf.rb"] 75 | s.add_dependency 'stringio' 76 | end 77 | 78 | s.add_dependency 'date' 79 | 80 | s.metadata['msys2_mingw_dependencies'] = 'libyaml' 81 | s.metadata['changelog_uri'] = s.homepage + '/releases' 82 | end 83 | -------------------------------------------------------------------------------- /test/lib/helper.rb: -------------------------------------------------------------------------------- 1 | require "test/unit" 2 | require "core_assertions" 3 | 4 | Test::Unit::TestCase.include Test::Unit::CoreAssertions 5 | -------------------------------------------------------------------------------- /test/psych/handlers/test_recorder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'psych/helper' 3 | require 'psych/handlers/recorder' 4 | 5 | module Psych 6 | module Handlers 7 | class TestRecorder < TestCase 8 | def test_replay 9 | yaml = "--- foo\n...\n" 10 | output = StringIO.new 11 | 12 | recorder = Psych::Handlers::Recorder.new 13 | parser = Psych::Parser.new recorder 14 | parser.parse yaml 15 | 16 | assert_equal 5, recorder.events.length 17 | 18 | emitter = Psych::Emitter.new output 19 | recorder.events.each do |m, args| 20 | emitter.send m, *args 21 | end 22 | assert_equal yaml, output.string 23 | end 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/psych/helper.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'test/unit' 3 | require 'stringio' 4 | require 'tempfile' 5 | 6 | require 'psych' 7 | 8 | module Psych 9 | class TestCase < Test::Unit::TestCase 10 | def self.suppress_warning 11 | verbose, $VERBOSE = $VERBOSE, nil 12 | yield 13 | ensure 14 | $VERBOSE = verbose 15 | end 16 | 17 | def with_default_external(enc) 18 | verbose, $VERBOSE = $VERBOSE, nil 19 | origenc, Encoding.default_external = Encoding.default_external, enc 20 | $VERBOSE = verbose 21 | yield 22 | ensure 23 | verbose, $VERBOSE = $VERBOSE, nil 24 | Encoding.default_external = origenc 25 | $VERBOSE = verbose 26 | end 27 | 28 | def with_default_internal(enc) 29 | verbose, $VERBOSE = $VERBOSE, nil 30 | origenc, Encoding.default_internal = Encoding.default_internal, enc 31 | $VERBOSE = verbose 32 | yield 33 | ensure 34 | verbose, $VERBOSE = $VERBOSE, nil 35 | Encoding.default_internal = origenc 36 | $VERBOSE = verbose 37 | end 38 | 39 | # 40 | # Convert between Psych and the object to verify correct parsing and 41 | # emitting 42 | # 43 | def assert_to_yaml( obj, yaml, loader = :load ) 44 | assert_equal( obj, Psych.send(loader, yaml) ) 45 | assert_equal( obj, Psych::parse( yaml ).transform ) 46 | assert_equal( obj, Psych.send(loader, obj.to_yaml) ) 47 | assert_equal( obj, Psych::parse( obj.to_yaml ).transform ) 48 | assert_equal( obj, Psych.send(loader, 49 | obj.to_yaml( 50 | :UseVersion => true, :UseHeader => true, :SortKeys => true 51 | ) 52 | )) 53 | rescue Psych::DisallowedClass, Psych::BadAlias, Psych::AliasesNotEnabled 54 | assert_to_yaml obj, yaml, :unsafe_load 55 | end 56 | 57 | # 58 | # Test parser only 59 | # 60 | def assert_parse_only( obj, yaml ) 61 | begin 62 | assert_equal obj, Psych::load( yaml ) 63 | rescue Psych::DisallowedClass, Psych::BadAlias, Psych::AliasesNotEnabled 64 | assert_equal obj, Psych::unsafe_load( yaml ) 65 | end 66 | assert_equal obj, Psych::parse( yaml ).transform 67 | end 68 | 69 | def assert_cycle( obj ) 70 | v = Visitors::YAMLTree.create 71 | v << obj 72 | if obj.nil? 73 | assert_nil Psych.load(v.tree.yaml) 74 | assert_nil Psych::load(Psych.dump(obj)) 75 | assert_nil Psych::load(obj.to_yaml) 76 | else 77 | begin 78 | assert_equal(obj, Psych.load(v.tree.yaml)) 79 | assert_equal(obj, Psych::load(Psych.dump(obj))) 80 | assert_equal(obj, Psych::load(obj.to_yaml)) 81 | rescue Psych::DisallowedClass, Psych::BadAlias, Psych::AliasesNotEnabled 82 | assert_equal(obj, Psych.unsafe_load(v.tree.yaml)) 83 | assert_equal(obj, Psych::unsafe_load(Psych.dump(obj))) 84 | assert_equal(obj, Psych::unsafe_load(obj.to_yaml)) 85 | end 86 | end 87 | end 88 | 89 | # 90 | # Make a time with the time zone 91 | # 92 | def mktime( year, mon, day, hour, min, sec, usec, zone = "Z" ) 93 | usec = Rational(usec.to_s) * 1000000 94 | val = Time::utc( year.to_i, mon.to_i, day.to_i, hour.to_i, min.to_i, sec.to_i, usec ) 95 | if zone != "Z" 96 | hour = zone[0,3].to_i * 3600 97 | min = zone[3,2].to_i * 60 98 | ofs = (hour + min) 99 | val = Time.at( val.tv_sec - ofs, val.tv_nsec / 1000.0 ) 100 | end 101 | return val 102 | end 103 | end 104 | end 105 | 106 | # backport so that tests will run on 2.0.0 107 | unless Tempfile.respond_to? :create 108 | def Tempfile.create(basename, *rest) 109 | tmpfile = nil 110 | Dir::Tmpname.create(basename, *rest) do |tmpname, n, opts| 111 | mode = File::RDWR|File::CREAT|File::EXCL 112 | perm = 0600 113 | if opts 114 | mode |= opts.delete(:mode) || 0 115 | opts[:perm] = perm 116 | perm = nil 117 | else 118 | opts = perm 119 | end 120 | tmpfile = File.open(tmpname, mode, opts) 121 | end 122 | if block_given? 123 | begin 124 | yield tmpfile 125 | ensure 126 | tmpfile.close if !tmpfile.closed? 127 | File.unlink tmpfile 128 | end 129 | else 130 | tmpfile 131 | end 132 | end 133 | end 134 | -------------------------------------------------------------------------------- /test/psych/json/test_stream.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'psych/helper' 3 | 4 | module Psych 5 | module JSON 6 | class TestStream < TestCase 7 | def setup 8 | @io = StringIO.new 9 | @stream = Psych::JSON::Stream.new(@io) 10 | @stream.start 11 | end 12 | 13 | def test_explicit_documents 14 | @io = StringIO.new 15 | @stream = Psych::JSON::Stream.new(@io) 16 | @stream.start 17 | 18 | @stream.push({ 'foo' => 'bar' }) 19 | 20 | assert !@stream.finished?, 'stream not finished' 21 | @stream.finish 22 | assert @stream.finished?, 'stream finished' 23 | 24 | assert_match(/^---/, @io.string) 25 | assert_match(/\.\.\.$/, @io.string) 26 | end 27 | 28 | def test_null 29 | @stream.push(nil) 30 | assert_match(/^--- null/, @io.string) 31 | end 32 | 33 | def test_string 34 | @stream.push "foo" 35 | assert_match(/(["])foo\1/, @io.string) 36 | end 37 | 38 | def test_symbol 39 | @stream.push :foo 40 | assert_match(/(["])foo\1/, @io.string) 41 | end 42 | 43 | def test_int 44 | @stream.push 10 45 | assert_match(/^--- 10/, @io.string) 46 | end 47 | 48 | def test_float 49 | @stream.push 1.2 50 | assert_match(/^--- 1.2/, @io.string) 51 | end 52 | 53 | def test_hash 54 | hash = { 'one' => 'two' } 55 | @stream.push hash 56 | 57 | json = @io.string 58 | assert_match(/}$/, json) 59 | assert_match(/^--- \{/, json) 60 | assert_match(/["]one['"]/, json) 61 | assert_match(/["]two['"]/, json) 62 | end 63 | 64 | def test_list_to_json 65 | list = %w{ one two } 66 | @stream.push list 67 | 68 | json = @io.string 69 | assert_match(/\]$/, json) 70 | assert_match(/^--- \[/, json) 71 | assert_match(/["]one["]/, json) 72 | assert_match(/["]two["]/, json) 73 | end 74 | 75 | class Foo; end 76 | 77 | def test_json_dump_exclude_tag 78 | @stream << Foo.new 79 | json = @io.string 80 | refute_match('Foo', json) 81 | end 82 | 83 | class Bar 84 | def encode_with coder 85 | coder.represent_seq 'omg', %w{ a b c } 86 | end 87 | end 88 | 89 | def test_json_list_dump_exclude_tag 90 | @stream << Bar.new 91 | json = @io.string 92 | refute_match('omg', json) 93 | end 94 | 95 | def test_time 96 | time = Time.utc(2010, 10, 10) 97 | @stream.push({'a' => time }) 98 | json = @io.string 99 | assert_match "{\"a\": \"2010-10-10 00:00:00.000000000 Z\"}\n", json 100 | end 101 | 102 | def test_datetime 103 | time = Time.new(2010, 10, 10).to_datetime 104 | @stream.push({'a' => time }) 105 | json = @io.string 106 | assert_match "{\"a\": \"#{time.strftime("%Y-%m-%d %H:%M:%S.%9N %:z")}\"}\n", json 107 | end 108 | end 109 | end 110 | end 111 | -------------------------------------------------------------------------------- /test/psych/nodes/test_enumerable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'psych/helper' 3 | 4 | module Psych 5 | module Nodes 6 | class TestEnumerable < TestCase 7 | def test_includes_enumerable 8 | yaml = '--- hello' 9 | assert_equal 3, Psych.parse_stream(yaml).to_a.length 10 | end 11 | 12 | def test_returns_enumerator 13 | yaml = '--- hello' 14 | assert_equal 3, Psych.parse_stream(yaml).each.map { |x| x }.length 15 | end 16 | 17 | def test_scalar 18 | assert_equal 3, calls('--- hello').length 19 | end 20 | 21 | def test_sequence 22 | assert_equal 4, calls("---\n- hello").length 23 | end 24 | 25 | def test_mapping 26 | assert_equal 5, calls("---\nhello: world").length 27 | end 28 | 29 | def test_alias 30 | assert_equal 5, calls("--- &yay\n- foo\n- *yay\n").length 31 | end 32 | 33 | private 34 | 35 | def calls yaml 36 | calls = [] 37 | Psych.parse_stream(yaml).each do |node| 38 | calls << node 39 | end 40 | calls 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /test/psych/test_alias_and_anchor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | class ObjectWithInstanceVariables 5 | attr_accessor :var1, :var2 6 | end 7 | 8 | class SubStringWithInstanceVariables < String 9 | attr_accessor :var1 10 | end 11 | 12 | module Psych 13 | class TestAliasAndAnchor < TestCase 14 | def test_mri_compatibility 15 | yaml = < 'b' }, 'foo'] 16 | end 17 | 18 | def test_enumerator 19 | x = [1, 2, 3, 4] 20 | y = Psych.load Psych.dump x.to_enum 21 | assert_equal x, y 22 | end 23 | 24 | def test_another_subclass_with_attributes 25 | y = Y.new.tap {|o| o.val = 1} 26 | y << "foo" << "bar" 27 | y = Psych.unsafe_load Psych.dump y 28 | 29 | assert_equal %w{foo bar}, y 30 | assert_equal Y, y.class 31 | assert_equal 1, y.val 32 | end 33 | 34 | def test_subclass 35 | yaml = Psych.dump X.new 36 | assert_match X.name, yaml 37 | 38 | list = X.new 39 | list << 1 40 | assert_equal X, list.class 41 | assert_equal 1, list.first 42 | end 43 | 44 | def test_subclass_with_attributes 45 | y = Psych.unsafe_load Psych.dump Y.new.tap {|o| o.val = 1} 46 | assert_equal Y, y.class 47 | assert_equal 1, y.val 48 | end 49 | 50 | def test_backwards_with_syck 51 | x = Psych.unsafe_load "--- !seq:#{X.name} []\n\n" 52 | assert_equal X, x.class 53 | end 54 | 55 | def test_self_referential 56 | @list << @list 57 | assert_cycle(@list) 58 | end 59 | 60 | def test_recursive_array 61 | @list << @list 62 | 63 | loaded = Psych.load(Psych.dump(@list), aliases: true) 64 | 65 | assert_same loaded, loaded.last 66 | end 67 | 68 | def test_recursive_array_uses_alias 69 | @list << @list 70 | 71 | assert_raise(AliasesNotEnabled) do 72 | Psych.load(Psych.dump(@list), aliases: false) 73 | end 74 | end 75 | 76 | def test_cycle 77 | assert_cycle(@list) 78 | end 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /test/psych/test_boolean.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | ### 6 | # Test booleans from YAML spec: 7 | # http://yaml.org/type/bool.html 8 | class TestBoolean < TestCase 9 | %w{ yes Yes YES true True TRUE on On ON }.each do |truth| 10 | define_method(:"test_#{truth}") do 11 | assert_equal true, Psych.load("--- #{truth}") 12 | end 13 | end 14 | 15 | %w{ no No NO false False FALSE off Off OFF }.each do |truth| 16 | define_method(:"test_#{truth}") do 17 | assert_equal false, Psych.load("--- #{truth}") 18 | end 19 | end 20 | 21 | ### 22 | # YAML spec says "y" and "Y" may be used as true, but Syck treats them 23 | # as literal strings 24 | def test_y 25 | assert_equal "y", Psych.load("--- y") 26 | assert_equal "Y", Psych.load("--- Y") 27 | end 28 | 29 | ### 30 | # YAML spec says "n" and "N" may be used as false, but Syck treats them 31 | # as literal strings 32 | def test_n 33 | assert_equal "n", Psych.load("--- n") 34 | assert_equal "N", Psych.load("--- N") 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/psych/test_class.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestClass < TestCase 6 | module Foo 7 | end 8 | 9 | def test_cycle_anonymous_class 10 | assert_raise(::TypeError) do 11 | assert_cycle(Class.new) 12 | end 13 | end 14 | 15 | def test_cycle_anonymous_module 16 | assert_raise(::TypeError) do 17 | assert_cycle(Module.new) 18 | end 19 | end 20 | 21 | def test_cycle 22 | assert_cycle(TestClass) 23 | end 24 | 25 | def test_dump 26 | Psych.dump TestClass 27 | end 28 | 29 | def test_cycle_module 30 | assert_cycle(Foo) 31 | end 32 | 33 | def test_dump_module 34 | Psych.dump Foo 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /test/psych/test_data.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | class PsychDataWithIvar < Data.define(:foo) 5 | attr_reader :bar 6 | def initialize(**) 7 | @bar = 'hello' 8 | super 9 | end 10 | end unless RUBY_VERSION < "3.2" 11 | 12 | module Psych 13 | class TestData < TestCase 14 | class SelfReferentialData < Data.define(:foo) 15 | attr_accessor :ref 16 | def initialize(foo:) 17 | @ref = self 18 | super 19 | end 20 | end unless RUBY_VERSION < "3.2" 21 | 22 | def setup 23 | omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2" 24 | end 25 | 26 | # TODO: move to another test? 27 | def test_dump_data 28 | assert_equal <<~eoyml, Psych.dump(PsychDataWithIvar["bar"]) 29 | --- !ruby/data-with-ivars:PsychDataWithIvar 30 | members: 31 | foo: bar 32 | ivars: 33 | "@bar": hello 34 | eoyml 35 | end 36 | 37 | def test_self_referential_data 38 | circular = SelfReferentialData.new("foo") 39 | 40 | loaded = Psych.unsafe_load(Psych.dump(circular)) 41 | assert_instance_of(SelfReferentialData, loaded.ref) 42 | 43 | assert_equal(circular, loaded) 44 | assert_same(loaded, loaded.ref) 45 | end 46 | 47 | def test_roundtrip 48 | thing = PsychDataWithIvar.new("bar") 49 | data = Psych.unsafe_load(Psych.dump(thing)) 50 | 51 | assert_equal "hello", data.bar 52 | assert_equal "bar", data.foo 53 | end 54 | 55 | def test_load 56 | obj = Psych.unsafe_load(<<~eoyml) 57 | --- !ruby/data-with-ivars:PsychDataWithIvar 58 | members: 59 | foo: bar 60 | ivars: 61 | "@bar": hello 62 | eoyml 63 | 64 | assert_equal "hello", obj.bar 65 | assert_equal "bar", obj.foo 66 | end 67 | end 68 | end 69 | 70 | -------------------------------------------------------------------------------- /test/psych/test_date_time.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestDateTime < TestCase 6 | def test_negative_year 7 | time = Time.utc(-1, 12, 16) 8 | assert_cycle time 9 | end 10 | 11 | def test_usec 12 | time = Time.utc(2017, 4, 13, 12, 0, 0, 5) 13 | assert_cycle time 14 | end 15 | 16 | def test_non_utc 17 | time = Time.new(2017, 4, 13, 12, 0, 0.5, "+09:00") 18 | assert_cycle time 19 | end 20 | 21 | def test_timezone_offset 22 | times = [Time.new(2017, 4, 13, 12, 0, 0, "+09:00"), 23 | Time.new(2017, 4, 13, 12, 0, 0, "-05:00")] 24 | cycled = Psych::unsafe_load(Psych.dump times) 25 | assert_match(/12:00:00 \+0900/, cycled.first.to_s) 26 | assert_match(/12:00:00 -0500/, cycled.last.to_s) 27 | end 28 | 29 | def test_new_datetime 30 | assert_cycle DateTime.new 31 | end 32 | 33 | def test_datetime_non_utc 34 | dt = DateTime.new(2017, 4, 13, 12, 0, 0.5, "+09:00") 35 | assert_cycle dt 36 | end 37 | 38 | def test_datetime_timezone_offset 39 | times = [DateTime.new(2017, 4, 13, 12, 0, 0, "+09:00"), 40 | DateTime.new(2017, 4, 13, 12, 0, 0, "-05:00")] 41 | cycled = Psych::unsafe_load(Psych.dump times) 42 | assert_match(/12:00:00\+09:00/, cycled.first.to_s) 43 | assert_match(/12:00:00-05:00/, cycled.last.to_s) 44 | end 45 | 46 | def test_julian_date 47 | d = Date.new(1582, 10, 4, Date::GREGORIAN) 48 | assert_cycle d 49 | end 50 | 51 | def test_proleptic_gregorian_date 52 | d = Date.new(1582, 10, 14, Date::GREGORIAN) 53 | assert_cycle d 54 | end 55 | 56 | def test_julian_datetime 57 | dt = DateTime.new(1582, 10, 4, 23, 58, 59, 0, Date::GREGORIAN) 58 | assert_cycle dt 59 | end 60 | 61 | def test_proleptic_gregorian_datetime 62 | dt = DateTime.new(1582, 10, 14, 23, 58, 59, 0, Date::GREGORIAN) 63 | assert_cycle dt 64 | end 65 | 66 | def test_invalid_date 67 | assert_cycle "2013-10-31T10:40:07-000000000000033" 68 | end 69 | 70 | def test_string_tag 71 | dt = DateTime.now 72 | yaml = Psych.dump dt 73 | assert_match(/DateTime/, yaml) 74 | end 75 | 76 | def test_round_trip 77 | dt = DateTime.now 78 | assert_cycle dt 79 | end 80 | 81 | def test_alias_with_time 82 | t = Time.now 83 | h = {:a => t, :b => t} 84 | yaml = Psych.dump h 85 | assert_match('&', yaml) 86 | assert_match('*', yaml) 87 | end 88 | 89 | def test_overwritten_to_s 90 | pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/ 91 | s = Psych.dump(Date.new(2023, 9, 2), permitted_classes: [Date]) 92 | assert_separately(%W[-rpsych -rdate - #{s}], "#{<<~"begin;"}\n#{<<~'end;'}") 93 | class Date 94 | undef to_s 95 | def to_s; strftime("%D"); end 96 | end 97 | expected = ARGV.shift 98 | begin; 99 | s = Psych.dump(Date.new(2023, 9, 2), permitted_classes: [Date]) 100 | assert_equal(expected, s) 101 | end; 102 | end 103 | end 104 | end 105 | -------------------------------------------------------------------------------- /test/psych/test_deprecated.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestDeprecated < TestCase 6 | def teardown 7 | $VERBOSE = @orig_verbose 8 | Psych.domain_types.clear 9 | end 10 | 11 | class QuickEmitter; end 12 | 13 | def setup 14 | @orig_verbose, $VERBOSE = $VERBOSE, false 15 | end 16 | 17 | class QuickEmitterEncodeWith 18 | attr_reader :name 19 | attr_reader :value 20 | 21 | def initialize 22 | @name = 'hello!!' 23 | @value = 'Friday!' 24 | end 25 | 26 | def encode_with coder 27 | coder.map do |map| 28 | map.add 'name', @name 29 | map.add 'value', nil 30 | end 31 | end 32 | 33 | def to_yaml opts = {} 34 | raise 35 | end 36 | end 37 | 38 | ### 39 | # An object that defines both to_yaml and encode_with should only call 40 | # encode_with. 41 | def test_recursive_quick_emit_encode_with 42 | qeew = QuickEmitterEncodeWith.new 43 | hash = { :qe => qeew } 44 | hash2 = Psych.unsafe_load Psych.dump hash 45 | qe = hash2[:qe] 46 | 47 | assert_equal qeew.name, qe.name 48 | assert_instance_of QuickEmitterEncodeWith, qe 49 | assert_nil qe.value 50 | end 51 | 52 | class YamlInitAndInitWith 53 | attr_reader :name 54 | attr_reader :value 55 | 56 | def initialize 57 | @name = 'shaners' 58 | @value = 'Friday!' 59 | end 60 | 61 | def init_with coder 62 | coder.map.each { |ivar, val| instance_variable_set "@#{ivar}", 'TGIF!' } 63 | end 64 | 65 | def yaml_initialize tag, vals 66 | raise 67 | end 68 | end 69 | 70 | ### 71 | # An object that implements both yaml_initialize and init_with should not 72 | # receive the yaml_initialize call. 73 | def test_yaml_initialize_and_init_with 74 | hash = { :yi => YamlInitAndInitWith.new } 75 | hash2 = Psych.unsafe_load Psych.dump hash 76 | yi = hash2[:yi] 77 | 78 | assert_equal 'TGIF!', yi.name 79 | assert_equal 'TGIF!', yi.value 80 | assert_instance_of YamlInitAndInitWith, yi 81 | end 82 | 83 | def test_coder_scalar 84 | coder = Psych::Coder.new 'foo' 85 | coder.scalar('tag', 'some string', :plain) 86 | assert_equal 'tag', coder.tag 87 | assert_equal 'some string', coder.scalar 88 | assert_equal :scalar, coder.type 89 | end 90 | end 91 | end 92 | -------------------------------------------------------------------------------- /test/psych/test_document.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestDocument < TestCase 6 | def setup 7 | super 8 | @stream = Psych.parse_stream(<<-eoyml) 9 | %YAML 1.1 10 | %TAG ! tag:tenderlovemaking.com,2009: 11 | --- !fun 12 | eoyml 13 | @doc = @stream.children.first 14 | end 15 | 16 | def test_parse_tag 17 | assert_equal([['!', 'tag:tenderlovemaking.com,2009:']], 18 | @doc.tag_directives) 19 | end 20 | 21 | def test_emit_tag 22 | assert_match('%TAG ! tag:tenderlovemaking.com,2009:', @stream.yaml) 23 | end 24 | 25 | def test_emit_multitag 26 | @doc.tag_directives << ['!!', 'foo.com,2009:'] 27 | yaml = @stream.yaml 28 | assert_match('%TAG ! tag:tenderlovemaking.com,2009:', yaml) 29 | assert_match('%TAG !! foo.com,2009:', yaml) 30 | end 31 | 32 | def test_emit_bad_tag 33 | assert_raise(RuntimeError) do 34 | @doc.tag_directives = [['!']] 35 | @stream.yaml 36 | end 37 | end 38 | 39 | def test_parse_version 40 | assert_equal([1,1], @doc.version) 41 | end 42 | 43 | def test_emit_version 44 | assert_match('%YAML 1.1', @stream.yaml) 45 | end 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /test/psych/test_emitter.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # frozen_string_literal: true 3 | 4 | require_relative 'helper' 5 | 6 | module Psych 7 | class TestEmitter < TestCase 8 | def setup 9 | super 10 | @out = StringIO.new(''.dup) 11 | @emitter = Psych::Emitter.new @out 12 | end 13 | 14 | def test_line_width 15 | @emitter.line_width = 10 16 | assert_equal 10, @emitter.line_width 17 | end 18 | 19 | def test_set_canonical 20 | @emitter.canonical = true 21 | assert_equal true, @emitter.canonical 22 | 23 | @emitter.canonical = false 24 | assert_equal false, @emitter.canonical 25 | end 26 | 27 | def test_indentation_set 28 | assert_equal 2, @emitter.indentation 29 | @emitter.indentation = 5 30 | assert_equal 5, @emitter.indentation 31 | end 32 | 33 | def test_emit_utf_8 34 | @emitter.start_stream Psych::Nodes::Stream::UTF8 35 | @emitter.start_document [], [], false 36 | @emitter.scalar '日本語', nil, nil, false, true, 1 37 | @emitter.end_document true 38 | @emitter.end_stream 39 | assert_match('日本語', @out.string) 40 | end 41 | 42 | def test_start_stream_arg_error 43 | assert_raise(TypeError) do 44 | @emitter.start_stream 'asdfasdf' 45 | end 46 | end 47 | 48 | def test_start_doc_arg_error 49 | @emitter.start_stream Psych::Nodes::Stream::UTF8 50 | 51 | [ 52 | [nil, [], false], 53 | [[nil, nil], [], false], 54 | [[], 'foo', false], 55 | [[], ['foo'], false], 56 | [[], [nil,nil], false], 57 | [[1,1], [[nil, "tag:TALOS"]], 0], 58 | ].each do |args| 59 | assert_raise(TypeError) do 60 | @emitter.start_document(*args) 61 | end 62 | end 63 | end 64 | 65 | def test_scalar_arg_error 66 | @emitter.start_stream Psych::Nodes::Stream::UTF8 67 | @emitter.start_document [], [], false 68 | 69 | [ 70 | [:foo, nil, nil, false, true, 1], 71 | ['foo', Object.new, nil, false, true, 1], 72 | ['foo', nil, Object.new, false, true, 1], 73 | ['foo', nil, nil, false, true, :foo], 74 | [nil, nil, nil, false, true, 1], 75 | ].each do |args| 76 | assert_raise(TypeError) do 77 | @emitter.scalar(*args) 78 | end 79 | end 80 | end 81 | 82 | def test_start_sequence_arg_error 83 | @emitter.start_stream Psych::Nodes::Stream::UTF8 84 | @emitter.start_document [], [], false 85 | 86 | assert_raise(TypeError) do 87 | @emitter.start_sequence(nil, Object.new, true, 1) 88 | end 89 | 90 | assert_raise(TypeError) do 91 | @emitter.start_sequence(nil, nil, true, :foo) 92 | end 93 | end 94 | 95 | def test_resizing_tags 96 | @emitter.start_stream Psych::Nodes::Stream::UTF8 97 | 98 | tags = [] 99 | version = [1,1] 100 | obj = Object.new 101 | obj.instance_variable_set(:@tags, tags) 102 | def obj.to_str 103 | (1..10).map{|x| @tags.push(["AAAA","BBBB"])} 104 | return "x" 105 | end 106 | 107 | tags.push([obj, "tag:TALOS"]) 108 | @emitter.start_document(version, tags, 0) 109 | assert(true) 110 | end 111 | end 112 | end 113 | -------------------------------------------------------------------------------- /test/psych/test_encoding.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # frozen_string_literal: true 3 | 4 | require_relative 'helper' 5 | 6 | module Psych 7 | class TestEncoding < TestCase 8 | class EncodingCatcher < Handler 9 | attr_reader :strings 10 | def initialize 11 | @strings = [] 12 | end 13 | 14 | (Handler.instance_methods(true) - 15 | Object.instance_methods).each do |m| 16 | class_eval <<~RUBY, __FILE__, __LINE__ + 1 17 | def #{m} *args 18 | @strings += args.flatten.find_all { |a| 19 | String === a 20 | } 21 | end 22 | RUBY 23 | end 24 | end 25 | 26 | def setup 27 | super 28 | @buffer = StringIO.new 29 | @handler = EncodingCatcher.new 30 | @parser = Psych::Parser.new @handler 31 | @utf8 = Encoding.find('UTF-8') 32 | @emitter = Psych::Emitter.new @buffer 33 | end 34 | 35 | def test_dump_load_encoding_object 36 | assert_cycle Encoding::US_ASCII 37 | assert_cycle Encoding::UTF_8 38 | end 39 | 40 | def test_transcode_shiftjis 41 | str = "こんにちは!" 42 | loaded = Psych.load("--- こんにちは!".encode('SHIFT_JIS')) 43 | assert_equal str, loaded 44 | end 45 | 46 | def test_transcode_utf16le 47 | str = "こんにちは!" 48 | loaded = Psych.load("--- こんにちは!".encode('UTF-16LE')) 49 | assert_equal str, loaded 50 | end 51 | 52 | def test_transcode_utf16be 53 | str = "こんにちは!" 54 | loaded = Psych.load("--- こんにちは!".encode('UTF-16BE')) 55 | assert_equal str, loaded 56 | end 57 | 58 | def test_io_shiftjis 59 | Tempfile.create(['shiftjis', 'yml'], :encoding => 'SHIFT_JIS') {|t| 60 | t.write '--- こんにちは!' 61 | t.close 62 | 63 | # If the external encoding isn't utf8, utf16le, or utf16be, we cannot 64 | # process the file. 65 | File.open(t.path, 'r', :encoding => 'SHIFT_JIS') do |f| 66 | assert_raise Psych::SyntaxError do 67 | Psych.load(f) 68 | end 69 | end 70 | } 71 | end 72 | 73 | def test_io_utf16le 74 | Tempfile.create(['utf16le', 'yml']) {|t| 75 | t.binmode 76 | t.write '--- こんにちは!'.encode('UTF-16LE') 77 | t.close 78 | 79 | File.open(t.path, 'rb', :encoding => 'UTF-16LE') do |f| 80 | assert_equal "こんにちは!", Psych.load(f) 81 | end 82 | } 83 | end 84 | 85 | def test_io_utf16be 86 | Tempfile.create(['utf16be', 'yml']) {|t| 87 | t.binmode 88 | t.write '--- こんにちは!'.encode('UTF-16BE') 89 | t.close 90 | 91 | File.open(t.path, 'rb', :encoding => 'UTF-16BE') do |f| 92 | assert_equal "こんにちは!", Psych.load(f) 93 | end 94 | } 95 | end 96 | 97 | def test_io_utf8 98 | Tempfile.create(['utf8', 'yml']) {|t| 99 | t.binmode 100 | t.write '--- こんにちは!'.encode('UTF-8') 101 | t.close 102 | 103 | File.open(t.path, 'rb', :encoding => 'UTF-8') do |f| 104 | assert_equal "こんにちは!", Psych.load(f) 105 | end 106 | } 107 | end 108 | 109 | def test_io_utf8_read_as_binary 110 | Tempfile.create(['utf8', 'yml']) {|t| 111 | t.binmode 112 | t.write '--- こんにちは!'.encode('UTF-8') 113 | t.close 114 | 115 | File.open(t.path, 'rb', :encoding => 'ascii-8bit') do |f| 116 | assert_equal "こんにちは!", Psych.load(f) 117 | end 118 | } 119 | end 120 | 121 | def test_emit_alias 122 | pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/ 123 | 124 | @emitter.start_stream Psych::Parser::UTF8 125 | @emitter.start_document [], [], true 126 | e = assert_raise(RuntimeError) do 127 | @emitter.alias 'ドラえもん'.encode('EUC-JP') 128 | end 129 | assert_match(/alias value/, e.message) 130 | end 131 | 132 | def test_to_yaml_is_valid 133 | with_default_external(Encoding::US_ASCII) do 134 | with_default_internal(nil) do 135 | s = "こんにちは!" 136 | # If no encoding is specified, use UTF-8 137 | assert_equal Encoding::UTF_8, Psych.dump(s).encoding 138 | assert_equal s, Psych.load(Psych.dump(s)) 139 | end 140 | end 141 | end 142 | 143 | def test_start_mapping 144 | foo = 'foo' 145 | bar = 'バー' 146 | 147 | @emitter.start_stream Psych::Parser::UTF8 148 | @emitter.start_document [], [], true 149 | @emitter.start_mapping( 150 | foo.encode('Shift_JIS'), 151 | bar.encode('UTF-16LE'), 152 | false, Nodes::Sequence::ANY) 153 | @emitter.end_mapping 154 | @emitter.end_document false 155 | @emitter.end_stream 156 | pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/ 157 | 158 | @parser.parse @buffer.string 159 | assert_encodings @utf8, @handler.strings 160 | assert_equal [foo, bar], @handler.strings 161 | end 162 | 163 | def test_start_sequence 164 | foo = 'foo' 165 | bar = 'バー' 166 | 167 | @emitter.start_stream Psych::Parser::UTF8 168 | @emitter.start_document [], [], true 169 | @emitter.start_sequence( 170 | foo.encode('Shift_JIS'), 171 | bar.encode('UTF-16LE'), 172 | false, Nodes::Sequence::ANY) 173 | @emitter.end_sequence 174 | @emitter.end_document false 175 | @emitter.end_stream 176 | pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/ 177 | 178 | @parser.parse @buffer.string 179 | assert_encodings @utf8, @handler.strings 180 | assert_equal [foo, bar], @handler.strings 181 | end 182 | 183 | def test_doc_tag_encoding 184 | key = '鍵' 185 | @emitter.start_stream Psych::Parser::UTF8 186 | @emitter.start_document( 187 | [1, 1], 188 | [['!'.encode('EUC-JP'), key.encode('EUC-JP')]], 189 | true 190 | ) 191 | @emitter.scalar 'foo', nil, nil, true, false, Nodes::Scalar::ANY 192 | @emitter.end_document false 193 | @emitter.end_stream 194 | pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/ 195 | 196 | @parser.parse @buffer.string 197 | assert_encodings @utf8, @handler.strings 198 | assert_equal key, @handler.strings[1] 199 | end 200 | 201 | def test_emitter_encoding 202 | str = "壁に耳あり、障子に目あり" 203 | thing = Psych.load Psych.dump str.encode('EUC-JP') 204 | assert_equal str, thing 205 | end 206 | 207 | def test_default_internal 208 | with_default_internal(Encoding::EUC_JP) do 209 | str = "壁に耳あり、障子に目あり" 210 | assert_equal @utf8, str.encoding 211 | 212 | @parser.parse str 213 | assert_encodings Encoding::EUC_JP, @handler.strings 214 | assert_equal str, @handler.strings.first.encode('UTF-8') 215 | end 216 | end 217 | 218 | def test_scalar 219 | @parser.parse("--- a") 220 | assert_encodings @utf8, @handler.strings 221 | end 222 | 223 | def test_alias 224 | @parser.parse(<<-eoyml) 225 | %YAML 1.1 226 | --- 227 | !!seq [ 228 | !!str "Without properties", 229 | &A !!str "Anchored", 230 | !!str "Tagged", 231 | *A, 232 | !!str "", 233 | ] 234 | eoyml 235 | assert_encodings @utf8, @handler.strings 236 | end 237 | 238 | def test_list_anchor 239 | list = %w{ a b } 240 | list << list 241 | @parser.parse(Psych.dump(list)) 242 | assert_encodings @utf8, @handler.strings 243 | end 244 | 245 | def test_map_anchor 246 | h = {} 247 | h['a'] = h 248 | @parser.parse(Psych.dump(h)) 249 | assert_encodings @utf8, @handler.strings 250 | end 251 | 252 | def test_map_tag 253 | @parser.parse(<<-eoyml) 254 | %YAML 1.1 255 | --- 256 | !!map { a : b } 257 | eoyml 258 | assert_encodings @utf8, @handler.strings 259 | end 260 | 261 | def test_doc_tag 262 | @parser.parse(<<-eoyml) 263 | %YAML 1.1 264 | %TAG ! tag:tenderlovemaking.com,2009: 265 | --- !fun 266 | eoyml 267 | assert_encodings @utf8, @handler.strings 268 | end 269 | 270 | def test_dump_non_ascii_string_to_file 271 | pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/ 272 | 273 | Tempfile.create(['utf8', 'yml'], :encoding => 'UTF-8') do |t| 274 | h = {'one' => 'いち'} 275 | Psych.dump(h, t) 276 | t.close 277 | assert_equal h, Psych.load_file(t.path) 278 | end 279 | end 280 | 281 | private 282 | def assert_encodings encoding, strings 283 | strings.each do |str| 284 | assert_equal encoding, str.encoding, str 285 | end 286 | end 287 | end 288 | end 289 | -------------------------------------------------------------------------------- /test/psych/test_exception.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestException < TestCase 6 | class Wups < Exception 7 | attr_reader :foo, :bar 8 | def initialize *args 9 | super 10 | @foo = 1 11 | @bar = 2 12 | end 13 | end 14 | 15 | def setup 16 | super 17 | @wups = Wups.new 18 | 19 | @orig_verbose, $VERBOSE = $VERBOSE, nil 20 | end 21 | 22 | def teardown 23 | $VERBOSE = @orig_verbose 24 | end 25 | 26 | def make_ex msg = 'oh no!' 27 | begin 28 | raise msg 29 | rescue ::Exception => e 30 | e 31 | end 32 | end 33 | 34 | def test_backtrace 35 | err = make_ex 36 | new_err = Psych.unsafe_load(Psych.dump(err)) 37 | assert_equal err.backtrace, new_err.backtrace 38 | end 39 | 40 | def test_naming_exception 41 | err = String.xxx rescue $! 42 | new_err = Psych.unsafe_load(Psych.dump(err)) 43 | assert_equal err.message, new_err.message 44 | end 45 | 46 | def test_load_takes_file 47 | ex = assert_raise(Psych::SyntaxError) do 48 | Psych.load '--- `' 49 | end 50 | assert_nil ex.file 51 | 52 | ex = assert_raise(Psych::SyntaxError) do 53 | Psych.load '--- `', filename: 'meow' 54 | end 55 | assert_equal 'meow', ex.file 56 | end 57 | 58 | def test_psych_parse_stream_takes_file 59 | ex = assert_raise(Psych::SyntaxError) do 60 | Psych.parse_stream '--- `' 61 | end 62 | assert_nil ex.file 63 | assert_match '()', ex.message 64 | 65 | ex = assert_raise(Psych::SyntaxError) do 66 | Psych.parse_stream '--- `', filename: 'omg!' 67 | end 68 | assert_equal 'omg!', ex.file 69 | assert_match 'omg!', ex.message 70 | end 71 | 72 | def test_load_stream_takes_file 73 | ex = assert_raise(Psych::SyntaxError) do 74 | Psych.load_stream '--- `' 75 | end 76 | assert_nil ex.file 77 | assert_match '()', ex.message 78 | 79 | ex = assert_raise(Psych::SyntaxError) do 80 | Psych.load_stream '--- `', filename: 'omg!' 81 | end 82 | assert_equal 'omg!', ex.file 83 | end 84 | 85 | def test_safe_load_stream_takes_file 86 | ex = assert_raise(Psych::SyntaxError) do 87 | Psych.safe_load_stream '--- `' 88 | end 89 | assert_nil ex.file 90 | assert_match '()', ex.message 91 | 92 | ex = assert_raise(Psych::SyntaxError) do 93 | Psych.safe_load_stream '--- `', filename: 'omg!' 94 | end 95 | assert_equal 'omg!', ex.file 96 | end 97 | 98 | def test_parse_file_exception 99 | Tempfile.create(['parsefile', 'yml']) {|t| 100 | t.binmode 101 | t.write '--- `' 102 | t.close 103 | ex = assert_raise(Psych::SyntaxError) do 104 | Psych.parse_file t.path 105 | end 106 | assert_equal t.path, ex.file 107 | } 108 | end 109 | 110 | def test_load_file_exception 111 | Tempfile.create(['loadfile', 'yml']) {|t| 112 | t.binmode 113 | t.write '--- `' 114 | t.close 115 | ex = assert_raise(Psych::SyntaxError) do 116 | Psych.load_file t.path 117 | end 118 | assert_equal t.path, ex.file 119 | } 120 | end 121 | 122 | def test_safe_load_file_exception 123 | Tempfile.create(['loadfile', 'yml']) {|t| 124 | t.binmode 125 | t.write '--- `' 126 | t.close 127 | ex = assert_raise(Psych::SyntaxError) do 128 | Psych.safe_load_file t.path 129 | end 130 | assert_equal t.path, ex.file 131 | } 132 | end 133 | 134 | def test_psych_parse_takes_file 135 | ex = assert_raise(Psych::SyntaxError) do 136 | Psych.parse '--- `' 137 | end 138 | assert_match '()', ex.message 139 | assert_nil ex.file 140 | 141 | ex = assert_raise(Psych::SyntaxError) do 142 | Psych.parse '--- `', filename: 'omg!' 143 | end 144 | assert_match 'omg!', ex.message 145 | end 146 | 147 | def test_attributes 148 | e = assert_raise(Psych::SyntaxError) { 149 | Psych.load '--- `foo' 150 | } 151 | 152 | assert_nil e.file 153 | assert_equal 1, e.line 154 | assert_equal 5, e.column 155 | # FIXME: offset isn't being set correctly by libyaml 156 | # assert_equal 5, e.offset 157 | 158 | assert e.problem 159 | assert e.context 160 | end 161 | 162 | def test_convert 163 | w = Psych.unsafe_load(Psych.dump(@wups)) 164 | assert_equal @wups.message, w.message 165 | assert_equal @wups.backtrace, w.backtrace 166 | assert_equal 1, w.foo 167 | assert_equal 2, w.bar 168 | end 169 | 170 | def test_psych_syntax_error 171 | Tempfile.create(['parsefile', 'yml']) do |t| 172 | t.binmode 173 | t.write '--- `' 174 | t.close 175 | 176 | begin 177 | Psych.parse_file t.path 178 | rescue StandardError 179 | assert true # count assertion 180 | ensure 181 | return unless $! 182 | 183 | ancestors = $!.class.ancestors.inspect 184 | 185 | flunk "Psych::SyntaxError not rescued by StandardError: #{ancestors}" 186 | end 187 | end 188 | end 189 | 190 | end 191 | end 192 | -------------------------------------------------------------------------------- /test/psych/test_hash.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestHash < TestCase 6 | class X < Hash 7 | end 8 | 9 | class HashWithIvar < Hash 10 | def initialize 11 | @keys = [] 12 | super 13 | end 14 | 15 | def []=(k, v) 16 | @keys << k 17 | super(k, v) 18 | end 19 | end 20 | 21 | class HashWithCustomInit < Hash 22 | attr_reader :obj 23 | def initialize(obj) 24 | @obj = obj 25 | end 26 | end 27 | 28 | class HashWithCustomInitNoIvar < Hash 29 | def initialize(obj) 30 | # *shrug* 31 | end 32 | end 33 | 34 | def setup 35 | super 36 | @hash = { :a => 'b' } 37 | end 38 | 39 | def test_hash_with_ivar 40 | t1 = HashWithIvar.new 41 | t1[:foo] = :bar 42 | t2 = Psych.unsafe_load(Psych.dump(t1)) 43 | assert_equal t1, t2 44 | assert_cycle t1 45 | end 46 | 47 | def test_referenced_hash_with_ivar 48 | a = [1,2,3,4,5] 49 | t1 = [HashWithCustomInit.new(a)] 50 | t1 << t1.first 51 | assert_cycle t1 52 | end 53 | 54 | def test_custom_initialized 55 | a = [1,2,3,4,5] 56 | t1 = HashWithCustomInit.new(a) 57 | t2 = Psych.unsafe_load(Psych.dump(t1)) 58 | assert_equal t1, t2 59 | assert_cycle t1 60 | end 61 | 62 | def test_custom_initialize_no_ivar 63 | t1 = HashWithCustomInitNoIvar.new(nil) 64 | t2 = Psych.unsafe_load(Psych.dump(t1)) 65 | assert_equal t1, t2 66 | assert_cycle t1 67 | end 68 | 69 | def test_hash_subclass_with_ivars 70 | x = X.new 71 | x[:a] = 'b' 72 | x.instance_variable_set :@foo, 'bar' 73 | dup = Psych.unsafe_load Psych.dump x 74 | assert_cycle x 75 | assert_equal 'bar', dup.instance_variable_get(:@foo) 76 | assert_equal X, dup.class 77 | end 78 | 79 | def test_load_with_class_syck_compatibility 80 | hash = Psych.unsafe_load "--- !ruby/object:Hash\n:user_id: 7\n:username: Lucas\n" 81 | assert_equal({ user_id: 7, username: 'Lucas'}, hash) 82 | end 83 | 84 | def test_empty_subclass 85 | assert_match "!ruby/hash:#{X}", Psych.dump(X.new) 86 | x = Psych.unsafe_load Psych.dump X.new 87 | assert_equal X, x.class 88 | end 89 | 90 | def test_map 91 | x = Psych.unsafe_load "--- !map:#{X} { }\n" 92 | assert_equal X, x.class 93 | end 94 | 95 | def test_self_referential 96 | @hash['self'] = @hash 97 | assert_cycle(@hash) 98 | end 99 | 100 | def test_cycles 101 | assert_cycle(@hash) 102 | end 103 | 104 | def test_ref_append 105 | hash = Psych.unsafe_load(<<~eoyml) 106 | --- 107 | foo: &foo 108 | hello: world 109 | bar: 110 | <<: *foo 111 | eoyml 112 | assert_equal({"foo"=>{"hello"=>"world"}, "bar"=>{"hello"=>"world"}}, hash) 113 | end 114 | 115 | def test_anchor_reuse 116 | hash = Psych.unsafe_load(<<~eoyml) 117 | --- 118 | foo: &foo 119 | hello: world 120 | bar: *foo 121 | eoyml 122 | assert_equal({"foo"=>{"hello"=>"world"}, "bar"=>{"hello"=>"world"}}, hash) 123 | assert_same(hash.fetch("foo"), hash.fetch("bar")) 124 | end 125 | 126 | def test_raises_if_anchor_not_defined 127 | assert_raise(Psych::AnchorNotDefined) do 128 | Psych.unsafe_load(<<~eoyml) 129 | --- 130 | foo: &foo 131 | hello: world 132 | bar: *not_foo 133 | eoyml 134 | end 135 | end 136 | 137 | def test_recursive_hash 138 | h = { } 139 | h["recursive_reference"] = h 140 | 141 | loaded = Psych.load(Psych.dump(h), aliases: true) 142 | 143 | assert_same loaded, loaded.fetch("recursive_reference") 144 | end 145 | 146 | def test_recursive_hash_uses_alias 147 | h = { } 148 | h["recursive_reference"] = h 149 | 150 | assert_raise(AliasesNotEnabled) do 151 | Psych.load(Psych.dump(h), aliases: false) 152 | end 153 | end 154 | 155 | def test_key_deduplication 156 | unless String.method_defined?(:-@) && (-("a" * 20)).equal?((-("a" * 20))) 157 | pend "This Ruby implementation doesn't support string deduplication" 158 | end 159 | 160 | hashes = Psych.load(<<~eoyml) 161 | --- 162 | - unique_identifier: 1 163 | - unique_identifier: 2 164 | eoyml 165 | 166 | assert_same hashes[0].keys.first, hashes[1].keys.first 167 | end 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /test/psych/test_json_tree.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestJSONTree < TestCase 6 | def test_string 7 | assert_match(/"foo"/, Psych.to_json("foo")) 8 | end 9 | 10 | def test_symbol 11 | assert_match(/"foo"/, Psych.to_json(:foo)) 12 | end 13 | 14 | def test_nil 15 | assert_match(/^null/, Psych.to_json(nil)) 16 | end 17 | 18 | def test_int 19 | assert_match(/^10/, Psych.to_json(10)) 20 | end 21 | 22 | def test_float 23 | assert_match(/^1.2/, Psych.to_json(1.2)) 24 | end 25 | 26 | def test_hash 27 | hash = { 'one' => 'two' } 28 | json = Psych.to_json(hash) 29 | assert_match(/}$/, json) 30 | assert_match(/^\{/, json) 31 | assert_match(/['"]one['"]/, json) 32 | assert_match(/['"]two['"]/, json) 33 | end 34 | 35 | class Bar 36 | def encode_with coder 37 | coder.represent_seq 'omg', %w{ a b c } 38 | end 39 | end 40 | 41 | def test_json_list_dump_exclude_tag 42 | json = Psych.to_json Bar.new 43 | refute_match('omg', json) 44 | end 45 | 46 | def test_list_to_json 47 | list = %w{ one two } 48 | json = Psych.to_json(list) 49 | assert_match(/\]$/, json) 50 | assert_match(/^\[/, json) 51 | assert_match(/"one"/, json) 52 | assert_match(/"two"/, json) 53 | end 54 | 55 | def test_time 56 | time = Time.utc(2010, 10, 10) 57 | assert_equal "{\"a\": \"2010-10-10 00:00:00.000000000 Z\"}\n", 58 | Psych.to_json({'a' => time }) 59 | end 60 | 61 | def test_datetime 62 | time = Time.new(2010, 10, 10).to_datetime 63 | assert_equal "{\"a\": \"#{time.strftime("%Y-%m-%d %H:%M:%S.%9N %:z")}\"}\n", Psych.to_json({'a' => time }) 64 | end 65 | end 66 | end 67 | -------------------------------------------------------------------------------- /test/psych/test_marshalable.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | require 'delegate' 4 | 5 | module Psych 6 | class TestMarshalable < TestCase 7 | def test_objects_defining_marshal_dump_and_marshal_load_can_be_dumped 8 | sd = SimpleDelegator.new(1) 9 | loaded = Psych.unsafe_load(Psych.dump(sd)) 10 | 11 | assert_instance_of(SimpleDelegator, loaded) 12 | assert_equal(sd, loaded) 13 | end 14 | 15 | class PsychCustomMarshalable < BasicObject 16 | attr_reader :foo 17 | 18 | def initialize(foo) 19 | @foo = foo 20 | end 21 | 22 | def marshal_dump 23 | [foo] 24 | end 25 | 26 | def mashal_load(data) 27 | @foo = data[0] 28 | end 29 | 30 | def init_with(coder) 31 | @foo = coder['foo'] 32 | end 33 | 34 | def encode_with(coder) 35 | coder['foo'] = 2 36 | end 37 | 38 | def respond_to?(method) 39 | [:marshal_dump, :marshal_load, :init_with, :encode_with].include?(method) 40 | end 41 | 42 | def class 43 | PsychCustomMarshalable 44 | end 45 | end 46 | 47 | def test_init_with_takes_priority_over_marshal_methods 48 | obj = PsychCustomMarshalable.new(1) 49 | loaded = Psych.unsafe_load(Psych.dump(obj)) 50 | 51 | assert(PsychCustomMarshalable === loaded) 52 | assert_equal(2, loaded.foo) 53 | end 54 | 55 | def test_init_symbolize_names 56 | obj = PsychCustomMarshalable.new(1) 57 | loaded = Psych.unsafe_load(Psych.dump(obj), symbolize_names: true) 58 | 59 | assert(PsychCustomMarshalable === loaded) 60 | assert_equal(2, loaded.foo) 61 | end 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /test/psych/test_merge_keys.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestMergeKeys < TestCase 6 | class Product 7 | attr_reader :bar 8 | end 9 | 10 | def test_merge_key_with_bare_hash 11 | doc = Psych.load <<-eodoc 12 | map: 13 | <<: 14 | hello: world 15 | eodoc 16 | hash = { "map" => { "hello" => "world" } } 17 | assert_equal hash, doc 18 | end 19 | 20 | def test_merge_key_with_bare_hash_symbolized_names 21 | doc = Psych.load <<-eodoc, symbolize_names: true 22 | map: 23 | <<: 24 | hello: world 25 | eodoc 26 | hash = { map: { hello: "world" } } 27 | assert_equal hash, doc 28 | end 29 | 30 | def test_roundtrip_with_chevron_key 31 | h = {} 32 | v = { 'a' => h, '<<' => h } 33 | assert_cycle v 34 | end 35 | 36 | def test_explicit_string 37 | doc = Psych.unsafe_load <<-eoyml 38 | a: &me { hello: world } 39 | b: { !!str '<<': *me } 40 | eoyml 41 | expected = { 42 | "a" => { "hello" => "world" }, 43 | "b" => { 44 | "<<" => { "hello" => "world" } 45 | } 46 | } 47 | assert_equal expected, doc 48 | end 49 | 50 | def test_mergekey_with_object 51 | s = <<-eoyml 52 | foo: &foo 53 | bar: 10 54 | product: 55 | !ruby/object:#{Product.name} 56 | <<: *foo 57 | eoyml 58 | hash = Psych.unsafe_load s 59 | assert_equal({"bar" => 10}, hash["foo"]) 60 | product = hash["product"] 61 | assert_equal 10, product.bar 62 | end 63 | 64 | def test_merge_nil 65 | yaml = <<-eoyml 66 | defaults: &defaults 67 | development: 68 | <<: *defaults 69 | eoyml 70 | assert_equal({'<<' => nil }, Psych.unsafe_load(yaml)['development']) 71 | end 72 | 73 | def test_merge_array 74 | yaml = <<-eoyml 75 | foo: &hello 76 | - 1 77 | baz: 78 | <<: *hello 79 | eoyml 80 | assert_equal({'<<' => [1]}, Psych.unsafe_load(yaml)['baz']) 81 | end 82 | 83 | def test_merge_is_not_partial 84 | yaml = <<-eoyml 85 | default: &default 86 | hello: world 87 | foo: &hello 88 | - 1 89 | baz: 90 | <<: [*hello, *default] 91 | eoyml 92 | doc = Psych.unsafe_load yaml 93 | refute doc['baz'].key? 'hello' 94 | assert_equal({'<<' => [[1], {"hello"=>"world"}]}, Psych.unsafe_load(yaml)['baz']) 95 | end 96 | 97 | def test_merge_seq_nil 98 | yaml = <<-eoyml 99 | foo: &hello 100 | baz: 101 | <<: [*hello] 102 | eoyml 103 | assert_equal({'<<' => [nil]}, Psych.unsafe_load(yaml)['baz']) 104 | end 105 | 106 | def test_bad_seq_merge 107 | yaml = <<-eoyml 108 | defaults: &defaults [1, 2, 3] 109 | development: 110 | <<: *defaults 111 | eoyml 112 | assert_equal({'<<' => [1,2,3]}, Psych.unsafe_load(yaml)['development']) 113 | end 114 | 115 | def test_missing_merge_key 116 | yaml = <<-eoyml 117 | bar: 118 | << : *foo 119 | eoyml 120 | exp = assert_raise(Psych::AnchorNotDefined) { Psych.load(yaml, aliases: true) } 121 | assert_match 'foo', exp.message 122 | end 123 | 124 | # [ruby-core:34679] 125 | def test_merge_key 126 | yaml = <<-eoyml 127 | foo: &foo 128 | hello: world 129 | bar: 130 | << : *foo 131 | baz: boo 132 | eoyml 133 | 134 | hash = { 135 | "foo" => { "hello" => "world"}, 136 | "bar" => { "hello" => "world", "baz" => "boo" } } 137 | assert_equal hash, Psych.unsafe_load(yaml) 138 | end 139 | 140 | def test_multiple_maps 141 | yaml = <<-eoyaml 142 | --- 143 | - &CENTER { x: 1, y: 2 } 144 | - &LEFT { x: 0, y: 2 } 145 | - &BIG { r: 10 } 146 | - &SMALL { r: 1 } 147 | 148 | # All the following maps are equal: 149 | 150 | - # Merge multiple maps 151 | << : [ *CENTER, *BIG ] 152 | label: center/big 153 | eoyaml 154 | 155 | hash = { 156 | 'x' => 1, 157 | 'y' => 2, 158 | 'r' => 10, 159 | 'label' => 'center/big' 160 | } 161 | 162 | assert_equal hash, Psych.unsafe_load(yaml)[4] 163 | end 164 | 165 | def test_override 166 | yaml = <<-eoyaml 167 | --- 168 | - &CENTER { x: 1, y: 2 } 169 | - &LEFT { x: 0, y: 2 } 170 | - &BIG { r: 10 } 171 | - &SMALL { r: 1 } 172 | 173 | # All the following maps are equal: 174 | 175 | - # Override 176 | << : [ *BIG, *LEFT, *SMALL ] 177 | x: 1 178 | label: center/big 179 | eoyaml 180 | 181 | hash = { 182 | 'x' => 1, 183 | 'y' => 2, 184 | 'r' => 10, 185 | 'label' => 'center/big' 186 | } 187 | 188 | assert_equal hash, Psych.unsafe_load(yaml)[4] 189 | end 190 | end 191 | end 192 | -------------------------------------------------------------------------------- /test/psych/test_nil.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestNil < TestCase 6 | def test_nil 7 | yml = Psych.dump nil 8 | assert_match(/---[ ]?\n(?:\.\.\.\n)?/, yml) 9 | assert_nil Psych.load(yml) 10 | end 11 | 12 | def test_array_nil 13 | yml = Psych.dump [nil] 14 | assert_match(/---\n-[ ]?\n/, yml) 15 | assert_equal [nil], Psych.load(yml) 16 | end 17 | 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/psych/test_null.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | ### 6 | # Test null from YAML spec: 7 | # http://yaml.org/type/null.html 8 | class TestNull < TestCase 9 | def test_null_list 10 | assert_equal [nil] * 5, Psych.load(<<-eoyml) 11 | --- 12 | - ~ 13 | - null 14 | - 15 | - Null 16 | - NULL 17 | eoyml 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /test/psych/test_numeric.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | begin 4 | require 'bigdecimal' 5 | rescue LoadError 6 | end 7 | 8 | module Psych 9 | ### 10 | # Test numerics from YAML spec: 11 | # http://yaml.org/type/float.html 12 | # http://yaml.org/type/int.html 13 | class TestNumeric < TestCase 14 | def setup 15 | @old_debug = $DEBUG 16 | $DEBUG = true 17 | end 18 | 19 | def teardown 20 | $DEBUG = @old_debug 21 | end 22 | 23 | def test_load_float_with_dot 24 | assert_equal 1.0, Psych.load('--- 1.') 25 | end 26 | 27 | def test_non_float_with_0 28 | str = Psych.load('--- 090') 29 | assert_equal '090', str 30 | end 31 | 32 | def test_big_decimal_tag 33 | decimal = BigDecimal("12.34") 34 | assert_match "!ruby/object:BigDecimal", Psych.dump(decimal) 35 | end if defined?(BigDecimal) 36 | 37 | def test_big_decimal_round_trip 38 | decimal = BigDecimal("12.34") 39 | $DEBUG = false 40 | assert_cycle decimal 41 | end if defined?(BigDecimal) 42 | 43 | def test_does_not_attempt_numeric 44 | str = Psych.load('--- 4 roses') 45 | assert_equal '4 roses', str 46 | str = Psych.load('--- 1.1.1') 47 | assert_equal '1.1.1', str 48 | end 49 | 50 | # This behavior is not to YML spec, but is kept for backwards compatibility 51 | def test_string_with_commas 52 | number = Psych.load('--- 12,34,56') 53 | assert_equal 123456, number 54 | end 55 | 56 | def test_string_with_commas_with_strict_integer 57 | str = Psych.load('--- 12,34,56', strict_integer: true) 58 | assert_equal '12,34,56', str 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/psych/test_object.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class Tagged 6 | yaml_tag '!foo' 7 | 8 | attr_accessor :baz 9 | 10 | def initialize 11 | @baz = 'bar' 12 | end 13 | end 14 | 15 | class Foo 16 | attr_accessor :parent 17 | 18 | def initialize parent 19 | @parent = parent 20 | end 21 | end 22 | 23 | class TestObject < TestCase 24 | def test_dump_with_tag 25 | tag = Tagged.new 26 | assert_match('foo', Psych.dump(tag)) 27 | end 28 | 29 | def test_tag_round_trip 30 | tag = Tagged.new 31 | tag2 = Psych.unsafe_load(Psych.dump(tag)) 32 | assert_equal tag.baz, tag2.baz 33 | assert_instance_of(Tagged, tag2) 34 | end 35 | 36 | def test_cyclic_references 37 | foo = Foo.new(nil) 38 | foo.parent = foo 39 | loaded = Psych.load(Psych.dump(foo), permitted_classes: [Foo], aliases: true) 40 | 41 | assert_instance_of(Foo, loaded) 42 | assert_same loaded, loaded.parent 43 | end 44 | 45 | def test_cyclic_reference_uses_alias 46 | foo = Foo.new(nil) 47 | foo.parent = foo 48 | 49 | assert_raise(AliasesNotEnabled) do 50 | Psych.load(Psych.dump(foo), permitted_classes: [Foo], aliases: false) 51 | end 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /test/psych/test_object_references.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestObjectReferences < TestCase 6 | def test_range_has_references 7 | assert_reference_trip 1..2 8 | end 9 | 10 | def test_module_has_references 11 | assert_reference_trip Psych 12 | end 13 | 14 | def test_class_has_references 15 | assert_reference_trip TestObjectReferences 16 | end 17 | 18 | def test_rational_has_references 19 | assert_reference_trip Rational('1.2') 20 | end 21 | 22 | def test_complex_has_references 23 | assert_reference_trip Complex(1, 2) 24 | end 25 | 26 | def test_datetime_has_references 27 | assert_reference_trip DateTime.now 28 | end 29 | 30 | def test_struct_has_references 31 | assert_reference_trip Struct.new(:foo).new(1) 32 | end 33 | 34 | def test_data_has_references 35 | omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2" 36 | assert_reference_trip Data.define(:foo).new(1) 37 | end 38 | 39 | def assert_reference_trip obj 40 | yml = Psych.dump([obj, obj]) 41 | assert_match(/\*-?\d+/, yml) 42 | begin 43 | data = Psych.load yml 44 | rescue Psych::DisallowedClass 45 | data = Psych.unsafe_load yml 46 | end 47 | assert_same data.first, data.last 48 | end 49 | 50 | def test_float_references 51 | data = Psych.unsafe_load <<-eoyml 52 | ---\s 53 | - &name 1.2 54 | - *name 55 | eoyml 56 | assert_equal data.first, data.last 57 | assert_same data.first, data.last 58 | end 59 | 60 | def test_binary_references 61 | data = Psych.unsafe_load <<-eoyml 62 | --- 63 | - &name !binary |- 64 | aGVsbG8gd29ybGQh 65 | - *name 66 | eoyml 67 | assert_equal data.first, data.last 68 | assert_same data.first, data.last 69 | end 70 | 71 | def test_regexp_references 72 | data = Psych.unsafe_load <<-eoyml 73 | ---\s 74 | - &name !ruby/regexp /pattern/i 75 | - *name 76 | eoyml 77 | assert_equal data.first, data.last 78 | assert_same data.first, data.last 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /test/psych/test_omap.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestOmap < TestCase 6 | def test_parse_as_map 7 | o = Psych.unsafe_load "--- !!omap\na: 1\nb: 2" 8 | assert_kind_of Psych::Omap, o 9 | assert_equal 1, o['a'] 10 | assert_equal 2, o['b'] 11 | end 12 | 13 | def test_self_referential 14 | map = Psych::Omap.new 15 | map['foo'] = 'bar' 16 | map['self'] = map 17 | assert_equal(map, Psych.unsafe_load(Psych.dump(map))) 18 | end 19 | 20 | def test_keys 21 | map = Psych::Omap.new 22 | map['foo'] = 'bar' 23 | assert_equal 'bar', map['foo'] 24 | end 25 | 26 | def test_order 27 | map = Psych::Omap.new 28 | map['a'] = 'b' 29 | map['b'] = 'c' 30 | assert_equal [%w{a b}, %w{b c}], map.to_a 31 | end 32 | 33 | def test_square 34 | list = [["a", "b"], ["b", "c"]] 35 | map = Psych::Omap[*list.flatten] 36 | assert_equal list, map.to_a 37 | assert_equal 'b', map['a'] 38 | assert_equal 'c', map['b'] 39 | end 40 | 41 | def test_dump 42 | map = Psych::Omap['a', 'b', 'c', 'd'] 43 | yaml = Psych.dump(map) 44 | assert_match('!omap', yaml) 45 | assert_match('- a: b', yaml) 46 | assert_match('- c: d', yaml) 47 | end 48 | 49 | def test_round_trip 50 | list = [["a", "b"], ["b", "c"]] 51 | map = Psych::Omap[*list.flatten] 52 | assert_cycle(map) 53 | end 54 | 55 | def test_load 56 | list = [["a", "b"], ["c", "d"]] 57 | map = Psych.load(<<-eoyml) 58 | --- !omap 59 | - a: b 60 | - c: d 61 | eoyml 62 | assert_equal list, map.to_a 63 | end 64 | 65 | # NOTE: This test will not work with Syck 66 | def test_load_shorthand 67 | list = [["a", "b"], ["c", "d"]] 68 | map = Psych.load(<<-eoyml) 69 | --- !!omap 70 | - a: b 71 | - c: d 72 | eoyml 73 | assert_equal list, map.to_a 74 | end 75 | end 76 | end 77 | -------------------------------------------------------------------------------- /test/psych/test_psych_set.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestPsychSet < TestCase 6 | def setup 7 | super 8 | @set = Psych::Set.new 9 | @set['foo'] = 'bar' 10 | @set['bar'] = 'baz' 11 | end 12 | 13 | def test_dump 14 | assert_match(/!set/, Psych.dump(@set)) 15 | end 16 | 17 | def test_roundtrip 18 | assert_cycle(@set) 19 | end 20 | 21 | ### 22 | # FIXME: Syck should also support !!set as shorthand 23 | def test_load_from_yaml 24 | loaded = Psych.unsafe_load(<<-eoyml) 25 | --- !set 26 | foo: bar 27 | bar: baz 28 | eoyml 29 | assert_equal(@set, loaded) 30 | end 31 | 32 | def test_loaded_class 33 | assert_instance_of(Psych::Set, Psych.unsafe_load(Psych.dump(@set))) 34 | end 35 | 36 | def test_set_shorthand 37 | loaded = Psych.unsafe_load(<<-eoyml) 38 | --- !!set 39 | foo: bar 40 | bar: baz 41 | eoyml 42 | assert_instance_of(Psych::Set, loaded) 43 | end 44 | 45 | def test_set_self_reference 46 | @set['self'] = @set 47 | assert_cycle(@set) 48 | end 49 | 50 | def test_stringify_names 51 | @set[:symbol] = :value 52 | 53 | assert_match(/^:symbol: :value/, Psych.dump(@set)) 54 | assert_match(/^symbol: :value/, Psych.dump(@set, stringify_names: true)) 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /test/psych/test_ractor.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | class TestPsychRactor < Test::Unit::TestCase 5 | def test_ractor_round_trip 6 | assert_ractor(<<~RUBY, require_relative: 'helper') 7 | obj = {foo: [42]} 8 | obj2 = Ractor.new(obj) do |obj| 9 | Psych.unsafe_load(Psych.dump(obj)) 10 | end.value 11 | assert_equal obj, obj2 12 | RUBY 13 | end 14 | 15 | def test_not_shareable 16 | # There's no point in making these frozen / shareable 17 | # and the C-ext disregards begin frozen 18 | assert_ractor(<<~RUBY, require_relative: 'helper') 19 | parser = Psych::Parser.new 20 | emitter = Psych::Emitter.new(nil) 21 | assert_raise(Ractor::Error) { Ractor.make_shareable(parser) } 22 | assert_raise(Ractor::Error) { Ractor.make_shareable(emitter) } 23 | RUBY 24 | end 25 | 26 | def test_ractor_config 27 | # Config is ractor-local 28 | # Test is to make sure it works, even though usage is probably very low. 29 | # The methods are not documented and might be deprecated one day 30 | assert_ractor(<<~RUBY, require_relative: 'helper') 31 | r = Ractor.new do 32 | Psych.add_builtin_type 'omap' do |type, val| 33 | val * 2 34 | end 35 | Psych.load('--- !!omap hello') 36 | end.value 37 | assert_equal 'hellohello', r 38 | assert_equal 'hello', Psych.load('--- !!omap hello') 39 | RUBY 40 | end 41 | 42 | def test_ractor_constants 43 | assert_ractor(<<~RUBY, require_relative: 'helper') 44 | r = Ractor.new do 45 | Psych.libyaml_version.join('.') == Psych::LIBYAML_VERSION 46 | end.value 47 | assert_equal true, r 48 | RUBY 49 | end 50 | end if defined?(Ractor) 51 | -------------------------------------------------------------------------------- /test/psych/test_safe_load.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'psych/helper' 3 | 4 | module Psych 5 | class TestSafeLoad < TestCase 6 | def setup 7 | @orig_verbose, $VERBOSE = $VERBOSE, nil 8 | end 9 | 10 | def teardown 11 | $VERBOSE = @orig_verbose 12 | end 13 | 14 | class Foo; end 15 | 16 | [1, 2.2, {}, [], "foo"].each do |obj| 17 | define_method(:"test_basic_#{obj.class}") do 18 | assert_safe_cycle obj 19 | end 20 | end 21 | 22 | def test_raises_when_alias_found_if_alias_parsing_not_enabled 23 | yaml_with_aliases = <<~YAML 24 | --- 25 | a: &ABC 26 | k1: v1 27 | k2: v2 28 | b: *ABC 29 | YAML 30 | 31 | assert_raise(Psych::AliasesNotEnabled) do 32 | Psych.safe_load(yaml_with_aliases) 33 | end 34 | end 35 | 36 | def test_aliases_are_parsed_when_alias_parsing_is_enabled 37 | yaml_with_aliases = <<~YAML 38 | --- 39 | a: &ABC 40 | k1: v1 41 | k2: v2 42 | b: *ABC 43 | YAML 44 | 45 | result = Psych.safe_load(yaml_with_aliases, aliases: true) 46 | assert_same result.fetch("a"), result.fetch("b") 47 | end 48 | 49 | def test_permitted_symbol 50 | yml = Psych.dump :foo 51 | assert_raise(Psych::DisallowedClass) do 52 | Psych.safe_load yml 53 | end 54 | assert_equal( 55 | :foo, 56 | Psych.safe_load( 57 | yml, 58 | permitted_classes: [Symbol], 59 | permitted_symbols: [:foo] 60 | ) 61 | ) 62 | end 63 | 64 | def test_symbol 65 | assert_raise(Psych::DisallowedClass) do 66 | assert_safe_cycle :foo 67 | end 68 | assert_raise(Psych::DisallowedClass) do 69 | Psych.safe_load '--- !ruby/symbol foo', permitted_classes: [] 70 | end 71 | 72 | assert_safe_cycle :foo, permitted_classes: [Symbol] 73 | assert_safe_cycle :foo, permitted_classes: %w{ Symbol } 74 | assert_equal :foo, Psych.safe_load('--- !ruby/symbol foo', permitted_classes: [Symbol]) 75 | end 76 | 77 | def test_foo 78 | assert_raise(Psych::DisallowedClass) do 79 | Psych.safe_load '--- !ruby/object:Foo {}', permitted_classes: [Foo] 80 | end 81 | 82 | assert_raise(Psych::DisallowedClass) do 83 | assert_safe_cycle Foo.new 84 | end 85 | assert_kind_of(Foo, Psych.safe_load(Psych.dump(Foo.new), permitted_classes: [Foo])) 86 | end 87 | 88 | X = Struct.new(:x) 89 | def test_struct_depends_on_sym 90 | assert_safe_cycle(X.new, permitted_classes: [X, Symbol]) 91 | assert_raise(Psych::DisallowedClass) do 92 | cycle X.new, permitted_classes: [X] 93 | end 94 | end 95 | 96 | def test_anon_struct 97 | assert Psych.safe_load(<<-eoyml, permitted_classes: [Struct, Symbol]) 98 | --- !ruby/struct 99 | foo: bar 100 | eoyml 101 | 102 | assert_raise(Psych::DisallowedClass) do 103 | Psych.safe_load(<<-eoyml, permitted_classes: [Struct]) 104 | --- !ruby/struct 105 | foo: bar 106 | eoyml 107 | end 108 | 109 | assert_raise(Psych::DisallowedClass) do 110 | Psych.safe_load(<<-eoyml, permitted_classes: [Symbol]) 111 | --- !ruby/struct 112 | foo: bar 113 | eoyml 114 | end 115 | end 116 | 117 | D = Data.define(:d) unless RUBY_VERSION < "3.2" 118 | 119 | def test_data_depends_on_sym 120 | omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2" 121 | assert_safe_cycle(D.new(nil), permitted_classes: [D, Symbol]) 122 | assert_raise(Psych::DisallowedClass) do 123 | cycle D.new(nil), permitted_classes: [D] 124 | end 125 | end 126 | 127 | def test_anon_data 128 | omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2" 129 | assert Psych.safe_load(<<-eoyml, permitted_classes: [Data, Symbol]) 130 | --- !ruby/data 131 | foo: bar 132 | eoyml 133 | 134 | assert_raise(Psych::DisallowedClass) do 135 | Psych.safe_load(<<-eoyml, permitted_classes: [Data]) 136 | --- !ruby/data 137 | foo: bar 138 | eoyml 139 | end 140 | 141 | assert_raise(Psych::DisallowedClass) do 142 | Psych.safe_load(<<-eoyml, permitted_classes: [Symbol]) 143 | --- !ruby/data 144 | foo: bar 145 | eoyml 146 | end 147 | end 148 | 149 | def test_safe_load_default_fallback 150 | assert_nil Psych.safe_load("") 151 | end 152 | 153 | def test_safe_load 154 | assert_equal %w[a b], Psych.safe_load("- a\n- b") 155 | end 156 | 157 | def test_safe_load_raises_on_bad_input 158 | assert_raise(Psych::SyntaxError) { Psych.safe_load("--- `") } 159 | end 160 | 161 | private 162 | 163 | def cycle object, permitted_classes: [] 164 | Psych.safe_load(Psych.dump(object), permitted_classes: permitted_classes) 165 | end 166 | 167 | def assert_safe_cycle object, permitted_classes: [] 168 | other = cycle object, permitted_classes: permitted_classes 169 | assert_equal object, other 170 | end 171 | end 172 | end 173 | -------------------------------------------------------------------------------- /test/psych/test_scalar.rb: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # frozen_string_literal: true 3 | 4 | require_relative 'helper' 5 | 6 | module Psych 7 | class TestScalar < TestCase 8 | def test_utf_8 9 | assert_equal "日本語", Psych.load("--- 日本語") 10 | end 11 | 12 | def test_some_bytes # Ticket #278 13 | x = "\xEF\xBF\xBD\x1F" 14 | assert_cycle x 15 | end 16 | end 17 | end 18 | -------------------------------------------------------------------------------- /test/psych/test_scalar_scanner.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestScalarScanner < TestCase 6 | attr_reader :ss 7 | 8 | def setup 9 | super 10 | @ss = Psych::ScalarScanner.new ClassLoader.new 11 | end 12 | 13 | def test_scan_time 14 | { '2001-12-15T02:59:43.1Z' => Time.utc(2001, 12, 15, 02, 59, 43, 100000), 15 | '2001-12-14t21:59:43.10-05:00' => Time.utc(2001, 12, 15, 02, 59, 43, 100000), 16 | '2001-12-14 21:59:43.10 -5' => Time.utc(2001, 12, 15, 02, 59, 43, 100000), 17 | '2001-12-15 2:59:43.10' => Time.utc(2001, 12, 15, 02, 59, 43, 100000), 18 | '2011-02-24 11:17:06 -0800' => Time.utc(2011, 02, 24, 19, 17, 06) 19 | }.each do |time_str, time| 20 | assert_equal time, @ss.tokenize(time_str) 21 | end 22 | end 23 | 24 | def test_scan_bad_time 25 | [ '2001-12-15T02:59:73.1Z', 26 | '2001-12-14t90:59:43.10-05:00', 27 | '2001-92-14 21:59:43.10 -5', 28 | '2001-12-15 92:59:43.10', 29 | '2011-02-24 81:17:06 -0800', 30 | ].each do |time_str| 31 | assert_equal time_str, @ss.tokenize(time_str) 32 | end 33 | end 34 | 35 | def test_scan_bad_dates 36 | x = '2000-15-01' 37 | assert_equal x, @ss.tokenize(x) 38 | 39 | x = '2000-10-51' 40 | assert_equal x, @ss.tokenize(x) 41 | 42 | x = '2000-10-32' 43 | assert_equal x, @ss.tokenize(x) 44 | end 45 | 46 | def test_scan_good_edge_date 47 | x = '2000-1-31' 48 | assert_equal Date.strptime(x, '%Y-%m-%d'), @ss.tokenize(x) 49 | end 50 | 51 | def test_scan_bad_edge_date 52 | x = '2000-11-31' 53 | assert_equal x, @ss.tokenize(x) 54 | end 55 | 56 | def test_scan_date 57 | date = '1980-12-16' 58 | token = @ss.tokenize date 59 | assert_equal 1980, token.year 60 | assert_equal 12, token.month 61 | assert_equal 16, token.day 62 | end 63 | 64 | def test_scan_inf 65 | assert_equal(1 / 0.0, ss.tokenize('.inf')) 66 | end 67 | 68 | def test_scan_plus_inf 69 | assert_equal(1 / 0.0, ss.tokenize('+.inf')) 70 | end 71 | 72 | def test_scan_minus_inf 73 | assert_equal(-1 / 0.0, ss.tokenize('-.inf')) 74 | end 75 | 76 | def test_scan_nan 77 | assert ss.tokenize('.nan').nan? 78 | end 79 | 80 | def test_scan_float_with_exponent_but_no_fraction 81 | assert_equal(0.0, ss.tokenize('0.E+0')) 82 | end 83 | 84 | def test_scan_null 85 | assert_nil ss.tokenize('null') 86 | assert_nil ss.tokenize('~') 87 | assert_nil ss.tokenize('') 88 | end 89 | 90 | def test_scan_symbol 91 | assert_equal :foo, ss.tokenize(':foo') 92 | end 93 | 94 | def test_scan_not_sexagesimal 95 | assert_equal '00:00:00:00:0f', ss.tokenize('00:00:00:00:0f') 96 | assert_equal '00:00:00:00:00', ss.tokenize('00:00:00:00:00') 97 | assert_equal '00:00:00:00:00.0', ss.tokenize('00:00:00:00:00.0') 98 | end 99 | 100 | def test_scan_sexagesimal_float 101 | assert_equal 685230.15, ss.tokenize('190:20:30.15') 102 | end 103 | 104 | def test_scan_sexagesimal_int 105 | assert_equal 685230, ss.tokenize('190:20:30') 106 | end 107 | 108 | def test_scan_float 109 | assert_equal 1.2, ss.tokenize('1.2') 110 | end 111 | 112 | def test_scan_true 113 | assert_equal true, ss.tokenize('true') 114 | end 115 | 116 | def test_scan_strings_starting_with_underscores 117 | assert_equal '_100', ss.tokenize('_100') 118 | end 119 | 120 | def test_scan_strings_starting_with_number 121 | assert_equal '450D', ss.tokenize('450D') 122 | end 123 | 124 | def test_scan_strings_ending_with_underscores 125 | assert_equal '100_', ss.tokenize('100_') 126 | end 127 | 128 | def test_scan_strings_with_legacy_int_delimiters 129 | assert_equal '0x_,_', ss.tokenize('0x_,_') 130 | assert_equal '+0__,,', ss.tokenize('+0__,,') 131 | assert_equal '-0b,_,', ss.tokenize('-0b,_,') 132 | end 133 | 134 | def test_scan_strings_with_strict_int_delimiters 135 | scanner = Psych::ScalarScanner.new ClassLoader.new, strict_integer: true 136 | assert_equal '0x___', scanner.tokenize('0x___') 137 | assert_equal '+0____', scanner.tokenize('+0____') 138 | assert_equal '-0b___', scanner.tokenize('-0b___') 139 | end 140 | 141 | def test_scan_int_commas_and_underscores 142 | # NB: This test is to ensure backward compatibility with prior Psych versions, 143 | # not to test against any actual YAML specification. 144 | assert_equal 123_456_789, ss.tokenize('123_456_789') 145 | assert_equal 123_456_789, ss.tokenize('123,456,789') 146 | assert_equal 123_456_789, ss.tokenize('1_2,3,4_5,6_789') 147 | 148 | assert_equal 1, ss.tokenize('1') 149 | assert_equal 1, ss.tokenize('+1') 150 | assert_equal(-1, ss.tokenize('-1')) 151 | 152 | assert_equal 0b010101010, ss.tokenize('0b010101010') 153 | assert_equal 0b010101010, ss.tokenize('0b0,1_0,1_,0,1_01,0') 154 | 155 | assert_equal 01234567, ss.tokenize('01234567') 156 | assert_equal 01234567, ss.tokenize('0_,,,1_2,_34567') 157 | 158 | assert_equal 0x123456789abcdef, ss.tokenize('0x123456789abcdef') 159 | assert_equal 0x123456789abcdef, ss.tokenize('0x12_,34,_56,_789abcdef') 160 | assert_equal 0x123456789abcdef, ss.tokenize('0x_12_,34,_56,_789abcdef') 161 | assert_equal 0x123456789abcdef, ss.tokenize('0x12_,34,_56,_789abcdef__') 162 | end 163 | 164 | def test_scan_strict_int_commas_and_underscores 165 | # this test is to ensure adherence to YML spec using the 'strict_integer' option 166 | scanner = Psych::ScalarScanner.new ClassLoader.new, strict_integer: true 167 | assert_equal 123_456_789, scanner.tokenize('123_456_789') 168 | assert_equal '123,456,789', scanner.tokenize('123,456,789') 169 | assert_equal '1_2,3,4_5,6_789', scanner.tokenize('1_2,3,4_5,6_789') 170 | 171 | assert_equal 1, scanner.tokenize('1') 172 | assert_equal 1, scanner.tokenize('+1') 173 | assert_equal(-1, scanner.tokenize('-1')) 174 | 175 | assert_equal 0b010101010, scanner.tokenize('0b010101010') 176 | assert_equal 0b010101010, scanner.tokenize('0b01_01_01_010') 177 | assert_equal '0b0,1_0,1_,0,1_01,0', scanner.tokenize('0b0,1_0,1_,0,1_01,0') 178 | 179 | assert_equal 01234567, scanner.tokenize('01234567') 180 | assert_equal '0_,,,1_2,_34567', scanner.tokenize('0_,,,1_2,_34567') 181 | 182 | assert_equal 0x123456789abcdef, scanner.tokenize('0x123456789abcdef') 183 | assert_equal 0x123456789abcdef, scanner.tokenize('0x12_34_56_789abcdef') 184 | assert_equal '0x12_,34,_56,_789abcdef', scanner.tokenize('0x12_,34,_56,_789abcdef') 185 | assert_equal '0x_12_,34,_56,_789abcdef', scanner.tokenize('0x_12_,34,_56,_789abcdef') 186 | assert_equal '0x12_,34,_56,_789abcdef__', scanner.tokenize('0x12_,34,_56,_789abcdef__') 187 | end 188 | 189 | def test_scan_dot 190 | assert_equal '.', ss.tokenize('.') 191 | end 192 | 193 | def test_scan_plus_dot 194 | assert_equal '+.', ss.tokenize('+.') 195 | end 196 | 197 | class MatchCallCounter < String 198 | attr_reader :match_call_count 199 | 200 | def match?(pat) 201 | @match_call_count ||= 0 202 | @match_call_count += 1 203 | super 204 | end 205 | end 206 | 207 | def test_scan_ascii_matches_quickly 208 | ascii = MatchCallCounter.new('abcdefghijklmnopqrstuvwxyz') 209 | ss.tokenize(ascii) 210 | assert_equal 1, ascii.match_call_count 211 | end 212 | 213 | def test_scan_unicode_matches_quickly 214 | unicode = MatchCallCounter.new('鳥かご関連用品') 215 | ss.tokenize(unicode) 216 | assert_equal 1, unicode.match_call_count 217 | end 218 | end 219 | end 220 | -------------------------------------------------------------------------------- /test/psych/test_serialize_subclasses.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestSerializeSubclasses < TestCase 6 | class SomeObject 7 | def initialize one, two 8 | @one = one 9 | @two = two 10 | end 11 | 12 | def == other 13 | @one == other.instance_eval { @one } && 14 | @two == other.instance_eval { @two } 15 | end 16 | end 17 | 18 | def test_some_object 19 | so = SomeObject.new('foo', [1,2,3]) 20 | assert_equal so, Psych.unsafe_load(Psych.dump(so)) 21 | end 22 | 23 | class StructSubclass < Struct.new(:foo) 24 | def initialize foo, bar 25 | super(foo) 26 | @bar = bar 27 | end 28 | 29 | def == other 30 | super(other) && @bar == other.instance_eval{ @bar } 31 | end 32 | end 33 | 34 | def test_struct_subclass 35 | so = StructSubclass.new('foo', [1,2,3]) 36 | assert_equal so, Psych.unsafe_load(Psych.dump(so)) 37 | end 38 | 39 | class DataSubclass < Data.define(:foo) 40 | def initialize(foo:) 41 | @bar = "hello #{foo}" 42 | super(foo: foo) 43 | end 44 | 45 | def == other 46 | super(other) && @bar == other.instance_eval{ @bar } 47 | end 48 | end unless RUBY_VERSION < "3.2" 49 | 50 | def test_data_subclass 51 | omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2" 52 | so = DataSubclass.new('foo') 53 | assert_equal so, Psych.unsafe_load(Psych.dump(so)) 54 | end 55 | 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /test/psych/test_set.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # frozen_string_literal: true 3 | require_relative 'helper' 4 | require 'set' unless defined?(Set) 5 | 6 | module Psych 7 | class TestSet < TestCase 8 | def setup 9 | @set = ::Set.new([1, 2, 3]) 10 | end 11 | 12 | def test_dump 13 | assert_equal <<~YAML, Psych.dump(@set) 14 | --- !ruby/object:Set 15 | hash: 16 | 1: true 17 | 2: true 18 | 3: true 19 | YAML 20 | end 21 | 22 | def test_load 23 | assert_equal @set, Psych.load(<<~YAML, permitted_classes: [::Set]) 24 | --- !ruby/object:Set 25 | hash: 26 | 1: true 27 | 2: true 28 | 3: true 29 | YAML 30 | end 31 | 32 | def test_roundtrip 33 | assert_equal @set, Psych.load(Psych.dump(@set), permitted_classes: [::Set]) 34 | end 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /test/psych/test_stream.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestStream < TestCase 6 | [ 7 | [Psych::Nodes::Alias, :alias?], 8 | [Psych::Nodes::Document, :document?], 9 | [Psych::Nodes::Mapping, :mapping?], 10 | [Psych::Nodes::Scalar, :scalar?], 11 | [Psych::Nodes::Sequence, :sequence?], 12 | [Psych::Nodes::Stream, :stream?], 13 | ].each do |klass, block| 14 | define_method :"test_predicate_#{block}" do 15 | rb = Psych.parse_stream("---\n- foo: bar\n- &a !!str Anchored\n- *a") 16 | nodes = rb.grep(klass) 17 | assert_operator nodes.length, :>, 0 18 | assert_equal nodes, rb.find_all(&block) 19 | end 20 | end 21 | 22 | def test_parse_partial 23 | rb = Psych.parse("--- foo\n...\n--- `").to_ruby 24 | assert_equal 'foo', rb 25 | end 26 | 27 | def test_load_partial 28 | rb = Psych.load("--- foo\n...\n--- `") 29 | assert_equal 'foo', rb 30 | end 31 | 32 | def test_parse_stream_yields_documents 33 | list = [] 34 | Psych.parse_stream("--- foo\n...\n--- bar") do |doc| 35 | list << doc.to_ruby 36 | end 37 | assert_equal %w{ foo bar }, list 38 | end 39 | 40 | def test_parse_stream_break 41 | list = [] 42 | Psych.parse_stream("--- foo\n...\n--- `") do |doc| 43 | list << doc.to_ruby 44 | break 45 | end 46 | assert_equal %w{ foo }, list 47 | end 48 | 49 | def test_load_stream_yields_documents 50 | list = [] 51 | Psych.load_stream("--- foo\n...\n--- bar") do |ruby| 52 | list << ruby 53 | end 54 | assert_equal %w{ foo bar }, list 55 | end 56 | 57 | def test_safe_load_stream_yields_documents 58 | list = [] 59 | Psych.safe_load_stream("--- foo\n...\n--- bar") do |ruby| 60 | list << ruby 61 | end 62 | assert_equal %w{ foo bar }, list 63 | end 64 | 65 | def test_load_stream_break 66 | list = [] 67 | Psych.load_stream("--- foo\n...\n--- `") do |ruby| 68 | list << ruby 69 | break 70 | end 71 | assert_equal %w{ foo }, list 72 | end 73 | 74 | def test_explicit_documents 75 | io = StringIO.new 76 | stream = Psych::Stream.new(io) 77 | stream.start 78 | stream.push({ 'foo' => 'bar' }) 79 | 80 | assert !stream.finished?, 'stream not finished' 81 | stream.finish 82 | assert stream.finished?, 'stream finished' 83 | 84 | assert_match(/^---/, io.string) 85 | assert_match(/\.\.\.$/, io.string) 86 | end 87 | 88 | def test_start_takes_block 89 | io = StringIO.new 90 | stream = Psych::Stream.new(io) 91 | stream.start do |emitter| 92 | emitter.push({ 'foo' => 'bar' }) 93 | end 94 | 95 | assert stream.finished?, 'stream finished' 96 | assert_match(/^---/, io.string) 97 | assert_match(/\.\.\.$/, io.string) 98 | end 99 | 100 | def test_no_backreferences 101 | io = StringIO.new 102 | stream = Psych::Stream.new(io) 103 | stream.start do |emitter| 104 | x = { 'foo' => 'bar' } 105 | emitter.push x 106 | emitter.push x 107 | end 108 | 109 | assert stream.finished?, 'stream finished' 110 | assert_match(/^---/, io.string) 111 | assert_match(/\.\.\.$/, io.string) 112 | assert_equal 2, io.string.scan('---').length 113 | assert_equal 2, io.string.scan('...').length 114 | assert_equal 2, io.string.scan('foo').length 115 | assert_equal 2, io.string.scan('bar').length 116 | end 117 | end 118 | end 119 | -------------------------------------------------------------------------------- /test/psych/test_string.rb: -------------------------------------------------------------------------------- 1 | # encoding: UTF-8 2 | # frozen_string_literal: true 3 | require_relative 'helper' 4 | 5 | module Psych 6 | class TestString < TestCase 7 | class X < String 8 | end 9 | 10 | class Y < String 11 | attr_accessor :val 12 | end 13 | 14 | class Z < String 15 | def initialize 16 | force_encoding Encoding::US_ASCII 17 | end 18 | end 19 | 20 | # 'y', 'Y', 'n', 'N' are kind of ambiguous. Syck treated those literals in 21 | # YAML documents as strings. But this is not what the YAML 1.1 spec says. 22 | # YAML 1.1 says they should be treated as booleans. When we're dumping 23 | # documents, we know it's a string, so adding quotes will eliminate the 24 | # "ambiguity" in the emitted document 25 | 26 | def test_all_yaml_1_1_booleans_are_quoted 27 | yaml_1_1_booleans = %w[y Y yes Yes YES n N no No NO true True TRUE false False FALSE on On ON off Off OFF] # from https://yaml.org/type/bool.html 28 | yaml_1_1_booleans.each do |boolean| 29 | assert_match(/"#{boolean}"|'#{boolean}'/, Psych.dump(boolean)) 30 | end 31 | end 32 | 33 | def test_string_with_newline 34 | assert_equal "1\n2", Psych.load("--- ! '1\n\n 2'\n") 35 | end 36 | 37 | def test_no_doublequotes_with_special_characters 38 | assert_equal 2, Psych.dump(%Q{<%= ENV["PATH"] %>}).count('"') 39 | end 40 | 41 | def test_no_quotes_when_start_with_non_ascii_character 42 | yaml = Psych.dump 'Český non-ASCII'.encode(Encoding::UTF_8) 43 | assert_match(/---\s*[^"'!]+$/, yaml) 44 | end 45 | 46 | def test_doublequotes_when_there_is_a_single 47 | str = "@123'abc" 48 | yaml = Psych.dump str 49 | assert_match(/---\s*"/, yaml) 50 | assert_equal str, Psych.load(yaml) 51 | end 52 | 53 | def test_single_quote_when_matching_date 54 | pend "Failing on JRuby" if RUBY_PLATFORM =~ /java/ 55 | 56 | lib = File.expand_path("../../../lib", __FILE__) 57 | assert_separately(["-I", lib, "-r", "psych"], __FILE__, __LINE__ + 1, <<~'RUBY') 58 | yml = Psych.dump('2024-11-19') 59 | assert_equal '2024-11-19', Psych.load(yml) 60 | RUBY 61 | end 62 | 63 | def test_plain_when_shorten_than_line_width_and_no_final_line_break 64 | str = "Lorem ipsum" 65 | yaml = Psych.dump str, line_width: 12 66 | assert_match(/---\s*[^>|]+\n/, yaml) 67 | assert_equal str, Psych.load(yaml) 68 | end 69 | 70 | def test_plain_when_shorten_than_line_width_and_with_final_line_break 71 | str = "Lorem ipsum\n" 72 | yaml = Psych.dump str, line_width: 12 73 | assert_match(/---\s*[^>|]+\n/, yaml) 74 | assert_equal str, Psych.load(yaml) 75 | end 76 | 77 | def test_folded_when_longer_than_line_width_and_with_final_line_break 78 | str = "Lorem ipsum dolor sit\n" 79 | yaml = Psych.dump str, line_width: 12 80 | assert_match(/---\s*>\n(.*\n){2}\Z/, yaml) 81 | assert_equal str, Psych.load(yaml) 82 | end 83 | 84 | # http://yaml.org/spec/1.2/2009-07-21/spec.html#id2593651 85 | def test_folded_strip_when_longer_than_line_width_and_no_newlines 86 | str = "Lorem ipsum dolor sit amet, consectetur" 87 | yaml = Psych.dump str, line_width: 12 88 | assert_match(/---\s*>-\n(.*\n){3}\Z/, yaml) 89 | assert_equal str, Psych.load(yaml) 90 | end 91 | 92 | def test_literal_when_inner_and_final_line_break 93 | [ 94 | "Lorem ipsum\ndolor\n", 95 | "Lorem ipsum\nZolor\n", 96 | ].each do |str| 97 | yaml = Psych.dump str, line_width: 12 98 | assert_match(/---\s*\|\n(.*\n){2}\Z/, yaml) 99 | assert_equal str, Psych.load(yaml) 100 | end 101 | end 102 | 103 | # http://yaml.org/spec/1.2/2009-07-21/spec.html#id2593651 104 | def test_literal_strip_when_inner_line_break_and_no_final_line_break 105 | [ 106 | "Lorem ipsum\ndolor", 107 | "Lorem ipsum\nZolor", 108 | ].each do |str| 109 | yaml = Psych.dump str, line_width: 12 110 | assert_match(/---\s*\|-\n(.*\n){2}\Z/, yaml) 111 | assert_equal str, Psych.load(yaml) 112 | end 113 | end 114 | 115 | def test_cycle_x 116 | str = X.new 'abc' 117 | assert_cycle str 118 | end 119 | 120 | def test_dash_dot 121 | assert_cycle '-.' 122 | assert_cycle '+.' 123 | end 124 | 125 | def test_float_with_no_fractional_before_exponent 126 | assert_cycle '0.E+0' 127 | end 128 | 129 | def test_string_subclass_with_anchor 130 | y = Psych.unsafe_load <<-eoyml 131 | --- 132 | body: 133 | string: &70121654388580 !ruby/string 134 | str: ! 'foo' 135 | x: 136 | body: *70121654388580 137 | eoyml 138 | assert_equal({"body"=>{"string"=>"foo", "x"=>{"body"=>"foo"}}}, y) 139 | end 140 | 141 | def test_self_referential_string 142 | y = Psych.unsafe_load <<-eoyml 143 | --- 144 | string: &70121654388580 !ruby/string 145 | str: ! 'foo' 146 | body: *70121654388580 147 | eoyml 148 | 149 | assert_equal({"string"=>"foo"}, y) 150 | value = y['string'] 151 | assert_equal value, value.instance_variable_get(:@body) 152 | end 153 | 154 | def test_another_subclass_with_attributes 155 | y = Psych.unsafe_load Psych.dump Y.new("foo").tap {|o| o.val = 1} 156 | assert_equal "foo", y 157 | assert_equal Y, y.class 158 | assert_equal 1, y.val 159 | end 160 | 161 | def test_backwards_with_syck 162 | x = Psych.unsafe_load "--- !str:#{X.name} foo\n\n" 163 | assert_equal X, x.class 164 | assert_equal 'foo', x 165 | end 166 | 167 | def test_empty_subclass 168 | assert_match "!ruby/string:#{X}", Psych.dump(X.new) 169 | x = Psych.unsafe_load Psych.dump X.new 170 | assert_equal X, x.class 171 | end 172 | 173 | def test_empty_character_subclass 174 | assert_match "!ruby/string:#{Z}", Psych.dump(Z.new) 175 | x = Psych.unsafe_load Psych.dump Z.new 176 | assert_equal Z, x.class 177 | end 178 | 179 | def test_subclass_with_attributes 180 | y = Psych.unsafe_load Psych.dump Y.new.tap {|o| o.val = 1} 181 | assert_equal Y, y.class 182 | assert_equal 1, y.val 183 | end 184 | 185 | def test_string_with_base_60 186 | yaml = Psych.dump '01:03:05' 187 | assert_match "'01:03:05'", yaml 188 | assert_equal '01:03:05', Psych.load(yaml) 189 | end 190 | 191 | def test_nonascii_string_as_binary 192 | string = "hello \x80 world!".dup 193 | string.force_encoding 'ascii-8bit' 194 | yml = Psych.dump string 195 | assert_match(/binary/, yml) 196 | assert_equal string, Psych.load(yml) 197 | end 198 | 199 | def test_binary_string_null 200 | string = "\x00\x92".b 201 | yml = Psych.dump string 202 | assert_match(/binary/, yml) 203 | assert_equal string, Psych.load(yml) 204 | end 205 | 206 | def test_binary_string 207 | string = binary_string 208 | yml = Psych.dump string 209 | assert_match(/binary/, yml) 210 | assert_equal string, Psych.load(yml) 211 | end 212 | 213 | def test_ascii_only_binary_string 214 | string = "non bnry string".b 215 | yml = Psych.dump string 216 | refute_match(/binary/, yml) 217 | assert_equal string, Psych.load(yml) 218 | end 219 | 220 | def test_ascii_only_8bit_string 221 | string = "abc".encode(Encoding::ASCII_8BIT) 222 | yml = Psych.dump string 223 | refute_match(/binary/, yml) 224 | assert_equal string, Psych.load(yml) 225 | end 226 | 227 | def test_string_with_ivars 228 | food = "is delicious".dup 229 | ivar = "on rock and roll" 230 | food.instance_variable_set(:@we_built_this_city, ivar) 231 | 232 | Psych.load Psych.dump food 233 | assert_equal ivar, food.instance_variable_get(:@we_built_this_city) 234 | end 235 | 236 | def test_binary 237 | string = [0, 123,22, 44, 9, 32, 34, 39].pack('C*') 238 | assert_cycle string 239 | end 240 | 241 | def test_float_confusion 242 | assert_cycle '1.' 243 | end 244 | 245 | def binary_string percentage = 0.31, length = 100 246 | string = ''.b 247 | (percentage * length).to_i.times do |i| 248 | string << "\x92".b 249 | end 250 | string << 'a' * (length - string.length) 251 | string 252 | end 253 | end 254 | end 255 | -------------------------------------------------------------------------------- /test/psych/test_stringio.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestStringIO < TestCase 6 | # The superclass of StringIO before Ruby 3.0 was `Data`, 7 | # which can interfere with the Ruby 3.2+ `Data` dumping. 8 | def test_stringio 9 | assert_nothing_raised do 10 | Psych.dump(StringIO.new("foo")) 11 | end 12 | end 13 | end 14 | end 15 | -------------------------------------------------------------------------------- /test/psych/test_struct.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | class PsychStructWithIvar < Struct.new(:foo) 5 | attr_reader :bar 6 | def initialize *args 7 | super 8 | @bar = 'hello' 9 | end 10 | end 11 | 12 | module Psych 13 | class TestStruct < TestCase 14 | class StructSubclass < Struct.new(:foo) 15 | def initialize foo, bar 16 | super(foo) 17 | @bar = bar 18 | end 19 | end 20 | 21 | def test_self_referential_struct 22 | ss = StructSubclass.new(nil, 'foo') 23 | ss.foo = ss 24 | 25 | loaded = Psych.unsafe_load(Psych.dump(ss)) 26 | assert_instance_of(StructSubclass, loaded.foo) 27 | 28 | assert_equal(ss, loaded) 29 | end 30 | 31 | def test_roundtrip 32 | thing = PsychStructWithIvar.new('bar') 33 | struct = Psych.unsafe_load(Psych.dump(thing)) 34 | 35 | assert_equal 'hello', struct.bar 36 | assert_equal 'bar', struct.foo 37 | end 38 | 39 | def test_load 40 | obj = Psych.unsafe_load(<<-eoyml) 41 | --- !ruby/struct:PsychStructWithIvar 42 | :foo: bar 43 | :@bar: hello 44 | eoyml 45 | 46 | assert_equal 'hello', obj.bar 47 | assert_equal 'bar', obj.foo 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/psych/test_symbol.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestSymbol < TestCase 6 | def test_cycle_empty 7 | assert_cycle :'' 8 | end 9 | 10 | def test_cycle_colon 11 | assert_cycle :':' 12 | end 13 | 14 | def test_cycle 15 | assert_cycle :a 16 | end 17 | 18 | def test_stringy 19 | assert_cycle :"1" 20 | end 21 | 22 | def test_load_quoted 23 | assert_equal :"1", Psych.load("--- :'1'\n") 24 | end 25 | end 26 | end 27 | -------------------------------------------------------------------------------- /test/psych/test_tree_builder.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | 4 | module Psych 5 | class TestTreeBuilder < TestCase 6 | def setup 7 | super 8 | @parser = Psych::Parser.new TreeBuilder.new 9 | @parser.parse(<<-eoyml) 10 | %YAML 1.1 11 | --- 12 | - foo 13 | - { 14 | bar : &A !!str baz, 15 | boo : *A 16 | } 17 | - *A 18 | eoyml 19 | @tree = @parser.handler.root 20 | end 21 | 22 | def test_stream 23 | assert_instance_of Nodes::Stream, @tree 24 | assert_location 0, 0, 8, 0, @tree 25 | end 26 | 27 | def test_documents 28 | assert_equal 1, @tree.children.length 29 | assert_instance_of Nodes::Document, @tree.children.first 30 | doc = @tree.children.first 31 | 32 | assert_equal [1,1], doc.version 33 | assert_equal [], doc.tag_directives 34 | assert_equal false, doc.implicit 35 | assert_location 0, 0, 8, 0, doc 36 | end 37 | 38 | def test_sequence 39 | doc = @tree.children.first 40 | assert_equal 1, doc.children.length 41 | 42 | seq = doc.children.first 43 | assert_instance_of Nodes::Sequence, seq 44 | assert_nil seq.anchor 45 | assert_nil seq.tag 46 | assert_equal true, seq.implicit 47 | assert_equal Nodes::Sequence::BLOCK, seq.style 48 | assert_location 2, 0, 8, 0, seq 49 | end 50 | 51 | def test_scalar 52 | doc = @tree.children.first 53 | seq = doc.children.first 54 | 55 | assert_equal 3, seq.children.length 56 | scalar = seq.children.first 57 | assert_instance_of Nodes::Scalar, scalar 58 | assert_equal 'foo', scalar.value 59 | assert_nil scalar.anchor 60 | assert_nil scalar.tag 61 | assert_equal true, scalar.plain 62 | assert_equal false, scalar.quoted 63 | assert_equal Nodes::Scalar::PLAIN, scalar.style 64 | assert_location 2, 2, 2, 5, scalar 65 | end 66 | 67 | def test_mapping 68 | doc = @tree.children.first 69 | seq = doc.children.first 70 | map = seq.children[1] 71 | 72 | assert_instance_of Nodes::Mapping, map 73 | assert_location 3, 2, 6, 1, map 74 | end 75 | 76 | def test_alias 77 | doc = @tree.children.first 78 | seq = doc.children.first 79 | assert_equal 3, seq.children.length 80 | al = seq.children[2] 81 | assert_instance_of Nodes::Alias, al 82 | assert_equal 'A', al.anchor 83 | assert_location 7, 2, 7, 4, al 84 | end 85 | 86 | private 87 | def assert_location(start_line, start_column, end_line, end_column, node) 88 | assert_equal start_line, node.start_line 89 | assert_equal start_column, node.start_column 90 | assert_equal end_line, node.end_line 91 | assert_equal end_column, node.end_column 92 | end 93 | end 94 | end 95 | -------------------------------------------------------------------------------- /test/psych/test_yaml_special_cases.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | 3 | require_relative 'helper' 4 | 5 | require 'stringio' 6 | require 'tempfile' 7 | 8 | module Psych 9 | class TestYamlSpecialCases < TestCase 10 | def setup 11 | super 12 | end 13 | 14 | def test_empty_string 15 | s = "" 16 | assert_equal false, Psych.unsafe_load(s) 17 | assert_equal [], Psych.load_stream(s) 18 | assert_equal [], Psych.safe_load_stream(s) 19 | assert_equal false, Psych.parse(s) 20 | assert_equal [], Psych.parse_stream(s).transform 21 | assert_nil Psych.safe_load(s) 22 | end 23 | 24 | def test_false 25 | s = "false" 26 | assert_equal false, Psych.load(s) 27 | assert_equal [false], Psych.load_stream(s) 28 | assert_equal [false], Psych.safe_load_stream(s) 29 | assert_equal false, Psych.parse(s).transform 30 | assert_equal [false], Psych.parse_stream(s).transform 31 | assert_equal false, Psych.safe_load(s) 32 | end 33 | 34 | def test_n 35 | s = "n" 36 | assert_equal "n", Psych.load(s) 37 | assert_equal ["n"], Psych.load_stream(s) 38 | assert_equal ["n"], Psych.safe_load_stream(s) 39 | assert_equal "n", Psych.parse(s).transform 40 | assert_equal ["n"], Psych.parse_stream(s).transform 41 | assert_equal "n", Psych.safe_load(s) 42 | end 43 | 44 | def test_off 45 | s = "off" 46 | assert_equal false, Psych.load(s) 47 | assert_equal [false], Psych.load_stream(s) 48 | assert_equal [false], Psych.safe_load_stream(s) 49 | assert_equal false, Psych.parse(s).transform 50 | assert_equal [false], Psych.parse_stream(s).transform 51 | assert_equal false, Psych.safe_load(s) 52 | end 53 | 54 | def test_inf 55 | s = "-.inf" 56 | assert_equal(-Float::INFINITY, Psych.load(s)) 57 | assert_equal([-Float::INFINITY], Psych.load_stream(s)) 58 | assert_equal([-Float::INFINITY], Psych.safe_load_stream(s)) 59 | assert_equal(-Float::INFINITY, Psych.parse(s).transform) 60 | assert_equal([-Float::INFINITY], Psych.parse_stream(s).transform) 61 | assert_equal(-Float::INFINITY, Psych.safe_load(s)) 62 | end 63 | 64 | def test_NaN 65 | s = ".NaN" 66 | assert Psych.load(s).nan? 67 | assert Psych.load_stream(s).first.nan? 68 | assert Psych.safe_load_stream(s).first.nan? 69 | assert Psych.parse(s).transform.nan? 70 | assert Psych.parse_stream(s).transform.first.nan? 71 | assert Psych.safe_load(s).nan? 72 | end 73 | 74 | def test_0xC 75 | s = "0xC" 76 | assert_equal 12, Psych.load(s) 77 | assert_equal [12], Psych.load_stream(s) 78 | assert_equal [12], Psych.safe_load_stream(s) 79 | assert_equal 12, Psych.parse(s).transform 80 | assert_equal [12], Psych.parse_stream(s).transform 81 | assert_equal 12, Psych.safe_load(s) 82 | end 83 | 84 | def test_arrows 85 | s = "<<" 86 | assert_equal "<<", Psych.load(s) 87 | assert_equal ["<<"], Psych.load_stream(s) 88 | assert_equal ["<<"], Psych.safe_load_stream(s) 89 | assert_equal "<<", Psych.parse(s).transform 90 | assert_equal ["<<"], Psych.parse_stream(s).transform 91 | assert_equal "<<", Psych.safe_load(s) 92 | end 93 | 94 | def test_arrows_hash 95 | s = "<<: {}" 96 | assert_equal({}, Psych.load(s)) 97 | assert_equal [{}], Psych.load_stream(s) 98 | assert_equal [{}], Psych.safe_load_stream(s) 99 | assert_equal({}, Psych.parse(s).transform) 100 | assert_equal [{}], Psych.parse_stream(s).transform 101 | assert_equal({}, Psych.safe_load(s)) 102 | end 103 | 104 | def test_thousand 105 | s = "- 1000\n- +1000\n- 1_000" 106 | assert_equal [1000, 1000, 1000], Psych.load(s) 107 | assert_equal [[1000, 1000, 1000]], Psych.load_stream(s) 108 | assert_equal [[1000, 1000, 1000]], Psych.safe_load_stream(s) 109 | assert_equal [1000, 1000, 1000], Psych.parse(s).transform 110 | assert_equal [[1000, 1000, 1000]], Psych.parse_stream(s).transform 111 | assert_equal [1000, 1000, 1000], Psych.safe_load(s) 112 | end 113 | 114 | def test_8 115 | s = "[8, 08, 0o10, 010]" 116 | assert_equal [8, "08", "0o10", 8], Psych.load(s) 117 | assert_equal [[8, "08", "0o10", 8]], Psych.load_stream(s) 118 | assert_equal [[8, "08", "0o10", 8]], Psych.safe_load_stream(s) 119 | assert_equal [8, "08", "0o10", 8], Psych.parse(s).transform 120 | assert_equal [[8, "08", "0o10", 8]], Psych.parse_stream(s).transform 121 | assert_equal [8, "08", "0o10", 8], Psych.safe_load(s) 122 | end 123 | 124 | def test_null 125 | s = "null" 126 | assert_nil Psych.load(s) 127 | assert_equal [nil], Psych.load_stream(s) 128 | assert_equal [nil], Psych.safe_load_stream(s) 129 | assert_nil Psych.parse(s).transform 130 | assert_equal [nil], Psych.parse_stream(s).transform 131 | assert_nil Psych.safe_load(s) 132 | end 133 | 134 | private 135 | 136 | def special_case_cycle(object) 137 | %w[load load_stream parse parse_stream safe_load].map do |m| 138 | Psych.public_send(m, object) 139 | end 140 | end 141 | end 142 | end 143 | -------------------------------------------------------------------------------- /test/psych/test_yamldbm.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | require 'tmpdir' 4 | 5 | begin 6 | require 'yaml/dbm' 7 | rescue LoadError 8 | end 9 | 10 | module Psych 11 | ::Psych::DBM = ::YAML::DBM unless defined?(::Psych::DBM) 12 | 13 | class YAMLDBMTest < TestCase 14 | def setup 15 | @dir = Dir.mktmpdir("rubytest-file") 16 | File.chown(-1, Process.gid, @dir) 17 | @yamldbm_file = make_tmp_filename("yamldbm") 18 | @yamldbm = YAML::DBM.new(@yamldbm_file) 19 | end 20 | 21 | def teardown 22 | @yamldbm.clear 23 | @yamldbm.close 24 | FileUtils.remove_entry_secure @dir 25 | end 26 | 27 | def make_tmp_filename(prefix) 28 | @dir + "/" + prefix + File.basename(__FILE__) + ".#{$$}.test" 29 | end 30 | 31 | def test_store 32 | @yamldbm.store('a','b') 33 | @yamldbm.store('c','d') 34 | assert_equal 'b', @yamldbm['a'] 35 | assert_equal 'd', @yamldbm['c'] 36 | assert_nil @yamldbm['e'] 37 | end 38 | 39 | def test_store_using_carret 40 | @yamldbm['a'] = 'b' 41 | @yamldbm['c'] = 'd' 42 | assert_equal 'b', @yamldbm['a'] 43 | assert_equal 'd', @yamldbm['c'] 44 | assert_nil @yamldbm['e'] 45 | end 46 | 47 | def test_to_a 48 | @yamldbm['a'] = 'b' 49 | @yamldbm['c'] = 'd' 50 | assert_equal([['a','b'],['c','d']], @yamldbm.to_a.sort) 51 | end 52 | 53 | def test_to_hash 54 | @yamldbm['a'] = 'b' 55 | @yamldbm['c'] = 'd' 56 | assert_equal({'a'=>'b','c'=>'d'}, @yamldbm.to_hash) 57 | end 58 | 59 | def test_has_value? 60 | @yamldbm['a'] = 'b' 61 | @yamldbm['c'] = 'd' 62 | assert_equal true, @yamldbm.has_value?('b') 63 | assert_equal true, @yamldbm.has_value?('d') 64 | assert_equal false, @yamldbm.has_value?('f') 65 | end 66 | 67 | # Note: 68 | # YAML::DBM#index makes warning from internal of ::DBM#index. 69 | # It says 'DBM#index is deprecated; use DBM#key', but DBM#key 70 | # behaves not same as DBM#index. 71 | # 72 | # def test_index 73 | # @yamldbm['a'] = 'b' 74 | # @yamldbm['c'] = 'd' 75 | # assert_equal 'a', @yamldbm.index('b') 76 | # assert_equal 'c', @yamldbm.index('d') 77 | # assert_nil @yamldbm.index('f') 78 | # end 79 | 80 | def test_key 81 | @yamldbm['a'] = 'b' 82 | @yamldbm['c'] = 'd' 83 | assert_equal 'a', @yamldbm.key('b') 84 | assert_equal 'c', @yamldbm.key('d') 85 | assert_nil @yamldbm.key('f') 86 | end 87 | 88 | def test_fetch 89 | assert_equal('bar', @yamldbm['foo']='bar') 90 | assert_equal('bar', @yamldbm.fetch('foo')) 91 | assert_nil @yamldbm.fetch('bar') 92 | assert_equal('baz', @yamldbm.fetch('bar', 'baz')) 93 | assert_equal('foobar', @yamldbm.fetch('bar') {|key| 'foo' + key }) 94 | end 95 | 96 | def test_shift 97 | @yamldbm['a'] = 'b' 98 | @yamldbm['c'] = 'd' 99 | assert_equal([['a','b'], ['c','d']], 100 | [@yamldbm.shift, @yamldbm.shift].sort) 101 | assert_nil @yamldbm.shift 102 | end 103 | 104 | def test_invert 105 | @yamldbm['a'] = 'b' 106 | @yamldbm['c'] = 'd' 107 | assert_equal({'b'=>'a','d'=>'c'}, @yamldbm.invert) 108 | end 109 | 110 | def test_update 111 | @yamldbm['a'] = 'b' 112 | @yamldbm['c'] = 'd' 113 | @yamldbm.update({'c'=>'d','e'=>'f'}) 114 | assert_equal 'b', @yamldbm['a'] 115 | assert_equal 'd', @yamldbm['c'] 116 | assert_equal 'f', @yamldbm['e'] 117 | end 118 | 119 | def test_replace 120 | @yamldbm['a'] = 'b' 121 | @yamldbm['c'] = 'd' 122 | @yamldbm.replace({'c'=>'d','e'=>'f'}) 123 | assert_nil @yamldbm['a'] 124 | assert_equal 'd', @yamldbm['c'] 125 | assert_equal 'f', @yamldbm['e'] 126 | end 127 | 128 | def test_delete 129 | @yamldbm['a'] = 'b' 130 | @yamldbm['c'] = 'd' 131 | assert_equal 'b', @yamldbm.delete('a') 132 | assert_nil @yamldbm['a'] 133 | assert_equal 'd', @yamldbm['c'] 134 | assert_nil @yamldbm.delete('e') 135 | end 136 | 137 | def test_delete_if 138 | @yamldbm['a'] = 'b' 139 | @yamldbm['c'] = 'd' 140 | @yamldbm['e'] = 'f' 141 | 142 | @yamldbm.delete_if {|k,v| k == 'a'} 143 | assert_nil @yamldbm['a'] 144 | assert_equal 'd', @yamldbm['c'] 145 | assert_equal 'f', @yamldbm['e'] 146 | 147 | @yamldbm.delete_if {|k,v| v == 'd'} 148 | assert_nil @yamldbm['c'] 149 | assert_equal 'f', @yamldbm['e'] 150 | 151 | @yamldbm.delete_if {|k,v| false } 152 | assert_equal 'f', @yamldbm['e'] 153 | end 154 | 155 | def test_reject 156 | @yamldbm['a'] = 'b' 157 | @yamldbm['c'] = 'd' 158 | @yamldbm['e'] = 'f' 159 | assert_equal({'c'=>'d','e'=>'f'}, @yamldbm.reject {|k,v| k == 'a'}) 160 | assert_equal({'a'=>'b','e'=>'f'}, @yamldbm.reject {|k,v| v == 'd'}) 161 | assert_equal({'a'=>'b','c'=>'d','e'=>'f'}, @yamldbm.reject {false}) 162 | end 163 | 164 | def test_values 165 | assert_equal [], @yamldbm.values 166 | @yamldbm['a'] = 'b' 167 | @yamldbm['c'] = 'd' 168 | assert_equal ['b','d'], @yamldbm.values.sort 169 | end 170 | 171 | def test_values_at 172 | @yamldbm['a'] = 'b' 173 | @yamldbm['c'] = 'd' 174 | assert_equal ['b','d'], @yamldbm.values_at('a','c') 175 | end 176 | 177 | def test_selsct 178 | @yamldbm['a'] = 'b' 179 | @yamldbm['c'] = 'd' 180 | @yamldbm['e'] = 'f' 181 | assert_equal(['b','d'], @yamldbm.select('a','c')) 182 | end 183 | 184 | def test_selsct_with_block 185 | @yamldbm['a'] = 'b' 186 | @yamldbm['c'] = 'd' 187 | @yamldbm['e'] = 'f' 188 | assert_equal([['a','b']], @yamldbm.select {|k,v| k == 'a'}) 189 | assert_equal([['c','d']], @yamldbm.select {|k,v| v == 'd'}) 190 | assert_equal([], @yamldbm.select {false}) 191 | end 192 | end 193 | end if defined?(YAML::DBM) && defined?(Psych) 194 | -------------------------------------------------------------------------------- /test/psych/test_yamlstore.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require_relative 'helper' 3 | require 'yaml/store' 4 | require 'tmpdir' 5 | 6 | module Psych 7 | class YAML::Store 8 | alias :old_load :load 9 | 10 | def load(content) 11 | table = YAML.load(content, fallback: false) 12 | if table == false 13 | {} 14 | else 15 | table 16 | end 17 | end 18 | end 19 | 20 | unless defined?(Psych::Store) 21 | Psych::Store = YAML::Store 22 | end 23 | 24 | class YAMLStoreTest < TestCase 25 | def setup 26 | if defined?(::PStore) 27 | @dir = Dir.mktmpdir("rubytest-file") 28 | File.chown(-1, Process.gid, @dir) 29 | @yamlstore_file = make_tmp_filename("yamlstore") 30 | @yamlstore = YAML::Store.new(@yamlstore_file) 31 | else 32 | omit "PStore is not available" 33 | end 34 | end 35 | 36 | def teardown 37 | FileUtils.remove_entry_secure(@dir) if @dir 38 | end 39 | 40 | def make_tmp_filename(prefix) 41 | @dir + "/" + prefix + File.basename(__FILE__) + ".#{$$}.test" 42 | end 43 | 44 | def test_opening_new_file_in_readonly_mode_should_result_in_empty_values 45 | @yamlstore.transaction(true) do 46 | assert_nil @yamlstore["foo"] 47 | assert_nil @yamlstore["bar"] 48 | end 49 | end 50 | 51 | def test_opening_new_file_in_readwrite_mode_should_result_in_empty_values 52 | @yamlstore.transaction do 53 | assert_nil @yamlstore["foo"] 54 | assert_nil @yamlstore["bar"] 55 | end 56 | end 57 | 58 | def test_data_should_be_loaded_correctly_when_in_readonly_mode 59 | @yamlstore.transaction do 60 | @yamlstore["foo"] = "bar" 61 | end 62 | @yamlstore.transaction(true) do 63 | assert_equal "bar", @yamlstore["foo"] 64 | end 65 | end 66 | 67 | def test_data_should_be_loaded_correctly_when_in_readwrite_mode 68 | @yamlstore.transaction do 69 | @yamlstore["foo"] = "bar" 70 | end 71 | @yamlstore.transaction do 72 | assert_equal "bar", @yamlstore["foo"] 73 | end 74 | end 75 | 76 | def test_changes_after_commit_are_discarded 77 | @yamlstore.transaction do 78 | @yamlstore["foo"] = "bar" 79 | @yamlstore.commit 80 | @yamlstore["foo"] = "baz" 81 | end 82 | @yamlstore.transaction(true) do 83 | assert_equal "bar", @yamlstore["foo"] 84 | end 85 | end 86 | 87 | def test_changes_are_not_written_on_abort 88 | @yamlstore.transaction do 89 | @yamlstore["foo"] = "bar" 90 | @yamlstore.abort 91 | end 92 | @yamlstore.transaction(true) do 93 | assert_nil @yamlstore["foo"] 94 | end 95 | end 96 | 97 | def test_writing_inside_readonly_transaction_raises_error 98 | assert_raise(PStore::Error) do 99 | @yamlstore.transaction(true) do 100 | @yamlstore["foo"] = "bar" 101 | end 102 | end 103 | end 104 | end if defined?(::PStore) 105 | end if defined?(Psych) 106 | -------------------------------------------------------------------------------- /test/psych/visitors/test_depth_first.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'psych/helper' 3 | 4 | module Psych 5 | module Visitors 6 | class TestDepthFirst < TestCase 7 | class Collector < Struct.new(:calls) 8 | def initialize(calls = []) 9 | super 10 | end 11 | 12 | def call obj 13 | calls << obj 14 | end 15 | end 16 | 17 | def test_scalar 18 | collector = Collector.new 19 | visitor = Visitors::DepthFirst.new collector 20 | visitor.accept Psych.parse_stream '--- hello' 21 | 22 | assert_equal 3, collector.calls.length 23 | end 24 | 25 | def test_sequence 26 | collector = Collector.new 27 | visitor = Visitors::DepthFirst.new collector 28 | visitor.accept Psych.parse_stream "---\n- hello" 29 | 30 | assert_equal 4, collector.calls.length 31 | end 32 | 33 | def test_mapping 34 | collector = Collector.new 35 | visitor = Visitors::DepthFirst.new collector 36 | visitor.accept Psych.parse_stream "---\nhello: world" 37 | 38 | assert_equal 5, collector.calls.length 39 | end 40 | 41 | def test_alias 42 | collector = Collector.new 43 | visitor = Visitors::DepthFirst.new collector 44 | visitor.accept Psych.parse_stream "--- &yay\n- foo\n- *yay\n" 45 | 46 | assert_equal 5, collector.calls.length 47 | end 48 | end 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /test/psych/visitors/test_emitter.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'psych/helper' 3 | 4 | module Psych 5 | module Visitors 6 | class TestEmitter < TestCase 7 | def setup 8 | super 9 | @io = StringIO.new 10 | @visitor = Visitors::Emitter.new @io 11 | end 12 | 13 | def test_options 14 | io = StringIO.new 15 | visitor = Visitors::Emitter.new io, :indentation => 3 16 | 17 | s = Nodes::Stream.new 18 | doc = Nodes::Document.new 19 | mapping = Nodes::Mapping.new 20 | m2 = Nodes::Mapping.new 21 | m2.children << Nodes::Scalar.new('a') 22 | m2.children << Nodes::Scalar.new('b') 23 | 24 | mapping.children << Nodes::Scalar.new('key') 25 | mapping.children << m2 26 | doc.children << mapping 27 | s.children << doc 28 | 29 | visitor.accept s 30 | assert_match(/^[ ]{3}a/, io.string) 31 | end 32 | 33 | def test_stream 34 | s = Nodes::Stream.new 35 | @visitor.accept s 36 | assert_equal '', @io.string 37 | end 38 | 39 | def test_document 40 | s = Nodes::Stream.new 41 | doc = Nodes::Document.new [1,1] 42 | scalar = Nodes::Scalar.new 'hello world' 43 | 44 | doc.children << scalar 45 | s.children << doc 46 | 47 | @visitor.accept s 48 | 49 | assert_match(/1.1/, @io.string) 50 | assert_equal @io.string, s.yaml 51 | end 52 | 53 | def test_document_implicit_end 54 | s = Nodes::Stream.new 55 | doc = Nodes::Document.new 56 | mapping = Nodes::Mapping.new 57 | mapping.children << Nodes::Scalar.new('key') 58 | mapping.children << Nodes::Scalar.new('value') 59 | doc.children << mapping 60 | s.children << doc 61 | 62 | @visitor.accept s 63 | 64 | assert_include(@io.string, "key: value") 65 | assert_equal @io.string, s.yaml 66 | assert_not_include(s.yaml, "...") 67 | end 68 | 69 | def test_scalar 70 | s = Nodes::Stream.new 71 | doc = Nodes::Document.new 72 | scalar = Nodes::Scalar.new 'hello world' 73 | 74 | doc.children << scalar 75 | s.children << doc 76 | 77 | @visitor.accept s 78 | 79 | assert_include(@io.string, "hello") 80 | assert_equal @io.string, s.yaml 81 | end 82 | 83 | def test_scalar_with_tag 84 | s = Nodes::Stream.new 85 | doc = Nodes::Document.new 86 | scalar = Nodes::Scalar.new 'hello world', nil, '!str', false, false, 5 87 | 88 | doc.children << scalar 89 | s.children << doc 90 | 91 | @visitor.accept s 92 | 93 | assert_include(@io.string, "str") 94 | assert_include(@io.string, "hello") 95 | assert_equal @io.string, s.yaml 96 | end 97 | 98 | def test_sequence 99 | s = Nodes::Stream.new 100 | doc = Nodes::Document.new 101 | scalar = Nodes::Scalar.new 'hello world' 102 | seq = Nodes::Sequence.new 103 | 104 | seq.children << scalar 105 | doc.children << seq 106 | s.children << doc 107 | 108 | @visitor.accept s 109 | 110 | assert_include(@io.string, "- hello") 111 | assert_equal @io.string, s.yaml 112 | end 113 | 114 | def test_mapping 115 | s = Nodes::Stream.new 116 | doc = Nodes::Document.new 117 | mapping = Nodes::Mapping.new 118 | mapping.children << Nodes::Scalar.new('key') 119 | mapping.children << Nodes::Scalar.new('value') 120 | doc.children << mapping 121 | s.children << doc 122 | 123 | @visitor.accept s 124 | 125 | assert_include(@io.string, "key: value") 126 | assert_equal @io.string, s.yaml 127 | end 128 | 129 | def test_alias 130 | s = Nodes::Stream.new 131 | doc = Nodes::Document.new 132 | mapping = Nodes::Mapping.new 133 | mapping.children << Nodes::Scalar.new('key', 'A') 134 | mapping.children << Nodes::Alias.new('A') 135 | doc.children << mapping 136 | s.children << doc 137 | 138 | @visitor.accept s 139 | 140 | assert_include(@io.string, "&A key: \*A") 141 | assert_equal @io.string, s.yaml 142 | end 143 | end 144 | end 145 | end 146 | -------------------------------------------------------------------------------- /test/psych/visitors/test_yaml_tree.rb: -------------------------------------------------------------------------------- 1 | # frozen_string_literal: true 2 | require 'psych/helper' 3 | require 'delegate' 4 | 5 | module Psych 6 | module Visitors 7 | class TestYAMLTree < TestCase 8 | class TestDelegatorClass < Delegator 9 | def initialize(obj); super; @obj = obj; end 10 | def __setobj__(obj); @obj = obj; end 11 | def __getobj__; @obj if defined?(@obj); end 12 | end 13 | 14 | class TestSimpleDelegatorClass < SimpleDelegator 15 | end 16 | 17 | def setup 18 | super 19 | @v = Visitors::YAMLTree.create 20 | end 21 | 22 | def test_tree_can_be_called_twice 23 | @v.start 24 | @v << Object.new 25 | t = @v.tree 26 | assert_equal t, @v.tree 27 | end 28 | 29 | def test_yaml_tree_can_take_an_emitter 30 | io = StringIO.new 31 | e = Psych::Emitter.new io 32 | v = Visitors::YAMLTree.create({}, e) 33 | v.start 34 | v << "hello world" 35 | v.finish 36 | 37 | assert_include io.string, "hello world" 38 | end 39 | 40 | def test_binary_formatting 41 | gif = "GIF89a\f\x00\f\x00\x84\x00\x00\xFF\xFF\xF7\xF5\xF5\xEE\xE9\xE9\xE5fff\x00\x00\x00\xE7\xE7\xE7^^^\xF3\xF3\xED\x8E\x8E\x8E\xE0\xE0\xE0\x9F\x9F\x9F\x93\x93\x93\xA7\xA7\xA7\x9E\x9E\x9Eiiiccc\xA3\xA3\xA3\x84\x84\x84\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9\xFF\xFE\xF9!\xFE\x0EMade with GIMP\x00,\x00\x00\x00\x00\f\x00\f\x00\x00\x05, \x8E\x810\x9E\xE3@\x14\xE8i\x10\xC4\xD1\x8A\b\x1C\xCF\x80M$z\xEF\xFF0\x85p\xB8\xB01f\r\e\xCE\x01\xC3\x01\x1E\x10' \x82\n\x01\x00;".b 42 | @v << gif 43 | scalar = @v.tree.children.first.children.first 44 | assert_equal Psych::Nodes::Scalar::LITERAL, scalar.style 45 | end 46 | 47 | def test_object_has_no_class 48 | yaml = Psych.dump(Object.new) 49 | assert(Psych.dump(Object.new) !~ /Object/, yaml) 50 | end 51 | 52 | def test_struct_const 53 | foo = Struct.new("Foo", :bar) 54 | assert_cycle foo.new('bar') 55 | Struct.instance_eval { remove_const(:Foo) } 56 | end 57 | 58 | A = Struct.new(:foo) 59 | 60 | def test_struct 61 | assert_cycle A.new('bar') 62 | end 63 | 64 | def test_struct_anon 65 | s = Struct.new(:foo).new('bar') 66 | obj = Psych.unsafe_load(Psych.dump(s)) 67 | assert_equal s.foo, obj.foo 68 | end 69 | 70 | def test_override_method 71 | s = Struct.new(:method).new('override') 72 | obj = Psych.unsafe_load(Psych.dump(s)) 73 | assert_equal s.method, obj.method 74 | end 75 | 76 | D = Data.define(:foo) unless RUBY_VERSION < "3.2" 77 | 78 | def test_data 79 | omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2" 80 | assert_cycle D.new('bar') 81 | end 82 | 83 | def test_data_anon 84 | omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2" 85 | d = Data.define(:foo).new('bar') 86 | obj = Psych.unsafe_load(Psych.dump(d)) 87 | assert_equal d.foo, obj.foo 88 | end 89 | 90 | def test_data_override_method 91 | omit "Data requires ruby >= 3.2" if RUBY_VERSION < "3.2" 92 | d = Data.define(:method).new('override') 93 | obj = Psych.unsafe_load(Psych.dump(d)) 94 | assert_equal d.method, obj.method 95 | end 96 | 97 | def test_exception 98 | ex = Exception.new 'foo' 99 | loaded = Psych.unsafe_load(Psych.dump(ex)) 100 | 101 | assert_equal ex.message, loaded.message 102 | assert_equal ex.class, loaded.class 103 | end 104 | 105 | def test_regexp 106 | assert_cycle(/foo/) 107 | assert_cycle(/foo/i) 108 | assert_cycle(/foo/mx) 109 | end 110 | 111 | def test_time 112 | t = Time.now 113 | assert_equal t, Psych.unsafe_load(Psych.dump(t)) 114 | end 115 | 116 | def test_date 117 | date = Date.strptime('2002-12-14', '%Y-%m-%d') 118 | assert_cycle date 119 | end 120 | 121 | def test_rational 122 | assert_cycle Rational(1,2) 123 | end 124 | 125 | def test_complex 126 | assert_cycle Complex(1,2) 127 | end 128 | 129 | def test_scalar 130 | assert_cycle 'foo' 131 | assert_cycle ':foo' 132 | assert_cycle '' 133 | assert_cycle ':' 134 | end 135 | 136 | def test_boolean 137 | assert_cycle true 138 | assert_cycle 'true' 139 | assert_cycle false 140 | assert_cycle 'false' 141 | end 142 | 143 | def test_range_inclusive 144 | assert_cycle 1..2 145 | end 146 | 147 | def test_range_exclusive 148 | assert_cycle 1...2 149 | end 150 | 151 | def test_anon_class 152 | assert_raise(TypeError) do 153 | @v.accept Class.new 154 | end 155 | 156 | assert_raise(TypeError) do 157 | Psych.dump(Class.new) 158 | end 159 | end 160 | 161 | def test_hash 162 | assert_cycle('a' => 'b') 163 | end 164 | 165 | def test_list 166 | assert_cycle(%w{ a b }) 167 | assert_cycle([1, 2.2]) 168 | end 169 | 170 | def test_symbol 171 | assert_cycle :foo 172 | end 173 | 174 | def test_int 175 | assert_cycle 1 176 | assert_cycle(-1) 177 | assert_cycle '1' 178 | assert_cycle '-1' 179 | end 180 | 181 | def test_float 182 | assert_cycle 1.2 183 | assert_cycle '1.2' 184 | 185 | assert Psych.load(Psych.dump(0.0 / 0.0)).nan? 186 | assert_equal 1, Psych.load(Psych.dump(1 / 0.0)).infinite? 187 | assert_equal(-1, Psych.load(Psych.dump(-1 / 0.0)).infinite?) 188 | end 189 | 190 | def test_string 191 | assert_include(Psych.dump({'a' => '017'}), "'017'") 192 | assert_include(Psych.dump({'a' => '019'}), "'019'") 193 | assert_include(Psych.dump({'a' => '01818'}), "'01818'") 194 | end 195 | 196 | # http://yaml.org/type/null.html 197 | def test_nil 198 | assert_cycle nil 199 | assert_nil Psych.load('null') 200 | assert_nil Psych.load('Null') 201 | assert_nil Psych.load('NULL') 202 | assert_nil Psych.load('~') 203 | assert_equal({'foo' => nil}, Psych.load('foo: ')) 204 | 205 | assert_cycle 'null' 206 | assert_cycle 'nUll' 207 | assert_cycle '~' 208 | end 209 | 210 | def test_delegator 211 | assert_cycle(TestDelegatorClass.new([1, 2, 3])) 212 | end 213 | 214 | def test_simple_delegator 215 | assert_cycle(TestSimpleDelegatorClass.new([1, 2, 3])) 216 | end 217 | end 218 | end 219 | end 220 | --------------------------------------------------------------------------------