pengushell 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 +0 -0
- package/README.md +195 -0
- package/bin/pengushell.js +5 -0
- package/package.json +39 -0
- package/src/commands/filesystem.js +266 -0
- package/src/commands/nano.js +250 -0
- package/src/commands/search.js +98 -0
- package/src/commands/system.js +66 -0
- package/src/config/defaults.js +70 -0
- package/src/core/executor.js +110 -0
- package/src/core/lexer.js +61 -0
- package/src/core/parser.js +160 -0
- package/src/core/repl.js +148 -0
- package/src/index.js +142 -0
- package/src/registry/commandRegistry.js +24 -0
- package/src/utils/colors.js +29 -0
- package/src/utils/pathNormalizer.js +56 -0
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
const fs = require('fs');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const { tokenize } = require('./lexer');
|
|
4
|
+
const { loadConfig } = require('../config/defaults');
|
|
5
|
+
const { toWinStyle } = require('../utils/pathNormalizer');
|
|
6
|
+
|
|
7
|
+
function expandGlob(arg, cwd = process.cwd()) {
|
|
8
|
+
if (!arg.includes('*') && !arg.includes('?')) {
|
|
9
|
+
return [arg];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let normalized = arg.replace(/\\/g, '/');
|
|
13
|
+
let isDriveMapped = false;
|
|
14
|
+
|
|
15
|
+
if (normalized.match(/^\/[a-zA-Z]\//) || normalized.match(/^\/[a-zA-Z]$/)) {
|
|
16
|
+
cwd = toWinStyle(normalized.substring(0, 3));
|
|
17
|
+
normalized = normalized.substring(3);
|
|
18
|
+
isDriveMapped = true;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const parts = normalized.split('/').filter(p => p !== '');
|
|
22
|
+
let currentDirs = [cwd];
|
|
23
|
+
|
|
24
|
+
if (path.isAbsolute(arg) && !isDriveMapped) {
|
|
25
|
+
currentDirs = [path.parse(arg).root];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < parts.length; i++) {
|
|
29
|
+
const part = parts[i];
|
|
30
|
+
if (part === '.' || part === '..') {
|
|
31
|
+
currentDirs = currentDirs.map(d => path.resolve(d, part));
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (!part.includes('*') && !part.includes('?')) {
|
|
36
|
+
currentDirs = currentDirs
|
|
37
|
+
.map(d => path.join(d, part))
|
|
38
|
+
.filter(p => fs.existsSync(p));
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const regexStr = '^' + part
|
|
43
|
+
.replace(/[-\/\\^$+?.()|[\]{}]/g, '\\$&')
|
|
44
|
+
.replace(/\*/g, '.*')
|
|
45
|
+
.replace(/\?/g, '.') + '$';
|
|
46
|
+
const regex = new RegExp(regexStr, 'i');
|
|
47
|
+
|
|
48
|
+
const nextDirs = [];
|
|
49
|
+
for (const dir of currentDirs) {
|
|
50
|
+
if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) continue;
|
|
51
|
+
try {
|
|
52
|
+
const entries = fs.readdirSync(dir);
|
|
53
|
+
for (const entry of entries) {
|
|
54
|
+
if (regex.test(entry)) {
|
|
55
|
+
nextDirs.push(path.join(dir, entry));
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
} catch (err) {}
|
|
59
|
+
}
|
|
60
|
+
currentDirs = nextDirs;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (currentDirs.length === 0) {
|
|
64
|
+
return [arg];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return currentDirs.map(p => {
|
|
68
|
+
if (path.isAbsolute(arg)) return p;
|
|
69
|
+
const rel = path.relative(process.cwd(), p);
|
|
70
|
+
return rel || '.';
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function expandAliases(tokens, aliases, expanded = new Set()) {
|
|
75
|
+
const result = [];
|
|
76
|
+
let isCommandStart = true;
|
|
77
|
+
|
|
78
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
79
|
+
const token = tokens[i];
|
|
80
|
+
if (token === '|') {
|
|
81
|
+
result.push(token);
|
|
82
|
+
isCommandStart = true;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (isCommandStart) {
|
|
87
|
+
isCommandStart = false;
|
|
88
|
+
if (aliases[token] && !expanded.has(token)) {
|
|
89
|
+
expanded.add(token);
|
|
90
|
+
const aliasTokens = tokenize(aliases[token]);
|
|
91
|
+
const expandedAlias = expandAliases(aliasTokens, aliases, new Set(expanded));
|
|
92
|
+
result.push(...expandedAlias);
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
result.push(token);
|
|
98
|
+
}
|
|
99
|
+
return result;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function parsePipeline(input) {
|
|
103
|
+
let tokens = tokenize(input);
|
|
104
|
+
if (tokens.length === 0) return [];
|
|
105
|
+
|
|
106
|
+
const config = loadConfig();
|
|
107
|
+
if (config && config.aliases) {
|
|
108
|
+
tokens = expandAliases(tokens, config.aliases);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const pipeline = [];
|
|
112
|
+
let currentCmd = null;
|
|
113
|
+
|
|
114
|
+
for (const token of tokens) {
|
|
115
|
+
if (token === '|') {
|
|
116
|
+
if (currentCmd) {
|
|
117
|
+
pipeline.push(currentCmd);
|
|
118
|
+
currentCmd = null;
|
|
119
|
+
}
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (!currentCmd) {
|
|
124
|
+
currentCmd = {
|
|
125
|
+
command: token,
|
|
126
|
+
args: [],
|
|
127
|
+
rawArgs: [],
|
|
128
|
+
flags: {}
|
|
129
|
+
};
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (token.startsWith('--')) {
|
|
134
|
+
currentCmd.rawArgs.push(token);
|
|
135
|
+
const parts = token.slice(2).split('=');
|
|
136
|
+
const flagName = parts[0];
|
|
137
|
+
const flagValue = parts.length > 1 ? parts.slice(1).join('=') : true;
|
|
138
|
+
currentCmd.flags[flagName] = flagValue;
|
|
139
|
+
} else if (token.startsWith('-') && token !== '-' && token !== '--') {
|
|
140
|
+
currentCmd.rawArgs.push(token);
|
|
141
|
+
const flags = token.slice(1);
|
|
142
|
+
for (const f of flags) {
|
|
143
|
+
currentCmd.flags[f] = true;
|
|
144
|
+
}
|
|
145
|
+
} else {
|
|
146
|
+
// Positional argument - expand wildcards
|
|
147
|
+
const expanded = expandGlob(token);
|
|
148
|
+
currentCmd.args.push(...expanded);
|
|
149
|
+
currentCmd.rawArgs.push(...expanded);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (currentCmd) {
|
|
154
|
+
pipeline.push(currentCmd);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
return pipeline;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
module.exports = { parsePipeline };
|
package/src/core/repl.js
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
const readline = require('readline');
|
|
2
|
+
const os = require('os');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const { hackerGreen, errorMsg, chalk } = require('../utils/colors');
|
|
6
|
+
const { toLinuxStyle, toWinStyle } = require('../utils/pathNormalizer');
|
|
7
|
+
const { parsePipeline } = require('./parser');
|
|
8
|
+
const { executePipeline } = require('./executor');
|
|
9
|
+
const { configFile } = require('../config/defaults');
|
|
10
|
+
|
|
11
|
+
const configDir = path.dirname(configFile);
|
|
12
|
+
const historyFile = path.join(configDir, 'history.log');
|
|
13
|
+
|
|
14
|
+
function getPrompt() {
|
|
15
|
+
const username = os.userInfo().username;
|
|
16
|
+
const hostname = os.hostname();
|
|
17
|
+
const rawCwd = process.cwd();
|
|
18
|
+
const homeDir = os.homedir();
|
|
19
|
+
|
|
20
|
+
let linuxCwd = toLinuxStyle(rawCwd);
|
|
21
|
+
const linuxHome = toLinuxStyle(homeDir);
|
|
22
|
+
|
|
23
|
+
if (linuxCwd.startsWith(linuxHome)) {
|
|
24
|
+
linuxCwd = linuxCwd.replace(linuxHome, '~');
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const promptText = `${username}@${hostname}:${linuxCwd}$ `;
|
|
28
|
+
return hackerGreen(promptText);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Tab completion function
|
|
32
|
+
function completer(line) {
|
|
33
|
+
const builtins = [
|
|
34
|
+
'ls', 'cd', 'pwd', 'clear', 'echo', 'whoami',
|
|
35
|
+
'hostname', 'cat', 'mkdir', 'rm', 'cp', 'mv',
|
|
36
|
+
'touch', 'grep', 'nano', 'config', 'exit'
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
const parts = line.split(/\s+/);
|
|
40
|
+
|
|
41
|
+
// 1. Autocomplete commands if typing the first word
|
|
42
|
+
if (parts.length <= 1) {
|
|
43
|
+
const hits = builtins.filter(c => c.startsWith(line));
|
|
44
|
+
return [hits.length ? hits : builtins, line];
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// 2. Autocomplete files/folders for parameters
|
|
48
|
+
const lastArg = parts[parts.length - 1];
|
|
49
|
+
let dir = '.';
|
|
50
|
+
let prefix = lastArg;
|
|
51
|
+
|
|
52
|
+
if (lastArg.includes('/') || lastArg.includes('\\')) {
|
|
53
|
+
const normalized = lastArg.replace(/\\/g, '/');
|
|
54
|
+
const slashIndex = normalized.lastIndexOf('/');
|
|
55
|
+
dir = lastArg.substring(0, slashIndex);
|
|
56
|
+
prefix = lastArg.substring(slashIndex + 1);
|
|
57
|
+
if (dir === '') dir = '/';
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
const resolvedDir = path.resolve(toWinStyle(dir));
|
|
62
|
+
if (fs.existsSync(resolvedDir) && fs.statSync(resolvedDir).isDirectory()) {
|
|
63
|
+
const entries = fs.readdirSync(resolvedDir);
|
|
64
|
+
const hits = entries
|
|
65
|
+
.filter(f => f.startsWith(prefix))
|
|
66
|
+
.map(f => {
|
|
67
|
+
const joined = path.join(dir, f).replace(/\\/g, '/');
|
|
68
|
+
try {
|
|
69
|
+
if (fs.statSync(path.join(resolvedDir, f)).isDirectory()) {
|
|
70
|
+
return joined + '/'; // Directory trailer
|
|
71
|
+
}
|
|
72
|
+
} catch (e) {}
|
|
73
|
+
return joined;
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return [hits, lastArg];
|
|
77
|
+
}
|
|
78
|
+
} catch (e) {
|
|
79
|
+
// Fail silently, returning empty completions
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return [[], lastArg];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function startRepl() {
|
|
86
|
+
const rl = readline.createInterface({
|
|
87
|
+
input: process.stdin,
|
|
88
|
+
output: process.stdout,
|
|
89
|
+
prompt: getPrompt(),
|
|
90
|
+
completer: completer, // Enable autocomplete
|
|
91
|
+
historySize: 1000
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// Seed history from file
|
|
95
|
+
if (fs.existsSync(historyFile)) {
|
|
96
|
+
try {
|
|
97
|
+
const historyLines = fs.readFileSync(historyFile, 'utf8')
|
|
98
|
+
.split(/\r?\n/)
|
|
99
|
+
.filter(l => l.trim().length > 0)
|
|
100
|
+
.reverse(); // Readline expects newest commands first
|
|
101
|
+
rl.history = historyLines;
|
|
102
|
+
} catch (err) {}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
console.log(hackerGreen('PenguShell v1.0.0 - Windows Compatibility Layer'));
|
|
106
|
+
console.log(hackerGreen('Type "exit" to quit. (Tab-completion and history enabled)'));
|
|
107
|
+
|
|
108
|
+
rl.prompt();
|
|
109
|
+
|
|
110
|
+
rl.on('line', async (line) => {
|
|
111
|
+
line = line.trim();
|
|
112
|
+
if (line === 'exit') {
|
|
113
|
+
rl.close();
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (line.length > 0) {
|
|
118
|
+
// Append to persistent history log
|
|
119
|
+
try {
|
|
120
|
+
fs.appendFileSync(historyFile, line + '\n');
|
|
121
|
+
} catch (err) {}
|
|
122
|
+
|
|
123
|
+
try {
|
|
124
|
+
rl.pause();
|
|
125
|
+
const pipeline = parsePipeline(line);
|
|
126
|
+
await executePipeline(pipeline, process.stdin, process.stdout);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.error(errorMsg(`pengushell: ${err.message}`));
|
|
129
|
+
} finally {
|
|
130
|
+
rl.resume();
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
rl.setPrompt(getPrompt());
|
|
135
|
+
rl.prompt();
|
|
136
|
+
}).on('close', () => {
|
|
137
|
+
console.log(hackerGreen('exit'));
|
|
138
|
+
process.exit(0);
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
rl.on('SIGINT', () => {
|
|
142
|
+
rl.write('^C\n');
|
|
143
|
+
rl.setPrompt(getPrompt());
|
|
144
|
+
rl.prompt();
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
module.exports = { startRepl };
|
package/src/index.js
ADDED
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
const { loadConfig, saveConfig, configFile } = require('./config/defaults');
|
|
2
|
+
const { startRepl } = require('./core/repl');
|
|
3
|
+
const { parsePipeline } = require('./core/parser');
|
|
4
|
+
const { executePipeline } = require('./core/executor');
|
|
5
|
+
const { chalk, errorMsg, hackerGreen } = require('./utils/colors');
|
|
6
|
+
const prompts = require('prompts');
|
|
7
|
+
|
|
8
|
+
function printHelp() {
|
|
9
|
+
console.log(hackerGreen('PenguShell - Windows PowerShell Compatibility Layer'));
|
|
10
|
+
console.log('\nUsage:');
|
|
11
|
+
console.log(' pengushell Start interactive shell (REPL)');
|
|
12
|
+
console.log(' pengushell <command> [args] Execute command directly and exit');
|
|
13
|
+
console.log(' pengushell config [action] Manage configuration');
|
|
14
|
+
console.log('\nConfig Actions:');
|
|
15
|
+
console.log(' interactive Start interactive config manager (default)');
|
|
16
|
+
console.log(' list / show Display current configuration settings');
|
|
17
|
+
console.log(' path Print the path of the config file');
|
|
18
|
+
console.log(' set <key> <value> Update a specific configuration value');
|
|
19
|
+
console.log('\nOptions:');
|
|
20
|
+
console.log(' -h, --help Show this help message');
|
|
21
|
+
console.log(' -v, --version Show version info');
|
|
22
|
+
console.log('\nBuilt-in Commands:');
|
|
23
|
+
console.log(' ls, pwd, cd, clear, echo, whoami, hostname, cat, mkdir, rm, cp, mv, touch, grep, nano');
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function interactiveConfig() {
|
|
27
|
+
const current = loadConfig();
|
|
28
|
+
const response = await prompts([
|
|
29
|
+
{
|
|
30
|
+
type: 'toggle',
|
|
31
|
+
name: 'useColors',
|
|
32
|
+
message: 'Enable colorized output?',
|
|
33
|
+
initial: current.useColors,
|
|
34
|
+
active: 'yes',
|
|
35
|
+
inactive: 'no'
|
|
36
|
+
},
|
|
37
|
+
{
|
|
38
|
+
type: 'toggle',
|
|
39
|
+
name: 'hackerGreenMode',
|
|
40
|
+
message: 'Enable hacker green terminal styling?',
|
|
41
|
+
initial: current.hackerGreenMode,
|
|
42
|
+
active: 'yes',
|
|
43
|
+
inactive: 'no'
|
|
44
|
+
}
|
|
45
|
+
]);
|
|
46
|
+
|
|
47
|
+
if (response.useColors !== undefined && response.hackerGreenMode !== undefined) {
|
|
48
|
+
const updated = {
|
|
49
|
+
...current,
|
|
50
|
+
useColors: response.useColors,
|
|
51
|
+
hackerGreenMode: response.hackerGreenMode
|
|
52
|
+
};
|
|
53
|
+
saveConfig(updated);
|
|
54
|
+
console.log(chalk.green('✔ Configuration saved successfully!'));
|
|
55
|
+
} else {
|
|
56
|
+
console.log(chalk.yellow('Configuration changes cancelled.'));
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function main() {
|
|
61
|
+
const args = process.argv.slice(2);
|
|
62
|
+
|
|
63
|
+
// 1. Version Check
|
|
64
|
+
if (args.includes('-v') || args.includes('--version')) {
|
|
65
|
+
console.log(`PenguShell v${require('../package.json').version}`);
|
|
66
|
+
process.exit(0);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// 2. Help Check
|
|
70
|
+
if (args.includes('-h') || args.includes('--help')) {
|
|
71
|
+
printHelp();
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 3. Config Subcommands
|
|
76
|
+
if (args[0] === 'config') {
|
|
77
|
+
const configAction = args[1];
|
|
78
|
+
if (!configAction || configAction === 'interactive') {
|
|
79
|
+
await interactiveConfig();
|
|
80
|
+
} else if (configAction === 'list' || configAction === 'show') {
|
|
81
|
+
const current = loadConfig();
|
|
82
|
+
console.log(chalk.cyan(`Config file: ${configFile}`));
|
|
83
|
+
console.log(JSON.stringify(current, null, 2));
|
|
84
|
+
} else if (configAction === 'path') {
|
|
85
|
+
console.log(configFile);
|
|
86
|
+
} else if (configAction === 'set') {
|
|
87
|
+
const key = args[2];
|
|
88
|
+
const val = args[3];
|
|
89
|
+
if (!key || val === undefined) {
|
|
90
|
+
console.error(errorMsg('Usage: pengushell config set <key> <value>'));
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
const current = loadConfig();
|
|
94
|
+
let typedVal = val;
|
|
95
|
+
if (val === 'true') typedVal = true;
|
|
96
|
+
if (val === 'false') typedVal = false;
|
|
97
|
+
|
|
98
|
+
current[key] = typedVal;
|
|
99
|
+
if (saveConfig(current)) {
|
|
100
|
+
console.log(chalk.green(`✔ Saved ${key} = ${typedVal}`));
|
|
101
|
+
} else {
|
|
102
|
+
console.error(errorMsg('Failed to save config.'));
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
console.error(errorMsg(`Unknown config command: ${configAction}`));
|
|
107
|
+
console.log('Available config actions: interactive, list, path, set');
|
|
108
|
+
process.exit(1);
|
|
109
|
+
}
|
|
110
|
+
process.exit(0);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// 4. Start REPL Mode
|
|
114
|
+
if (args.length === 0) {
|
|
115
|
+
startRepl();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// 5. Direct Execution Mode (e.g. pengushell ls -la)
|
|
120
|
+
let cmdString;
|
|
121
|
+
if (args.length === 1) {
|
|
122
|
+
cmdString = args[0];
|
|
123
|
+
} else {
|
|
124
|
+
cmdString = args.map(arg => {
|
|
125
|
+
if (arg.includes(' ') || arg.includes('|') || arg.includes('>') || arg.includes('<')) {
|
|
126
|
+
return `"${arg.replace(/"/g, '\\"')}"`;
|
|
127
|
+
}
|
|
128
|
+
return arg;
|
|
129
|
+
}).join(' ');
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
try {
|
|
133
|
+
const pipeline = parsePipeline(cmdString);
|
|
134
|
+
await executePipeline(pipeline, process.stdin, process.stdout);
|
|
135
|
+
process.exit(process.exitCode !== undefined ? process.exitCode : 0);
|
|
136
|
+
} catch (err) {
|
|
137
|
+
console.error(errorMsg(`pengushell: ${err.message}`));
|
|
138
|
+
process.exit(1);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = { main };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const systemCmds = require('../commands/system');
|
|
2
|
+
const fsCmds = require('../commands/filesystem');
|
|
3
|
+
const searchCmds = require('../commands/search');
|
|
4
|
+
const nanoCmd = require('../commands/nano');
|
|
5
|
+
|
|
6
|
+
const registry = {
|
|
7
|
+
...systemCmds,
|
|
8
|
+
...fsCmds,
|
|
9
|
+
...searchCmds,
|
|
10
|
+
...nanoCmd
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function getCommand(name) {
|
|
14
|
+
return registry[name] || null;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function registerCommand(name, cmdObj) {
|
|
18
|
+
registry[name] = cmdObj;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = {
|
|
22
|
+
getCommand,
|
|
23
|
+
registerCommand
|
|
24
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
const chalk = require('chalk');
|
|
2
|
+
const { loadConfig } = require('../config/defaults');
|
|
3
|
+
|
|
4
|
+
const config = loadConfig();
|
|
5
|
+
if (!config.useColors) {
|
|
6
|
+
chalk.level = 0;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
function hackerGreen(text) {
|
|
10
|
+
if (config.hackerGreenMode) {
|
|
11
|
+
return chalk.bgBlack.greenBright(text);
|
|
12
|
+
}
|
|
13
|
+
return chalk.greenBright(text);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function colorizePath(pathStr) {
|
|
17
|
+
return chalk.blueBright(pathStr);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function errorMsg(text) {
|
|
21
|
+
return chalk.red(text);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = {
|
|
25
|
+
hackerGreen,
|
|
26
|
+
colorizePath,
|
|
27
|
+
errorMsg,
|
|
28
|
+
chalk
|
|
29
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
// Convert C:\Users\name to /c/Users/name
|
|
4
|
+
function toLinuxStyle(winPath) {
|
|
5
|
+
if (!winPath) return '/';
|
|
6
|
+
|
|
7
|
+
// Normalize slashes
|
|
8
|
+
let linuxPath = winPath.replace(/\\/g, '/');
|
|
9
|
+
|
|
10
|
+
// Handle drive letter C:/ -> /c/
|
|
11
|
+
const driveMatch = linuxPath.match(/^([a-zA-Z]):\/(.*)$/);
|
|
12
|
+
if (driveMatch) {
|
|
13
|
+
const drive = driveMatch[1].toLowerCase();
|
|
14
|
+
const rest = driveMatch[2];
|
|
15
|
+
linuxPath = `/${drive}/${rest}`;
|
|
16
|
+
} else {
|
|
17
|
+
const driveMatchRoot = linuxPath.match(/^([a-zA-Z]):$/);
|
|
18
|
+
if(driveMatchRoot) {
|
|
19
|
+
linuxPath = `/${driveMatchRoot[1].toLowerCase()}`;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Remove trailing slash unless it's just root
|
|
24
|
+
if (linuxPath.length > 1 && linuxPath.endsWith('/')) {
|
|
25
|
+
linuxPath = linuxPath.slice(0, -1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return linuxPath;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Convert /c/Users/name to C:\Users\name
|
|
32
|
+
function toWinStyle(linuxPath) {
|
|
33
|
+
if (!linuxPath) return process.cwd();
|
|
34
|
+
|
|
35
|
+
// Convert /c/ to C:\
|
|
36
|
+
const driveMatch = linuxPath.match(/^\/([a-zA-Z])(?:\/(.*))?$/);
|
|
37
|
+
if (driveMatch) {
|
|
38
|
+
const drive = driveMatch[1].toUpperCase();
|
|
39
|
+
const rest = driveMatch[2] || '';
|
|
40
|
+
return path.normalize(`${drive}:\\${rest}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// If it's just / return current drive root (assuming C:\ as default)
|
|
44
|
+
if (linuxPath === '/') {
|
|
45
|
+
const currentDrive = path.parse(process.cwd()).root;
|
|
46
|
+
return currentDrive;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Otherwise, probably relative, just normalize
|
|
50
|
+
return path.normalize(linuxPath);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
toLinuxStyle,
|
|
55
|
+
toWinStyle
|
|
56
|
+
};
|