compile/compiler.js

const path = require('path');
const fs = require('fs');
const solc = require('solc');
const util = require('util');
const ora = require('ora');

const solcutil = require('./solc-util');
const SolcUtil = new solcutil();

const Logger = require('../logging/logger');

/** Compiler class, abstracting solc. */
class Compiler {

  /** 
   * Initialize a Compiler object.
   * @param {object} options - User options
   * @param {Logger.state.ENUM} logSetting - Log setting, as represented by the Logger state enum. 
   */  
  constructor(options, logSetting = Logger.state.NORMAL) {
    this.options = options;
    this.log = new Logger(logSetting);
  }


  //solc input object --> Compiled stuff
  /**
   * 
   * @param {object} solcInput - The JSON input for the solc.js Solidity Compiler. See: https://solidity.readthedocs.io/en/v0.5.7/using-the-compiler.html#input-description
   * @return {object} output - The returned output generated by solc. See link above!
   * 
   */
  compile(solcInput) {

    this.log.print(Logger.state.SUPER, 
      util.inspect(solcInput),
      "\n"
    );

    //this.log.print(Logger.state.NORMAL, "Compiling...\n");
    const spinner = ora('Compiling...\n').start();
    const output = JSON.parse(solc.compile(JSON.stringify(solcInput))); 
    //Logic on what to show post-compilation (regarding the output post-compilation)
    if(this.log.setting >= Logger.state.SUPER) {
      this.log.print(Logger.state.SUPER,
        "OUTPUT",
        util.inspect(output, { depth: null })
      );
    } else if(output.errors) {
      spinner.fail();
      this.log.print(Logger.state.NORMAL,
        "Error: ",
        util.inspect(output, { depth: null })
      );
      throw "Failed to compile!";
    }
    spinner.stop();
    return output;
  }

  /**
   * A function which recursively searches through the given directory passed, pasgenerating a solc input, compiling it, and returning the raw compiler output. 
   *
   * If no root filepath is passed, it will assume that the "contracts" folder one level higher than the current dir is the root where all contracts are located.
   *
   * @param {string} [root=path.resolve(__dirname, '../contracts')] - The root directory in which the function will start from, recursively navigated through to parse all contracts found into a solc input.
   * @return {object} output - The returned output generated by solc. See: https://solidity.readthedocs.io/en/v0.5.7/using-the-compiler.html#output-description
   */
  compileDirectory(root = path.resolve(__dirname, '../contracts')) {

    this.log.print(Logger.state.MASTER, 
      "Config: ",
      "  Root contract directory: " + root,
      "\n"
    );
    this.log.print(Logger.state.NORMAL, "Generating solc_input...\n");
    return this.compile(SolcUtil.generateSolcInputDirectory(root));
  }

  /**
   * A function which simply compiles a single file located at the given filepath.    
   *
   * If no root is provided in the second argument, it will assume that the "contracts" folder one level higher than the current dir is the root where all contracts are located.
   *
   * @param {string} filepath - The filepath at which the file is located.
   * @param {string} [root=path.resolve(__dirname, '../contracts')] - The root directory in which all contracts are located; this is for contract naming specification within the solc input.
   * @return {object} output - The returned output generated by solc. See: https://solidity.readthedocs.io/en/v0.5.7/using-the-compiler.html#output-description
   * 
   * 
   */
  compileFile(filepath, root = path.resolve(__dirname, '../contracts')) {

    this.log.print(Logger.state.MASTER, 
      "Config: ",
      "  Root contract directory: " + root,
      "\n"
    );

    this.log.print(Logger.state.NORMAL, "Generating solc_input...\n");
    return this.compile(SolcUtil.generateSolcInputFile(filepath, root));

  }

  /**
   * A function which compiles a raw "contract solc input name" and source code. 
   *
   * @param {string} base - The "raw contract solc input name" (i.e. 'erc20/ERC20Interface.sol') 
   * @param {string} src - The raw Solidity smart contract code as a string.
   * @return {object} output - The returned output generated by solc. See: https://solidity.readthedocs.io/en/v0.5.7/using-the-compiler.html#output-description
   */
  compileSource(base, src) {
    return this.compile(SolcUtil.generateSolcInput(base, src));
  }

  //Not working... :/ Uncertain how to handle this sort of callback stuff synchronously
  async getPackageVersion(module) {
    //Assume package-lock.json exists, by default
    let packageJSON = "../package-lock.json";
  /*  if(fs.access(packageJSON, fs.constants.F_OK)){
      packageJSON = "../package.json";
      if(fs.access(packageJSON, fs.constants.F_OK)){
        throw "Neither package.json or package-lock.json exists";
      }
    }*/

    fs.access(packageJSON, fs.constants.F_OK, (err) => {
      if(err) {
        packageJSON = "../package.json";
        fs.access(packageJSON, fs.constants.F_OK, (err2) => {
          if(err2) {
            throw "Neither package.json or package-lock.json exists\n" + err + "\n" + err2;
          } else {
            const packages = require(packageJSON);
            return packages.dependencies[module];
          }
        });
      } else {
        const packages = require(packageJSON);
        console.log(packages.dependencies)
        return packages.dependencies[module];
      }
    });

  }


}

module.exports = Compiler;