@ecmaos/coreutils 0.1.5 → 0.2.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.
Files changed (226) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +31 -0
  3. package/dist/commands/basename.d.ts +4 -0
  4. package/dist/commands/basename.d.ts.map +1 -0
  5. package/dist/commands/basename.js +78 -0
  6. package/dist/commands/basename.js.map +1 -0
  7. package/dist/commands/cal.d.ts +4 -0
  8. package/dist/commands/cal.d.ts.map +1 -0
  9. package/dist/commands/cal.js +105 -0
  10. package/dist/commands/cal.js.map +1 -0
  11. package/dist/commands/cat.d.ts.map +1 -1
  12. package/dist/commands/cat.js +33 -25
  13. package/dist/commands/cat.js.map +1 -1
  14. package/dist/commands/cd.d.ts.map +1 -1
  15. package/dist/commands/cd.js +38 -10
  16. package/dist/commands/cd.js.map +1 -1
  17. package/dist/commands/chmod.d.ts.map +1 -1
  18. package/dist/commands/chmod.js +31 -9
  19. package/dist/commands/chmod.js.map +1 -1
  20. package/dist/commands/comm.d.ts +4 -0
  21. package/dist/commands/comm.d.ts.map +1 -0
  22. package/dist/commands/comm.js +162 -0
  23. package/dist/commands/comm.js.map +1 -0
  24. package/dist/commands/cp.d.ts.map +1 -1
  25. package/dist/commands/cp.js +61 -12
  26. package/dist/commands/cp.js.map +1 -1
  27. package/dist/commands/cut.d.ts +4 -0
  28. package/dist/commands/cut.d.ts.map +1 -0
  29. package/dist/commands/cut.js +208 -0
  30. package/dist/commands/cut.js.map +1 -0
  31. package/dist/commands/date.d.ts +4 -0
  32. package/dist/commands/date.d.ts.map +1 -0
  33. package/dist/commands/date.js +100 -0
  34. package/dist/commands/date.js.map +1 -0
  35. package/dist/commands/diff.d.ts +4 -0
  36. package/dist/commands/diff.d.ts.map +1 -0
  37. package/dist/commands/diff.js +194 -0
  38. package/dist/commands/diff.js.map +1 -0
  39. package/dist/commands/dirname.d.ts +4 -0
  40. package/dist/commands/dirname.d.ts.map +1 -0
  41. package/dist/commands/dirname.js +50 -0
  42. package/dist/commands/dirname.js.map +1 -0
  43. package/dist/commands/echo.d.ts.map +1 -1
  44. package/dist/commands/echo.js +49 -10
  45. package/dist/commands/echo.js.map +1 -1
  46. package/dist/commands/false.d.ts +4 -0
  47. package/dist/commands/false.d.ts.map +1 -0
  48. package/dist/commands/false.js +27 -0
  49. package/dist/commands/false.js.map +1 -0
  50. package/dist/commands/find.d.ts +4 -0
  51. package/dist/commands/find.d.ts.map +1 -0
  52. package/dist/commands/find.js +181 -0
  53. package/dist/commands/find.js.map +1 -0
  54. package/dist/commands/grep.d.ts.map +1 -1
  55. package/dist/commands/grep.js +45 -10
  56. package/dist/commands/grep.js.map +1 -1
  57. package/dist/commands/head.d.ts.map +1 -1
  58. package/dist/commands/head.js +46 -9
  59. package/dist/commands/head.js.map +1 -1
  60. package/dist/commands/hex.d.ts.map +1 -1
  61. package/dist/commands/hex.js +16 -6
  62. package/dist/commands/hex.js.map +1 -1
  63. package/dist/commands/id.d.ts +4 -0
  64. package/dist/commands/id.d.ts.map +1 -0
  65. package/dist/commands/id.js +97 -0
  66. package/dist/commands/id.js.map +1 -0
  67. package/dist/commands/join.d.ts +4 -0
  68. package/dist/commands/join.d.ts.map +1 -0
  69. package/dist/commands/join.js +152 -0
  70. package/dist/commands/join.js.map +1 -0
  71. package/dist/commands/less.d.ts.map +1 -1
  72. package/dist/commands/less.js +16 -7
  73. package/dist/commands/less.js.map +1 -1
  74. package/dist/commands/ln.d.ts.map +1 -1
  75. package/dist/commands/ln.js +54 -12
  76. package/dist/commands/ln.js.map +1 -1
  77. package/dist/commands/ls.d.ts.map +1 -1
  78. package/dist/commands/ls.js +14 -6
  79. package/dist/commands/ls.js.map +1 -1
  80. package/dist/commands/mkdir.d.ts.map +1 -1
  81. package/dist/commands/mkdir.js +34 -9
  82. package/dist/commands/mkdir.js.map +1 -1
  83. package/dist/commands/mv.d.ts.map +1 -1
  84. package/dist/commands/mv.js +25 -7
  85. package/dist/commands/mv.js.map +1 -1
  86. package/dist/commands/nc.d.ts +4 -0
  87. package/dist/commands/nc.d.ts.map +1 -0
  88. package/dist/commands/nc.js +451 -0
  89. package/dist/commands/nc.js.map +1 -0
  90. package/dist/commands/nl.d.ts +4 -0
  91. package/dist/commands/nl.d.ts.map +1 -0
  92. package/dist/commands/nl.js +208 -0
  93. package/dist/commands/nl.js.map +1 -0
  94. package/dist/commands/passkey.d.ts.map +1 -1
  95. package/dist/commands/passkey.js +44 -18
  96. package/dist/commands/passkey.js.map +1 -1
  97. package/dist/commands/paste.d.ts +4 -0
  98. package/dist/commands/paste.d.ts.map +1 -0
  99. package/dist/commands/paste.js +161 -0
  100. package/dist/commands/paste.js.map +1 -0
  101. package/dist/commands/pwd.d.ts.map +1 -1
  102. package/dist/commands/pwd.js +13 -4
  103. package/dist/commands/pwd.js.map +1 -1
  104. package/dist/commands/rm.d.ts.map +1 -1
  105. package/dist/commands/rm.js +105 -12
  106. package/dist/commands/rm.js.map +1 -1
  107. package/dist/commands/rmdir.d.ts.map +1 -1
  108. package/dist/commands/rmdir.js +34 -9
  109. package/dist/commands/rmdir.js.map +1 -1
  110. package/dist/commands/sed.d.ts.map +1 -1
  111. package/dist/commands/sed.js +73 -28
  112. package/dist/commands/sed.js.map +1 -1
  113. package/dist/commands/seq.d.ts +4 -0
  114. package/dist/commands/seq.d.ts.map +1 -0
  115. package/dist/commands/seq.js +148 -0
  116. package/dist/commands/seq.js.map +1 -0
  117. package/dist/commands/sockets.d.ts +4 -0
  118. package/dist/commands/sockets.d.ts.map +1 -0
  119. package/dist/commands/sockets.js +239 -0
  120. package/dist/commands/sockets.js.map +1 -0
  121. package/dist/commands/sort.d.ts +4 -0
  122. package/dist/commands/sort.d.ts.map +1 -0
  123. package/dist/commands/sort.js +175 -0
  124. package/dist/commands/sort.js.map +1 -0
  125. package/dist/commands/split.d.ts +4 -0
  126. package/dist/commands/split.d.ts.map +1 -0
  127. package/dist/commands/split.js +147 -0
  128. package/dist/commands/split.js.map +1 -0
  129. package/dist/commands/stat.d.ts.map +1 -1
  130. package/dist/commands/stat.js +14 -6
  131. package/dist/commands/stat.js.map +1 -1
  132. package/dist/commands/tail.d.ts.map +1 -1
  133. package/dist/commands/tail.js +46 -9
  134. package/dist/commands/tail.js.map +1 -1
  135. package/dist/commands/tee.d.ts.map +1 -1
  136. package/dist/commands/tee.js +45 -10
  137. package/dist/commands/tee.js.map +1 -1
  138. package/dist/commands/test.d.ts +4 -0
  139. package/dist/commands/test.d.ts.map +1 -0
  140. package/dist/commands/test.js +99 -0
  141. package/dist/commands/test.js.map +1 -0
  142. package/dist/commands/touch.d.ts.map +1 -1
  143. package/dist/commands/touch.js +34 -9
  144. package/dist/commands/touch.js.map +1 -1
  145. package/dist/commands/tr.d.ts +4 -0
  146. package/dist/commands/tr.d.ts.map +1 -0
  147. package/dist/commands/tr.js +162 -0
  148. package/dist/commands/tr.js.map +1 -0
  149. package/dist/commands/true.d.ts +4 -0
  150. package/dist/commands/true.d.ts.map +1 -0
  151. package/dist/commands/true.js +27 -0
  152. package/dist/commands/true.js.map +1 -0
  153. package/dist/commands/uniq.d.ts +4 -0
  154. package/dist/commands/uniq.d.ts.map +1 -0
  155. package/dist/commands/uniq.js +187 -0
  156. package/dist/commands/uniq.js.map +1 -0
  157. package/dist/commands/wc.d.ts +4 -0
  158. package/dist/commands/wc.d.ts.map +1 -0
  159. package/dist/commands/wc.js +178 -0
  160. package/dist/commands/wc.js.map +1 -0
  161. package/dist/commands/which.d.ts +4 -0
  162. package/dist/commands/which.d.ts.map +1 -0
  163. package/dist/commands/which.js +78 -0
  164. package/dist/commands/which.js.map +1 -0
  165. package/dist/commands/whoami.d.ts +4 -0
  166. package/dist/commands/whoami.d.ts.map +1 -0
  167. package/dist/commands/whoami.js +28 -0
  168. package/dist/commands/whoami.js.map +1 -0
  169. package/dist/index.d.ts +13 -0
  170. package/dist/index.d.ts.map +1 -1
  171. package/dist/index.js +64 -1
  172. package/dist/index.js.map +1 -1
  173. package/dist/shared/terminal-command.d.ts +8 -2
  174. package/dist/shared/terminal-command.d.ts.map +1 -1
  175. package/dist/shared/terminal-command.js +42 -17
  176. package/dist/shared/terminal-command.js.map +1 -1
  177. package/package.json +4 -2
  178. package/src/commands/basename.ts +84 -0
  179. package/src/commands/cal.ts +111 -0
  180. package/src/commands/cat.ts +37 -27
  181. package/src/commands/cd.ts +40 -12
  182. package/src/commands/chmod.ts +37 -11
  183. package/src/commands/comm.ts +169 -0
  184. package/src/commands/cp.ts +73 -15
  185. package/src/commands/cut.ts +214 -0
  186. package/src/commands/date.ts +97 -0
  187. package/src/commands/diff.ts +204 -0
  188. package/src/commands/dirname.ts +57 -0
  189. package/src/commands/echo.ts +51 -11
  190. package/src/commands/false.ts +31 -0
  191. package/src/commands/find.ts +184 -0
  192. package/src/commands/grep.ts +44 -11
  193. package/src/commands/head.ts +46 -10
  194. package/src/commands/hex.ts +19 -7
  195. package/src/commands/id.ts +94 -0
  196. package/src/commands/join.ts +162 -0
  197. package/src/commands/less.ts +20 -8
  198. package/src/commands/ln.ts +50 -13
  199. package/src/commands/ls.ts +17 -8
  200. package/src/commands/mkdir.ts +41 -12
  201. package/src/commands/mv.ts +31 -9
  202. package/src/commands/nc.ts +499 -0
  203. package/src/commands/nl.ts +201 -0
  204. package/src/commands/passkey.ts +46 -19
  205. package/src/commands/paste.ts +172 -0
  206. package/src/commands/pwd.ts +16 -4
  207. package/src/commands/rm.ts +118 -13
  208. package/src/commands/rmdir.ts +41 -12
  209. package/src/commands/sed.ts +64 -30
  210. package/src/commands/seq.ts +147 -0
  211. package/src/commands/sockets.ts +283 -0
  212. package/src/commands/sort.ts +175 -0
  213. package/src/commands/split.ts +154 -0
  214. package/src/commands/stat.ts +17 -8
  215. package/src/commands/tail.ts +46 -10
  216. package/src/commands/tee.ts +43 -11
  217. package/src/commands/test.ts +116 -0
  218. package/src/commands/touch.ts +41 -12
  219. package/src/commands/tr.ts +170 -0
  220. package/src/commands/true.ts +31 -0
  221. package/src/commands/uniq.ts +189 -0
  222. package/src/commands/wc.ts +181 -0
  223. package/src/commands/which.ts +89 -0
  224. package/src/commands/whoami.ts +32 -0
  225. package/src/index.ts +64 -1
  226. package/src/shared/terminal-command.ts +43 -16
