UNPKG

js-ini-parser

Version:

A simple ini parser that save comments and spaces

247 lines (244 loc) 8.15 kB
'use strict'; const DEFAULT_SECTION_NAME = ""; class Line { constructor(text, number) { this.text = text; this.number = number; this.text = text; this.number = number; } isComment() { return this.text.startsWith(";"); } isSection() { return this.text.startsWith("[") && this.text.endsWith("]"); } isEmptyKeyValuePair() { if (this.text.length <= 1) { return false; } if (this.isComment() || this.isSection()) { return false; } const [, v] = this.text.trim().split("="); return v === void 0 || v === ""; } isKeyValuePair() { if (this.isEmptyKeyValuePair()) { return false; } const hasEqualSign = this.text.indexOf("=") !== -1; if (!hasEqualSign) { return false; } const startsWithEqualSign = this.text.startsWith("="); if (startsWithEqualSign) { return false; } if (this.text.trim().split("=").length === 2) { return hasEqualSign && !startsWithEqualSign && !this.isComment() && !this.isSection(); } if (this.isComment() && this.isSection()) { return false; } return !startsWithEqualSign; } } function parseIni(text, options) { const sections = []; let currentSection; let currentBlock; let globalSection; let debug = console.debug; if (options.debug !== true) { debug = () => { }; } if (options.allowGlobalSection) { globalSection = { section: options.globalSectionName || DEFAULT_SECTION_NAME, blocks: [] }; } const lines = text.split("\n").filter((a) => a.trim() !== ""); for (let index = 0; index < lines.length; index++) { const currentLine = lines[index]; const trimmedLine = new Line(currentLine.trim(), index); if (trimmedLine.isSection()) { currentSection = { section: trimmedLine.text.slice(1, -1), blocks: [] }; sections.push(currentSection); currentBlock = void 0; } else if (trimmedLine.isComment()) { if (index < lines.length - 1) { const nextLine = lines[index + 1]; const trimmedNextLine = new Line(nextLine.trim(), index + 1); if (trimmedNextLine.isKeyValuePair()) { debug("comment linked to a key-value pair"); debug(`l:${index} ${trimmedLine.text}`); } else if (currentSection) { currentSection.blocks.push({ type: "comment", text: trimmedLine.text }); } else if (options.allowGlobalSection && globalSection) { globalSection.blocks.push({ type: "comment", text: trimmedLine.text }); } else { console.error(`Error at line ${index} : comment outside of section`); console.error(`l:${index} ${trimmedLine.text}`); } } else { debug("last line of the file"); if (currentSection) { currentSection.blocks.push({ type: "comment", text: trimmedLine.text }); } else if (options.allowGlobalSection && globalSection) { globalSection.blocks.push({ type: "comment", text: trimmedLine.text }); } else { console.error(`Error at line ${index} : comment outside of section`); console.error(`l:${index} ${trimmedLine.text}`); } } } else if (currentSection) { const isKeyValuePair = trimmedLine.isKeyValuePair(); debug("isKeyValuePair", isKeyValuePair); if (isKeyValuePair) { const separatorIndex = trimmedLine.text.indexOf("="); const key = trimmedLine.text.slice(0, separatorIndex).trim(); const value = trimmedLine.text.slice(separatorIndex + 1).trim(); currentBlock = { type: "data", key, value }; if (index === 0) ; else if (index > 0) { const previousLine = lines[index - 1]; const trimmedPreviousLine = new Line(previousLine.trim(), index - 1); if (trimmedPreviousLine.isComment()) { currentBlock.comment = { type: "comment", text: trimmedPreviousLine.text }; } } currentSection.blocks.push(currentBlock); } else if (trimmedLine.isEmptyKeyValuePair()) { if (options.allowEmptyValue) { debug( "empty key value pair at line " + index + " : " + trimmedLine.text ); const separatorIndex = trimmedLine.text.indexOf("="); currentBlock = { type: "data", key: trimmedLine.text.slice(0, separatorIndex).trim(), value: "" }; currentSection.blocks.push(currentBlock); } else { console.error( `Empty value at ${index}. This line should have a value.` ); console.error(`Line content: ${trimmedLine.text}`); } } else { console.error( `Invalid line ${index}. This line should be a key-value pair.` ); console.error(`Line content: ${trimmedLine.text}`); } } else if (options.allowGlobalSection && globalSection) { sections.push(globalSection); } else { console.warn(`Ignoring data outside of section`); console.warn(`l:${index} ${trimmedLine.text}`); } } return sections; } function stringifyIni(sections, options) { const lines = []; for (const section of sections) { lines.push(`[${section.section}]`); for (const block of section.blocks) { if (block.type === "data") { if (block.comment && block.comment.type === "comment") { lines.push(`${block.comment.text}`); lines.push(`${block.key}=${block.value}`); } else { lines.push(`${block.key}=${block.value}`); } } else if (block.type === "comment") { lines.push(`${block.text}`); } } } return lines.join("\n"); } function addSection(obj, sectionName) { obj.push({ section: sectionName, blocks: [] }); return { section: sectionName, blocks: [] }; } function addKeyValue(obj, section, key, value, options) { const targetSection = obj.find((s) => s.section === section); if (!targetSection) { throw new Error(`Section ${section} not found`); } const block = targetSection.blocks.find( (b) => b.type === "data" && b.key === key ); if (block) { if (options?.override) { if (block && block.type === "data") { block.value = value; } return block; } throw new Error( `Key ${key} already exists in section ${section}, see options.override` ); } targetSection.blocks.push({ type: "data", key, value }); return { type: "data", key, value }; } function addComment(obj, section, comment, options) { comment = ";" + comment; const targetSection = obj.find((s) => s.section === section); if (!targetSection) { throw new Error(`Section ${section} not found`); } if (options?.attachToKey) { const keyPosition = targetSection.blocks.findIndex( (b) => b.type === "data" && b.key === options.attachToKey ); const commentPosition = keyPosition - 1; targetSection.blocks.splice(commentPosition, 0, { type: "comment", text: comment }); return targetSection.blocks[commentPosition]; } targetSection.blocks.push({ type: "comment", text: comment }); return { type: "comment", text: comment }; } function getSection(sections, sectionName) { return sections.find((section) => section.section === sectionName); } function updateKeyValue(obj, section, key, value) { const targetSection = obj.find((s) => s.section === section); if (!targetSection) { throw new Error(`Section ${section} not found`); } const block = targetSection.blocks.find( (b) => b.type === "data" && b.key === key ); if (block && block.type === "data") { block.value = value; return block; } throw new Error(`Key ${key} not found in section ${section}. Can't update.`); } exports.Line = Line; exports.addComment = addComment; exports.addKeyValue = addKeyValue; exports.addSection = addSection; exports.getSection = getSection; exports.parseIni = parseIni; exports.stringifyIni = stringifyIni; exports.updateKeyValue = updateKeyValue;