🎩 You're Invited:Meet the Socket team at Black Hat in Las Vegas, August 3-6.RSVP
Sign In

toml

Package Overview
Dependencies
Maintainers
1
Versions
37
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

toml - npm Package Compare versions

Comparing version
3.0.0
to
4.0.0
+26
.github/workflows/ci.yml
name: CI
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [20, 22, 24]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- run: npm install
- run: npm run build
- run: npm test
- name: Spec compliance tests
run: |
git clone --depth 1 https://github.com/toml-lang/toml-test.git .binarymuse/toml-test
npm run test:spec
"use strict";
/**
* toml-test spec compliance runner
*
* Runs the official toml-lang/toml-test suite against this parser and reports
* results broken down by category and individual test.
*
* Usage:
* node test/spec-test.js # summary
* node test/spec-test.js --failures # show failure details
* node test/spec-test.js --all # show all test results
* node test/spec-test.js --json # machine-readable output
*/
var fs = require("fs");
var path = require("path");
var toml = require("../index");
var parser = require("../lib/parser");
// ---------------------------------------------------------------------------
// Configuration
// ---------------------------------------------------------------------------
var TESTS_DIR = path.join(__dirname, "..", ".binarymuse", "toml-test", "tests");
var FILES_LIST = path.join(TESTS_DIR, "files-toml-1.0.0");
// Known failures due to JS platform limitations, not parser bugs.
// These are excluded from the pass/fail exit code.
var KNOWN_FAILURES = [
// Number can't represent 64-bit integers beyond Number.MAX_SAFE_INTEGER
"valid/integer/long",
// Node.js handles UTF-8 decoding at the engine level; invalid byte sequences
// are replaced before the parser sees the data, so we can't reject them.
"invalid/encoding/bad-codepoint",
"invalid/encoding/bad-utf8-in-comment",
"invalid/encoding/bad-utf8-in-multiline",
"invalid/encoding/bad-utf8-in-multiline-literal",
"invalid/encoding/bad-utf8-in-string",
"invalid/encoding/bad-utf8-in-string-literal",
];
// ---------------------------------------------------------------------------
// Tagged JSON conversion
//
// The toml-test suite expects every leaf value wrapped as:
// { "type": "<toml_type>", "value": "<string_representation>" }
// Tables become plain JSON objects, arrays become JSON arrays.
// ---------------------------------------------------------------------------
// ---------------------------------------------------------------------------
// AST type extraction
//
// Walk the parser's AST nodes to build a map of path → TOML type.
// This lets us distinguish Float from Integer even when the JS number
// is identical (e.g., 3e2 = 300 is Float, not Integer).
// ---------------------------------------------------------------------------
function buildTypeMap(nodes) {
var typeMap = {};
var currentPath = [];
for (var i = 0; i < nodes.length; i++) {
var node = nodes[i];
if (node.type === "ObjectPath") {
currentPath = node.value;
} else if (node.type === "ArrayPath") {
currentPath = node.value;
} else if (node.type === "Assign") {
var keys = Array.isArray(node.key) ? node.key : [node.key];
var fullPath = currentPath.concat(keys).join(".");
collectTypes(typeMap, fullPath, node.value);
}
}
return typeMap;
}
function collectTypes(typeMap, path, valueNode) {
if (valueNode.type === "Float" || valueNode.type === "Integer") {
typeMap[path] = valueNode.type === "Float" ? "float" : "integer";
} else if (valueNode.type === "Date") {
typeMap[path] = "datetime";
} else if (valueNode.type === "LocalDateTime") {
typeMap[path] = "datetime-local";
} else if (valueNode.type === "LocalDate") {
typeMap[path] = "date-local";
} else if (valueNode.type === "LocalTime") {
typeMap[path] = "time-local";
} else if (valueNode.type === "Array") {
for (var i = 0; i < valueNode.value.length; i++) {
collectTypes(typeMap, path + "." + i, valueNode.value[i]);
}
} else if (valueNode.type === "InlineTable") {
for (var j = 0; j < valueNode.value.length; j++) {
var entry = valueNode.value[j];
var entryKeys = Array.isArray(entry.key) ? entry.key : [entry.key];
collectTypes(typeMap, path + "." + entryKeys.join("."), entry.value);
}
}
}
function toTaggedJSON(value, typeMap, currentPath) {
if (value === null || value === undefined) {
return value;
}
if (value instanceof Date) {
return { type: "datetime", value: formatDatetime(value) };
}
if (Array.isArray(value)) {
return value.map(function (item, i) {
return toTaggedJSON(item, typeMap, currentPath + "." + i);
});
}
if (typeof value === "object") {
var result = {};
var keys = Object.keys(value);
for (var i = 0; i < keys.length; i++) {
var childPath = currentPath ? currentPath + "." + keys[i] : keys[i];
result[keys[i]] = toTaggedJSON(value[keys[i]], typeMap, childPath);
}
return result;
}
if (typeof value === "string") {
// Check if this is a local date/time type
var strType = typeMap ? typeMap[currentPath] : null;
if (strType === "datetime-local" || strType === "date-local" || strType === "time-local") {
return { type: strType, value: value };
}
return { type: "string", value: value };
}
if (typeof value === "boolean") {
return { type: "bool", value: String(value) };
}
if (typeof value === "number") {
if (Number.isNaN(value)) {
return { type: "float", value: "nan" };
}
if (!Number.isFinite(value)) {
return { type: "float", value: value > 0 ? "inf" : "-inf" };
}
// Use AST type map if available to distinguish float from integer
var astType = typeMap ? typeMap[currentPath] : null;
if (astType === "float") {
return { type: "float", value: formatFloat(value) };
}
if (astType === "integer") {
return { type: "integer", value: String(value) };
}
// Fallback heuristic when type map doesn't have info
if (Number.isInteger(value)) {
return { type: "integer", value: String(value) };
}
return { type: "float", value: formatFloat(value) };
}
return { type: "string", value: String(value) };
}
function formatDatetime(d) {
return d.toISOString().replace(/\.000Z$/, "Z").replace("Z", "+00:00");
}
function formatFloat(f) {
// toml-test expects floats to have a decimal point or be in scientific notation
var s = String(f);
if (s.indexOf(".") === -1 && s.indexOf("e") === -1 && s.indexOf("E") === -1) {
s = s + ".0";
}
return s;
}
// ---------------------------------------------------------------------------
// Deep comparison of tagged JSON structures
// ---------------------------------------------------------------------------
function deepEqual(actual, expected, path) {
path = path || "";
if (actual === expected) return null;
if (actual === null || actual === undefined) {
return path + ": expected " + JSON.stringify(expected) + " but got " + actual;
}
if (expected === null || expected === undefined) {
return path + ": expected " + expected + " but got " + JSON.stringify(actual);
}
// Both are tagged values
if (expected.type && expected.value !== undefined && typeof expected.type === "string") {
if (!actual.type) {
return path + ": expected tagged value {type:" + expected.type + "} but got " + JSON.stringify(actual);
}
if (actual.type !== expected.type) {
return path + ": type mismatch: got '" + actual.type + "' expected '" + expected.type + "'";
}
// Compare values with type-aware logic
if (expected.type === "float") {
return compareFloats(actual.value, expected.value, path);
}
if (expected.type === "datetime" || expected.type === "datetime-local" ||
expected.type === "date-local" || expected.type === "time-local") {
return compareDatetimes(actual.value, expected.value, path);
}
if (actual.value !== expected.value) {
return path + ": value mismatch: got " + JSON.stringify(actual.value) + " expected " + JSON.stringify(expected.value);
}
return null;
}
// Both are arrays
if (Array.isArray(expected)) {
if (!Array.isArray(actual)) {
return path + ": expected array but got " + typeof actual;
}
if (actual.length !== expected.length) {
return path + ": array length mismatch: got " + actual.length + " expected " + expected.length;
}
for (var i = 0; i < expected.length; i++) {
var err = deepEqual(actual[i], expected[i], path + "[" + i + "]");
if (err) return err;
}
return null;
}
// Both are objects (tables)
if (typeof expected === "object" && typeof actual === "object") {
var expectedKeys = Object.keys(expected).sort();
var actualKeys = Object.keys(actual).sort();
// Check for missing keys
for (var j = 0; j < expectedKeys.length; j++) {
if (actualKeys.indexOf(expectedKeys[j]) === -1) {
return path + ": missing key '" + expectedKeys[j] + "'";
}
}
// Check for extra keys
for (var k = 0; k < actualKeys.length; k++) {
if (expectedKeys.indexOf(actualKeys[k]) === -1) {
return path + ": unexpected key '" + actualKeys[k] + "'";
}
}
// Compare values
for (var m = 0; m < expectedKeys.length; m++) {
var key = expectedKeys[m];
var err2 = deepEqual(actual[key], expected[key], path ? path + "." + key : key);
if (err2) return err2;
}
return null;
}
return path + ": " + JSON.stringify(actual) + " !== " + JSON.stringify(expected);
}
function compareFloats(actual, expected, path) {
if (expected === "nan" || expected === "+nan" || expected === "-nan") {
if (actual === "nan" || actual === "+nan" || actual === "-nan") return null;
return path + ": expected nan but got " + actual;
}
if (expected === "inf" || expected === "+inf") {
if (actual === "inf" || actual === "+inf") return null;
return path + ": expected inf but got " + actual;
}
if (expected === "-inf") {
if (actual === "-inf") return null;
return path + ": expected -inf but got " + actual;
}
// Numeric comparison for floats to handle precision
var actualNum = parseFloat(actual);
var expectedNum = parseFloat(expected);
if (Math.abs(actualNum - expectedNum) < 1e-15) return null;
if (actual !== expected) {
return path + ": float mismatch: got " + actual + " expected " + expected;
}
return null;
}
function compareDatetimes(actual, expected, path) {
// Normalize and compare datetimes
if (actual === expected) return null;
// Try parsing both to see if they represent the same instant
var da = new Date(actual);
var de = new Date(expected);
if (!isNaN(da.getTime()) && !isNaN(de.getTime()) && da.getTime() === de.getTime()) {
return null;
}
if (actual !== expected) {
return path + ": datetime mismatch: got " + JSON.stringify(actual) + " expected " + JSON.stringify(expected);
}
return null;
}
// ---------------------------------------------------------------------------
// Test runner
// ---------------------------------------------------------------------------
function loadFileList() {
var content = fs.readFileSync(FILES_LIST, "utf8");
var lines = content.trim().split("\n").filter(function (l) { return l.length > 0; });
var valid = [];
var invalid = [];
lines.forEach(function (line) {
if (line.indexOf("valid/") === 0) {
// strip the .toml extension to get test name
valid.push(line.replace(/\.toml$/, "").replace(/\.json$/, ""));
} else if (line.indexOf("invalid/") === 0) {
invalid.push(line.replace(/\.toml$/, ""));
}
});
// Deduplicate (valid tests appear twice: once for .toml, once for .json)
valid = valid.filter(function (v, i, self) { return self.indexOf(v) === i; });
invalid = invalid.filter(function (v, i, self) { return self.indexOf(v) === i; });
return { valid: valid, invalid: invalid };
}
function runValidTest(testPath) {
var tomlFile = path.join(TESTS_DIR, testPath + ".toml");
var jsonFile = path.join(TESTS_DIR, testPath + ".json");
var tomlContent, expectedJSON;
try {
tomlContent = fs.readFileSync(tomlFile, "utf8");
} catch (e) {
return { pass: false, error: "Could not read .toml file: " + e.message };
}
try {
expectedJSON = JSON.parse(fs.readFileSync(jsonFile, "utf8"));
} catch (e) {
return { pass: false, error: "Could not read/parse .json file: " + e.message };
}
try {
var astNodes = parser.parse(tomlContent);
var typeMap = buildTypeMap(astNodes);
var parsed = toml.parse(tomlContent);
var tagged = toTaggedJSON(parsed, typeMap, "");
var diff = deepEqual(tagged, expectedJSON);
if (diff) {
return { pass: false, error: diff };
}
return { pass: true };
} catch (e) {
return { pass: false, error: "Parse error: " + e.message };
}
}
function runInvalidTest(testPath) {
var tomlFile = path.join(TESTS_DIR, testPath + ".toml");
var tomlContent;
try {
tomlContent = fs.readFileSync(tomlFile, "utf8");
} catch (e) {
return { pass: false, error: "Could not read .toml file: " + e.message };
}
try {
toml.parse(tomlContent);
return { pass: false, error: "Expected parse error but parsing succeeded" };
} catch (e) {
return { pass: true };
}
}
function categorize(testPath) {
// e.g. "valid/string/escapes" -> "string"
// e.g. "invalid/array/no-close-01" -> "array"
var parts = testPath.split("/");
if (parts.length >= 3) return parts[1];
return parts[1] ? parts[1].replace(/\..*/, "").replace(/-\d+$/, "") : "root";
}
function run() {
var args = process.argv.slice(2);
var showFailures = args.indexOf("--failures") !== -1;
var showAll = args.indexOf("--all") !== -1;
var jsonOutput = args.indexOf("--json") !== -1;
var files = loadFileList();
var results = {
valid: { pass: 0, fail: 0, tests: [], byCategory: {} },
invalid: { pass: 0, fail: 0, tests: [], byCategory: {} }
};
// Run valid tests
files.valid.forEach(function (testPath) {
var result = runValidTest(testPath);
var cat = categorize(testPath);
if (!results.valid.byCategory[cat]) {
results.valid.byCategory[cat] = { pass: 0, fail: 0, failures: [] };
}
if (result.pass) {
results.valid.pass++;
results.valid.byCategory[cat].pass++;
} else {
results.valid.fail++;
results.valid.byCategory[cat].fail++;
results.valid.byCategory[cat].failures.push({ test: testPath, error: result.error });
}
results.valid.tests.push({ test: testPath, pass: result.pass, error: result.error });
});
// Run invalid tests
files.invalid.forEach(function (testPath) {
var result = runInvalidTest(testPath);
var cat = categorize(testPath);
if (!results.invalid.byCategory[cat]) {
results.invalid.byCategory[cat] = { pass: 0, fail: 0, failures: [] };
}
if (result.pass) {
results.invalid.pass++;
results.invalid.byCategory[cat].pass++;
} else {
results.invalid.fail++;
results.invalid.byCategory[cat].fail++;
results.invalid.byCategory[cat].failures.push({ test: testPath, error: result.error });
}
results.invalid.tests.push({ test: testPath, pass: result.pass, error: result.error });
});
if (jsonOutput) {
console.log(JSON.stringify(results, null, 2));
return;
}
// Print results
var totalPass = results.valid.pass + results.invalid.pass;
var totalFail = results.valid.fail + results.invalid.fail;
var total = totalPass + totalFail;
console.log("==========================================================");
console.log(" TOML Spec Compliance Test Results (TOML v1.0.0)");
console.log("==========================================================");
console.log("");
console.log(" Total: " + totalPass + "/" + total + " passed (" + pct(totalPass, total) + ")");
console.log(" Valid: " + results.valid.pass + "/" + (results.valid.pass + results.valid.fail) + " passed (" + pct(results.valid.pass, results.valid.pass + results.valid.fail) + ")");
console.log(" Invalid: " + results.invalid.pass + "/" + (results.invalid.pass + results.invalid.fail) + " passed (" + pct(results.invalid.pass, results.invalid.pass + results.invalid.fail) + ")");
console.log("");
// Valid tests by category
console.log("----------------------------------------------------------");
console.log(" VALID TESTS (should parse successfully)");
console.log("----------------------------------------------------------");
printCategoryTable(results.valid.byCategory);
console.log("");
console.log("----------------------------------------------------------");
console.log(" INVALID TESTS (should reject)");
console.log("----------------------------------------------------------");
printCategoryTable(results.invalid.byCategory);
// Show failures if requested
if (showFailures || showAll) {
console.log("");
console.log("----------------------------------------------------------");
console.log(" FAILURE DETAILS");
console.log("----------------------------------------------------------");
var validFailures = results.valid.tests.filter(function (t) { return !t.pass; });
if (validFailures.length > 0) {
console.log("");
console.log(" Valid tests that FAILED (" + validFailures.length + "):");
validFailures.forEach(function (t) {
console.log(" FAIL " + t.test);
console.log(" " + t.error);
});
}
var invalidFailures = results.invalid.tests.filter(function (t) { return !t.pass; });
if (invalidFailures.length > 0) {
console.log("");
console.log(" Invalid tests that should have REJECTED (" + invalidFailures.length + "):");
invalidFailures.forEach(function (t) {
console.log(" FAIL " + t.test);
console.log(" " + t.error);
});
}
}
if (showAll) {
console.log("");
console.log("----------------------------------------------------------");
console.log(" ALL PASSING TESTS");
console.log("----------------------------------------------------------");
results.valid.tests.filter(function (t) { return t.pass; }).forEach(function (t) {
console.log(" PASS " + t.test);
});
results.invalid.tests.filter(function (t) { return t.pass; }).forEach(function (t) {
console.log(" PASS " + t.test);
});
}
// Determine unexpected failures (not in KNOWN_FAILURES list)
var allFailures = results.valid.tests.concat(results.invalid.tests).filter(function (t) { return !t.pass; });
var knownCount = 0;
var unexpectedFailures = [];
allFailures.forEach(function (t) {
if (KNOWN_FAILURES.indexOf(t.test) !== -1) {
knownCount++;
} else {
unexpectedFailures.push(t);
}
});
console.log("");
if (knownCount > 0) {
console.log(" Known failures (JS platform limitations): " + knownCount);
}
if (unexpectedFailures.length > 0) {
console.log(" UNEXPECTED FAILURES: " + unexpectedFailures.length);
unexpectedFailures.forEach(function (t) {
console.log(" FAIL " + t.test);
console.log(" " + t.error);
});
}
console.log("");
// Exit with error code only for unexpected failures
process.exit(unexpectedFailures.length > 0 ? 1 : 0);
}
function pct(n, total) {
if (total === 0) return "N/A";
return (n / total * 100).toFixed(1) + "%";
}
function printCategoryTable(categories) {
var cats = Object.keys(categories).sort();
var maxLen = 0;
cats.forEach(function (c) { if (c.length > maxLen) maxLen = c.length; });
cats.forEach(function (cat) {
var c = categories[cat];
var total = c.pass + c.fail;
var pad = new Array(maxLen - cat.length + 1).join(" ");
var status = c.fail === 0 ? " ✓" : " ✗";
console.log(" " + status + " " + cat + pad + " " + c.pass + "/" + total + " (" + pct(c.pass, total) + ")");
});
}
run();
+83
-7
var toml = require('./index');
var fs = require('fs');
var data = fs.readFileSync('./test/example.toml', 'utf8');
var iterations = 1000;
var classic = fs.readFileSync('./test/example.toml', 'utf8');
var comprehensive = [
'# TOML v1.0.0 benchmark payload',
'title = "Benchmark"',
'',
'[owner]',
'name = "Test User"',
'bio = """Multi-line \\',
' string with continuation"""',
"regex = '<\\i\\c*\\s*>'",
'dob = 1979-05-27T07:32:00Z',
'updated = 1979-05-27 07:32:00-07:00',
'',
'[database]',
'server = "192.168.1.1"',
'ports = [8001, 8001, 8003]',
'connection_max = 5_000',
'max_temp = 87.1',
'enabled = true',
'hex = 0xDEADBEEF',
'oct = 0o755',
'bin = 0b11010110',
'pos_inf = inf',
'neg_inf = -inf',
'not_a_number = nan',
'',
'[servers.alpha]',
'ip = "10.0.0.1"',
'dc = "eqdc10"',
'',
'[servers.beta]',
'ip = "10.0.0.2"',
'dc = "eqdc10"',
'',
'[clients]',
'data = [["gamma", "delta"], [1, 2]]',
'mixed = [1, "two", 3.0, true]',
'hosts = ["alpha", "omega"]',
'',
'[inline]',
'point = {x = 1, y = 2}',
'name = {first = "Tom", last = "Preston-Werner"}',
'animal = {type.name = "pug"}',
'',
'fruit.apple.color = "red"',
'fruit.apple.taste.sweet = true',
'',
'[dates]',
'odt1 = 1979-05-27T07:32:00Z',
'odt2 = 1979-05-27T00:32:00-07:00',
'ldt1 = 1979-05-27T07:32:00',
'ld1 = 1979-05-27',
'lt1 = 07:32:00',
'',
'[[products]]',
'name = "Hammer"',
'sku = 738594937',
'',
'[[products]]',
'',
'[[products]]',
'name = "Nail"',
'sku = 284758393',
'color = "gray"',
''
].join('\n');
var start = new Date();
for(var i = 0; i < iterations; i++) {
toml.parse(data);
function bench(name, input, iterations) {
// Warmup
for (var i = 0; i < 100; i++) toml.parse(input);
var start = process.hrtime.bigint();
for (var i = 0; i < iterations; i++) {
toml.parse(input);
}
var elapsed = Number(process.hrtime.bigint() - start) / 1e6;
var opsPerSec = Math.round(iterations / (elapsed / 1000));
console.log(' %s: %s iterations in %sms (%s ops/sec)',
name, iterations, elapsed.toFixed(0), opsPerSec.toLocaleString());
}
var end = new Date();
console.log("%s iterations in %sms", iterations, end - start);
console.log('toml-node benchmark');
console.log('-------------------');
bench('classic (v0.4.0 example)', classic, 5000);
bench('comprehensive (v1.0.0 features)', comprehensive, 5000);

@@ -0,1 +1,8 @@

4.0.0 - March 31 2026
=====================
* Modernize tooling and support TOML v1.0.0 spec ([#66](https://github.com/BinaryMuse/toml-node/issues/66))
---
2.3.0 - July 13 2015

@@ -2,0 +9,0 @@ ====================

+80
-30

@@ -5,2 +5,4 @@ "use strict";

var valueAssignments = [];
var explicitTablePaths = [];
var tableArrayPaths = [];
var currentPath = "";

@@ -41,3 +43,3 @@ var data = Object.create(null);

function assign(node) {
var key = node.key;
var keys = node.key;
var value = node.value;

@@ -47,13 +49,36 @@ var line = node.line;

var fullPath;
if (currentPath) {
fullPath = currentPath + "." + key;
} else {
fullPath = key;
// Support both legacy single-string keys and new array-of-keys format
if (!Array.isArray(keys)) keys = [keys];
var reduced = reduceValueNode(value);
// Navigate to the right context for dotted keys, creating intermediate tables
var target = context;
for (var i = 0; i < keys.length - 1; i++) {
var k = keys[i];
var intermediatePath = currentPath ? currentPath + "." + keys.slice(0, i + 1).join(".") : keys.slice(0, i + 1).join(".");
if (typeof target[k] === "undefined") {
target[k] = Object.create(null);
if (!pathAssigned(intermediatePath)) {
assignedPaths.push(intermediatePath);
}
} else if (typeof target[k] !== "object" || target[k] === null || Array.isArray(target[k])) {
genError("Cannot redefine existing key '" + intermediatePath + "'.", line, column);
} else if (valueAssignments.indexOf(intermediatePath) > -1) {
genError("Cannot redefine existing key '" + intermediatePath + "'.", line, column);
} else if (explicitTablePaths.indexOf(intermediatePath) > -1 && intermediatePath !== (Array.isArray(currentPath) ? currentPath.join(".") : currentPath)) {
genError("Cannot use dotted keys to extend table '" + intermediatePath + "' defined elsewhere.", line, column);
}
target = target[k];
}
if (typeof context[key] !== "undefined") {
var lastKey = keys[keys.length - 1];
var fullPath = currentPath ? currentPath + "." + keys.join(".") : keys.join(".");
if (typeof target[lastKey] !== "undefined") {
genError("Cannot redefine existing key '" + fullPath + "'.", line, column);
}
context[key] = reduceValueNode(value);
target[lastKey] = reduced;

@@ -73,3 +98,3 @@ if (!pathAssigned(fullPath)) {

if (node.type === "Array") {
return reduceArrayWithTypeChecking(node.value);
return reduceArray(node.value);
} else if (node.type === "InlineTable") {

@@ -84,9 +109,19 @@ return reduceInlineTableNode(node.value);

var obj = Object.create(null);
// Track paths that were explicitly defined (either as values or as inline
// table results). Dotted keys can create implicit intermediate tables but
// cannot modify explicitly defined ones.
var definedKeys = [];
for (var i = 0; i < values.length; i++) {
var val = values[i];
if (val.value.type === "InlineTable") {
obj[val.key] = reduceInlineTableNode(val.value.value);
} else if (val.type === "InlineTableValue") {
obj[val.key] = reduceValueNode(val.value);
}
if (val.type !== "InlineTableValue") continue;
var keys = val.key;
if (!Array.isArray(keys)) keys = [keys];
var reduced = reduceValueNode(val.value);
setNestedKey(obj, keys, reduced, val.line, val.column, definedKeys);
// Track the full path as a defined key
definedKeys.push(keys.join("."));
}

@@ -97,2 +132,23 @@

function setNestedKey(obj, keys, value, line, column, definedKeys) {
for (var i = 0; i < keys.length - 1; i++) {
var k = keys[i];
var intermediatePath = keys.slice(0, i + 1).join(".");
if (typeof obj[k] === "undefined") {
obj[k] = Object.create(null);
} else if (typeof obj[k] !== "object" || obj[k] === null || Array.isArray(obj[k])) {
genError("Cannot redefine existing key '" + intermediatePath + "'.", line, column);
} else if (definedKeys && definedKeys.indexOf(intermediatePath) > -1) {
// Cannot extend an explicitly-defined inline table
genError("Cannot extend inline table '" + intermediatePath + "'.", line, column);
}
obj = obj[k];
}
var lastKey = keys[keys.length - 1];
if (typeof obj[lastKey] !== "undefined") {
genError("Cannot redefine existing key '" + keys.join(".") + "'.", line, column);
}
obj[lastKey] = value;
}
function setPath(node) {

@@ -108,2 +164,3 @@ var path = node.value;

assignedPaths.push(quotedPath);
explicitTablePaths.push(quotedPath);
context = deepRef(data, path, Object.create(null), line, column);

@@ -119,2 +176,7 @@ currentPath = path;

// Check before filtering: cannot append to a statically-defined array
if (valueAssignments.indexOf(quotedPath) > -1) {
genError("Cannot append to statically defined array '" + quotedPath + "'.", line, column);
}
if (!pathAssigned(quotedPath)) {

@@ -126,2 +188,5 @@ assignedPaths.push(quotedPath);

});
valueAssignments = valueAssignments.filter(function(p) {
return p.indexOf(quotedPath) !== 0;
});
assignedPaths.push(quotedPath);

@@ -174,18 +239,3 @@ context = deepRef(data, path, [], line, column);

function reduceArrayWithTypeChecking(array) {
// Ensure that all items in the array are of the same type
var firstType = null;
for (var i = 0; i < array.length; i++) {
var node = array[i];
if (firstType === null) {
firstType = node.type;
} else {
if (node.type !== firstType) {
genError("Cannot add value of type " + node.type + " to array of type " +
firstType + ".", node.line, node.column);
}
}
}
// Recursively reduce array of nodes into array of the nodes' values
function reduceArray(array) {
return array.map(reduceValueNode);

@@ -192,0 +242,0 @@ }

{
"name": "toml",
"version": "3.0.0",
"description": "TOML parser for Node.js (parses TOML spec v0.4.0)",
"version": "4.0.0",
"description": "TOML parser for Node.js (TOML v1.0.0 compliant)",
"main": "index.js",
"types": "index.d.ts",
"engines": {
"node": ">=20"
},
"scripts": {
"build": "pegjs --cache src/toml.pegjs lib/parser.js",
"test": "jshint lib/compiler.js && nodeunit test/test_*.js",
"prepublish": "npm run build"
"build": "peggy --cache -o lib/parser.js src/toml.pegjs",
"test": "node --test test/test_toml.js",
"test:spec": "node test/spec-test.js",
"test:spec:failures": "node test/spec-test.js --failures",
"test:all": "node --test test/test_toml.js && node test/spec-test.js",
"prepublishOnly": "npm run build"
},
"repository": "git://github.com/BinaryMuse/toml-node.git",
"repository": {
"type": "git",
"url": "git://github.com/BinaryMuse/toml-node.git"
},
"keywords": [

@@ -20,6 +29,4 @@ "toml",

"devDependencies": {
"jshint": "*",
"nodeunit": "~0.9.0",
"pegjs": "~0.8.0"
"peggy": "^5.1.0"
}
}
+71
-45
TOML Parser for Node.js
=======================
[![Build Status](https://travis-ci.org/BinaryMuse/toml-node.png?branch=master)](https://travis-ci.org/BinaryMuse/toml-node)
[![CI](https://github.com/BinaryMuse/toml-node/actions/workflows/ci.yml/badge.svg)](https://github.com/BinaryMuse/toml-node/actions/workflows/ci.yml)
[![NPM](https://nodei.co/npm/toml.png?downloads=true)](https://nodei.co/npm/toml/)
If you haven't heard of TOML, well you're just missing out. [Go check it out now.](https://toml.io) Back? Good.
If you haven't heard of TOML, well you're just missing out. [Go check it out now.](https://github.com/mojombo/toml) Back? Good.
TOML Spec Support
-----------------
toml-node supports version 0.4.0 the TOML spec as specified by [mojombo/toml@v0.4.0](https://github.com/mojombo/toml/blob/master/versions/en/toml-v0.4.0.md)
toml-node supports [TOML v1.0.0](https://toml.io/en/v1.0.0), scoring **671/678 (99.0%)** on the official [toml-test](https://github.com/toml-lang/toml-test) compliance suite:
| | Pass | Total | Rate |
|---|---|---|---|
| Valid tests | 204 | 205 | 99.5% |
| Invalid tests | 467 | 473 | 98.7% |
| **Total** | **671** | **678** | **99.0%** |
The 7 remaining failures are inherent JavaScript platform limitations shared by all JS TOML parsers:
- 1 valid test: 64-bit integer precision (`Number` can't represent values beyond `Number.MAX_SAFE_INTEGER`)
- 6 invalid tests: UTF-8 encoding validation (Node.js handles UTF-8 decoding at the engine level before the parser sees the data)
### v1.0.0 Feature Support
- **Strings**: basic, literal, multiline, all escape sequences (`\uXXXX`, `\UXXXXXXXX`)
- **Integers**: decimal, hexadecimal (`0xDEADBEEF`), octal (`0o755`), binary (`0b11010110`)
- **Floats**: decimal, scientific notation, `inf`, `-inf`, `nan`
- **Booleans**: `true`, `false`
- **Dates/Times**: offset date-time, local date-time, local date, local time
- **Arrays**: mixed types allowed
- **Tables**: standard, inline (with dotted and quoted keys), array of tables
- **Keys**: bare, quoted, dotted (`fruit.apple.color = "red"`)
- **Comments**: `# line comments`
Installation
------------
toml-node is available via npm.
```
npm install toml
```
npm install toml
Requires Node.js 20 or later. Zero runtime dependencies.
toml-node also works with browser module bundlers like Browserify and webpack.
Usage
-----
### Standalone
Say you have some awesome TOML in a variable called `someTomlString`. Maybe it came from the web; maybe it came from a file; wherever it came from, it came asynchronously! Let's turn that sucker into a JavaScript object.
```javascript
var toml = require('toml');
var data = toml.parse(someTomlString);
console.dir(data);
const toml = require('toml');
const data = toml.parse(someTomlString);
```
`toml.parse` throws an exception in the case of a parsing error; such exceptions have a `line` and `column` property on them to help identify the offending text.
`toml.parse` throws an exception on parse errors with `line` and `column` properties:
```javascript
try {
toml.parse(someCrazyKnuckleHeadedTrblToml);
toml.parse(someBadToml);
} catch (e) {
console.error("Parsing error on line " + e.line + ", column " + e.column +
": " + e.message);
console.error(`Parsing error on line ${e.line}, column ${e.column}: ${e.message}`);
}
```
### Streaming
### Date/Time Values
As of toml-node version 1.0, the streaming interface has been removed. Instead, use a module like [concat-stream](https://npmjs.org/package/concat-stream):
Offset date-times are returned as JavaScript `Date` objects. Local date-times, local dates, and local times are returned as strings since they have no timezone information and can't be losslessly represented as `Date`:
```javascript
var toml = require('toml');
var concat = require('concat-stream');
var fs = require('fs');
const data = toml.parse(`
odt = 1979-05-27T07:32:00Z # Date object
ldt = 1979-05-27T07:32:00 # string: "1979-05-27T07:32:00"
ld = 1979-05-27 # string: "1979-05-27"
lt = 07:32:00 # string: "07:32:00"
`);
fs.createReadStream('tomlFile.toml', 'utf8').pipe(concat(function(data) {
var parsed = toml.parse(data);
}));
data.odt instanceof Date // true
typeof data.ldt // "string"
typeof data.ld // "string"
typeof data.lt // "string"
```
Thanks [@ForbesLindesay](https://github.com/ForbesLindesay) for the suggestion.
### Special Float Values
### Requiring with Node.js
`inf` and `nan` are returned as JavaScript `Infinity` and `NaN`:
You can use the [toml-require package](https://github.com/BinaryMuse/toml-require) to `require()` your `.toml` files with Node.js
```javascript
const data = toml.parse(`
pos_inf = inf
neg_inf = -inf
not_a_number = nan
`);
Live Demo
---------
data.pos_inf === Infinity // true
data.neg_inf === -Infinity // true
Number.isNaN(data.not_a_number) // true
```
You can experiment with TOML online at http://binarymuse.github.io/toml-node/, which uses the latest version of this library.
### Requiring .toml Files
You can use the [toml-require package](https://github.com/BinaryMuse/toml-require) to `require()` your `.toml` files with Node.js.
Building & Testing
------------------
toml-node uses [the PEG.js parser generator](http://pegjs.majda.cz/).
toml-node uses the [Peggy parser generator](https://peggyjs.org/) (successor to PEG.js).
npm install
npm run build
npm test
```
npm install
npm run build
npm test
npm run test:spec # run toml-test compliance suite
npm run test:spec:failures # show failure details
```
Any changes to `src/toml.peg` requires a regeneration of the parser with `npm run build`.
Changes to `src/toml.pegjs` require a rebuild with `npm run build`.
toml-node is tested on Travis CI and is tested against:
* Node 0.10
* Node 0.12
* Latest stable io.js
License

@@ -91,0 +117,0 @@ -------

@@ -1,16 +0,45 @@

var toml = require('../');
var fs = require('fs');
"use strict";
var assert = require("nodeunit").assert;
var { describe, it } = require("node:test");
var assert = require("node:assert");
var toml = require("../");
var fs = require("fs");
var path = require("path");
assert.parsesToml = function(tomlStr, expected) {
function parsesToml(tomlStr, expected) {
var actual;
try {
var actual = toml.parse(tomlStr);
actual = toml.parse(tomlStr);
} catch (e) {
var errInfo = "line: " + e.line + ", column: " + e.column;
return assert.fail("TOML parse error: " + e.message, errInfo, null, "at", assert.parsesToml);
assert.fail(
"TOML parse error at line " + e.line + ", column " + e.column + ": " + e.message
);
}
return assert.deepEqual(actual, expected);
};
// The compiler uses Object.create(null), so we normalize prototypes via
// a custom recursive comparison that treats null-prototype objects like {}.
assert.deepStrictEqual(normalize(actual), normalize(expected));
}
function normalize(val) {
if (val instanceof Date) return val;
if (Array.isArray(val)) return val.map(normalize);
if (val !== null && typeof val === "object") {
var out = {};
var keys = Object.keys(val);
for (var i = 0; i < keys.length; i++) {
out[keys[i]] = normalize(val[keys[i]]);
}
return out;
}
return val;
}
function readFixture(name) {
return fs.readFileSync(path.join(__dirname, name), "utf8");
}
// ---------------------------------------------------------------------------
// Expected values for fixture files
// ---------------------------------------------------------------------------
var exampleExpected = {

@@ -22,3 +51,3 @@ title: "TOML Example",

bio: "GitHub Cofounder & CEO\n\tLikes \"tater tots\" and beer and backslashes: \\",
dob: new Date("1979-05-27T07:32:00Z")
dob: new Date("1979-05-27T07:32:00Z"),
},

@@ -32,17 +61,14 @@ database: {

min_temp: -17.76,
enabled: true
enabled: true,
},
servers: {
alpha: {
ip: "10.0.0.1",
dc: "eqdc10"
},
beta: {
ip: "10.0.0.2",
dc: "eqdc10"
}
alpha: { ip: "10.0.0.1", dc: "eqdc10" },
beta: { ip: "10.0.0.2", dc: "eqdc10" },
},
clients: {
data: [ ["gamma", "delta"], [1, 2] ]
}
data: [
["gamma", "delta"],
[1, 2],
],
},
};

@@ -53,547 +79,359 @@

hard: {
another_test_string: ' Same thing, but with a string #',
'bit#': {
multi_line_array: [']'],
'what?': "You don't think some user won't do that?"
another_test_string: " Same thing, but with a string #",
"bit#": {
multi_line_array: ["]"],
"what?": "You don't think some user won't do that?",
},
harder_test_string: " And when \"'s are in the string, along with # \"",
test_array: ['] ', ' # '],
test_array2: ['Test #11 ]proved that', 'Experiment #9 was a success']
harder_test_string: ' And when "\'s are in the string, along with # "',
test_array: ["] ", " # "],
test_array2: ["Test #11 ]proved that", "Experiment #9 was a success"],
},
test_string: "You'll hate me after this - #"
}
test_string: "You'll hate me after this - #",
},
};
var easyTableArrayExpected = {
"products": [
{ "name": "Hammer", "sku": 738594937 },
{ },
{ "name": "Nail", "sku": 284758393, "color": "gray" }
]
products: [
{ name: "Hammer", sku: 738594937 },
{},
{ name: "Nail", sku: 284758393, color: "gray" },
],
};
var hardTableArrayExpected = {
"fruit": [
fruit: [
{ name: "durian", variety: [] },
{
"name": "durian",
"variety": []
name: "apple",
physical: { color: "red", shape: "round" },
variety: [{ name: "red delicious" }, { name: "granny smith" }],
},
{
"name": "apple",
"physical": {
"color": "red",
"shape": "round"
},
"variety": [
{ "name": "red delicious" },
{ "name": "granny smith" }
]
},
{},
{
"name": "banana",
"variety": [
{ "name": "plantain" }
]
},
{
"name": "orange",
"physical": {
"color": "orange",
"shape": "round"
}
}
]
}
{ name: "banana", variety: [{ name: "plantain" }] },
{ name: "orange", physical: { color: "orange", shape: "round" } },
],
};
var badInputs = [
'[error] if you didn\'t catch this, your parser is broken',
"[error] if you didn't catch this, your parser is broken",
'string = "Anything other than tabs, spaces and newline after a table or key value pair has ended should produce an error unless it is a comment" like this',
'array = [\n \"This might most likely happen in multiline arrays\",\n Like here,\n \"or here,\n and here\"\n ] End of array comment, forgot the #',
'number = 3.14 pi <--again forgot the #'
'array = [\n "This might most likely happen in multiline arrays",\n Like here,\n "or here,\n and here"\n ] End of array comment, forgot the #',
"number = 3.14 pi <--again forgot the #",
];
exports.testParsesExample = function(test) {
var str = fs.readFileSync(__dirname + "/example.toml", 'utf-8')
test.parsesToml(str, exampleExpected);
test.done();
};
// ---------------------------------------------------------------------------
// Tests
// ---------------------------------------------------------------------------
exports.testParsesHardExample = function(test) {
var str = fs.readFileSync(__dirname + "/hard_example.toml", 'utf-8')
test.parsesToml(str, hardExampleExpected);
test.done();
};
describe("fixture files", function () {
it("parses example.toml", function () {
parsesToml(readFixture("example.toml"), exampleExpected);
});
exports.testEasyTableArrays = function(test) {
var str = fs.readFileSync(__dirname + "/table_arrays_easy.toml", 'utf8')
test.parsesToml(str, easyTableArrayExpected);
test.done();
};
it("parses hard_example.toml", function () {
parsesToml(readFixture("hard_example.toml"), hardExampleExpected);
});
exports.testHarderTableArrays = function(test) {
var str = fs.readFileSync(__dirname + "/table_arrays_hard.toml", 'utf8')
test.parsesToml(str, hardTableArrayExpected);
test.done();
};
exports.testSupportsTrailingCommasInArrays = function(test) {
var str = 'arr = [1, 2, 3,]';
var expected = { arr: [1, 2, 3] };
test.parsesToml(str, expected);
test.done();
};
exports.testSingleElementArrayWithNoTrailingComma = function(test) {
var str = "a = [1]";
test.parsesToml(str, {
a: [1]
it("parses easy table arrays", function () {
parsesToml(readFixture("table_arrays_easy.toml"), easyTableArrayExpected);
});
test.done();
};
exports.testEmptyArray = function(test) {
var str = "a = []";
test.parsesToml(str, {
a: []
it("parses hard table arrays", function () {
parsesToml(readFixture("table_arrays_hard.toml"), hardTableArrayExpected);
});
test.done();
};
});
exports.testArrayWithWhitespace = function(test) {
var str = "[versions]\nfiles = [\n 3, \n 5 \n\n ]";
test.parsesToml(str, {
versions: {
files: [3, 5]
}
describe("arrays", function () {
it("supports trailing commas", function () {
parsesToml("arr = [1, 2, 3,]", { arr: [1, 2, 3] });
});
test.done();
};
exports.testEmptyArrayWithWhitespace = function(test) {
var str = "[versions]\nfiles = [\n \n ]";
test.parsesToml(str, {
versions: {
files: []
}
it("single element with no trailing comma", function () {
parsesToml("a = [1]", { a: [1] });
});
test.done();
};
exports.testDefineOnSuperkey = function(test) {
var str = "[a.b]\nc = 1\n\n[a]\nd = 2";
var expected = {
a: {
b: {
c: 1
},
d: 2
}
};
test.parsesToml(str, expected);
test.done();
};
exports.testWhitespace = function(test) {
var str = "a = 1\n \n b = 2 ";
test.parsesToml(str, {
a: 1, b: 2
it("empty array", function () {
parsesToml("a = []", { a: [] });
});
test.done();
};
exports.testUnicode = function(test) {
var str = "str = \"My name is Jos\\u00E9\"";
test.parsesToml(str, {
str: "My name is Jos\u00E9"
it("array with whitespace", function () {
parsesToml("[versions]\nfiles = [\n 3, \n 5 \n\n ]", {
versions: { files: [3, 5] },
});
});
var str = "str = \"My name is Jos\\U000000E9\"";
test.parsesToml(str, {
str: "My name is Jos\u00E9"
it("empty array with whitespace", function () {
parsesToml("[versions]\nfiles = [\n \n ]", {
versions: { files: [] },
});
});
test.done();
};
});
exports.testMultilineStrings = function(test) {
var str = fs.readFileSync(__dirname + "/multiline_strings.toml", 'utf8');
test.parsesToml(str, {
key1: "One\nTwo",
key2: "One\nTwo",
key3: "One\nTwo"
describe("tables", function () {
it("define on superkey", function () {
parsesToml("[a.b]\nc = 1\n\n[a]\nd = 2", {
a: { b: { c: 1 }, d: 2 },
});
});
test.done();
};
exports.testMultilineEatWhitespace = function(test) {
var str = fs.readFileSync(__dirname + "/multiline_eat_whitespace.toml", 'utf8');
test.parsesToml(str, {
key1: "The quick brown fox jumps over the lazy dog.",
key2: "The quick brown fox jumps over the lazy dog.",
key3: "The quick brown fox jumps over the lazy dog."
it("whitespace around key names", function () {
parsesToml("[ a ]\nb = 1", { a: { b: 1 } });
});
test.done();
};
exports.testLiteralStrings = function(test) {
var str = fs.readFileSync(__dirname + "/literal_strings.toml", 'utf8');
test.parsesToml(str, {
winpath: "C:\\Users\\nodejs\\templates",
winpath2: "\\\\ServerX\\admin$\\system32\\",
quoted: "Tom \"Dubs\" Preston-Werner",
regex: "<\\i\\c*\\s*>"
it("whitespace around dots", function () {
parsesToml("[ a . b . c]\nd = 1", { a: { b: { c: { d: 1 } } } });
});
test.done();
};
});
exports.testMultilineLiteralStrings = function(test) {
var str = fs.readFileSync(__dirname + "/multiline_literal_strings.toml", 'utf8');
test.parsesToml(str, {
regex2: "I [dw]on't need \\d{2} apples",
lines: "The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n"
describe("inline tables", function () {
it("parses inline tables", function () {
parsesToml(readFixture("inline_tables.toml"), {
name: { first: "Tom", last: "Preston-Werner" },
point: { x: 1, y: 2 },
nested: { x: { a: { b: 3 } } },
points: [
{ x: 1, y: 2, z: 3 },
{ x: 7, y: 8, z: 9 },
{ x: 2, y: 4, z: 8 },
],
arrays: [
{ x: [1, 2, 3], y: [4, 5, 6] },
{ x: [7, 8, 9], y: [0, 1, 2] },
],
});
});
test.done();
};
exports.testIntegerFormats = function(test) {
var str = "a = +99\nb = 42\nc = 0\nd = -17\ne = 1_000_001\nf = 1_2_3_4_5 # why u do dis";
test.parsesToml(str, {
a: 99,
b: 42,
c: 0,
d: -17,
e: 1000001,
f: 12345
it("parses empty inline tables", function () {
parsesToml("a = { }", { a: {} });
});
test.done();
};
});
exports.testFloatFormats = function(test) {
var str = "a = +1.0\nb = 3.1415\nc = -0.01\n" +
"d = 5e+22\ne = 1e6\nf = -2E-2\n" +
"g = 6.626e-34\n" +
"h = 9_224_617.445_991_228_313\n" +
"i = 1e1_000";
test.parsesToml(str, {
a: 1.0,
b: 3.1415,
c: -0.01,
d: 5e22,
e: 1e6,
f: -2e-2,
g: 6.626e-34,
h: 9224617.445991228313,
i: 1e1000
describe("strings", function () {
it("unicode escapes", function () {
parsesToml('str = "My name is Jos\\u00E9"', { str: "My name is Jos\u00E9" });
parsesToml('str = "My name is Jos\\U000000E9"', { str: "My name is Jos\u00E9" });
});
test.done();
};
exports.testDate = function(test) {
var date = new Date("1979-05-27T07:32:00Z");
test.parsesToml("a = 1979-05-27T07:32:00Z", {
a: date
it("multiline strings", function () {
parsesToml(readFixture("multiline_strings.toml"), {
key1: "One\nTwo",
key2: "One\nTwo",
key3: "One\nTwo",
});
});
test.done();
};
exports.testDateWithOffset = function(test) {
var date1 = new Date("1979-05-27T07:32:00-07:00"),
date2 = new Date("1979-05-27T07:32:00+02:00");
test.parsesToml("a = 1979-05-27T07:32:00-07:00\nb = 1979-05-27T07:32:00+02:00", {
a: date1,
b: date2
it("multiline eat whitespace", function () {
parsesToml(readFixture("multiline_eat_whitespace.toml"), {
key1: "The quick brown fox jumps over the lazy dog.",
key2: "The quick brown fox jumps over the lazy dog.",
key3: "The quick brown fox jumps over the lazy dog.",
});
});
test.done();
};
exports.testDateWithSecondFraction = function(test) {
var date = new Date("1979-05-27T00:32:00.999999-07:00");
test.parsesToml("a = 1979-05-27T00:32:00.999999-07:00", {
a: date
it("literal strings", function () {
parsesToml(readFixture("literal_strings.toml"), {
winpath: "C:\\Users\\nodejs\\templates",
winpath2: "\\\\ServerX\\admin$\\system32\\",
quoted: 'Tom "Dubs" Preston-Werner',
regex: "<\\i\\c*\\s*>",
});
});
test.done();
};
exports.testDateFromIsoString = function(test) {
// https://github.com/BinaryMuse/toml-node/issues/20
var date = new Date(),
dateStr = date.toISOString(),
tomlStr = "a = " + dateStr;
test.parsesToml(tomlStr, {
a: date
it("multiline literal strings", function () {
parsesToml(readFixture("multiline_literal_strings.toml"), {
regex2: "I [dw]on't need \\d{2} apples",
lines:
"The first newline is\ntrimmed in raw strings.\n All other whitespace\n is preserved.\n",
});
});
test.done();
};
});
exports.testLeadingNewlines = function(test) {
// https://github.com/BinaryMuse/toml-node/issues/22
var str = "\ntest = \"ing\"";
test.parsesToml(str, {
test: "ing"
describe("numbers", function () {
it("integer formats", function () {
parsesToml(
"a = +99\nb = 42\nc = 0\nd = -17\ne = 1_000_001\nf = 1_2_3_4_5 # why u do dis",
{ a: 99, b: 42, c: 0, d: -17, e: 1000001, f: 12345 }
);
});
test.done();
};
exports.testInlineTables = function(test) {
var str = fs.readFileSync(__dirname + "/inline_tables.toml", 'utf8');
test.parsesToml(str, {
name: {
first: "Tom",
last: "Preston-Werner"
},
point: {
x: 1,
y: 2
},
nested: {
x: {
a: {
b: 3
}
it("float formats", function () {
parsesToml(
"a = +1.0\nb = 3.1415\nc = -0.01\n" +
"d = 5e+22\ne = 1e6\nf = -2E-2\n" +
"g = 6.626e-34\n" +
"h = 9_224_617.445_991_228_313\n" +
"i = 1e1_000",
{
a: 1.0,
b: 3.1415,
c: -0.01,
d: 5e22,
e: 1e6,
f: -2e-2,
g: 6.626e-34,
h: 9224617.445991228313,
i: 1e1000,
}
},
points: [
{ x: 1, y: 2, z: 3 },
{ x: 7, y: 8, z: 9 },
{ x: 2, y: 4, z: 8 }
],
arrays: [
{ x: [1, 2, 3], y: [4, 5, 6] },
{ x: [7, 8, 9], y: [0, 1, 2] }
]
);
});
test.done();
};
});
exports.testEmptyInlineTables = function(test) {
// https://github.com/BinaryMuse/toml-node/issues/24
var str = "a = { }";
test.parsesToml(str, {
a: {}
describe("whitespace", function () {
it("handles whitespace", function () {
parsesToml("a = 1\n \n b = 2 ", { a: 1, b: 2 });
});
test.done();
};
exports.testKeyNamesWithWhitespaceAroundStartAndFinish = function(test) {
var str = "[ a ]\nb = 1";
test.parsesToml(str, {
a: {
b: 1
}
it("leading newlines", function () {
parsesToml("\ntest = \"ing\"", { test: "ing" });
});
test.done();
};
});
exports.testKeyNamesWithWhitespaceAroundDots = function(test) {
var str = "[ a . b . c]\nd = 1";
test.parsesToml(str, {
a: {
b: {
c: {
d: 1
}
}
}
describe("datetimes", function () {
it("parses UTC dates", function () {
parsesToml("a = 1979-05-27T07:32:00Z", {
a: new Date("1979-05-27T07:32:00Z"),
});
});
test.done();
};
exports.testSimpleQuotedKeyNames = function(test) {
var str = "[\"ʞ\"]\na = 1";
test.parsesToml(str, {
"ʞ": {
a: 1
}
it("parses dates with offsets", function () {
parsesToml("a = 1979-05-27T07:32:00-07:00\nb = 1979-05-27T07:32:00+02:00", {
a: new Date("1979-05-27T07:32:00-07:00"),
b: new Date("1979-05-27T07:32:00+02:00"),
});
});
test.done();
};
exports.testComplexQuotedKeyNames = function(test) {
var str = "[ a . \"ʞ\" . c ]\nd = 1";
test.parsesToml(str, {
a: {
"ʞ": {
c: {
d: 1
}
}
}
it("parses dates with fractional seconds", function () {
parsesToml("a = 1979-05-27T00:32:00.999999-07:00", {
a: new Date("1979-05-27T00:32:00.999999-07:00"),
});
});
test.done();
};
exports.testEscapedQuotesInQuotedKeyNames = function(test) {
test.parsesToml("[\"the \\\"thing\\\"\"]\na = true", {
'the "thing"': {
a: true
}
it("parses dates from Date.toISOString()", function () {
var date = new Date();
parsesToml("a = " + date.toISOString(), { a: date });
});
test.done();
};
});
exports.testMoreComplexQuotedKeyNames = function(test) {
// https://github.com/BinaryMuse/toml-node/issues/21
test.parsesToml('["the\\ key"]\n\none = "one"\ntwo = 2\nthree = false', {
"the\\ key": {
one: "one",
two: 2,
three: false
}
describe("quoted keys", function () {
it("simple quoted key", function () {
parsesToml('["ʞ"]\na = 1', { ʞ: { a: 1 } });
});
test.parsesToml('[a."the\\ key"]\n\none = "one"\ntwo = 2\nthree = false', {
a: {
"the\\ key": {
one: "one",
two: 2,
three: false
}
}
it("complex quoted key", function () {
parsesToml('[ a . "ʞ" . c ]\nd = 1', { a: { ʞ: { c: { d: 1 } } } });
});
test.parsesToml('[a."the-key"]\n\none = "one"\ntwo = 2\nthree = false', {
a: {
"the-key": {
one: "one",
two: 2,
three: false
}
}
it("escaped quotes in quoted keys", function () {
parsesToml('["the \\"thing\\""]\na = true', { 'the "thing"': { a: true } });
});
test.parsesToml('[a."the.key"]\n\none = "one"\ntwo = 2\nthree = false', {
a: {
"the.key": {
one: "one",
two: 2,
three: false
}
}
it("more complex quoted keys", function () {
parsesToml('["the\\\\ key"]\n\none = "one"\ntwo = 2\nthree = false', {
"the\\ key": { one: "one", two: 2, three: false },
});
parsesToml('[a."the\\\\ key"]\n\none = "one"\ntwo = 2\nthree = false', {
a: { "the\\ key": { one: "one", two: 2, three: false } },
});
parsesToml('[a."the-key"]\n\none = "one"\ntwo = 2\nthree = false', {
a: { "the-key": { one: "one", two: 2, three: false } },
});
parsesToml('[a."the.key"]\n\none = "one"\ntwo = 2\nthree = false', {
a: { "the.key": { one: "one", two: 2, three: false } },
});
parsesToml("[table]\n'a \"quoted value\"' = \"value\"", {
table: { 'a "quoted value"': "value" },
});
parsesToml('[module]\n"foo=bar" = "zzz"', {
module: { "foo=bar": "zzz" },
});
});
// https://github.com/BinaryMuse/toml-node/issues/34
test.parsesToml('[table]\n\'a "quoted value"\' = "value"', {
table: {
'a "quoted value"': "value"
}
});
describe("error handling", function () {
it("rejects bad unicode", function () {
assert.throws(function () {
toml.parse('str = "My name is Jos\\uD800"');
});
});
// https://github.com/BinaryMuse/toml-node/issues/33
test.parsesToml('[module]\n"foo=bar" = "zzz"', {
module: {
"foo=bar": "zzz"
}
it("rejects dot at start of key", function () {
assert.throws(function () {
toml.parse("[.a]\nb = 1");
});
});
test.done();
};
exports.testErrorOnBadUnicode = function(test) {
var str = "str = \"My name is Jos\\uD800\"";
test.throws(function() {
toml.parse(str);
it("rejects dot at end of key", function () {
assert.throws(function () {
toml.parse("[a.]\nb = 1");
});
});
test.done();
};
exports.testErrorOnDotAtStartOfKey = function(test) {
test.throws(function() {
var str = "[.a]\nb = 1";
toml.parse(str);
it("rejects table override", function () {
assert.throws(function () {
toml.parse("[a]\nb = 1\n\n[a]\nc = 2");
});
});
test.done()
};
exports.testErrorOnDotAtEndOfKey = function(test) {
test.throws(function() {
var str = "[.a]\nb = 1";
toml.parse(str);
it("rejects key override", function () {
assert.throws(function () {
toml.parse("[a]\nb = 1\n[a.b]\nc = 2");
});
});
test.done()
};
exports.testErrorOnTableOverride = function(test) {
test.throws(function() {
var str = "[a]\nb = 1\n\n[a]\nc = 2";
toml.parse(str);
it("rejects key override with nested path", function () {
assert.throws(function () {
toml.parse('[a]\nb = "a"\n[a.b.c]');
});
});
test.done()
};
exports.testErrorOnKeyOverride = function(test) {
test.throws(function() {
var str = "[a]\nb = 1\n[a.b]\nc = 2";
toml.parse(str);
it("rejects key override with array table", function () {
assert.throws(function () {
toml.parse("[a]\nb = 1\n[[a]]\nc = 2");
});
});
test.done()
};
exports.testErrorOnKeyOverrideWithNested = function(test) {
// https://github.com/BinaryMuse/toml-node/issues/23
test.throws(function() {
var str = "[a]\nb = \"a\"\n[a.b.c]";
toml.parse(str);
}, "existing key 'a.b'");
test.done();
};
exports.testErrorOnKeyOverrideWithArrayTable = function(test) {
test.throws(function() {
var str = "[a]\nb = 1\n[[a]]\nc = 2";
toml.parse(str);
it("rejects key replace", function () {
assert.throws(function () {
toml.parse("[a]\nb = 1\nb = 2");
});
});
test.done()
};
exports.testErrorOnKeyReplace = function(test) {
test.throws(function() {
var str = "[a]\nb = 1\nb = 2";
toml.parse(str);
it("rejects inline table replace", function () {
assert.throws(function () {
toml.parse("a = { b = 1 }\n[a]\nc = 2");
});
});
test.done()
};
exports.testErrorOnInlineTableReplace = function(test) {
// https://github.com/BinaryMuse/toml-node/issues/25
test.throws(function() {
var str = "a = { b = 1 }\n[a]\nc = 2";
toml.parse(str);
}, "existing key 'a'");
test.done();
};
exports.testErrorOnArrayMismatch = function(test) {
test.throws(function() {
var str = 'data = [1, 2, "test"]'
toml.parse(str);
it("allows mixed-type arrays (TOML v1.0.0)", function () {
parsesToml('data = [1, 2, "test"]', { data: [1, 2, "test"] });
});
test.done();
};
exports.testErrorOnBadInputs = function(test) {
var count = 0;
for (i in badInputs) {
(function(num) {
test.throws(function() {
toml.parse(badInputs[num]);
it("rejects bad inputs", function () {
badInputs.forEach(function (input) {
assert.throws(function () {
toml.parse(input);
});
})(i);
}
test.done();
};
});
});
exports.testErrorsHaveCorrectLineAndColumn = function(test) {
var str = "[a]\nb = 1\n [a.b]\nc = 2";
try { toml.parse(str); }
catch (e) {
test.equal(e.line, 3);
test.equal(e.column, 2);
test.done();
}
};
it("errors have correct line and column", function () {
try {
toml.parse("[a]\nb = 1\n [a.b]\nc = 2");
assert.fail("Should have thrown");
} catch (e) {
assert.strictEqual(e.line, 3);
assert.strictEqual(e.column, 2);
}
});
});
exports.testUsingConstructorAsKey = function(test) {
test.parsesToml("[empty]\n[emptier]\n[constructor]\nconstructor = 1\n[emptiest]", {
"empty": {},
"emptier": {},
"constructor": { "constructor": 1 },
"emptiest": {}
describe("edge cases", function () {
it("using 'constructor' as key", function () {
parsesToml("[empty]\n[emptier]\n[constructor]\nconstructor = 1\n[emptiest]", {
empty: {},
emptier: {},
constructor: { constructor: 1 },
emptiest: {},
});
});
test.done();
};
});

Sorry, the diff of this file is too big to display

Sorry, the diff of this file is not supported yet