@@ -1,10 +1,19 @@
1
1
  import path from 'path'
2
- import type { CommandLineOptions } from 'command-line-args'
3
2
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
3
  import { TerminalEvents } from '@ecmaos/types'
5
4
  import { TerminalCommand } from '../shared/terminal-command.js'
6
5
  import { writelnStderr } from '../shared/helpers.js'
7
6
 
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: grep [OPTION]... PATTERN [FILE]...
9
+ Search for PATTERN in each FILE.
10
+
11
+ -i, --ignore-case ignore case distinctions
12
+ -n, --line-number print line number with output lines
13
+ --help display this help and exit`
14
+ writelnStderr(process, terminal, usage)
15
+ }
16
+
8
17
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
9
18
  return new TerminalCommand({
10
19
  command: 'grep',
@@ -12,16 +21,42 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
12
21
  kernel,
13
22
  shell,
14
23
  terminal,
15
- options: [
16
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
17
- { name: 'ignore-case', type: Boolean, alias: 'i', description: 'Ignore case distinctions' },
18
- { name: 'line-number', type: Boolean, alias: 'n', description: 'Print line number with output lines' },
19
- { name: 'args', type: String, defaultOption: true, multiple: true, description: 'Pattern and file(s) to search' }
20
- ],
21
- run: async (argv: CommandLineOptions, process?: Process) => {
24
+ run: async (pid: number, argv: string[]) => {
25
+ const process = kernel.processes.get(pid) as Process | undefined
26
+
22
27
  if (!process) return 1
23
28
 
24
- const args = (argv.args as string[]) || []
29
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
30
+ printUsage(process, terminal)
31
+ return 0
32
+ }
33
+
34
+ let ignoreCase = false
35
+ let showLineNumbers = false
36
+ const args: string[] = []
37
+
38
+ for (const arg of argv) {
39
+ if (arg === '--help' || arg === '-h') {
40
+ printUsage(process, terminal)
41
+ return 0
42
+ } else if (arg === '-i' || arg === '--ignore-case') {
43
+ ignoreCase = true
44
+ } else if (arg === '-n' || arg === '--line-number') {
45
+ showLineNumbers = true
46
+ } else if (arg.startsWith('-')) {
47
+ const flags = arg.slice(1).split('')
48
+ if (flags.includes('i')) ignoreCase = true
49
+ if (flags.includes('n')) showLineNumbers = true
50
+ const invalidFlags = flags.filter(f => !['i', 'n'].includes(f))
51
+ if (invalidFlags.length > 0) {
52
+ await writelnStderr(process, terminal, `grep: invalid option -- '${invalidFlags[0]}'`)
53
+ return 1
54
+ }
55
+ } else {
56
+ args.push(arg)
57
+ }
58
+ }
59
+
25
60
  if (args.length === 0 || !args[0]) {
26
61
  await writelnStderr(process, terminal, 'grep: pattern is required')
27
62
  return 1
@@ -29,8 +64,6 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
29
64
 
30
65
  const pattern = args[0]
31
66
  const files = args.slice(1)
32
- const ignoreCase = (argv['ignore-case'] as boolean) || false
33
- const showLineNumbers = (argv['line-number'] as boolean) || false
34
67
 
35
68
  const flags = ignoreCase ? 'i' : ''
36
69
  let regex: RegExp
@@ -1,8 +1,17 @@
1
1
  import path from 'path'
2
- import type { CommandLineOptions } from 'command-line-args'
3
2
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
3
  import { TerminalEvents } from '@ecmaos/types'
5
4
  import { TerminalCommand } from '../shared/terminal-command.js'
5
+ import { writelnStdout } from '../shared/helpers.js'
6
+
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: head [OPTION]... [FILE]...
9
+ Print the first 10 lines of each FILE to standard output.
10
+
11
+ -n, -nNUMBER print the first NUMBER lines instead of 10
12
+ --help display this help and exit`
13
+ writelnStdout(process, terminal, usage)
14
+ }
6
15
 
