file-writer.js 4.83 KB
'use strict';
/*
 Copyright 2012-2015, Yahoo Inc.
 Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms.
 */
const path = require('path');
const fs = require('fs');
const mkdirp = require('make-dir');
const supportsColor = require('supports-color');

/**
 * Base class for writing content
 * @class ContentWriter
 * @constructor
 */
class ContentWriter {
    /**
     * returns the colorized version of a string. Typically,
     * content writers that write to files will return the
     * same string and ones writing to a tty will wrap it in
     * appropriate escape sequences.
     * @param {String} str the string to colorize
     * @param {String} clazz one of `high`, `medium` or `low`
     * @returns {String} the colorized form of the string
     */
    colorize(str /*, clazz*/) {
        return str;
    }

    /**
     * writes a string appended with a newline to the destination
     * @param {String} str the string to write
     */
    println(str) {
        this.write(`${str}\n`);
    }

    /**
     * closes this content writer. Should be called after all writes are complete.
     */
    close() {}
}

/**
 * a content writer that writes to a file
 * @param {Number} fd - the file descriptor
 * @extends ContentWriter
 * @constructor
 */
class FileContentWriter extends ContentWriter {
    constructor(fd) {
        super();

        this.fd = fd;
    }

    write(str) {
        fs.writeSync(this.fd, str);
    }

    close() {
        fs.closeSync(this.fd);
    }
}

// allow stdout to be captured for tests.
let capture = false;
let output = '';

/**
 * a content writer that writes to the console
 * @extends ContentWriter
 * @constructor
 */
class ConsoleWriter extends ContentWriter {
    write(str) {
        if (capture) {
            output += str;
        } else {
            process.stdout.write(str);
        }
    }

    colorize(str, clazz) {
        const colors = {
            low: '31;1',
            medium: '33;1',
            high: '32;1'
        };

        /* istanbul ignore next: different modes for CI and local */
        if (supportsColor.stdout && colors[clazz]) {
            return `\u001b[${colors[clazz]}m${str}\u001b[0m`;
        }
        return str;
    }
}

/**
 * utility for writing files under a specific directory
 * @class FileWriter
 * @param {String} baseDir the base directory under which files should be written
 * @constructor
 */
class FileWriter {
    constructor(baseDir) {
        if (!baseDir) {
            throw new Error('baseDir must be specified');
        }
        this.baseDir = baseDir;
    }

    /**
     * static helpers for capturing stdout report output;
     * super useful for tests!
     */
    static startCapture() {
        capture = true;
    }

    static stopCapture() {
        capture = false;
    }

    static getOutput() {
        return output;
    }

    static resetOutput() {
        output = '';
    }

    /**
     * returns a FileWriter that is rooted at the supplied subdirectory
     * @param {String} subdir the subdirectory under which to root the
     *  returned FileWriter
     * @returns {FileWriter}
     */
    writerForDir(subdir) {
        if (path.isAbsolute(subdir)) {
            throw new Error(
                `Cannot create subdir writer for absolute path: ${subdir}`
            );
        }
        return new FileWriter(`${this.baseDir}/${subdir}`);
    }

    /**
     * copies a file from a source directory to a destination name
     * @param {String} source path to source file
     * @param {String} dest relative path to destination file
     * @param {String} [header=undefined] optional text to prepend to destination
     *  (e.g., an "this file is autogenerated" comment, copyright notice, etc.)
     */
    copyFile(source, dest, header) {
        if (path.isAbsolute(dest)) {
            throw new Error(`Cannot write to absolute path: ${dest}`);
        }
        dest = path.resolve(this.baseDir, dest);
        mkdirp.sync(path.dirname(dest));
        let contents;
        if (header) {
            contents = header + fs.readFileSync(source, 'utf8');
        } else {
            contents = fs.readFileSync(source);
        }
        fs.writeFileSync(dest, contents);
    }

    /**
     * returns a content writer for writing content to the supplied file.
     * @param {String|null} file the relative path to the file or the special
     *  values `"-"` or `null` for writing to the console
     * @returns {ContentWriter}
     */
    writeFile(file) {
        if (file === null || file === '-') {
            return new ConsoleWriter();
        }
        if (path.isAbsolute(file)) {
            throw new Error(`Cannot write to absolute path: ${file}`);
        }
        file = path.resolve(this.baseDir, file);
        mkdirp.sync(path.dirname(file));
        return new FileContentWriter(fs.openSync(file, 'w'));
    }
}

module.exports = FileWriter;