CSVDoc.js

// src/CSVDoc.js

import fs from 'node:fs';
import { Aggregation } from './Aggregation.js';

/**
 * This class is responsible for reading a CSV file,
 * processing its content, and providing methods to access its data.
 * It reads the file, processes the CSV content, and provides methods to get
 * the file path, file content, column names, and perform aggregations on
 * specific columns.
 */
export class CSVDoc {
  // Instance variables
  filePath;
  file;
  fileColumn;
  fileData;
  separator;

  /**
   * Setup the CSVDoc instance with the file path.
   * methods to access its data. It reads the file, processes the CSV content,
   * and provides methods to get the file path, file content, column names,
   * and perform aggregations on specific columns.
   * @param {string} filePath The absolute path to the csv file
   * @param {string} [separator] The separator of the CSV
   */
  constructor(filePath, separator = ',') {
    this.#checkValid(filePath);
    this.filePath = filePath;
    this.file = fs.readFileSync(filePath);
    this.separator = separator;
    this.#processCSV();
  }

  /**
   * Get the path of the file
   * @returns {string} The string of the absolute file path
   */
  getFilePath() {
    return this.filePath;
  }

  /**
   * Get the fs object of the file
   * @returns {object} The fs.readFileSync() object of the file
   */
  getFile() {
    return this.file;
  }

  /**
   * Get the column name of the csv that is imported
   * @returns {Array} An array of column names
   */
  getColumns() {
    return this.fileColumn;
  }

  /**
   * Get the aggregation data for the given column.
   * @param {string} name The name of the column
   * @returns {Aggregation} Returns the aggregation class for the column
   */
  aggregateColumn(name) {
    if (!name || typeof name !== 'string') {
      throw new TypeError('Column name must be a non-empty string.');
    }
    if (!this.fileColumn.includes(name)) {
      throw new Error(`Column "${name}" does not exist in the CSV file.`);
    }

    const index = this.fileColumn.indexOf(name);
    const column = this.fileData.map((value) => value[index]);
    return new Aggregation(column);
  }

  /**
   * Process the csv
   */
  #processCSV() {
    let content = this.file.toString('utf-8').split('\n');
    content = content.map((content) => content.split(this.separator));
    this.fileColumn = content.shift();
    this.fileData = content;
  }

  /**
   * Checks if the filepath is valid and if the file exists at the given path.
   * @param {string} filePath The absolute path to the file to check.
   * @throws {TypeError} If filePath is not provided.
   * @throws {Error} If the file does not exist at the provided path.
   * @returns {boolean} True if the file is in the right format, otherwise throws an error.
   */
  #checkValid(filePath) {
    if (!filePath || '' == filePath || undefined == filePath) {
      throw new TypeError('filePath must be provided.');
    }
    if (!fs.existsSync(filePath)) {
      throw new Error('File does not exist at the provided path: ' + filePath);
    }
    if (!filePath.toUpperCase().endsWith('.CSV')) {
      throw new TypeError('File is not a CSV file: ' + filePath);
    }
    return true;
  }
}