| 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); |
+7
-0
@@ -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 @@ } |
+16
-9
| { | ||
| "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 | ||
| ======================= | ||
| [](https://travis-ci.org/BinaryMuse/toml-node) | ||
| [](https://github.com/BinaryMuse/toml-node/actions/workflows/ci.yml) | ||
| [](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 @@ ------- |
+308
-470
@@ -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
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
204545
42.05%1
-66.67%26
8.33%5993
41.08%120
27.66%