Skip to content

Instantly share code, notes, and snippets.

@Aadv1k
Last active June 23, 2022 13:15
Show Gist options
  • Save Aadv1k/bfa9f874345c6204e4871e66b2f6d99b to your computer and use it in GitHub Desktop.
Save Aadv1k/bfa9f874345c6204e4871e66b2f6d99b to your computer and use it in GitHub Desktop.
program to fetch data from leetcode and generate tests + starter code
#!/usr/bin/env node
/*
* Author: Aadv1k<aadv1k@outlook.com>
* License: GNU Public License
* Source: https://gist.github.com/Aadv1k/bfa9f874345c6204e4871e66b2f6d99b
* Install: npx https://gist.github.com/Aadv1k/bfa9f874345c6204e4871e66b2f6d99b
*/
const axios = require("axios");
const cheerio = require("cheerio");
const { writeFileSync, existsSync, mkdirSync } = require("fs");
const path = require("path");
async function getQuestion(title) {
const payload = {
query:
"query getQuestionDetail($titleSlug: String!) { isCurrentUserAuthenticated question(titleSlug: $titleSlug) { questionId questionFrontendId questionTitle translatedTitle questionTitleSlug content translatedContent difficulty stats allowDiscuss contributors { username profileUrl __typename } similarQuestions mysqlSchemas randomQuestionUrl sessionId categoryTitle submitUrl interpretUrl codeDefinition sampleTestCase enableTestMode metaData enableRunCode enableSubmit judgerAvailable infoVerified envInfo urlManager article questionDetailUrl libraryUrl adminUrl companyTags { name slug translatedName __typename } companyTagStats topicTags { name slug translatedName __typename } __typename } interviewed { interviewedUrl companies { id name slug __typename } timeOptions { id name __typename } stageOptions { id name __typename } __typename } subscribeUrl isPremium loginUrl}",
variables: { titleSlug: title },
};
const url = "https://leetcode.com/graphql";
const headers = { "content-type": "application/json" };
let res = await axios.post(url, payload, headers);
let que = res.data["data"]["question"];
let content = cheerio.load(que.content);
return {
id: que.questionId,
title: que.questionTitle,
titleUrl: que.questionTitleSlug,
content: content.text(),
codeDefinition: que.codeDefinition,
input: content("pre")
.text()
.split("\n")
.filter((e) => e.trim().includes("Input"))
.map((e) => e.split(":")[1].trim())
.map((e) => e.replace(/"/g, "'")),
output: content("pre")
.text()
.split("\n")
.filter((e) => e.trim().includes("Output"))
.map((e) => e.split(":")[1].trim())
.map((e) => e.replace(/"/g, "'")),
stats: que.stats,
};
}
const ARGSIZE = 4;
const RED = "\x1b[31m";
const GREEN = "\x1b[32m";
const CLREND = "\x1b[0m";
const args = process.argv;
if (args.length < ARGSIZE) {
console.log(
"Not enough arguments! \nUSAGE: ./leetcode.js <leetcode-problem-in-dash-case> <target-folder>"
);
process.exit(1);
// process.exitCode = 1;
// ^- This doesn't work the way I want it to.
}
let parsedArgs = args
.slice(2, 3)
.map((e) => e[0].toLowerCase() + e.slice(1))
.join("-");
let srcPath = path.join(process.cwd(), args[3]);
getQuestion(parsedArgs)
.then((data) => {
let probName = `problem${data["id"]}-${data["titleUrl"]}`;
console.log(GREEN + `[SUCCESS] Found leetcode ${probName}` + CLREND);
if (!existsSync(srcPath)) {
console.error(
RED + `[WARN] Folder ${srcPath} not found, creating new folder` + CLREND
);
mkdirSync(srcPath);
}
writeFileSync(path.join(srcPath, `${probName}.txt`), data["content"]);
let probs = data["input"].map((e, i) => [e, data["output"][i]]);
let camelName = data["titleUrl"]
.split("-")
.map((elem, i) => {
if (i === 0) {
return elem;
}
return `${elem[0].toUpperCase()}${elem.slice(1)}`;
})
.join("");
/*******************************************/
console.log(GREEN + "[SUCCESS] Writing source..." + CLREND);
// Generate source
let sourceFileContent = JSON.parse(data["codeDefinition"]).find(
(e) => e["value"] === "javascript"
);
writeFileSync(
path.join(srcPath, `${camelName}.js`),
sourceFileContent["defaultCode"]
);
/*******************************************/
/*******************************************/
console.log(GREEN + `[SUCCESS] Writing tests...` + CLREND);
// Generate tests
const testArgs = probs.map((e) => [
e[0]
.split("=")
.map((e, i) => {
if (i === 1) {
let tar = e.trim().split(" ").shift();
return tar.slice(0, tar.length - 1);
}
return e.trim();
})
.filter((_, i) => i !== 0),
e[1],
]);
let testFileTests = testArgs
.map((prob) => {
let inp = prob[0].join(", ");
let out = prob[1];
return ` test("${inp} -> ${out}", () => {\n expect(res(${inp})).toEqual(${out}); \n });`;
})
.join("\n\n");
let testFileContent = `const res = require('./${camelName}');\n\ndescribe('Leetcode problem#${data["id"]}: ${data["title"]}', () => {\n${testFileTests}\n});`;
// Write tests to the file
writeFileSync(path.join(srcPath, `${camelName}.test.js`), testFileContent);
/*******************************************/
})
.catch((err) => console.error(err));
{
"name": "leetcode.js",
"version": "1.0.0",
"author": "Aadvik <aadv1k@outlook.com>",
"license": "GPL-3.0-or-later",
"dependencies": {
"axios": "^0.27.2",
"cheerio": "^1.0.0-rc.11"
},
"bin": "./leetcode.js"
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment