{"size": 5780, "ext": "js", "max_stars_repo_path": "index.js", "max_stars_repo_name": "qhacks/devpost-stats-cli", "max_stars_repo_stars_event_min_datetime": "2017-12-17T21:53:52.000Z", "max_stars_repo_stars_event_max_datetime": "2018-09-25T13:31:30.000Z", "max_issues_repo_path": "index.js", "max_issues_repo_name": "qhacks/devpost-stats-cli", "max_issues_repo_issues_event_min_datetime": null, "max_issues_repo_issues_event_max_datetime": null, "max_forks_repo_path": "index.js", "max_forks_repo_name": "qhacks/devpost-stats-cli", "max_forks_repo_forks_event_min_datetime": null, "max_forks_repo_forks_event_max_datetime": null}
{"max_stars_count": 3, "max_issues_count": null, "max_forks_count": null, "avg_line_length": 28.4729064039, "max_line_length": 108, "alphanum_fraction": 0.6242214533}
#!/usr/bin/env node

const _ = require('lodash');
const fs = require('fs');
const meow = require('meow');
const mkdirp = require('mkdirp');
const ora = require('ora');
const parse = require('csv-parse');
const path = require('path');
const { promisify } = require('util');

/* Promisify callback function */
const parseAsync = promisify(parse);
const readFileAsync = promisify(fs.readFile);
const writeFileAsync = promisify(fs.writeFile);

const spinner = ora();

const CLI_INPUT_VALIDATORS = [
    [
        [_.isString, '<csv> must be a string']
    ]
];
const DEVPOST_KEYS = {
    PRIZES: 'Desired Prizes',
    SCHOOLS: 'College/Universities Of Team Members',
    TECHNOLOGIES: 'Built With'
};
const MIXINS = [
    getUniversities,
    getTechnologies,
    getPrizes
];

main().catch(console.error);

async function main() {
    const cli = initializeCli();
    const csvPath = path.resolve(cli.input[0]);
    if (!fs.existsSync(csvPath)) {
        throw Error(`File "${csvPath}" does not exist`);
    }

    const stats = await createStats(csvPath);
    await outputStats(stats, cli.flags);
}

function initializeCli() {
    const cliOptions = {
        flags: {
            outputFile: {
                type: 'string',
                alias: 'o'
            }
        }
    };
    const cli = meow(`
	Usage
	  $ devpost-stats <csv>
	  
	Options
	  --output-file, -o  Specify a path and JSON file to save the statistics to

	Examples
	  $ devpost-stats ../path/to.csv
        { ...someStats }
	  
	  $ devpost-stats ../path/to.csv -o ./path/to/output.json
        ✔ Statistics saved to file
`, cliOptions);
    try {
        validateCli(cli, CLI_INPUT_VALIDATORS);
        return cli;
    } catch (e) {
        cli.showHelp()
    }
}

function validateCli(cli, inputValidators) {
    if (cli.input.length !== inputValidators.length) {
        throw Error('Invalid number of inputs supplied!');
    }

    const mappedInputValidator = inputValidators.map((validator, index) => [cli.input[index], validator]);
    mappedInputValidator.forEach(([input, validators]) => {
        executeValidatorsForInput(input, validators);
    });
}


function executeValidatorsForInput(input, validators) {
    validators.forEach(([validator, errorMessage]) => {
        if (!_.isFunction(validator)) {
            throw Error('Invalid validator. Only supply functions as validators!')
        }
        if (!validator(input)) {
            throw Error(errorMessage);
        }
    });
}


async function createStats(csvPath) {
    const submissions = await csvToJson(csvPath);
    const defaultStats = {
        count: submissions.length,
        submissions
    };
    return await (_.flow(
        () => defaultStats,
        ...MIXINS.map(mixin => mixin(submissions))
    ))();
}

async function csvToJson(csvPath) {
    const csv = (await readFileAsync(csvPath)).toString();
    const json = await parseCsvToJson(csv);
    const headers = _.head(json);
    const submissions = json.slice(1);
    return submissions.map(submission => _.zipObject(headers, submission))
}

async function parseCsvToJson(csv) {
    const options = { delimiter: ',', quote: '"', relax_column_count: true };
    return await parseAsync(csv, options);
}

function getFieldFromSubmissions(submissions, fieldKey) {
    return _(submissions)
        .map(
            ({ [fieldKey]: field }) =>
                _.isEmpty(field)
                    ? null
                    : field.split(', ').map(s => s.trim())
        )
        .compact()
        .flatten()
        .value();
}

function getCountFromArray(arr) {
    const INITIAL_COUNT = 1;
    return arr.reduce((counter, field) => {
        return Object.assign({}, counter, {
            [field]: isNaN(counter[field])
                ? INITIAL_COUNT
                : counter[field] + 1
        })
    }, {});
}

function getUniversities(submissions) {
    return (stats) => {
        spinner.start('Getting university count from submissions');
        const universitiesForSubmissions = getFieldFromSubmissions(submissions, DEVPOST_KEYS.SCHOOLS);
        const universities = getCountFromArray(universitiesForSubmissions);
        spinner.succeed('University count successful!');
        return Object.assign({}, stats, { universities });
    }
}

function getTechnologies(submissions) {
    return (stats) => {
        spinner.start('Getting technology count from submissions');
        const technologiesFromSubmissions = getFieldFromSubmissions(submissions, DEVPOST_KEYS.TECHNOLOGIES);
        const technologies = getCountFromArray(technologiesFromSubmissions);
        spinner.succeed('Technology count successful!');
        return Object.assign({}, stats, { technologies });
    }
}

function getPrizes(submissions) {
    return (stats) => {
        spinner.start('Getting submissions per prize');
        const prizesFromSubmissions = getFieldFromSubmissions(submissions, DEVPOST_KEYS.PRIZES);
        const prizes = getCountFromArray(prizesFromSubmissions);
        spinner.succeed('Submissions per prize calculated successfully!');
        return Object.assign({}, stats, { prizes });
    }
}

async function outputStats(stats, flags) {
    if (flags.outputFile) {
        const outputFile = path.resolve(flags.outputFile);
        try {
            spinner.start('Writing statistics to file');
            await saveStatsToFile(stats, outputFile);
            spinner.succeed('Statistics saved to file')
        } catch (error) {
            throw Error(JSON.stringify({
                message: 'Unable to save stats to file!',
                error
            }));
        }
    } else {
        console.log(stats);
    }
}

async function saveStatsToFile(stats, outputFile) {
    await writeFileAsync(outputFile, JSON.stringify(stats));
}
