"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const _ = require("highland");
const lineStream_1 = require("./lineStream");
const bind_1 = require("./bind");
const files_1 = require("./files");
function toLine(obj) {
return `${obj.data}\n`;
}
function toObject() {
let count = 0;
return (line) => {
return {
data: line.toString(),
// tslint:disable-next-line:no-increment-decrement
num: ++count
};
};
}
function reset() {
let count = 0;
return _.map((line) => {
// tslint:disable-next-line:no-increment-decrement
line.num = ++count;
return line;
});
}
function setline(lineNumber, to) {
return _.map((line) => {
if (line.num === lineNumber) {
line.data = to;
}
return line;
});
}
function map(fn) {
return _.map((line) => {
line.data = fn(line.data);
return line;
});
}
function filter(fn) {
return _.filter((line) => {
return fn(line.data);
});
}
function deleteLine(n) {
return _.filter((line) => {
return line.num !== n;
});
}
function replace(from, to) {
return _.map((line) => {
line.data = line.data.replace(from, to);
return line;
});
}
/** @class */
/** @implements {Editor} */
class StreamEditor {
constructor(filename) {
this.filename = filename;
this._prepend = [];
this._append = [];
this.transforms = [];
bind_1.default(this);
}
// tslint:disable-next-line:valid-jsdoc
/**
* sets a `line` at a given line number `N`
*
* @method
* set
* @param {string} line
* @param {number} n
* @return Editor
* @example
* import FileSurgeon from 'FileSurgeon';
*
* const contents = FileSurgeon.edit(filename)
* .set(1, line)
* .set(10, anotherline)
* .save();
*/
set(i, line) {
this.transforms.push(setline(i, line));
return this;
}
// tslint:disable-next-line:valid-jsdoc
/**
* Prepends a given `line` to a file
*
* @method
* prepend
* @param {string} line
* @return Editor
* @example
* import FileSurgeon from 'FileSurgeon';
*
* const contents = FileSurgeon.edit(filename)
* .prepend(line)
* .save();
*/
prepend(line) {
if (line !== undefined && line !== null) {
this._prepend.push(`${line}`);
}
return this;
}
// tslint:disable-next-line:valid-jsdoc
/**
* Appends a given `line` to a file
*
* @method
* append
* @param {string} line
* @return Editor
* @example
* import FileSurgeon from 'FileSurgeon';
*
* const contents = FileSurgeon.edit(filename)
* .append(line)
* .save();
*/
append(line) {
if (line !== undefined && line !== null) {
this._append.push(`${line}\n`);
}
return this;
}
// tslint:disable-next-line:valid-jsdoc
/**
* replaces a given string or regex for every line
*
* @method
* replace
* @param {string|RegExp} source
* @param {string} replacement
* @return Editor
* @example
* import FileSurgeon from 'FileSurgeon';
*
* const contents = FileSurgeon.edit(filename)
* .replace('x', 'y')
* .replace(/x/, 'y')
* .save();
*/
replace(x, y) {
this.transforms.push(replace(x, y));
return this;
}
// tslint:disable-next-line:valid-jsdoc
/**
* Maps each line using a given function
*
* @method
* map
* @param {function} fn
* @return Editor
* @example
* import FileSurgeon from 'FileSurgeon';
*
* const contents = FileSurgeon.edit(filename)
* .map((line) => {
* return line.toLowerCase() < 10;
* })
* .save();
*/
map(fn) {
this.transforms.push(map(fn));
return this;
}
// tslint:disable-next-line:valid-jsdoc
/**
* Filters the file contents using a given function
*
* @method
* filter
* @param {function} fn
* @return Editor
* @example
* import FileSurgeon from 'FileSurgeon';
*
* const contents = FileSurgeon.edit(filename)
* .filter((line) => {
* return line.length < 10;
* })
* .save();
*/
filter(fn) {
this.transforms.push(filter(fn));
this.transforms.push(reset());
return this;
}
// tslint:disable-next-line:valid-jsdoc
/**
* Deletes line by line number
*
* @method
* delete
* @param {function} fn
* @return Editor
* @example
* import FileSurgeon from 'FileSurgeon';
*
* const contents = FileSurgeon.edit(filename)
* .delete(10)
* .save(); // delete line 10
*/
delete(n) {
this.transforms.push(deleteLine(n));
this.transforms.push(reset());
return this;
}
// tslint:disable-next-line:valid-jsdoc
/**
* Writes any modifications to the file
*
* @method
* save
* @return Editor
* @example
* import FileSurgeon from 'FileSurgeon';
*
* const contents = FileSurgeon.edit(filename)
* .set(1, line)
* .filter(fn)
* .map(fn)
* .save();
*/
save() {
return __awaiter(this, void 0, void 0, function* () {
yield files_1.overwrite(this.modify, this.filename);
});
}
// tslint:disable-next-line:valid-jsdoc
/**
* Writes any modifications to a given file
*
* @method
* saveAs
* @param {string} filename
* @return Editor
* @example
* import FileSurgeon from 'FileSurgeon';
*
* const contents = FileSurgeon.edit(filename)
* .set(1, line)
* .filter(fn)
* .map(fn)
* .saveAs('myFile');
*/
saveAs(file) {
return __awaiter(this, void 0, void 0, function* () {
return files_1.save(this.filename, file, this.modify);
});
}
// tslint:disable-next-line:valid-jsdoc
/**
* Writes changes to stdout without modifying the source file. Useful for testing changes.
*
* @method
* preview
* @return Editor
* @example
* import FileSurgeon from 'FileSurgeon';
*
* const contents = FileSurgeon.edit(filename)
* .set(1, line)
* .filter(fn)
* .map(fn)
* .preview(); // writes changes to stdout
*/
preview() {
return __awaiter(this, void 0, void 0, function* () {
let source;
const dest = process.stdout;
try {
source = this.createSourceStream();
yield this.modify(source, dest);
}
finally {
source.destroy();
}
});
}
createPipeline() {
const transforms = [
_.map(toObject()),
...this.transforms,
_.map(toLine)
];
this.transforms = [];
return _.pipeline(...transforms);
}
createSourceStream() {
const source = lineStream_1.createStream(this.filename);
return source;
}
consume(source) {
let dest = _();
let count = 0;
let last;
return new Promise((resolve, reject) => {
source.on('readable', () => {
let line;
// tslint:disable-next-line:no-conditional-assignment
while (null !== (line = source.read())) {
if (line instanceof Buffer) {
line = line.toString('utf8');
}
last = line;
// tslint:disable-next-line:no-increment-decrement
count++;
dest.write(line);
}
});
source.on('end', () => {
dest.end();
if (last === '') { // remove extra blank line
dest = _(dest).take(count - 1);
}
resolve({
contents: dest,
length: count
});
});
});
}
modify(destination, source) {
return __awaiter(this, void 0, void 0, function* () {
const { contents, length } = yield this.consume(source);
return new Promise((resolve) => {
if (length > 0) {
_(this._prepend)
.concat(contents)
.pipe(this.createPipeline())
.concat(_(this._append))
.pipe(destination);
}
else if (this._append.length > 0 || this._prepend.length > 0) {
_(this._prepend.concat(this._append))
.pipe(destination);
}
else {
resolve();
}
destination.on('finish', () => {
resolve();
});
});
});
}
}
exports.StreamEditor = StreamEditor;