Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add YAML module #528

Closed
wants to merge 9 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Expand Up @@ -4,4 +4,5 @@ deno.d.ts
node_modules
package.json
package-lock.json
yarn.lock
.vscode
21 changes: 21 additions & 0 deletions encoding/README.md
Expand Up @@ -217,3 +217,24 @@ const obj = {
};
const tomlString = stringify(obj);
```

## YAML

YAML parser / dumper for Deno

Heavily inspired from [js-yaml]

### Example

See [`./yaml/example`](./yaml/example) folder and [js-yaml] repository.

### :warning: Limitations

- `binary` type is currently not stable
- `function`, `regexp`, and `undefined` type are currently not supported

# Basic usage

TBD

[js-yaml]: https://github.com/nodeca/js-yaml
1 change: 1 addition & 0 deletions encoding/test.ts
Expand Up @@ -2,3 +2,4 @@
import "./hex_test.ts";
import "./toml_test.ts";
import "./csv_test.ts";
import "./yaml_test.ts";
8 changes: 8 additions & 0 deletions encoding/yaml.ts
@@ -0,0 +1,8 @@
// Ported from js-yaml v3.13.1:
// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.

export * from "./yaml/parse.ts";
export * from "./yaml/stringify.ts";
export * from "./yaml/schema/mod.ts";
76 changes: 76 additions & 0 deletions encoding/yaml/Mark.ts
@@ -0,0 +1,76 @@
// Ported from js-yaml v3.13.1:
// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.

import { repeat } from "./utils.ts";
lsagetlethias marked this conversation as resolved.
Show resolved Hide resolved

export class Mark {
constructor(
public name: string,
public buffer: string,
public position: number,
public line: number,
public column: number
) {}

public getSnippet(indent = 4, maxLength = 75): string | null {
if (!this.buffer) return null;

let head = "";
let start = this.position;

while (
start > 0 &&
"\x00\r\n\x85\u2028\u2029".indexOf(this.buffer.charAt(start - 1)) === -1
) {
start -= 1;
if (this.position - start > maxLength / 2 - 1) {
head = " ... ";
start += 5;
break;
}
}

let tail = "";
let end = this.position;

while (
end < this.buffer.length &&
"\x00\r\n\x85\u2028\u2029".indexOf(this.buffer.charAt(end)) === -1
) {
end += 1;
if (end - this.position > maxLength / 2 - 1) {
tail = " ... ";
end -= 5;
break;
}
}

const snippet = this.buffer.slice(start, end);
return `${repeat(" ", indent)}${head}${snippet}${tail}\n${repeat(
" ",
indent + this.position - start + head.length
)}^`;
}

public toString(compact?: boolean): string {
let snippet,
where = "";

if (this.name) {
where += `in "${this.name}" `;
}

where += `at line ${this.line + 1}, column ${this.column + 1}`;

if (!compact) {
snippet = this.getSnippet();

if (snippet) {
where += `:\n${snippet}`;
}
}

return where;
}
}
101 changes: 101 additions & 0 deletions encoding/yaml/Schema.ts
@@ -0,0 +1,101 @@
// Ported from js-yaml v3.13.1:
// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.

import { YAMLError } from "./error.ts";
import { KindType, Type } from "./type.ts";
import { ArrayObject, Any } from "./utils.ts";

function compileList(
schema: Schema,
name: "implicit" | "explicit",
result: Type[]
): Type[] {
const exclude: number[] = [];

for (const includedSchema of schema.include) {
result = compileList(includedSchema, name, result);
}

for (const currentType of schema[name]) {
for (
let previousIndex = 0;
previousIndex < result.length;
previousIndex++
) {
const previousType = result[previousIndex];
if (
previousType.tag === currentType.tag &&
previousType.kind === currentType.kind
) {
exclude.push(previousIndex);
}
}

result.push(currentType);
}

return result.filter((type, index): unknown => !exclude.includes(index));
}

export type TypeMap = { [k in KindType | "fallback"]: ArrayObject<Type> };
function compileMap(...typesList: Type[][]): TypeMap {
const result: TypeMap = {
fallback: {},
mapping: {},
scalar: {},
sequence: {}
};

for (const types of typesList) {
for (const type of types) {
if (type.kind !== null) {
result[type.kind][type.tag] = result["fallback"][type.tag] = type;
}
}
}
return result;
}

export class Schema implements SchemaDefinition {
public static SCHEMA_DEFAULT?: Schema;

public implicit: Type[];
public explicit: Type[];
public include: Schema[];

public compiledImplicit: Type[];
public compiledExplicit: Type[];
public compiledTypeMap: TypeMap;

constructor(definition: SchemaDefinition) {
this.explicit = definition.explicit || [];
this.implicit = definition.implicit || [];
this.include = definition.include || [];

for (const type of this.implicit) {
if (type.loadKind && type.loadKind !== "scalar") {
throw new YAMLError(
// eslint-disable-next-line max-len
"There is a non-scalar type in the implicit list of a schema. Implicit resolving of such types is not supported."
);
}
}

this.compiledImplicit = compileList(this, "implicit", []);
this.compiledExplicit = compileList(this, "explicit", []);
this.compiledTypeMap = compileMap(
this.compiledImplicit,
this.compiledExplicit
);
}

public static create(): void {}
}

export interface SchemaDefinition {
implicit?: Any[];
explicit?: Type[];
include?: Schema[];
}
11 changes: 11 additions & 0 deletions encoding/yaml/State.ts
@@ -0,0 +1,11 @@
// Ported from js-yaml v3.13.1:
// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.

import { SchemaDefinition } from "./schema.ts";
import { DEFAULT_SCHEMA } from "./schema/mod.ts";

export abstract class State {
constructor(public schema: SchemaDefinition = DEFAULT_SCHEMA) {}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that these objects are so well organized but... splitting them into 4 files means there are 4 more deps to download. I'd consider putting them into single file.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do you mean all 4 kinds of schema to be grouped as one file ?

55 changes: 55 additions & 0 deletions encoding/yaml/Type.ts
@@ -0,0 +1,55 @@
// Ported from js-yaml v3.13.1:
// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.

import { ArrayObject, Any } from "./utils.ts";

export type KindType = "sequence" | "scalar" | "mapping";
export type StyleVariant = "lowercase" | "uppercase" | "camelcase" | "decimal";
export type RepresentFn = (data: Any, style?: StyleVariant) => Any;

const DEFAULT_RESOLVE = (): boolean => true;
const DEFAULT_CONSTRUCT = (data: Any): Any => data;

interface TypeOptions {
kind: KindType;
resolve?: (data: Any) => boolean;
construct?: (data: string) => Any;
instanceOf?: Any;
predicate?: (data: object) => boolean;
represent?: RepresentFn | ArrayObject<RepresentFn>;
defaultStyle?: StyleVariant;
styleAliases?: ArrayObject;
}

function checkTagFormat(tag: string): string {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

seems superfluous

return tag;
}

export class Type {
public tag: string;
public kind: KindType | null = null;
public instanceOf: Any;
public predicate?: (data: object) => boolean;
public represent?: RepresentFn | ArrayObject<RepresentFn>;
public defaultStyle?: StyleVariant;
public styleAliases?: ArrayObject;
public loadKind?: KindType;

constructor(tag: string, options?: TypeOptions) {
this.tag = checkTagFormat(tag);
if (options) {
this.kind = options.kind;
this.resolve = options.resolve || DEFAULT_RESOLVE;
this.construct = options.construct || DEFAULT_CONSTRUCT;
this.instanceOf = options.instanceOf;
this.predicate = options.predicate;
this.represent = options.represent;
this.defaultStyle = options.defaultStyle;
this.styleAliases = options.styleAliases;
}
}
public resolve: (data?: Any) => boolean = (): boolean => true;
public construct: (data?: Any) => Any = (data): Any => data;
}
17 changes: 17 additions & 0 deletions encoding/yaml/__test__/00-units.js
@@ -0,0 +1,17 @@
// Ported from js-yaml v3.13.1:
// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.

var path = require('path');
var fs = require('fs');


suite('Units', function () {
var directory = path.resolve(__dirname, 'units');

fs.readdirSync(directory).forEach(function (file) {
if (path.extname(file) === '.js') {
require(path.resolve(directory, file));
}
});
});
39 changes: 39 additions & 0 deletions encoding/yaml/__test__/10-loader.js
@@ -0,0 +1,39 @@
// Ported from js-yaml v3.13.1:
// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.

var assert = require('assert');
var path = require('path');
var fs = require('fs');
var yaml = require('../');

var TEST_SCHEMA = require('./support/schema').TEST_SCHEMA;


suite('Loader', function () {
var samplesDir = path.resolve(__dirname, 'samples-common');

fs.readdirSync(samplesDir).forEach(function (jsFile) {
if (path.extname(jsFile) !== '.js') return; // continue

var yamlFile = path.resolve(samplesDir, path.basename(jsFile, '.js') + '.yml');

test(path.basename(jsFile, '.js'), function () {
var expected = require(path.resolve(samplesDir, jsFile));
var actual = [];

yaml.loadAll(fs.readFileSync(yamlFile, { encoding: 'utf8' }), function (doc) { actual.push(doc); }, {
filename: yamlFile,
schema: TEST_SCHEMA
});

if (actual.length === 1) actual = actual[0];

if (typeof expected === 'function') {
expected.call(this, actual);
} else {
assert.deepEqual(actual, expected);
}
});
});
});
35 changes: 35 additions & 0 deletions encoding/yaml/__test__/11-load-errors.js
@@ -0,0 +1,35 @@
// Ported from js-yaml v3.13.1:
// https://github.com/nodeca/js-yaml/commit/665aadda42349dcae869f12040d9b10ef18d12da
// Copyright 2018-2019 the Deno authors. All rights reserved. MIT license.

var assert = require('assert');
var path = require('path');
var fs = require('fs');
var yaml = require('../');

var TEST_SCHEMA = require('./support/schema').TEST_SCHEMA;


suite('Load errors', function () {
var samplesDir = path.resolve(__dirname, 'samples-load-errors');

fs.readdirSync(samplesDir).forEach(function (sampleName) {
var yamlFile = path.resolve(samplesDir, sampleName);

test(path.basename(sampleName, '.yml'), function () {
var yamlSource = fs.readFileSync(yamlFile, { encoding: 'utf8' });

assert.throws(function () {
yaml.loadAll(
yamlSource,
function () {},
{
filename: yamlFile,
schema: TEST_SCHEMA,
onWarning: function (e) { throw e; }
}
);
}, yaml.YAMLException, yamlFile);
});
});
});