key-value-file
Version:
A simple key/value file parser/writer
240 lines • 7.16 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.KeyValue = void 0;
const token_1 = require("./token");
/**
* Class for manipulating key/value data (`.env` files f.ex)
*/
class KeyValue {
/**
* Constructor
* @param tokens {@see tokenize:tokenize|tokenize()}
*/
constructor(tokens) {
this._tokens = [];
if (tokens) {
this._tokens = tokens;
}
}
/**
* Returns the value of key `key`
* @param key The key to get the value for
* @returns `undefined` if the `key` is not found
*/
get(key) {
const t = this.getValueForKey(key);
return t && t.value;
}
/**
* Set the value of key `key`. If the `key` doesn't exist it's created
* @param key
* @param value
* @returns The instance being called
*/
set(key, value) {
const t = this.getValueForKey(key);
if (t) {
t.value = `${value}`;
}
else {
this.addNewlineBeforeNewValue();
this._tokens.push({ type: token_1.TokenType.Key, value: key });
this._tokens.push({ type: token_1.TokenType.Delimiter, value: '=' });
this._tokens.push({ type: token_1.TokenType.Value, value: `${value}` });
}
return this;
}
/**
* Rename the key `key` to `newName`
* @param key The name of the key to rename
* @param newName The new name of the key
* @returns The instance being called
*/
rename(key, newName) {
const t = this.getKey(key);
if (t) {
t.value = newName;
}
return this;
}
/**
* Delete the key `key`
* @param key The name of the key to delete
* @returns The instance being called
*/
delete(key) {
const k = this.getKeyIndex(key);
if (k > -1) {
const tokens = this._tokens;
let i = k + 1;
for (; i < tokens.length; i++) {
const t = tokens[i];
if (t.type === token_1.TokenType.Value) {
break;
}
}
this._tokens.splice(k, i - k);
}
return this;
}
/**
* Add new newline token
*/
addNewline() {
return this.pushNewlineToken();
}
/**
* Add a comment node.
*
* Note! The comment should not have a leading `#`.
* The comment can be multi-line
* @param comment
*/
addComment(comment) {
if (this._tokens.length) {
this.pushNewlineToken();
}
comment = comment
.split('\n')
.map((s) => `# ${s}`)
.join('\n');
this._tokens.push({ type: token_1.TokenType.Comment, value: comment });
return this;
}
/**
* Remove all comment tokens
*/
removeComments() {
const tokens = this._tokens;
const newTokens = [];
const rmLeadingWs = () => {
const i = newTokens.length - 2;
if (newTokens[i] && newTokens[i].type === token_1.TokenType.Whitespace) {
while (newTokens[i]) {
if (newTokens[i].type === token_1.TokenType.Whitespace) {
newTokens.splice(i, 1);
}
}
}
};
// tslint:disable-next-line: prefer-for-of
for (let i = 0; i < tokens.length; i++) {
if (tokens[i].type === token_1.TokenType.Comment) {
if (!this.isTrailingComment(i)) {
if (tokens[i - 1] && tokens[i - 1].type === token_1.TokenType.Whitespace) {
rmLeadingWs();
}
i += 1;
}
continue;
}
newTokens.push(tokens[i]);
}
this._tokens = newTokens;
return this;
}
/**
* Stringify the tokens
* @param collapseWhitespace If `true` all whitespaces, except newlines,
* will be removed
*/
toString(collapseWhitespace = false) {
const tokens = collapseWhitespace ? this.collapseWhitespace() : this._tokens;
return tokens.map((t) => t.value).join('');
}
/**
* Check if the comment at `pos` is a trailing comment or not
* @param pos
*/
isTrailingComment(pos) {
const tokens = this._tokens;
const c = tokens[pos];
if (c.type !== token_1.TokenType.Comment) {
throw new Error(`Expected a comment node, got ${c.type}`);
}
let i = pos - 1;
while (tokens[i]) {
const t = tokens[i];
i -= 1;
if (t.type === token_1.TokenType.Whitespace) {
continue;
}
else if (t.type === token_1.TokenType.Value) {
return true;
}
else if (t.type === token_1.TokenType.Newline) {
return false;
}
}
return false;
}
/**
* Check if we need no add a newline before adding a new key/value pair
*/
addNewlineBeforeNewValue() {
if (!this._tokens.length) {
return;
}
const last = this._tokens[this._tokens.length - 1];
if (last.type !== token_1.TokenType.Newline) {
this.pushNewlineToken();
}
}
/**
* Push a newline token
*/
pushNewlineToken() {
this._tokens.push({ type: token_1.TokenType.Newline, value: '\n' });
return this;
}
/**
* Get the key token with name `key`
* @param key
*/
getKey(key) {
return this._tokens.find((t) => t.type === token_1.TokenType.Key && t.value === key);
}
/**
* Returns the index of the key token with name `key`
* @param key
*/
getKeyIndex(key) {
return this._tokens.findIndex((val) => {
return val.type === token_1.TokenType.Key && val.value === key;
});
}
/**
* Remove all unnecessary whitespace tokens
*/
collapseWhitespace() {
const len = this._tokens.length - 1;
return this._tokens.filter((t, i) => {
if ((this._tokens[i - 1] &&
this._tokens[i - 1].type === token_1.TokenType.Newline &&
t.type === token_1.TokenType.Newline) ||
(i === len && t.type === token_1.TokenType.Newline)) {
return false;
}
return t.type === token_1.TokenType.Newline || t.type !== token_1.TokenType.Whitespace;
});
}
/**
* Returns the value token for key `key`
* @param key
*/
getValueForKey(key) {
const keyPos = this.getKeyIndex(key);
const tokens = this._tokens;
if (keyPos > -1) {
for (let i = keyPos + 1; i < tokens.length; i++) {
const t = tokens[i];
if (t.type === token_1.TokenType.Value) {
return t;
}
}
}
return undefined;
}
}
exports.KeyValue = KeyValue;
//# sourceMappingURL=keyvalue.js.map