Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/v1.2 #4

Merged
merged 6 commits into from
Jan 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
754 changes: 131 additions & 623 deletions src/shell/command.ts

Large diffs are not rendered by default.

13 changes: 13 additions & 0 deletions src/shell/commands/cd.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { BJShell } from "@/shell";

export default function cd(that: BJShell, arg: string[]) {
return () => {
try {
const path = arg[0] ?? "";
process.chdir(path);
} catch (e) {
if (e instanceof Error) console.log(e.message);
else console.log(e);
}
};
}
30 changes: 30 additions & 0 deletions src/shell/commands/exec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { BJShell } from "@/shell";
import { spawn } from "child_process";

export default function exec(that: BJShell, arg: string[]) {
return async () => {
if (arg.length === 0) {
console.log("exec <command>");
return;
}
// https://velog.io/@dev2820/nodejs%EC%9D%98-%EC%9E%90%EC%8B%9D%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4
// https://kisaragi-hiu.com/nodejs-cmd/
// FIXME: stdio full sync

const command = arg.join(" ");
that.r.setPrompt("");
that.cp = spawn(command, [], { shell: true });
await new Promise((resolveFunc) => {
that.cp!.stdout?.on("data", (x: string) => {
process.stdout.write(x.toString());
});
that.cp!.stderr?.on("data", (x: string) => {
process.stderr.write(x.toString());
});
that.cp!.on("exit", (code: number) => {
resolveFunc(code);
});
});
that.cp = null;
};
}
67 changes: 67 additions & 0 deletions src/shell/commands/help.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { BJShell } from "@/shell";
import chalk from "chalk";
import { table } from "table";
import { Command } from "../command";

export default function help(that: BJShell, arg: string[]) {
return (commands: { [key: string]: Command }) => {
if (arg[0] == "all") {
const data = [];
data.push(["단축어", "명령어", "설명"]);
for (const key in commands) {
const cmd = commands[key];
data.push([cmd.alias ?? "", key, cmd.desc]);
}
console.log(table(data));
}
if (arg[0] == "testcase") {
console.log(
`${chalk.yellow("커스텀 테스트 케이스 문법 설명")}
각 언어의 주석에 <BJTestcase> </BJTestcase> 태그 를 삽입합니다. 해당 태그 밖에 있는 테스트케이스는 무시됩니다.
주석의 종류(라인, 블록)는 상관없으며 , 태그의 대소문자는 구분하지 않습니다.
해당 태그 안에 있는 일반 문자들은 무시됩니다. 테스트케이스를 설명하는데 사용할 수 있습니다.
해당 태그 안에 다음과 같은 방식으로 테스트케이스 입출력 쌍을 추가할 수 있습니다.
<< 와 -- 사이에 있는 문자(개행문자 포함)는 입력으로, >> 와 -- 는 출력 결과로 인식됩니다.
<< 혹은 -- 다음에 오는 문자는 <<, -- 와 반드시 들여쓰기 공백 (탭) 개수를 일치시켜야 합니다.
<< (input) -- (output) >> 가 하나의 테스트케이스이며, 태그에 여러개의 테스트케이스를 추가할 수 있습니다.
커스텀 테스트케이스 실행 결과에 (커스텀) 이라는 접두어가 붙습니다.

${chalk.green(`예시) 1000.py

"""
<BJTestcase>
1. 음수가 포함된 덧셈
<<
-1 1
--
0
>>

2. 큰수의 덧셈
<<
999999999999 1
---
1000000000000
>>
</BJTestcase>
"""
a, b = map(int, input().split())
print(a + b)
`)}
`
);
} else if (arg.length === 0) {
const data = [];
data.push(["단축어", "명령어", "설명"]);
for (const key in commands) {
const cmd = commands[key];
if (cmd.important) data.push([cmd.alias ?? "", key, cmd.desc]);
}
console.log(table(data));
console.log("모든 명령어를 보려면 'help all' 를 타이핑하세요.");
console.log(
"커스텀 테스트케이스 문법을 보려면 'help testcase' 를 타이핑하세요."
);
}
};
}
46 changes: 46 additions & 0 deletions src/shell/commands/lang.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { BJShell } from "@/shell";
import { getLanguages } from "@/net/parse";
import chalk from "chalk";
import conf from "@/config";
import { table } from "table";

export default function lang(that: BJShell, arg: string[]) {
return async () => {
if (arg[0] == "list") {
const rawint = parseInt(arg[1]);
const col_num = isNaN(rawint) ? 3 : rawint;
const data = [];
const langs = await getLanguages();
for (let i = 0; i < langs.length; i += col_num) {
const row = [];
for (let j = 0; j < col_num; j++) {
row.push(langs[i + j]?.name ?? "");
row.push(langs[i + j]?.extension ?? "");
row.push(langs[i + j]?.num ?? "");
}
data.push(row);
}
console.log(table(data, { drawVerticalLine: (i) => i % 3 === 0 }));
console.log(
`원하는 언어를 사용하기 위해서 ${chalk.blueBright(
"lang <language number>"
)}를 타이핑하세요.`
);
console.log(
`언어를 사용하기 전에, 자동으로 불러온 언어 설정이 유효한지 확인하세요. 그렇지 않으면, ${chalk.blueBright(
conf.LANGPATH
)} 파일의 \`compile\` 과 \`run\` 명령어를 수동으로 바꿔주셔야 합니다.`
);
return;
}
if (arg.length !== 1 || isNaN(parseInt(arg[0]))) {
console.log("lang <language number>");
console.log("언어 목록을 보고 싶다면 lang list를 타이핑하세요.");
return;
} else if (!that.findLang(parseInt(arg[0]))) {
console.log("유효하지 않은 언어 번호입니다.");
return;
}
await that.user.setLang(parseInt(arg[0]));
};
}
10 changes: 10 additions & 0 deletions src/shell/commands/logout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { BJShell } from "@/shell";

export default function logout(that: BJShell, arg: string[]) {
return async () => {
await that.user.setToken("");
await that.user.setAutologin("");
that.setLoginLock(0);
console.log("로그아웃 되었습니다.");
};
}
20 changes: 20 additions & 0 deletions src/shell/commands/ls.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { BJShell } from "@/shell";
import fs from "fs/promises";
import chalk from "chalk";

export default function ls(that: BJShell, arg: string[]) {
return async () => {
try {
const files = await fs.readdir(process.cwd());
let output = "";
for (const file of files) {
const isDir = (await fs.stat(file)).isDirectory();
output += `${isDir ? chalk.blue(file) : file} `;
}
console.log(output);
} catch (e) {
if (e instanceof Error) console.log(e.message);
else console.log(e);
}
};
}
36 changes: 36 additions & 0 deletions src/shell/commands/probset.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { BJShell } from "@/shell";
import probset_set from "./probset/set";
import probset_select from "./probset/select";
import probset_list from "./probset/list";
import probset_load from "./probset/load";
import probset_clear from "./probset/clear";

export default function probset(that: BJShell, arg: string[]) {
return async () => {
switch (arg[0]) {
case "set":
case "s":
await probset_set(that, arg)();
break;
case "clear":
case "c":
await probset_clear(that, arg)();
break;
case "next":
case "n":
await probset_select(that, arg)(true);
break;
case "prev":
case "p":
await probset_select(that, arg)(false);
break;
case "list":
case "l":
await probset_list(that, arg)();
break;
default:
await probset_load(that, arg)();
break;
}
};
}
9 changes: 9 additions & 0 deletions src/shell/commands/probset/clear.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { BJShell } from "@/shell";
import { saveToLocal } from "@/storage/localstorage";

export default function probset_clear(that: BJShell, arg: string[]) {
return async () => {
await saveToLocal("ps", undefined);
console.log("문제 셋을 초기화했습니다.");
};
}
40 changes: 40 additions & 0 deletions src/shell/commands/probset/list.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { BJShell } from "@/shell";
import { loadFromLocal } from "@/storage/localstorage";
import chalk from "chalk";
import { table } from "table";

export default function probset_list(that: BJShell, arg: string[]) {
return async () => {
const probsObj = await loadFromLocal("ps");
if (!probsObj) {
console.log("저장된 문제 셋이 없습니다.");
return;
}
console.log(chalk.yellow(`문제집 이름: ${probsObj.title}`));
console.log(chalk.blue(`링크: ${probsObj?.url}`));
const data = [];
const col = 3;
const row = Math.ceil(probsObj.probset.length / col);
for (let i = 0; i < row; i++) {
const rowdata = [];
for (let j = 0; j < col; j++) {
const idx = i * col + j;
if (idx >= probsObj.probset.length) {
rowdata.push(...["", "", ""]);
continue;
}
const qnum = probsObj.probset[idx][0];
const title = probsObj.probset[idx][1];
if (qnum == that.user.getQnum()) {
rowdata.push(
...[chalk.green(idx), chalk.green(qnum), chalk.green(title)]
);
} else {
rowdata.push(...[idx, qnum, title]);
}
}
data.push(rowdata);
}
console.log(table(data, { drawVerticalLine: (i) => i % 3 === 0 }));
};
}
31 changes: 31 additions & 0 deletions src/shell/commands/probset/load.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { BJShell } from "@/shell";
import { saveToLocal } from "@/storage/localstorage";
import set from "../set";
import { getProblemSet } from "@/net/parse";

export default function probset_load(that: BJShell, arg: string[]) {
return async () => {
const urlReg =
/https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
if (!urlReg.test(arg[0])) {
console.log("올바른 URL이 아닙니다.");
return;
}
const probset = await getProblemSet(arg[0]);
if (probset.length == 0) {
console.log("문제가 없습니다.");
return;
}
const probsetTitle = arg[1] ? arg.slice(1).join(" ") : "My Problem Set";
const probsObj = {
title: probsetTitle,
url: arg[0],
probset,
};
await saveToLocal("ps", probsObj);
console.log(`${probsetTitle}: 문제 ${probset.length}개를 저장했습니다.`);
console.log("첫번째 문제를 불러오고 있습니다...");

await set(that, arg)(probset[0][0]);
};
}
30 changes: 30 additions & 0 deletions src/shell/commands/probset/select.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { BJShell } from "@/shell";
import { loadFromLocal } from "@/storage/localstorage";
import set from "../set";

export default function probset_select(that: BJShell, arg: string[]) {
return async (next: boolean) => {
const probsObj = await loadFromLocal("ps");
if (!probsObj) {
console.log("저장된 문제 셋이 없습니다.");
return;
}
const qnum = that.user.getQnum();
const idx = probsObj.probset.findIndex(
(x: [number, string]) => x[0] == qnum
);
if (idx == -1) {
console.log("현재 문제가 저장된 문제 셋에 없습니다.");
return;
}
if (idx == 0 && !next) {
console.log("첫 번째 문제입니다.");
return;
}
if (idx == probsObj.probset.length - 1 && next) {
console.log("마지막 문제입니다.");
return;
}
await set(that, arg)(probsObj.probset[next ? idx + 1 : idx - 1][0]);
};
}
23 changes: 23 additions & 0 deletions src/shell/commands/probset/set.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { BJShell } from "@/shell";
import { loadFromLocal } from "@/storage/localstorage";
import set from "../set";

export default function probset_set(that: BJShell, arg: string[]) {
return async () => {
if (arg.length == 1) {
console.log("probset set <number>");
return;
}
const probsObj = await loadFromLocal("ps");
if (!probsObj) {
console.log("저장된 문제 셋이 없습니다.");
return;
}
const val = parseInt(arg[1]);
if (isNaN(val) || val < 0 || val >= probsObj.probset.length) {
console.log("probset set <number>");
return;
}
await set(that, arg)(probsObj.probset[val][0]);
};
}
Loading