7
16
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
8
17
  return new TerminalCommand({
@@ -11,19 +20,47 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
11
20
  kernel,
12
21
  shell,
13
22
  terminal,
14
- options: [
15
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
16
- { name: 'lines', type: Number, alias: 'n', description: 'Print the first NUM lines instead of the first 10' },
17
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, multiple: true, description: 'The path(s) to the file(s) to read' }
18
- ],
19
- run: async (argv: CommandLineOptions, process?: Process) => {
23
+ run: async (pid: number, argv: string[]) => {
24
+ const process = kernel.processes.get(pid) as Process | undefined
25
+
20
26
  if (!process) return 1
21
27
 
28
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
29
+ printUsage(process, terminal)
30
+ return 0
31
+ }
32
+
33
+ let numLines = 10
34
+ const files: string[] = []
35
+
36
+ for (let i = 0; i < argv.length; i++) {
37
+ const arg = argv[i]
38
+ if (!arg) continue
39
+
40
+ if (arg === '--help' || arg === '-h') {
41
+ printUsage(process, terminal)
42
+ return 0
43
+ } else if (arg === '-n' || arg.startsWith('-n')) {
44
+ if (arg === '-n' && i + 1 < argv.length) {
45
+ i++
46
+ const nextArg = argv[i]
47
+ if (nextArg !== undefined) {
48
+ const num = parseInt(nextArg, 10)
49
+ if (!isNaN(num)) numLines = num
50
+ }
51
+ } else if (arg.startsWith('-n') && arg.length > 2) {
52
+ const num = parseInt(arg.slice(2), 10)
53
+ if (!isNaN(num)) numLines = num
54
+ }
55
+ } else if (!arg.startsWith('-')) {
56
+ files.push(arg)
57
+ }
58
+ }
59
+
22
60
  const writer = process.stdout.getWriter()
23
- const numLines = (argv.lines as number) ?? 10
24
61
 
25
62
  try {
26
- if (!argv.path || !(argv.path as string[])[0]) {
63
+ if (files.length === 0) {
27
64
  if (!process.stdin) {
28
65
  return 0
29
66
  }
@@ -76,7 +113,6 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
76
113
  return 0
77
114
  }
78
115
 
79
- const files = (argv.path as string[]) || []
80
116
  const isMultipleFiles = files.length > 1
81
117
 
82
118
  for (let i = 0; i < files.length; i++) {
@@ -1,9 +1,17 @@
1
1
  import path from 'path'
2
- import type { CommandLineOptions } from 'command-line-args'
3
2
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
3
  import { TerminalCommand } from '../shared/terminal-command.js'
5
4
  import { writelnStdout, writelnStderr } from '../shared/helpers.js'
6
5
 
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: hex [FILE]
8
+ Display file contents or stdin in hexadecimal format.
9
+
10
+ FILE the file to display (if omitted, reads from stdin)
11
+ --help display this help and exit`
12
+ writelnStderr(process, terminal, usage)
13
+ }
14
+
7
15
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
8
16
  return new TerminalCommand({
9
17
  command: 'hex',
@@ -11,14 +19,18 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
11
19
  kernel,
12
20
  shell,
13
21
  terminal,
14
- options: [
15
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
16
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, description: 'The path to the file to display (if omitted, reads from stdin)' }
17
- ],
18
- run: async (argv: CommandLineOptions, process?: Process) => {
22
+ run: async (pid: number, argv: string[]) => {
23
+ const process = kernel.processes.get(pid) as Process | undefined
24
+
19
25
  if (!process) return 1
20
26
 
21
- const filePath = argv.path as string | undefined
27
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
28
+ printUsage(process, terminal)
29
+ return 0
30
+ }
31
+
32
+ const firstArg = argv.length > 0 ? argv[0] : undefined
33
+ const filePath = firstArg !== undefined && !firstArg.startsWith('-') ? firstArg : undefined
22
34
  let data: Uint8Array
23
35
 
24
36
  try {
@@ -0,0 +1,94 @@
1
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
+ import { TerminalCommand } from '../shared/terminal-command.js'
3
+ import { writelnStdout } from '../shared/helpers.js'
4
+
5
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
6
+ const usage = `Usage: id [OPTION]...
7
+ Print user and group IDs.
8
+
9
+ -u, --user print only the effective user ID
10
+ -g, --group print only the effective group ID
11
+ -G, --groups print all group IDs
12
+ -n, --name print names instead of numeric IDs
13
+ --help display this help and exit`
14
+ writelnStdout(process, terminal, usage)
15
+ }
16
+
17
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
18
+ return new TerminalCommand({
19
+ command: 'id',
20
+ description: 'Print user and group IDs',
21
+ kernel,
22
+ shell,
23
+ terminal,
24
+ run: async (pid: number, argv: string[]) => {
25
+ const process = kernel.processes.get(pid) as Process | undefined
26
+
27
+ let userOnly = false
28
+ let groupOnly = false
29
+ let groupsOnly = false
30
+ let nameOnly = false
31
+
32
+ for (const arg of argv) {
33
+ if (arg === '--help' || arg === '-h') {
34
+ printUsage(process, terminal)
35
+ return 0
36
+ } else if (arg === '-u' || arg === '--user') {
37
+ userOnly = true
38
+ } else if (arg === '-g' || arg === '--group') {
39
+ groupOnly = true
40
+ } else if (arg === '-G' || arg === '--groups') {
41
+ groupsOnly = true
42
+ } else if (arg === '-n' || arg === '--name') {
43
+ nameOnly = true
44
+ } else if (arg.startsWith('-')) {
45
+ const flags = arg.slice(1).split('')
46
+ if (flags.includes('u')) userOnly = true
47
+ if (flags.includes('g')) groupOnly = true
48
+ if (flags.includes('G')) groupsOnly = true
49
+ if (flags.includes('n')) nameOnly = true
50
+ const invalidFlags = flags.filter(f => !['u', 'g', 'G', 'n'].includes(f))
51
+ if (invalidFlags.length > 0) {
52
+ await writelnStdout(process, terminal, `id: invalid option -- '${invalidFlags[0]}'`)
53
+ return 1
54
+ }
55
+ }
56
+ }
57
+
58
+ const user = kernel.users.get(shell.credentials.uid)
59
+ const group = kernel.users.get(shell.credentials.gid)
60
+ const groups = shell.credentials.groups || []
61
+
62
+ let output = ''
63
+
64
+ if (userOnly) {
65
+ output = nameOnly ? (user?.username || shell.credentials.uid.toString()) : shell.credentials.euid.toString()
66
+ } else if (groupOnly) {
67
+ output = nameOnly ? (group?.username || shell.credentials.gid.toString()) : shell.credentials.egid.toString()
68
+ } else if (groupsOnly) {
69
+ output = groups.map(gid => {
70
+ if (nameOnly) {
71
+ const g = kernel.users.get(gid)
72
+ return g?.username || gid.toString()
73
+ }
74
+ return gid.toString()
75
+ }).join(' ')
76
+ } else {
77
+ const uid = nameOnly ? (user?.username || shell.credentials.uid.toString()) : shell.credentials.uid.toString()
78
+ const gid = nameOnly ? (group?.username || shell.credentials.gid.toString()) : shell.credentials.gid.toString()
79
+ const groupsStr = groups.map(gid => {
80
+ if (nameOnly) {
81
+ const g = kernel.users.get(gid)
82
+ return g?.username || gid.toString()
83
+ }
84
+ return gid.toString()
85
+ }).join(',')
86
+
87
+ output = `uid=${uid} gid=${gid} groups=${groupsStr}`
88
+ }
89
+
90
+ await writelnStdout(process, terminal, output)
91
+ return 0
92
+ }
93
+ })
94
+ }
@@ -0,0 +1,162 @@
1
+ import path from 'path'
2
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
+ import { TerminalEvents } from '@ecmaos/types'
4
+ import { TerminalCommand } from '../shared/terminal-command.js'
5
+ import { writelnStderr } from '../shared/helpers.js'
6
+
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: join [OPTION]... FILE1 FILE2
9
+ Join lines of two files on a common field.
10
+
11
+ -1 FIELD join on this FIELD of file 1
12
+ -2 FIELD join on this FIELD of file 2
13
+ -t CHAR use CHAR as input and output field separator
14
+ --help display this help and exit`
15
+ writelnStderr(process, terminal, usage)
16
+ }
17
+
18
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
19
+ return new TerminalCommand({
20
+ command: 'join',
21
+ description: 'Join lines of two files on a common field',
22
+ kernel,
23
+ shell,
24
+ terminal,
25
+ run: async (pid: number, argv: string[]) => {
26
+ const process = kernel.processes.get(pid) as Process | undefined
27
+
28
+ if (!process) return 1
29
+
30
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
31
+ printUsage(process, terminal)
32
+ return 0
33
+ }
34
+
35
+ const files: string[] = []
36
+ let field1 = 1
37
+ let field2 = 1
38
+ let delimiter = ' '
39
+
40
+ for (let i = 0; i < argv.length; i++) {
41
+ const arg = argv[i]
42
+ if (!arg) continue
43
+
44
+ if (arg === '--help' || arg === '-h') {
45
+ printUsage(process, terminal)
46
+ return 0
47
+ } else if (arg === '-1' && i + 1 < argv.length) {
48
+ const nextArg = argv[++i]
49
+ if (nextArg !== undefined) {
50
+ field1 = parseInt(nextArg, 10) || 1
51
+ }
52
+ } else if (arg === '-2' && i + 1 < argv.length) {
53
+ const nextArg = argv[++i]
54
+ if (nextArg !== undefined) {
55
+ field2 = parseInt(nextArg, 10) || 1
56
+ }
57
+ } else if (arg.startsWith('-t')) {
58
+ delimiter = arg.slice(2) || ' '
59
+ } else if (arg === '-t' && i + 1 < argv.length) {
60
+ const nextArg = argv[++i]
61
+ if (nextArg !== undefined) {
62
+ delimiter = nextArg
63
+ }
64
+ } else if (!arg.startsWith('-')) {
65
+ if (files.length < 2) {
66
+ files.push(arg)
67
+ }
68
+ }
69
+ }
70
+
71
+ if (files.length !== 2) {
72
+ await writelnStderr(process, terminal, 'join: exactly two files must be specified')
73
+ return 1
74
+ }
75
+
76
+ const file1 = files[0]
77
+ const file2 = files[1]
78
+ if (!file1 || !file2) {
79
+ await writelnStderr(process, terminal, 'join: exactly two files must be specified')
80
+ return 1
81
+ }
82
+
83
+ const writer = process.stdout.getWriter()
84
+
85
+ const readFileLines = async (filePath: string): Promise<string[]> => {
86
+ if (filePath.startsWith('/dev')) {
87
+ throw new Error('cannot join device files')
88
+ }
89
+
90
+ let interrupted = false
91
+ const interruptHandler = () => { interrupted = true }
92
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
93
+
94
+ try {
95
+ const handle = await shell.context.fs.promises.open(filePath, 'r')
96
+ const stat = await shell.context.fs.promises.stat(filePath)
97
+
98
+ const decoder = new TextDecoder()
99
+ let content = ''
100
+ let bytesRead = 0
101
+ const chunkSize = 1024
102
+
103
+ while (bytesRead < stat.size) {
104
+ if (interrupted) break
105
+ const data = new Uint8Array(chunkSize)
106
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
107
+ await handle.read(data, 0, readSize, bytesRead)
108
+ const chunk = data.subarray(0, readSize)
109
+ content += decoder.decode(chunk, { stream: true })
110
+ bytesRead += readSize
111
+ }
112
+
113
+ const lines = content.split('\n')
114
+ if (lines[lines.length - 1] === '') {
115
+ lines.pop()
116
+ }
117
+ return lines
118
+ } finally {
119
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
120
+ }
121
+ }
122
+
123
+ try {
124
+ const fullPath1 = path.resolve(shell.cwd || '/', file1)
125
+ const fullPath2 = path.resolve(shell.cwd || '/', file2)
126
+
127
+ const lines1 = await readFileLines(fullPath1)
128
+ const lines2 = await readFileLines(fullPath2)
129
+
130
+ const map1 = new Map<string, string[]>()
131
+ for (const line of lines1) {
132
+ const parts = line.split(delimiter)
133
+ const key = parts[field1 - 1] || ''
134
+ if (!map1.has(key)) {
135
+ map1.set(key, [])
136
+ }
137
+ map1.get(key)!.push(line)
138
+ }
139
+
140
+ for (const line of lines2) {
141
+ const parts = line.split(delimiter)
142
+ const key = parts[field2 - 1] || ''
143
+ const matches = map1.get(key)
144
+ if (matches) {
145
+ for (const match of matches) {
146
+ const matchParts = match.split(delimiter)
147
+ const output = [...matchParts, ...parts.slice(field2)].join(delimiter)
148
+ await writer.write(new TextEncoder().encode(output + '\n'))
149
+ }
150
+ }
151
+ }
152
+
153
+ return 0
154
+ } catch (error) {
155
+ await writelnStderr(process, terminal, `join: ${error instanceof Error ? error.message : 'Unknown error'}`)
156
+ return 1
157
+ } finally {
158
+ writer.releaseLock()
159
+ }
160
+ }
161
+ })
162
+ }
@@ -1,11 +1,19 @@
1
1
  import path from 'path'
2
2
  import ansi from 'ansi-escape-sequences'
3
- import type { CommandLineOptions } from 'command-line-args'
4
3
  import type { IDisposable } from '@xterm/xterm'
5
4
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
6
5
  import { TerminalCommand } from '../shared/terminal-command.js'
7
6
  import { writelnStderr } from '../shared/helpers.js'
8
7
 
8
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
9
+ const usage = `Usage: less [OPTION]... FILE
10
+ View file contents interactively.
11
+
12
+ FILE the file to view (if omitted, reads from stdin)
13
+ --help display this help and exit`
14
+ writelnStderr(process, terminal, usage)
15
+ }
16
+
9
17
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
10
18
  return new TerminalCommand({
11
19
  command: 'less',
@@ -13,21 +21,25 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
13
21
  kernel,
14
22
  shell,
15
23
  terminal,
16
- options: [
17
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
18
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, description: 'The path to the file to view' }
19
- ],
20
- run: async (argv: CommandLineOptions, process?: Process) => {
24
+ run: async (pid: number, argv: string[]) => {
25
+ const process = kernel.processes.get(pid) as Process | undefined
26
+
21
27
  if (!process) return 1
22
28
 
29
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
30
+ printUsage(process, terminal)
31
+ return 0
32
+ }
33
+
23
34
  let lines: string[] = []
24
35
  let currentLine = 0
25
36
  let keyListener: IDisposable | null = null
26
37
  let linesRendered = 0
27
38
 
28
39
  try {
29
- if (argv.path) {
30
- const filePath = argv.path as string
40
+ const filePath = argv.length > 0 && argv[0] !== undefined && !argv[0].startsWith('-') ? argv[0] : undefined
41
+
42
+ if (filePath) {
31
43
  const expandedPath = shell.expandTilde(filePath)
32
44
  const fullPath = path.resolve(shell.cwd, expandedPath)
33
45
 
@@ -1,10 +1,22 @@
1
1
  import path from 'path'
2
2
  import chalk from 'chalk'
3
- import type { CommandLineOptions } from 'command-line-args'
4
3
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
5
4
  import { TerminalCommand } from '../shared/terminal-command.js'
6
5
  import { writelnStdout, writelnStderr } from '../shared/helpers.js'
7
6
 
7
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
8
+ const usage = `Usage: ln [OPTION]... [-T] TARGET LINK_NAME
9
+ or: ln [OPTION]... TARGET
10
+ or: ln [OPTION]... TARGET... DIRECTORY
11
+ Create links between files.
12
+
13
+ -s, --symbolic make symbolic links instead of hard links
14
+ -f, --force remove existing destination files
15
+ -v, --verbose print name of each linked file
16
+ --help display this help and exit`
17
+ writelnStderr(process, terminal, usage)
18
+ }
19
+
8
20
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
9
21
  return new TerminalCommand({
10
22
  command: 'ln',
@@ -12,18 +24,43 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
12
24
  kernel,
13
25
  shell,
14
26
  terminal,
15
- options: [
16
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
17
- { name: 'symbolic', type: Boolean, alias: 's', description: 'Create symbolic links instead of hard links' },
18
- { name: 'force', type: Boolean, alias: 'f', description: 'Remove existing destination files' },
19
- { name: 'verbose', type: Boolean, alias: 'v', description: 'Print name of each linked file' },
20
- { name: 'args', type: String, multiple: true, defaultOption: true, description: 'The target and optional link name' }
21
- ],
22
- run: async (argv: CommandLineOptions, process?: Process) => {
23
- const args = (argv.args as string[]) || []
24
- const symbolic = argv.symbolic as boolean || false
25
- const force = argv.force as boolean || false
26
- const verbose = argv.verbose as boolean || false
27
+ run: async (pid: number, argv: string[]) => {
28
+ const process = kernel.processes.get(pid) as Process | undefined
29
+
30
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
31
+ printUsage(process, terminal)
32
+ return 0
33
+ }
34
+
35
+ const args: string[] = []
36
+ let symbolic = false
37
+ let force = false
38
+ let verbose = false
39
+
40
+ for (const arg of argv) {
41
+ if (arg === '--help' || arg === '-h') {
42
+ printUsage(process, terminal)
43
+ return 0
44
+ } else if (arg === '-s' || arg === '--symbolic') {
45
+ symbolic = true
46
+ } else if (arg === '-f' || arg === '--force') {
47
+ force = true
48
+ } else if (arg === '-v' || arg === '--verbose') {
49
+ verbose = true
50
+ } else if (arg.startsWith('-')) {
51
+ const flags = arg.slice(1).split('')
52
+ if (flags.includes('s')) symbolic = true
53
+ if (flags.includes('f')) force = true
54
+ if (flags.includes('v')) verbose = true
55
+ const invalidFlags = flags.filter(f => !['s', 'f', 'v'].includes(f))
56
+ if (invalidFlags.length > 0) {
57
+ await writelnStderr(process, terminal, `ln: invalid option -- '${invalidFlags[0]}'`)
58
+ return 1
59
+ }
60
+ } else {
61
+ args.push(arg)
62
+ }
63
+ }
27
64
 
28
65
  if (args.length === 0) {
29
66
  await writelnStderr(process, terminal, chalk.red('ln: missing file operand'))
@@ -1,11 +1,18 @@
1
1
  import path from 'path'
2
2
  import chalk from 'chalk'
3
3
  import humanFormat from 'human-format'
4
- import type { CommandLineOptions } from 'command-line-args'
5
4
  import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
6
5
  import { TerminalCommand } from '../shared/terminal-command.js'
7
6
  import { writelnStdout } from '../shared/helpers.js'
8
7
 
8
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
9
+ const usage = `Usage: ls [OPTION]... [FILE]...
10
+ List information about the FILEs (the current directory by default).
11
+
12
+ --help display this help and exit`
13
+ writelnStdout(process, terminal, usage)
14
+ }
15
+
9
16
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
10
17
  return new TerminalCommand({
11
18
  command: 'ls',
@@ -13,12 +20,15 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
13
20
  kernel,
14
21
  shell,
15
22
  terminal,
16
- options: [
17
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
18
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, description: 'The path to the directory to list' }
19
- ],
20
- run: async (argv: CommandLineOptions, process?: Process) => {
21
- const target = (argv.path as string) || shell.cwd
23
+ run: async (pid: number, argv: string[]) => {
24
+ const process = kernel.processes.get(pid) as Process | undefined
25
+
26
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
27
+ printUsage(process, terminal)
28
+ return 0
29
+ }
30
+
31
+ const target = argv.length > 0 && argv[0] !== undefined && !argv[0].startsWith('-') ? argv[0] : shell.cwd
22
32
  const fullPath = target ? path.resolve(shell.cwd, target === '' ? '.' : target) : shell.cwd
23
33
  const stats = await shell.context.fs.promises.stat(fullPath)
24
34
  const entries: string[] = stats.isDirectory() ? await shell.context.fs.promises.readdir(fullPath) : [fullPath]
@@ -230,4 +240,3 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
230
240
  }
231
241
  })
232
242
  }
233
-