Skip to content

Instantly share code, notes, and snippets.

@learosema
Last active September 19, 2024 21:30
Show Gist options
  • Save learosema/578c8501009344560a6000e046c26251 to your computer and use it in GitHub Desktop.
Save learosema/578c8501009344560a6000e046c26251 to your computer and use it in GitHub Desktop.
smolYAML

smolYAML

smolYAML is a minimal zero-dependency yaml parser. Well, subset-of-yaml parser.

What can it parse?

Objects

name: Lea
role: Frontend Dev
pronouns: ["she", "her"]

Returns:

{
  name: "Lea",
  role: "Frontend Dev",
  pronouns: ["she", "her"]
}

Lists

- Foo
- Bar
- Baz

Nested Objects

favoriteColors:
  - red
  - green
  - blue
enemies: 
  - name: Goblin Mage
    hitpoints: 56
    mana: 100
    abilities:
      - Fireball
      - Heal
  - name: Bulky Orc
    hitpoints: 200
    mana: 0
    rage: 2000
    abilities:
      - Smash
      - Thrash
      - Bash

Limitations

As soon as it encounters Brackets, Doublequotes ({ } [ ] "), it will just hand it over to JSON.parse. It doesn't yet support multi-line json.

License

Lea wrote this. If you find this useful, you can buy me a coffee in return. Do wtf you want with it :)

// smolYAML is a subset of YAML
const parseValue = (str) => str === 'NaN' ? NaN : str === 'undefined' ? undefined : /^\-?\d+(?:\.\d+)?(?:e\d+)?$/.test(str) ||
['true', 'false', 'null'].includes(str) || /^['"\{\}\[\]\/]/.test(str) ? JSON.parse(str) : str;
function buildObject(lines) {
if (lines.length === 0) {
return null;
}
if (lines.length === 1 && lines[0].t === 3) {
return parseValue(lines[0].v)
}
const result = lines[0].t === 0 ? {} : [];
let ref = result;
const stack = [];
let temp = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.t >= 3) {
throw new Error('unsupported Syntax');
}
const nextLine = i < lines.length - 1 ? lines[i + 1] : null;
if (line.t === 2 && line.k && ref instanceof Array) {
temp = {[line.k]: parseValue(line.v)};
ref.push(temp);
stack.push([ref, line.i]);
ref = temp;
}
if (line.t === 0 && line.k && ref instanceof Array === false) {
if (line.v) {
ref[line.k] = parseValue(line.v);
} else {
ref[line.k] = nextLine.t === 0 ? {} : [];
}
if (nextLine && nextLine.i > line.i) {
stack.push([ref, line.i]);
ref = ref[line.k];
continue;
}
}
if (line.t === 1 && line.v && ref instanceof Array) {
ref.push(parseValue(line.v));
}
if (nextLine && nextLine.i < line.i) {
let indent = line.i;
while (indent > nextLine.i) {
const stackItem = stack.pop();
if (!stackItem) {
throw new Error('stack underflow');
}
const [formerRef, formerIndent] = stackItem;
ref = formerRef;
indent = formerIndent;
}
}
}
return result;
}
export function smolYAML(str) {
const analyzed = str.split(/\r?\n/).map(line => {
const m0 = line.match(/^(\s*)(\w+):\s*(.+)?$/);
if (m0) {
return {t: 0, i: m0[1].length, k: m0[2], v: m0[3]};
}
const m2 = line.match(/^(\s*)- (\w+):\s*(.+)$/)
if (m2) {
return {t: 2, i: m2[1].length, k: m2[2], v: m2[3]};
}
const m1 = line.match(/^(\s*)- (.+)$/);
if (m1) {
return {t:1, i: m1[1].length, v: m1[2]};
}
if (line.trim() === '' || /\s*#|(\/\/)/.test(line)) {
return undefined;
}
const m3 = line.match(/^(\s*)(.+)\s*$/);
return {t: 3, i: m3[1].length, v: m3[2]};
}).filter(Boolean);
return buildObject(analyzed);
}
import { describe, it } from 'node:test';
import assert from 'node:assert/strict';
import { smolYAML } from '../../src/transforms/smolyaml.js';
const yamlPlain = `
title: Lea is a Frontend Developer
age: 42
details: Front of the Frontend
fullstack: Do not call me Full Stack Dev, although I can see sharp.
`
const plain = {
title: 'Lea is a Frontend Developer',
age: 42,
details: 'Front of the Frontend',
fullstack: 'Do not call me Full Stack Dev, although I can see sharp.'
};
const yamlWithJson = `
name: "Lea"
pronouns: ["she", "her"]
`
const withJson = {
"name": "Lea",
"pronouns": [
"she",
"her"
]
};
const yamlEnumeration = `
- 1
- 1
- 2
- 3
- 5
- 8
- 13
- 21
`
const enumeration = [1, 1, 2, 3, 5, 8, 13, 21];
const yamlNested = `
favoriteColors:
- red
- rebeccapurple
- deepskyblue
`
const nested = {
"favoriteColors": [
"red",
"rebeccapurple",
"deepskyblue"
]
}
const yamlNestedObjectwithArray = `
enemies:
- name: Goblin Mage
hitpoints: 56
mana: 100
abilities:
- Fireball
- Heal
- name: Bulky Orc
hitpoints: 200
mana: 0
rage: 2000
abilities:
- Smash
- Thrash
- Bash
`
const nestedObjectWithArray = {
"enemies": [
{
"name": "Goblin Mage",
"hitpoints": 56,
"mana": 100,
"abilities": [
"Fireball",
"Heal"
]
},
{
"name": "Bulky Orc",
"hitpoints": 200,
"mana": 0,
"rage": 2000,
"abilities": [
"Smash",
"Thrash",
"Bash"
]
}
]
};
const yamlDOOM = `
welcome:
to:
the:
pyramid:
of:
doom: true
pixel:
of:
destiny: true
`
const doom = {
"welcome": {
"to": {
"the": {
"pyramid": {
"of": {
"doom": true
}
},
"pixel": {
"of": {
"destiny": true
}
}
}
}
}
};
describe('smol YAML', () => {
it('should parse primitives', () => {
assert.equal(smolYAML('true'), true);
assert.equal(smolYAML('false'), false);
assert.equal(smolYAML('I am a string'), 'I am a string');
assert.equal(smolYAML('"I am a quoted string with \\"escaped\\" double quotes"'), 'I am a quoted string with "escaped" double quotes');
assert.equal(smolYAML('42'), 42);
assert.equal(smolYAML('3.1415'), 3.1415);
assert.equal(smolYAML('-3.14e2'), -314);
assert(typeof smolYAML('undefined') === 'undefined');
assert(Number.isNaN(smolYAML('NaN')));
})
it('should parse plain object definitions correctly', () => {
assert.deepEqual(smolYAML(yamlPlain), plain);
});
it('should parse objects with json correctly', () => {
assert.deepEqual(smolYAML(yamlWithJson), withJson);
});
it('should parse enumerations correctly', () => {
assert.deepEqual(smolYAML(yamlEnumeration), enumeration);
});
it('should parse nested YAML correctly', () => {
assert.deepEqual(smolYAML(yamlNested), nested);
});
it('should parse nested YAML (array of objects) correctly', () => {
assert.deepEqual(smolYAML(yamlNestedObjectwithArray), nestedObjectWithArray);
});
it('should parse deeply nested YAML correctly', () => {
assert.deepEqual(smolYAML(yamlDOOM), doom)
});
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment