terminal-quest 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/bin/terminal-quest.js +2 -0
- package/dist/App.d.ts +2 -0
- package/dist/App.js +33 -0
- package/dist/components/HintBar.d.ts +9 -0
- package/dist/components/HintBar.js +11 -0
- package/dist/components/MenuItem.d.ts +9 -0
- package/dist/components/MenuItem.js +9 -0
- package/dist/components/ObjectivePanel.d.ts +8 -0
- package/dist/components/ObjectivePanel.js +10 -0
- package/dist/components/ProgressBar.d.ts +8 -0
- package/dist/components/ProgressBar.js +10 -0
- package/dist/components/TerminalOutput.d.ts +11 -0
- package/dist/components/TerminalOutput.js +25 -0
- package/dist/components/TerminalPrompt.d.ts +10 -0
- package/dist/components/TerminalPrompt.js +46 -0
- package/dist/data/commands-meta.d.ts +17 -0
- package/dist/data/commands-meta.js +256 -0
- package/dist/data/stories/00-beginner-pc.d.ts +3 -0
- package/dist/data/stories/00-beginner-pc.js +841 -0
- package/dist/data/stories/01-first-server.d.ts +3 -0
- package/dist/data/stories/01-first-server.js +364 -0
- package/dist/data/stories/02-messy-project.d.ts +3 -0
- package/dist/data/stories/02-messy-project.js +433 -0
- package/dist/data/stories/03-log-detective.d.ts +3 -0
- package/dist/data/stories/03-log-detective.js +291 -0
- package/dist/data/stories/04-deploy-day.d.ts +3 -0
- package/dist/data/stories/04-deploy-day.js +337 -0
- package/dist/data/stories/05-git-incident.d.ts +3 -0
- package/dist/data/stories/05-git-incident.js +534 -0
- package/dist/data/stories/06-pipe-master.d.ts +3 -0
- package/dist/data/stories/06-pipe-master.js +377 -0
- package/dist/data/stories/07-dangerous-commands.d.ts +3 -0
- package/dist/data/stories/07-dangerous-commands.js +411 -0
- package/dist/data/stories/index.d.ts +4 -0
- package/dist/data/stories/index.js +14 -0
- package/dist/data/stories/k1-treasure-hunt.d.ts +3 -0
- package/dist/data/stories/k1-treasure-hunt.js +815 -0
- package/dist/data/types.d.ts +97 -0
- package/dist/data/types.js +2 -0
- package/dist/engine/Achievements.d.ts +5 -0
- package/dist/engine/Achievements.js +93 -0
- package/dist/engine/CommandHandler.d.ts +17 -0
- package/dist/engine/CommandHandler.js +177 -0
- package/dist/engine/HintEngine.d.ts +10 -0
- package/dist/engine/HintEngine.js +26 -0
- package/dist/engine/MissionEngine.d.ts +17 -0
- package/dist/engine/MissionEngine.js +84 -0
- package/dist/engine/TabCompletion.d.ts +14 -0
- package/dist/engine/TabCompletion.js +93 -0
- package/dist/engine/VirtualFS.d.ts +33 -0
- package/dist/engine/VirtualFS.js +276 -0
- package/dist/engine/commands/cat.d.ts +4 -0
- package/dist/engine/commands/cat.js +18 -0
- package/dist/engine/commands/cd.d.ts +4 -0
- package/dist/engine/commands/cd.js +12 -0
- package/dist/engine/commands/chmod.d.ts +4 -0
- package/dist/engine/commands/chmod.js +98 -0
- package/dist/engine/commands/clear.d.ts +4 -0
- package/dist/engine/commands/clear.js +4 -0
- package/dist/engine/commands/cp.d.ts +4 -0
- package/dist/engine/commands/cp.js +26 -0
- package/dist/engine/commands/cut.d.ts +4 -0
- package/dist/engine/commands/cut.js +76 -0
- package/dist/engine/commands/echo.d.ts +4 -0
- package/dist/engine/commands/echo.js +4 -0
- package/dist/engine/commands/find.d.ts +4 -0
- package/dist/engine/commands/find.js +60 -0
- package/dist/engine/commands/git.d.ts +4 -0
- package/dist/engine/commands/git.js +510 -0
- package/dist/engine/commands/grep.d.ts +4 -0
- package/dist/engine/commands/grep.js +127 -0
- package/dist/engine/commands/head.d.ts +4 -0
- package/dist/engine/commands/head.js +59 -0
- package/dist/engine/commands/help.d.ts +4 -0
- package/dist/engine/commands/help.js +32 -0
- package/dist/engine/commands/hint.d.ts +4 -0
- package/dist/engine/commands/hint.js +4 -0
- package/dist/engine/commands/index.d.ts +8 -0
- package/dist/engine/commands/index.js +51 -0
- package/dist/engine/commands/ls.d.ts +4 -0
- package/dist/engine/commands/ls.js +50 -0
- package/dist/engine/commands/man.d.ts +4 -0
- package/dist/engine/commands/man.js +51 -0
- package/dist/engine/commands/mkdir.d.ts +4 -0
- package/dist/engine/commands/mkdir.js +31 -0
- package/dist/engine/commands/mv.d.ts +4 -0
- package/dist/engine/commands/mv.js +15 -0
- package/dist/engine/commands/pwd.d.ts +4 -0
- package/dist/engine/commands/pwd.js +4 -0
- package/dist/engine/commands/rm.d.ts +4 -0
- package/dist/engine/commands/rm.js +49 -0
- package/dist/engine/commands/sort.d.ts +4 -0
- package/dist/engine/commands/sort.js +100 -0
- package/dist/engine/commands/tail.d.ts +4 -0
- package/dist/engine/commands/tail.js +59 -0
- package/dist/engine/commands/touch.d.ts +4 -0
- package/dist/engine/commands/touch.js +18 -0
- package/dist/engine/commands/uniq.d.ts +4 -0
- package/dist/engine/commands/uniq.js +61 -0
- package/dist/engine/commands/wc.d.ts +4 -0
- package/dist/engine/commands/wc.js +67 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.js +6 -0
- package/dist/screens/MissionBriefScreen.d.ts +9 -0
- package/dist/screens/MissionBriefScreen.js +27 -0
- package/dist/screens/MissionCompleteScreen.d.ts +9 -0
- package/dist/screens/MissionCompleteScreen.js +30 -0
- package/dist/screens/ProgressScreen.d.ts +8 -0
- package/dist/screens/ProgressScreen.js +24 -0
- package/dist/screens/SettingsScreen.d.ts +8 -0
- package/dist/screens/SettingsScreen.js +45 -0
- package/dist/screens/StorySelectScreen.d.ts +8 -0
- package/dist/screens/StorySelectScreen.js +81 -0
- package/dist/screens/TerminalScreen.d.ts +12 -0
- package/dist/screens/TerminalScreen.js +150 -0
- package/dist/screens/TitleScreen.d.ts +7 -0
- package/dist/screens/TitleScreen.js +27 -0
- package/dist/state/GameState.d.ts +8 -0
- package/dist/state/GameState.js +12 -0
- package/dist/state/ProgressStore.d.ts +9 -0
- package/dist/state/ProgressStore.js +45 -0
- package/dist/state/useGameState.d.ts +11 -0
- package/dist/state/useGameState.js +92 -0
- package/dist/utils/ascii-art.d.ts +4 -0
- package/dist/utils/ascii-art.js +22 -0
- package/dist/utils/colors.d.ts +17 -0
- package/dist/utils/colors.js +17 -0
- package/dist/utils/text.d.ts +4 -0
- package/dist/utils/text.js +28 -0
- package/package.json +58 -0
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export function head(fs, args) {
|
|
2
|
+
// Extract stdin
|
|
3
|
+
let stdin;
|
|
4
|
+
const stdinIdx = args.findIndex(a => a.startsWith('__stdin__:'));
|
|
5
|
+
if (stdinIdx !== -1) {
|
|
6
|
+
stdin = args[stdinIdx].slice('__stdin__:'.length);
|
|
7
|
+
args = [...args.slice(0, stdinIdx), ...args.slice(stdinIdx + 1)];
|
|
8
|
+
}
|
|
9
|
+
let lineCount = 10;
|
|
10
|
+
const files = [];
|
|
11
|
+
for (let i = 0; i < args.length; i++) {
|
|
12
|
+
const arg = args[i];
|
|
13
|
+
if (arg === '-n') {
|
|
14
|
+
i++;
|
|
15
|
+
if (i >= args.length) {
|
|
16
|
+
return { output: '', error: 'head: option requires an argument -- n' };
|
|
17
|
+
}
|
|
18
|
+
const n = parseInt(args[i], 10);
|
|
19
|
+
if (isNaN(n)) {
|
|
20
|
+
return { output: '', error: `head: invalid number of lines: '${args[i]}'` };
|
|
21
|
+
}
|
|
22
|
+
lineCount = n;
|
|
23
|
+
}
|
|
24
|
+
else if (arg.startsWith('-n')) {
|
|
25
|
+
const n = parseInt(arg.slice(2), 10);
|
|
26
|
+
if (isNaN(n)) {
|
|
27
|
+
return { output: '', error: `head: invalid number of lines: '${arg.slice(2)}'` };
|
|
28
|
+
}
|
|
29
|
+
lineCount = n;
|
|
30
|
+
}
|
|
31
|
+
else if (arg.startsWith('-')) {
|
|
32
|
+
return { output: '', error: `head: invalid option -- '${arg}'` };
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
files.push(arg);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
let content;
|
|
39
|
+
if (files.length > 0) {
|
|
40
|
+
try {
|
|
41
|
+
content = fs.readFile(files[0]);
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
return { output: '', error: `head: ${e.message}` };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (stdin !== undefined) {
|
|
48
|
+
content = stdin;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
return { output: '', error: 'head: missing file operand' };
|
|
52
|
+
}
|
|
53
|
+
if (content === '')
|
|
54
|
+
return { output: '' };
|
|
55
|
+
const lines = content.split('\n');
|
|
56
|
+
const selected = lines.slice(0, lineCount);
|
|
57
|
+
return { output: selected.join('\n') };
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=head.js.map
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
const HELP_TEXT = `使用可能なコマンド:
|
|
2
|
+
pwd 現在のディレクトリを表示
|
|
3
|
+
ls [path] ファイル一覧 (-l 詳細, -a 全表示)
|
|
4
|
+
cd [path] ディレクトリ移動
|
|
5
|
+
cat file... ファイル内容を表示
|
|
6
|
+
grep [opts] pattern file パターン検索 (-i, -n, -r)
|
|
7
|
+
cp [-r] src dest コピー
|
|
8
|
+
mv src dest 移動・名前変更
|
|
9
|
+
mkdir [-p] path ディレクトリ作成
|
|
10
|
+
rm [-r] [-f] path 削除
|
|
11
|
+
touch file 空ファイル作成
|
|
12
|
+
find [path] [-name p] [-type t] ファイル検索
|
|
13
|
+
head [-n N] file 先頭N行を表示
|
|
14
|
+
tail [-n N] file 末尾N行を表示
|
|
15
|
+
wc [-l] [-w] [-c] file 行数/単語数/バイト数
|
|
16
|
+
sort [-r] [-n] file ソート
|
|
17
|
+
uniq [-c] file 重複除去
|
|
18
|
+
cut -d delim -f N file フィールド切り出し
|
|
19
|
+
chmod mode file 権限変更
|
|
20
|
+
echo text [> file] テキスト出力/ファイル書き込み
|
|
21
|
+
git <subcmd> Gitコマンド (status/log/diff/branch/checkout/merge/stash)
|
|
22
|
+
clear 画面クリア
|
|
23
|
+
help このヘルプを表示
|
|
24
|
+
hint ヒントを表示
|
|
25
|
+
objectives / obj 現在の目標を表示
|
|
26
|
+
|
|
27
|
+
ヒント: コマンドは | (パイプ) で繋げられます
|
|
28
|
+
例: cat file.txt | grep ERROR | wc -l`;
|
|
29
|
+
export function help(_fs, _args) {
|
|
30
|
+
return { output: HELP_TEXT };
|
|
31
|
+
}
|
|
32
|
+
//# sourceMappingURL=help.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { VirtualFS } from '../VirtualFS.js';
|
|
2
|
+
export interface CommandResult {
|
|
3
|
+
output: string;
|
|
4
|
+
error?: string;
|
|
5
|
+
}
|
|
6
|
+
export type CommandFn = (fs: VirtualFS, args: string[]) => CommandResult;
|
|
7
|
+
export declare const commandRegistry: Record<string, CommandFn>;
|
|
8
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { pwd } from './pwd.js';
|
|
2
|
+
import { ls } from './ls.js';
|
|
3
|
+
import { cd } from './cd.js';
|
|
4
|
+
import { cat } from './cat.js';
|
|
5
|
+
import { grep } from './grep.js';
|
|
6
|
+
import { cp } from './cp.js';
|
|
7
|
+
import { echo } from './echo.js';
|
|
8
|
+
import { help } from './help.js';
|
|
9
|
+
import { hint } from './hint.js';
|
|
10
|
+
import { clear } from './clear.js';
|
|
11
|
+
import { git } from './git.js';
|
|
12
|
+
import { mkdir } from './mkdir.js';
|
|
13
|
+
import { mv } from './mv.js';
|
|
14
|
+
import { rm } from './rm.js';
|
|
15
|
+
import { find } from './find.js';
|
|
16
|
+
import { touch } from './touch.js';
|
|
17
|
+
import { head } from './head.js';
|
|
18
|
+
import { tail } from './tail.js';
|
|
19
|
+
import { wc } from './wc.js';
|
|
20
|
+
import { sort } from './sort.js';
|
|
21
|
+
import { uniq } from './uniq.js';
|
|
22
|
+
import { cut } from './cut.js';
|
|
23
|
+
import { chmod } from './chmod.js';
|
|
24
|
+
import { man } from './man.js';
|
|
25
|
+
export const commandRegistry = {
|
|
26
|
+
pwd,
|
|
27
|
+
ls,
|
|
28
|
+
cd,
|
|
29
|
+
cat,
|
|
30
|
+
grep,
|
|
31
|
+
cp,
|
|
32
|
+
echo,
|
|
33
|
+
help,
|
|
34
|
+
hint,
|
|
35
|
+
clear,
|
|
36
|
+
git,
|
|
37
|
+
mkdir,
|
|
38
|
+
mv,
|
|
39
|
+
rm,
|
|
40
|
+
find,
|
|
41
|
+
touch,
|
|
42
|
+
head,
|
|
43
|
+
tail,
|
|
44
|
+
wc,
|
|
45
|
+
sort,
|
|
46
|
+
uniq,
|
|
47
|
+
cut,
|
|
48
|
+
chmod,
|
|
49
|
+
man,
|
|
50
|
+
};
|
|
51
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
export function ls(fs, args) {
|
|
2
|
+
let showAll = false;
|
|
3
|
+
let longFormat = false;
|
|
4
|
+
const paths = [];
|
|
5
|
+
for (const arg of args) {
|
|
6
|
+
if (arg.startsWith('-')) {
|
|
7
|
+
for (const ch of arg.slice(1)) {
|
|
8
|
+
if (ch === 'a')
|
|
9
|
+
showAll = true;
|
|
10
|
+
else if (ch === 'l')
|
|
11
|
+
longFormat = true;
|
|
12
|
+
else
|
|
13
|
+
return { output: '', error: `ls: invalid option -- '${ch}'` };
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
else {
|
|
17
|
+
paths.push(arg);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const targetPath = paths.length > 0 ? paths[0] : '.';
|
|
21
|
+
if (!fs.exists(targetPath)) {
|
|
22
|
+
return { output: '', error: `ls: cannot access '${targetPath}': No such file or directory` };
|
|
23
|
+
}
|
|
24
|
+
if (fs.isFile(targetPath)) {
|
|
25
|
+
const resolved = fs.resolvePath(targetPath);
|
|
26
|
+
const name = resolved.split('/').filter(Boolean).pop() ?? resolved;
|
|
27
|
+
if (longFormat) {
|
|
28
|
+
const perms = fs.getPermissions(targetPath);
|
|
29
|
+
return { output: `${perms} ${name}` };
|
|
30
|
+
}
|
|
31
|
+
return { output: name };
|
|
32
|
+
}
|
|
33
|
+
if (longFormat) {
|
|
34
|
+
const entries = fs.listDirDetailed(targetPath);
|
|
35
|
+
const lines = entries
|
|
36
|
+
.filter(e => showAll || !e.name.startsWith('.'))
|
|
37
|
+
.map(e => {
|
|
38
|
+
const resolvedDir = fs.resolvePath(targetPath);
|
|
39
|
+
const fullPath = resolvedDir === '/' ? `/${e.name}` : `${resolvedDir}/${e.name}`;
|
|
40
|
+
const perms = fs.getPermissions(fullPath);
|
|
41
|
+
const suffix = e.type === 'directory' ? '/' : '';
|
|
42
|
+
return `${perms} ${e.name}${suffix}`;
|
|
43
|
+
});
|
|
44
|
+
return { output: lines.join('\n') };
|
|
45
|
+
}
|
|
46
|
+
const entries = fs.listDir(targetPath);
|
|
47
|
+
const filtered = entries.filter(e => showAll || !e.startsWith('.'));
|
|
48
|
+
return { output: filtered.join(' ') };
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=ls.js.map
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { commandsMeta } from '../../data/commands-meta.js';
|
|
2
|
+
const categories = {
|
|
3
|
+
'Navigation': ['pwd', 'cd', 'ls'],
|
|
4
|
+
'File Operations': ['cat', 'cp', 'mv', 'rm', 'touch', 'mkdir', 'echo'],
|
|
5
|
+
'Text Processing': ['grep', 'head', 'tail', 'wc', 'sort', 'uniq', 'cut'],
|
|
6
|
+
'Search': ['find'],
|
|
7
|
+
'Permissions': ['chmod'],
|
|
8
|
+
'Version Control': ['git'],
|
|
9
|
+
'Help': ['help', 'hint', 'man'],
|
|
10
|
+
};
|
|
11
|
+
export function man(fs, args) {
|
|
12
|
+
if (args.length === 0) {
|
|
13
|
+
const lines = ['Terminal Quest コマンドリファレンス', ''];
|
|
14
|
+
for (const [category, cmds] of Object.entries(categories)) {
|
|
15
|
+
lines.push(` ${category}:`);
|
|
16
|
+
for (const cmd of cmds) {
|
|
17
|
+
const meta = commandsMeta.find(m => m.name === cmd);
|
|
18
|
+
lines.push(` ${cmd.padEnd(10)} ${meta?.description ?? ''}`);
|
|
19
|
+
}
|
|
20
|
+
lines.push('');
|
|
21
|
+
}
|
|
22
|
+
lines.push('詳細: man <コマンド名>');
|
|
23
|
+
return { output: lines.join('\n') };
|
|
24
|
+
}
|
|
25
|
+
const cmdName = args[0];
|
|
26
|
+
const meta = commandsMeta.find(m => m.name === cmdName);
|
|
27
|
+
if (!meta) {
|
|
28
|
+
return { output: '', error: `man: ${cmdName} のマニュアルはありません` };
|
|
29
|
+
}
|
|
30
|
+
const lines = [
|
|
31
|
+
`NAME`,
|
|
32
|
+
` ${meta.name} - ${meta.description}`,
|
|
33
|
+
'',
|
|
34
|
+
`USAGE`,
|
|
35
|
+
` ${meta.usage}`,
|
|
36
|
+
'',
|
|
37
|
+
];
|
|
38
|
+
if (meta.options && meta.options.length > 0) {
|
|
39
|
+
lines.push('OPTIONS');
|
|
40
|
+
for (const opt of meta.options) {
|
|
41
|
+
lines.push(` ${opt.flag.padEnd(15)} ${opt.description}`);
|
|
42
|
+
}
|
|
43
|
+
lines.push('');
|
|
44
|
+
}
|
|
45
|
+
lines.push('EXAMPLES');
|
|
46
|
+
for (const ex of meta.examples) {
|
|
47
|
+
lines.push(` $ ${ex.cmd.padEnd(28)} ${ex.desc}`);
|
|
48
|
+
}
|
|
49
|
+
return { output: lines.join('\n') };
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=man.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export function mkdir(fs, args) {
|
|
2
|
+
let recursive = false;
|
|
3
|
+
const paths = [];
|
|
4
|
+
for (const arg of args) {
|
|
5
|
+
if (arg === '-p') {
|
|
6
|
+
recursive = true;
|
|
7
|
+
}
|
|
8
|
+
else if (arg.startsWith('-')) {
|
|
9
|
+
return { output: '', error: `mkdir: invalid option -- '${arg}'` };
|
|
10
|
+
}
|
|
11
|
+
else {
|
|
12
|
+
paths.push(arg);
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
if (paths.length === 0) {
|
|
16
|
+
return { output: '', error: 'mkdir: missing operand' };
|
|
17
|
+
}
|
|
18
|
+
for (const path of paths) {
|
|
19
|
+
if (!recursive && fs.exists(path)) {
|
|
20
|
+
return { output: '', error: `mkdir: cannot create directory '${path}': File exists` };
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
fs.mkdir(path, recursive);
|
|
24
|
+
}
|
|
25
|
+
catch (e) {
|
|
26
|
+
return { output: '', error: `mkdir: ${e.message}` };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
return { output: '' };
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=mkdir.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function mv(fs, args) {
|
|
2
|
+
if (args.length < 2) {
|
|
3
|
+
return { output: '', error: 'mv: missing operand' };
|
|
4
|
+
}
|
|
5
|
+
const source = args[0];
|
|
6
|
+
const dest = args[1];
|
|
7
|
+
try {
|
|
8
|
+
fs.move(source, dest);
|
|
9
|
+
}
|
|
10
|
+
catch (e) {
|
|
11
|
+
return { output: '', error: `mv: ${e.message}` };
|
|
12
|
+
}
|
|
13
|
+
return { output: '' };
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=mv.js.map
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
export function rm(fs, args) {
|
|
2
|
+
let recursive = false;
|
|
3
|
+
let force = false;
|
|
4
|
+
const paths = [];
|
|
5
|
+
for (const arg of args) {
|
|
6
|
+
if (arg === '--recursive') {
|
|
7
|
+
recursive = true;
|
|
8
|
+
}
|
|
9
|
+
else if (arg === '--force') {
|
|
10
|
+
force = true;
|
|
11
|
+
}
|
|
12
|
+
else if (arg.startsWith('-') && arg.length > 1 && !arg.startsWith('--')) {
|
|
13
|
+
for (const ch of arg.slice(1)) {
|
|
14
|
+
if (ch === 'r' || ch === 'R')
|
|
15
|
+
recursive = true;
|
|
16
|
+
else if (ch === 'f')
|
|
17
|
+
force = true;
|
|
18
|
+
else
|
|
19
|
+
return { output: '', error: `rm: invalid option -- '${ch}'` };
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
paths.push(arg);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
if (paths.length === 0) {
|
|
27
|
+
if (force)
|
|
28
|
+
return { output: '' };
|
|
29
|
+
return { output: '', error: 'rm: missing operand' };
|
|
30
|
+
}
|
|
31
|
+
for (const path of paths) {
|
|
32
|
+
try {
|
|
33
|
+
if (!fs.exists(path)) {
|
|
34
|
+
if (!force) {
|
|
35
|
+
return { output: '', error: `rm: cannot remove '${path}': No such file or directory` };
|
|
36
|
+
}
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
fs.remove(path, recursive);
|
|
40
|
+
}
|
|
41
|
+
catch (e) {
|
|
42
|
+
if (!force) {
|
|
43
|
+
return { output: '', error: `rm: ${e.message}` };
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return { output: '' };
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=rm.js.map
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
export function sort(fs, args) {
|
|
2
|
+
// Extract stdin
|
|
3
|
+
let stdin;
|
|
4
|
+
const stdinIdx = args.findIndex(a => a.startsWith('__stdin__:'));
|
|
5
|
+
if (stdinIdx !== -1) {
|
|
6
|
+
stdin = args[stdinIdx].slice('__stdin__:'.length);
|
|
7
|
+
args = [...args.slice(0, stdinIdx), ...args.slice(stdinIdx + 1)];
|
|
8
|
+
}
|
|
9
|
+
let reverse = false;
|
|
10
|
+
let numeric = false;
|
|
11
|
+
let delimiter;
|
|
12
|
+
let keyField;
|
|
13
|
+
const files = [];
|
|
14
|
+
for (let i = 0; i < args.length; i++) {
|
|
15
|
+
const arg = args[i];
|
|
16
|
+
if (arg === '-t') {
|
|
17
|
+
i++;
|
|
18
|
+
if (i >= args.length) {
|
|
19
|
+
return { output: '', error: 'sort: option requires an argument -- t' };
|
|
20
|
+
}
|
|
21
|
+
delimiter = args[i];
|
|
22
|
+
}
|
|
23
|
+
else if (arg.startsWith('-t') && arg.length > 2) {
|
|
24
|
+
delimiter = arg.slice(2);
|
|
25
|
+
}
|
|
26
|
+
else if (arg === '-k') {
|
|
27
|
+
i++;
|
|
28
|
+
if (i >= args.length) {
|
|
29
|
+
return { output: '', error: 'sort: option requires an argument -- k' };
|
|
30
|
+
}
|
|
31
|
+
keyField = parseInt(args[i], 10);
|
|
32
|
+
if (isNaN(keyField)) {
|
|
33
|
+
return { output: '', error: `sort: invalid number at field start: '${args[i]}'` };
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
else if (arg.startsWith('-k') && arg.length > 2) {
|
|
37
|
+
keyField = parseInt(arg.slice(2), 10);
|
|
38
|
+
if (isNaN(keyField)) {
|
|
39
|
+
return { output: '', error: `sort: invalid number at field start: '${arg.slice(2)}'` };
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
else if (arg.startsWith('-') && arg.length > 1 && !arg.startsWith('--')) {
|
|
43
|
+
for (const ch of arg.slice(1)) {
|
|
44
|
+
if (ch === 'r')
|
|
45
|
+
reverse = true;
|
|
46
|
+
else if (ch === 'n')
|
|
47
|
+
numeric = true;
|
|
48
|
+
else
|
|
49
|
+
return { output: '', error: `sort: invalid option -- '${ch}'` };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
files.push(arg);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
let content;
|
|
57
|
+
if (files.length > 0) {
|
|
58
|
+
try {
|
|
59
|
+
content = fs.readFile(files[0]);
|
|
60
|
+
}
|
|
61
|
+
catch (e) {
|
|
62
|
+
return { output: '', error: `sort: ${e.message}` };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else if (stdin !== undefined) {
|
|
66
|
+
content = stdin;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
return { output: '', error: 'sort: missing file operand' };
|
|
70
|
+
}
|
|
71
|
+
const lines = content.split('\n');
|
|
72
|
+
const getKey = (line) => {
|
|
73
|
+
if (delimiter !== undefined && keyField !== undefined) {
|
|
74
|
+
const fields = line.split(delimiter);
|
|
75
|
+
return fields[keyField - 1] ?? '';
|
|
76
|
+
}
|
|
77
|
+
return line;
|
|
78
|
+
};
|
|
79
|
+
if (numeric) {
|
|
80
|
+
lines.sort((a, b) => {
|
|
81
|
+
const ka = getKey(a);
|
|
82
|
+
const kb = getKey(b);
|
|
83
|
+
const na = parseFloat(ka) || 0;
|
|
84
|
+
const nb = parseFloat(kb) || 0;
|
|
85
|
+
return na - nb;
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
lines.sort((a, b) => {
|
|
90
|
+
const ka = getKey(a);
|
|
91
|
+
const kb = getKey(b);
|
|
92
|
+
return ka.localeCompare(kb);
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
if (reverse) {
|
|
96
|
+
lines.reverse();
|
|
97
|
+
}
|
|
98
|
+
return { output: lines.join('\n') };
|
|
99
|
+
}
|
|
100
|
+
//# sourceMappingURL=sort.js.map
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
export function tail(fs, args) {
|
|
2
|
+
// Extract stdin
|
|
3
|
+
let stdin;
|
|
4
|
+
const stdinIdx = args.findIndex(a => a.startsWith('__stdin__:'));
|
|
5
|
+
if (stdinIdx !== -1) {
|
|
6
|
+
stdin = args[stdinIdx].slice('__stdin__:'.length);
|
|
7
|
+
args = [...args.slice(0, stdinIdx), ...args.slice(stdinIdx + 1)];
|
|
8
|
+
}
|
|
9
|
+
let lineCount = 10;
|
|
10
|
+
const files = [];
|
|
11
|
+
for (let i = 0; i < args.length; i++) {
|
|
12
|
+
const arg = args[i];
|
|
13
|
+
if (arg === '-n') {
|
|
14
|
+
i++;
|
|
15
|
+
if (i >= args.length) {
|
|
16
|
+
return { output: '', error: 'tail: option requires an argument -- n' };
|
|
17
|
+
}
|
|
18
|
+
const n = parseInt(args[i], 10);
|
|
19
|
+
if (isNaN(n)) {
|
|
20
|
+
return { output: '', error: `tail: invalid number of lines: '${args[i]}'` };
|
|
21
|
+
}
|
|
22
|
+
lineCount = n;
|
|
23
|
+
}
|
|
24
|
+
else if (arg.startsWith('-n')) {
|
|
25
|
+
const n = parseInt(arg.slice(2), 10);
|
|
26
|
+
if (isNaN(n)) {
|
|
27
|
+
return { output: '', error: `tail: invalid number of lines: '${arg.slice(2)}'` };
|
|
28
|
+
}
|
|
29
|
+
lineCount = n;
|
|
30
|
+
}
|
|
31
|
+
else if (arg.startsWith('-')) {
|
|
32
|
+
return { output: '', error: `tail: invalid option -- '${arg}'` };
|
|
33
|
+
}
|
|
34
|
+
else {
|
|
35
|
+
files.push(arg);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
let content;
|
|
39
|
+
if (files.length > 0) {
|
|
40
|
+
try {
|
|
41
|
+
content = fs.readFile(files[0]);
|
|
42
|
+
}
|
|
43
|
+
catch (e) {
|
|
44
|
+
return { output: '', error: `tail: ${e.message}` };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else if (stdin !== undefined) {
|
|
48
|
+
content = stdin;
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
return { output: '', error: 'tail: missing file operand' };
|
|
52
|
+
}
|
|
53
|
+
if (content === '')
|
|
54
|
+
return { output: '' };
|
|
55
|
+
const lines = content.split('\n');
|
|
56
|
+
const selected = lines.slice(-lineCount);
|
|
57
|
+
return { output: selected.join('\n') };
|
|
58
|
+
}
|
|
59
|
+
//# sourceMappingURL=tail.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export function touch(fs, args) {
|
|
2
|
+
if (args.length === 0) {
|
|
3
|
+
return { output: '', error: 'touch: missing file operand' };
|
|
4
|
+
}
|
|
5
|
+
const filename = args[0];
|
|
6
|
+
if (fs.exists(filename)) {
|
|
7
|
+
// File or directory already exists, do nothing (success)
|
|
8
|
+
return { output: '' };
|
|
9
|
+
}
|
|
10
|
+
try {
|
|
11
|
+
fs.writeFile(filename, '');
|
|
12
|
+
}
|
|
13
|
+
catch (e) {
|
|
14
|
+
return { output: '', error: `touch: ${e.message}` };
|
|
15
|
+
}
|
|
16
|
+
return { output: '' };
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=touch.js.map
|