Fork me on GitHub

Source: lib/wwwRoot.mjs

import Promise from 'bluebird';
import path from 'path';
import fs from 'fs';
import csvParse from 'csv-parse';
import glob from 'glob';

import theConfig from './config';


const FS_READ_OPTIONS = Object.freeze({
  encoding: 'utf8'
});
const CSV_PARSE_OPTIONS = Object.freeze({
  cast: false,
  comment: undefined, // '#'
  delimiter: '\t', // TSV
  skip_empty_lines: true,
  trim: true,
});


/**
 * #### A Library of Functions relating to `wwwRoot` from {@link config}
 *
 *  
 *
 * @namespace wwwRoot
 */
export default {
  /**
   * @returns {String} the value of `wwwRoot` from {@link config}
   */
  get basePath() {
    return theConfig.get('wwwRoot');
  },

  /**
   * @see lib.dataColumnMap
   * @memberof wwwRoot
   * @param {String} relativePath a path relative to {@link wwwRoot}
   * @param {Object<Integer>} [options] options for a [csv](https://www.npmjs.com/package/csv) parser
   * @returns {Promise<Array>} a Promise resolving the rows of the TSV file at `relativePath`
   */
  async willLoadTSV(relativePath, options = CSV_PARSE_OPTIONS) {
    const headersAndColumns = await new Promise((resolve, reject) => {
      try {
        const filepath = path.join(this.basePath, relativePath);
        const reader = fs.createReadStream(filepath, FS_READ_OPTIONS);
        const parser = csvParse(options);
        const results = [];

        // http://csv.adaltas.com/parse/examples/
        parser.on('error', reject);
        parser.on('readable', () => {
          let data;
          while (data = parser.read()) { // eslint-disable-line no-cond-assign
            results.push(data);
          }
        });
        parser.on('end', () => {
          // do not release Zalgo
          setImmediate(() => {
            resolve(results);
          });
        });

        reader.on('error', reject);
        reader.pipe(parser);
      }
      catch (err) {
        reject(err);
      }
    });

    // drop the the headers
    return headersAndColumns.slice(1);
  },

  /**
   * @memberof wwwRoot
   * @param {String} relativePath a path relative to {@link wwwRoot}
   * @returns {Promise<String>} a Promise resolving the contents of the file at `relativePath`
   *   or a blank String if the file is not present
   */
  async willLoadFile(relativePath) {
    const exists = await this.willDetectFile(relativePath);
    if (! exists) {
      return '';
    }

    // Promisify on-demand for mocking purposes
    const filepath = path.join(this.basePath, relativePath);
    const file = await Promise.promisify(fs.readFile, {
      context: fs,
    })(filepath, FS_READ_OPTIONS);

    return file;
  },

  /**
   * @memberof wwwRoot
   * @param {String} fileglob a path-and-glob-pattern relative to {@link wwwRoot}
   * @returns {Promise<Array>} a Promise resolving the filenames matching `fileglob`
   */
  async willGetFilenames(fileglob) {
    const pathglob = path.join(this.basePath, fileglob);

    // Promisify on-demand for mocking purposes
    const files = await Promise.promisify(glob)(pathglob);

    // just the filenames, please
    return files.map((file) => path.basename(file));
  },

  /**
   * @memberof wwwRoot
   * @param {String} relativePath a path relative to {@link wwwRoot}
   * @returns {Promise<Array>} a Promise resolving true if a file exists at `relativePath`
   */
  async willDetectFile(relativePath) {
    const filepath = path.join(this.basePath, relativePath);

    try {
      // Promisify on-demand for mocking purposes
      const stat = await Promise.promisify(fs.stat, {
        context: fs
      })(filepath);

      return stat.isFile();
    }
    catch (errThrown) {
      // traverse the cause chain, eg. `OperationalError`
      let err = errThrown;
      let cause;
      while (cause = err.cause) { // eslint-disable-line no-cond-assign
        err = cause;
      }

      if (err.code === 'ENOENT') {
        return false;
      }
      throw err;
    }
  },
};