From 7f03a7790b9c09eed04f1649bd52ac9f0d03f77b Mon Sep 17 00:00:00 2001 From: SangHoon Lee Date: Fri, 15 Mar 2024 02:21:35 +0900 Subject: [PATCH] =?UTF-8?q?runTestCase=20c,=20c++,=20java,=20js,=20rust,?= =?UTF-8?q?=20python=20=EC=A7=80=EC=9B=90=20=EB=A7=A5=EB=B6=81=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 6148 -> 0 bytes .gitignore | 2 + package.json | 4 + src/commands/runTestCase.ts | 168 ++++++++++++++++++++++++++++++++++++ src/extension.ts | 9 ++ src/libs/getProbNum.ts | 21 +++++ src/libs/searchProblem.ts | 24 ++++-- 7 files changed, 222 insertions(+), 6 deletions(-) delete mode 100644 .DS_Store create mode 100644 src/commands/runTestCase.ts create mode 100644 src/libs/getProbNum.ts diff --git a/.DS_Store b/.DS_Store deleted file mode 100644 index 15dc83b1a316eb3143d0f6e0cc3f31ca24dac623..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5Z-NTn^1%v6nb3nS}?7DND*SKFJMFuDm5{s4W`-Br1nq>IqS`5@!$*i z3ci3Z;_OewT7PauWd>%y+1Z)h?3b{cA%xJz(616wB7_*A0y8EwpNJS2Iwu+J$pcb3 zN1MdnDDHb9_vWI}@f#Ts?=DXQ(jzYEklFY5==H-OPAu!0X0o}Ze4$_#iiHcO%STS) zCd0Vl4$egHnDfw^+OBsJv`4Md$^nlOH;CE;nGpDG2su9uBA<^Md>Hw$%yra&X&Pp$ zR349OTRRn2v#d#ljdyESg>7zZPbQ|Zy0%_FYF&rDh~FwADSUHMmNib{4vd*Sg`KK% z#e4l}z{l|(Z#MNeYrH(9_w~J}{rMRG$C!Ti`*m)dK@{?+19MNG{oNy>i3}hE$iTuF z&}WcQT-b`Z5Hf%a{1pbo`M^L0+8R@Z^5}p@mH>b`a7zJO)Dj|xX|y$_3Sk6+t#g|bcP#_G1QjCUQiX3RV92W&V#QTl29*MSCJaDZW2z85 PAowF7X}|^("extension", ""); + + const maxWidth = 40; + + const preMessage1 = centerText( + ` ${problemNumber}. ${sp.title} `, + maxWidth + ); + const preMessage2 = `문제링크: https://boj.kr/${problemNumber}`; + + outputChannel.appendLine(preMessage1); + outputChannel.appendLine(preMessage2); + outputChannel.appendLine(``); + + const createOutputMessage = ( + testCaseIndex, + result, + expected, + actual + ) => { + const passedMessage = `✅ Test Case #${testCaseIndex + 1}: Passed`; + const failedMessage = `❌ Test Case #${ + testCaseIndex + 1 + }: Failed\n${centerText( + ` Expected `, + maxWidth + )}\n${expected}${centerText(` Actual `, maxWidth)}\n${actual}`; + + return result ? passedMessage : failedMessage; + }; + + const runTest = (input, expectedOutput, testCaseIndex) => { + return new Promise((resolve, reject) => { + const processIO = getProcessIO(extension, filePath); + let outputData = ""; + + processIO!.stdout.on("data", (data) => { + outputData += data.toString(); + }); + + processIO!.stderr.on("data", (data) => { + console.error(`에러: ${data}`); + reject(new Error(data.toString())); + }); + + processIO!.on("close", (code) => { + const actualOutput = outputData.trim(); + const isPassed = actualOutput === expectedOutput.trim(); + const message = createOutputMessage( + testCaseIndex, + isPassed, + expectedOutput, + actualOutput + ); + outputChannel.appendLine(message); + + resolve(); + }); + + processIO!.on("error", (err) => { + console.error(`에러: ${err}`); + reject(err); + }); + + processIO!.stdin.write(input); + processIO!.stdin.end(); + }); + }; + + for (let i = 0; i < sp.sampleInputs.length; i++) { + await runTest(sp.sampleInputs[i], sp.sampleOutputs[i], i); + } + + const postMessage1 = centerText(` 채점 종료 `, maxWidth); + const postMessage2 = `결과 창은 1분 뒤에 닫힙니다.`; + + outputChannel.appendLine(``); + outputChannel.appendLine(postMessage1); + outputChannel.appendLine(postMessage2); + + outputChannel.show(true); + setTimeout(() => { + outputChannel.dispose(); + }, 60000); + } catch (error) { + vscode.window.showErrorMessage( + "테스트 케이스 실행 중 오류가 발생했습니다. 직접 실행해서 오류를 확인해주세요." + ); + } +} + +function centerText(text, maxWidth) { + const padding = Math.max(0, maxWidth - text.length); + const paddingLeft = Math.floor(padding / 2); + const paddingRight = padding - paddingLeft; + return "-".repeat(paddingLeft) + text + "-".repeat(paddingRight); +} + +function getProcessIO(extension: string, filePath: string) { + if (extension === "c") { + const objectFileURL = filePath.replace(/\.[^/.]+$/, ""); + execSync(`gcc "${filePath}" -o "${objectFileURL}"`); + return spawn(`${objectFileURL}`); + } else if (extension === "cpp") { + const objectFileURL = filePath.slice(0, filePath.length - 4); + execSync(`g++ -std=c++17 "${filePath}" -o "${objectFileURL}"`); + return spawn(`${objectFileURL}`); + } else if (extension === "java") { + const dirName = path.dirname(filePath); + return spawn(`java`, ["-cp", dirName, filePath]); + } else if (extension === "js") { + return spawn("node", [filePath]); + } else if (extension === "rs") { + const fileName = path.basename(filePath, ".rs"); + const crateName = fileName.replace(/[^a-zA-Z0-9]+/g, "_").toLowerCase(); + const objectFileURL = path.join(".", `${crateName}.out`); + const dirName = path.dirname(filePath); + const options = { + cwd: path.dirname(filePath), + env: { + ...process.env, + RUSTC_FLAGS: "-D tempdir=/tmp", + }, + }; + execSync( + `rustc --crate-name "${crateName}" "${filePath}" -o "${objectFileURL}"`, + options + ); + return spawn(`${dirName}/${objectFileURL}`); + } else if (extension === "py") { + return spawn("python3", [filePath]); + } +} diff --git a/src/extension.ts b/src/extension.ts index dddc807..895e47d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -5,6 +5,8 @@ import { createProblem } from "./commands/createProblem"; import { pushToGithub } from "./commands/pushToGithub"; import { makeWorkflow } from "./libs/makeWorkflow"; import { showManual } from "./commands/showManual"; +import { runTestCase } from "./commands/runTestCase"; +import { getProbNum } from "./libs/getProbNum"; export function activate(context: vscode.ExtensionContext) { // 확장프로그램이 처음 실행될 때, 설정이 되어있지 않으면 설정창을 띄워준다. @@ -79,6 +81,13 @@ export function activate(context: vscode.ExtensionContext) { }) ); + // runTestCase 커맨드 등록 + context.subscriptions.push( + vscode.commands.registerCommand("BOJ-EX.runTestCase", () => { + runTestCase(context); + }) + ); + // pushToGithub 커맨드 등록 context.subscriptions.push( vscode.commands.registerCommand("BOJ-EX.pushToGithub", () => { diff --git a/src/libs/getProbNum.ts b/src/libs/getProbNum.ts new file mode 100644 index 0000000..c6a1c0d --- /dev/null +++ b/src/libs/getProbNum.ts @@ -0,0 +1,21 @@ +import * as vscode from "vscode"; +import * as path from "path"; + +export function getProbNum(): string | null { + const editor = vscode.window.activeTextEditor; + if (!editor) { + vscode.window.showErrorMessage("No editor found."); + return null; + } + + const filePath = editor.document.uri.fsPath; + const dirName = path.basename(path.dirname(filePath)); + + const match = dirName.match(/^(\d+)/); + if (match) { + return match[1]; + } else { + vscode.window.showErrorMessage("Problem number not found."); + return null; + } +} diff --git a/src/libs/searchProblem.ts b/src/libs/searchProblem.ts index e6ace63..3a0b81d 100644 --- a/src/libs/searchProblem.ts +++ b/src/libs/searchProblem.ts @@ -2,10 +2,7 @@ import * as vscode from "vscode"; import axios from "axios"; import * as cheerio from "cheerio"; -export async function searchProblem( - problemNumber: string, - context: vscode.ExtensionContext -): Promise<{ +interface ProblemData { title: string; info: string | null; description: string; @@ -17,7 +14,18 @@ export async function searchProblem( sampleExplains: string[] | null; hint: string | null; source: string | null; -}> { +} + +export async function searchProblem( + problemNumber: string, + context: vscode.ExtensionContext +): Promise { + const cacheKey = `problem-${problemNumber}`; + const cachedData = context.globalState.get(cacheKey); + if (cachedData) { + return cachedData; + } + const response = await axios.get( `https://www.acmicpc.net/problem/${problemNumber}`, { @@ -89,7 +97,7 @@ export async function searchProblem( // 출처 추출 const source = $("#source").html(); - return { + const problemData: ProblemData = { title, info, description, @@ -102,4 +110,8 @@ export async function searchProblem( hint, source, }; + + await context.globalState.update(cacheKey, problemData); + + return problemData; }