diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bb7c65a..78cae154 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,18 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [4.1.0] - WIP +### Added +- Types are now exported as `yaml.types.XXX`. +- Every type now has options object with original arguments kept as they were + (see `yaml.types.int.options` as an example). + +### Changed +- Schema.extend now keeps old type order in case of conflicts + (e.g. Schema.extend([ a, b, c ]).extend([ b, a, d ]) is now ordered as `abcd` instead of `cbad`). + + ## [4.0.0] - 2021-01-03 ### Changed - Check [migration guide](migrate_v3_to_v4.md) to see details for all breaking changes. diff --git a/examples/.eslintrc.yml b/examples/.eslintrc.yml index 43158490..834f19a9 100644 --- a/examples/.eslintrc.yml +++ b/examples/.eslintrc.yml @@ -2,4 +2,4 @@ env: es6: true parserOptions: - ecmaVersion: 2017 + ecmaVersion: 2020 diff --git a/examples/int_type_override.js b/examples/int_type_override.js new file mode 100644 index 00000000..b495fe49 --- /dev/null +++ b/examples/int_type_override.js @@ -0,0 +1,59 @@ +// This example overrides built-in !!int type to return BigInt instead of a Number +// + +'use strict'; + +/*global BigInt*/ +/*eslint-disable no-console*/ + +const util = require('util'); +const yaml = require('../'); + + +// keep most of the original `int` options as is +let options = Object.assign({}, yaml.types.int.options); + +options.construct = data => { + let value = data, sign = 1n, ch; + + if (value.indexOf('_') !== -1) { + value = value.replace(/_/g, ''); + } + + ch = value[0]; + + if (ch === '-' || ch === '+') { + if (ch === '-') sign = -1n; + value = value.slice(1); + ch = value[0]; + } + + return sign * BigInt(value); +}; + + +options.predicate = object => { + return Object.prototype.toString.call(object) === '[object BigInt]' || + yaml.types.int.options.predicate(object); +}; + + +let BigIntType = new yaml.Type('tag:yaml.org,2002:int', options); + +const SCHEMA = yaml.DEFAULT_SCHEMA.extend({ implicit: [ BigIntType ] }); + +const data = ` +bigint: -12_345_678_901_234_567_890 +`; + +const loaded = yaml.load(data, { schema: SCHEMA }); + +console.log('Parsed as:'); +console.log('-'.repeat(70)); +console.log(util.inspect(loaded, false, 20, true)); + +console.log(''); +console.log(''); +console.log('Dumped as:'); +console.log('-'.repeat(70)); +console.log(yaml.dump(loaded, { schema: SCHEMA })); diff --git a/index.js b/index.js index 486f6afa..bcb7eba7 100644 --- a/index.js +++ b/index.js @@ -24,6 +24,23 @@ module.exports.loadAll = loader.loadAll; module.exports.dump = dumper.dump; module.exports.YAMLException = require('./lib/exception'); +// Re-export all types in case user wants to create custom schema +module.exports.types = { + binary: require('./lib/type/binary'), + float: require('./lib/type/float'), + map: require('./lib/type/map'), + null: require('./lib/type/null'), + pairs: require('./lib/type/pairs'), + set: require('./lib/type/set'), + timestamp: require('./lib/type/timestamp'), + bool: require('./lib/type/bool'), + int: require('./lib/type/int'), + merge: require('./lib/type/merge'), + omap: require('./lib/type/omap'), + seq: require('./lib/type/seq'), + str: require('./lib/type/str') +}; + // Removed functions from JS-YAML 3.0.x module.exports.safeLoad = renamed('safeLoad', 'load'); module.exports.safeLoadAll = renamed('safeLoadAll', 'loadAll'); diff --git a/lib/schema.js b/lib/schema.js index ae193562..65b41f40 100644 --- a/lib/schema.js +++ b/lib/schema.js @@ -6,25 +6,25 @@ var YAMLException = require('./exception'); var Type = require('./type'); -function compileList(schema, name, result) { - var exclude = []; +function compileList(schema, name) { + var result = []; schema[name].forEach(function (currentType) { + var newIndex = result.length; + result.forEach(function (previousType, previousIndex) { if (previousType.tag === currentType.tag && previousType.kind === currentType.kind && previousType.multi === currentType.multi) { - exclude.push(previousIndex); + newIndex = previousIndex; } }); - result.push(currentType); + result[newIndex] = currentType; }); - return result.filter(function (type, index) { - return exclude.indexOf(index) === -1; - }); + return result; } @@ -110,8 +110,8 @@ Schema.prototype.extend = function extend(definition) { result.implicit = (this.implicit || []).concat(implicit); result.explicit = (this.explicit || []).concat(explicit); - result.compiledImplicit = compileList(result, 'implicit', []); - result.compiledExplicit = compileList(result, 'explicit', []); + result.compiledImplicit = compileList(result, 'implicit'); + result.compiledExplicit = compileList(result, 'explicit'); result.compiledTypeMap = compileMap(result.compiledImplicit, result.compiledExplicit); return result; diff --git a/lib/type.js b/lib/type.js index 5928702f..5e57877f 100644 --- a/lib/type.js +++ b/lib/type.js @@ -45,6 +45,7 @@ function Type(tag, options) { }); // TODO: Add tag format check. + this.options = options; // keep original options in case user wants to extend this type later this.tag = tag; this.kind = options['kind'] || null; this.resolve = options['resolve'] || function () { return true; }; diff --git a/test/.eslintrc.yml b/test/.eslintrc.yml index 9b94212e..5fa543ad 100644 --- a/test/.eslintrc.yml +++ b/test/.eslintrc.yml @@ -4,7 +4,7 @@ env: es6: true parserOptions: - ecmaVersion: 2017 + ecmaVersion: 2020 rules: no-undefined: 0 diff --git a/test/issues/0586.js b/test/issues/0586.js new file mode 100644 index 00000000..5160f53b --- /dev/null +++ b/test/issues/0586.js @@ -0,0 +1,49 @@ +'use strict'; + +/* eslint-disable no-use-before-define, new-cap */ + + +const assert = require('assert'); +const yaml = require('../../'); + + +it('Should allow custom formatting through implicit custom tags', function () { + function CustomDump(data, opts) { + if (!(this instanceof CustomDump)) return new CustomDump(data, opts); + this.data = data; + this.opts = opts; + } + + CustomDump.prototype.represent = function () { + let result = yaml.dump(this.data, Object.assign({ replacer, schema }, this.opts)); + result = result.trim(); + if (result.includes('\n')) result = '\n' + result; + return result; + }; + + + let CustomDumpType = new yaml.Type('!format', { + kind: 'scalar', + resolve: () => false, + instanceOf: CustomDump, + represent: d => d.represent() + }); + + + let schema = yaml.DEFAULT_SCHEMA.extend({ implicit: [ CustomDumpType ] }); + + function replacer(key, value) { + if (key === '') return value; // top-level, don't change this + if (key === 'flow_choices') return CustomDump(value, { flowLevel: 0 }); + if (key === 'block_choices') return CustomDump(value, { flowLevel: Infinity }); + return value; // default + } + + let result = CustomDump({ flow_choices : [ 1, 2 ], block_choices: [ 4, 5 ] }).represent().trim(); + + assert.strictEqual(result, ` +flow_choices: [1, 2] +block_choices: +- 4 +- 5`.replace(/^\n/, '')); +}); diff --git a/test/issues/0614.js b/test/issues/0614.js new file mode 100644 index 00000000..18d9e119 --- /dev/null +++ b/test/issues/0614.js @@ -0,0 +1,47 @@ +'use strict'; + +/* global BigInt */ + + +const assert = require('assert'); +const yaml = require('../../'); + + +it('Should allow int override', function () { + let options = Object.assign({}, yaml.types.int.options); + + options.construct = data => { + let value = data, sign = 1n, ch; + + if (value.indexOf('_') !== -1) { + value = value.replace(/_/g, ''); + } + + ch = value[0]; + + if (ch === '-' || ch === '+') { + if (ch === '-') sign = -1n; + value = value.slice(1); + ch = value[0]; + } + + return sign * BigInt(value); + }; + + + let BigIntType = new yaml.Type('tag:yaml.org,2002:int', options); + + const SCHEMA = yaml.DEFAULT_SCHEMA.extend({ implicit: [ BigIntType ] }); + + const data = ` +int: -123_456_789 +bigint: -12_345_678_901_234_567_890 +float: -12_345_678_901_234_567_890.1234 +`; + + assert.deepStrictEqual(yaml.load(data, { schema: SCHEMA }), { + int: -123456789n, + bigint: -12345678901234567890n, + float: -12345678901234567000 // precision loss expected + }); +});