@ecmaos/coreutils 0.1.4 → 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 (229) hide show
  1. package/.turbo/turbo-build.log +1 -1
  2. package/CHANGELOG.md +55 -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 +49 -22
  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 +51 -9
  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 +4 -0
  55. package/dist/commands/grep.d.ts.map +1 -0
  56. package/dist/commands/grep.js +175 -0
  57. package/dist/commands/grep.js.map +1 -0
  58. package/dist/commands/head.d.ts +4 -0
  59. package/dist/commands/head.d.ts.map +1 -0
  60. package/dist/commands/head.js +199 -0
  61. package/dist/commands/head.js.map +1 -0
  62. package/dist/commands/hex.d.ts.map +1 -1
  63. package/dist/commands/hex.js +82 -20
  64. package/dist/commands/hex.js.map +1 -1
  65. package/dist/commands/id.d.ts +4 -0
  66. package/dist/commands/id.d.ts.map +1 -0
  67. package/dist/commands/id.js +97 -0
  68. package/dist/commands/id.js.map +1 -0
  69. package/dist/commands/join.d.ts +4 -0
  70. package/dist/commands/join.d.ts.map +1 -0
  71. package/dist/commands/join.js +152 -0
  72. package/dist/commands/join.js.map +1 -0
  73. package/dist/commands/less.d.ts.map +1 -1
  74. package/dist/commands/less.js +16 -7
  75. package/dist/commands/less.js.map +1 -1
  76. package/dist/commands/ln.d.ts.map +1 -1
  77. package/dist/commands/ln.js +54 -12
  78. package/dist/commands/ln.js.map +1 -1
  79. package/dist/commands/ls.d.ts.map +1 -1
  80. package/dist/commands/ls.js +19 -9
  81. package/dist/commands/ls.js.map +1 -1
  82. package/dist/commands/mkdir.d.ts.map +1 -1
  83. package/dist/commands/mkdir.js +34 -9
  84. package/dist/commands/mkdir.js.map +1 -1
  85. package/dist/commands/mv.d.ts.map +1 -1
  86. package/dist/commands/mv.js +25 -7
  87. package/dist/commands/mv.js.map +1 -1
  88. package/dist/commands/nc.d.ts +4 -0
  89. package/dist/commands/nc.d.ts.map +1 -0
  90. package/dist/commands/nc.js +451 -0
  91. package/dist/commands/nc.js.map +1 -0
  92. package/dist/commands/nl.d.ts +4 -0
  93. package/dist/commands/nl.d.ts.map +1 -0
  94. package/dist/commands/nl.js +208 -0
  95. package/dist/commands/nl.js.map +1 -0
  96. package/dist/commands/passkey.d.ts.map +1 -1
  97. package/dist/commands/passkey.js +44 -18
  98. package/dist/commands/passkey.js.map +1 -1
  99. package/dist/commands/paste.d.ts +4 -0
  100. package/dist/commands/paste.d.ts.map +1 -0
  101. package/dist/commands/paste.js +161 -0
  102. package/dist/commands/paste.js.map +1 -0
  103. package/dist/commands/pwd.d.ts.map +1 -1
  104. package/dist/commands/pwd.js +13 -4
  105. package/dist/commands/pwd.js.map +1 -1
  106. package/dist/commands/rm.d.ts.map +1 -1
  107. package/dist/commands/rm.js +105 -12
  108. package/dist/commands/rm.js.map +1 -1
  109. package/dist/commands/rmdir.d.ts.map +1 -1
  110. package/dist/commands/rmdir.js +34 -9
  111. package/dist/commands/rmdir.js.map +1 -1
  112. package/dist/commands/sed.d.ts.map +1 -1
  113. package/dist/commands/sed.js +73 -28
  114. package/dist/commands/sed.js.map +1 -1
  115. package/dist/commands/seq.d.ts +4 -0
  116. package/dist/commands/seq.d.ts.map +1 -0
  117. package/dist/commands/seq.js +148 -0
  118. package/dist/commands/seq.js.map +1 -0
  119. package/dist/commands/sockets.d.ts +4 -0
  120. package/dist/commands/sockets.d.ts.map +1 -0
  121. package/dist/commands/sockets.js +239 -0
  122. package/dist/commands/sockets.js.map +1 -0
  123. package/dist/commands/sort.d.ts +4 -0
  124. package/dist/commands/sort.d.ts.map +1 -0
  125. package/dist/commands/sort.js +175 -0
  126. package/dist/commands/sort.js.map +1 -0
  127. package/dist/commands/split.d.ts +4 -0
  128. package/dist/commands/split.d.ts.map +1 -0
  129. package/dist/commands/split.js +147 -0
  130. package/dist/commands/split.js.map +1 -0
  131. package/dist/commands/stat.d.ts.map +1 -1
  132. package/dist/commands/stat.js +14 -6
  133. package/dist/commands/stat.js.map +1 -1
  134. package/dist/commands/tail.d.ts +4 -0
  135. package/dist/commands/tail.d.ts.map +1 -0
  136. package/dist/commands/tail.js +189 -0
  137. package/dist/commands/tail.js.map +1 -0
  138. package/dist/commands/tee.d.ts.map +1 -1
  139. package/dist/commands/tee.js +45 -10
  140. package/dist/commands/tee.js.map +1 -1
  141. package/dist/commands/test.d.ts +4 -0
  142. package/dist/commands/test.d.ts.map +1 -0
  143. package/dist/commands/test.js +99 -0
  144. package/dist/commands/test.js.map +1 -0
  145. package/dist/commands/touch.d.ts.map +1 -1
  146. package/dist/commands/touch.js +34 -9
  147. package/dist/commands/touch.js.map +1 -1
  148. package/dist/commands/tr.d.ts +4 -0
  149. package/dist/commands/tr.d.ts.map +1 -0
  150. package/dist/commands/tr.js +162 -0
  151. package/dist/commands/tr.js.map +1 -0
  152. package/dist/commands/true.d.ts +4 -0
  153. package/dist/commands/true.d.ts.map +1 -0
  154. package/dist/commands/true.js +27 -0
  155. package/dist/commands/true.js.map +1 -0
  156. package/dist/commands/uniq.d.ts +4 -0
  157. package/dist/commands/uniq.d.ts.map +1 -0
  158. package/dist/commands/uniq.js +187 -0
  159. package/dist/commands/uniq.js.map +1 -0
  160. package/dist/commands/wc.d.ts +4 -0
  161. package/dist/commands/wc.d.ts.map +1 -0
  162. package/dist/commands/wc.js +178 -0
  163. package/dist/commands/wc.js.map +1 -0
  164. package/dist/commands/which.d.ts +4 -0
  165. package/dist/commands/which.d.ts.map +1 -0
  166. package/dist/commands/which.js +78 -0
  167. package/dist/commands/which.js.map +1 -0
  168. package/dist/commands/whoami.d.ts +4 -0
  169. package/dist/commands/whoami.d.ts.map +1 -0
  170. package/dist/commands/whoami.js +28 -0
  171. package/dist/commands/whoami.js.map +1 -0
  172. package/dist/index.d.ts +15 -0
  173. package/dist/index.d.ts.map +1 -1
  174. package/dist/index.js +72 -1
  175. package/dist/index.js.map +1 -1
  176. package/dist/shared/terminal-command.d.ts +8 -2
  177. package/dist/shared/terminal-command.d.ts.map +1 -1
  178. package/dist/shared/terminal-command.js +42 -17
  179. package/dist/shared/terminal-command.js.map +1 -1
  180. package/package.json +4 -2
  181. package/src/commands/basename.ts +84 -0
  182. package/src/commands/cal.ts +111 -0
  183. package/src/commands/cat.ts +55 -24
  184. package/src/commands/cd.ts +40 -12
  185. package/src/commands/chmod.ts +37 -11
  186. package/src/commands/comm.ts +169 -0
  187. package/src/commands/cp.ts +73 -15
  188. package/src/commands/cut.ts +214 -0
  189. package/src/commands/date.ts +97 -0
  190. package/src/commands/diff.ts +204 -0
  191. package/src/commands/dirname.ts +57 -0
  192. package/src/commands/echo.ts +53 -10
  193. package/src/commands/false.ts +31 -0
  194. package/src/commands/find.ts +184 -0
  195. package/src/commands/grep.ts +187 -0
  196. package/src/commands/head.ts +206 -0
  197. package/src/commands/hex.ts +93 -25
  198. package/src/commands/id.ts +94 -0
  199. package/src/commands/join.ts +162 -0
  200. package/src/commands/less.ts +20 -8
  201. package/src/commands/ln.ts +50 -13
  202. package/src/commands/ls.ts +21 -10
  203. package/src/commands/mkdir.ts +41 -12
  204. package/src/commands/mv.ts +31 -9
  205. package/src/commands/nc.ts +499 -0
  206. package/src/commands/nl.ts +201 -0
  207. package/src/commands/passkey.ts +46 -19
  208. package/src/commands/paste.ts +172 -0
  209. package/src/commands/pwd.ts +16 -4
  210. package/src/commands/rm.ts +118 -13
  211. package/src/commands/rmdir.ts +41 -12
  212. package/src/commands/sed.ts +64 -30
  213. package/src/commands/seq.ts +147 -0
  214. package/src/commands/sockets.ts +283 -0
  215. package/src/commands/sort.ts +175 -0
  216. package/src/commands/split.ts +154 -0
  217. package/src/commands/stat.ts +17 -8
  218. package/src/commands/tail.ts +200 -0
  219. package/src/commands/tee.ts +43 -11
  220. package/src/commands/test.ts +116 -0
  221. package/src/commands/touch.ts +41 -12
  222. package/src/commands/tr.ts +170 -0
  223. package/src/commands/true.ts +31 -0
  224. package/src/commands/uniq.ts +189 -0
  225. package/src/commands/wc.ts +181 -0
  226. package/src/commands/which.ts +89 -0
  227. package/src/commands/whoami.ts +32 -0
  228. package/src/index.ts +72 -1
  229. package/src/shared/terminal-command.ts +43 -16
@@ -0,0 +1,116 @@
1
+ import path from 'path'
2
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
3
+ import { TerminalCommand } from '../shared/terminal-command.js'
4
+ import { writelnStderr } from '../shared/helpers.js'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: test EXPRESSION
8
+ test [OPTION]
9
+ Check file types and compare values.
10
+
11
+ -f FILE FILE exists and is a regular file
12
+ -d FILE FILE exists and is a directory
13
+ -e FILE FILE exists
14
+ -r FILE FILE exists and is readable
15
+ -w FILE FILE exists and is writable
16
+ -x FILE FILE exists and is executable
17
+ -n STRING STRING is not empty
18
+ -z STRING STRING is empty (zero length)
19
+ STRING1 = STRING2 strings are equal
20
+ STRING1 != STRING2 strings are not equal
21
+ --help display this help and exit`
22
+ writelnStderr(process, terminal, usage)
23
+ }
24
+
25
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
26
+ return new TerminalCommand({
27
+ command: 'test',
28
+ description: 'Check file types and compare values',
29
+ kernel,
30
+ shell,
31
+ terminal,
32
+ run: async (pid: number, argv: string[]) => {
33
+ const process = kernel.processes.get(pid) as Process | undefined
34
+
35
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
36
+ printUsage(process, terminal)
37
+ return 0
38
+ }
39
+
40
+ const checkFile = async (filePath: string, check: string): Promise<boolean> => {
41
+ const fullPath = path.resolve(shell.cwd, filePath)
42
+
43
+ try {
44
+ const stat = await shell.context.fs.promises.stat(fullPath)
45
+
46
+ switch (check) {
47
+ case 'f':
48
+ return stat.isFile()
49
+ case 'd':
50
+ return stat.isDirectory()
51
+ case 'e':
52
+ return true
53
+ case 'r':
54
+ case 'w':
55
+ case 'x':
56
+ return true
57
+ default:
58
+ return false
59
+ }
60
+ } catch {
61
+ return false
62
+ }
63
+ }
64
+
65
+ if (argv.length === 0) {
66
+ return 1
67
+ }
68
+
69
+ const operator = argv[0]
70
+
71
+ if (operator === '-f' && argv[1]) {
72
+ return (await checkFile(argv[1], 'f')) ? 0 : 1
73
+ }
74
+
75
+ if (operator === '-d' && argv[1]) {
76
+ return (await checkFile(argv[1], 'd')) ? 0 : 1
77
+ }
78
+
79
+ if (operator === '-e' && argv[1]) {
80
+ return (await checkFile(argv[1], 'e')) ? 0 : 1
81
+ }
82
+
83
+ if (operator === '-r' && argv[1]) {
84
+ return (await checkFile(argv[1], 'r')) ? 0 : 1
85
+ }
86
+
87
+ if (operator === '-w' && argv[1]) {
88
+ return (await checkFile(argv[1], 'w')) ? 0 : 1
89
+ }
90
+
91
+ if (operator === '-x' && argv[1]) {
92
+ return (await checkFile(argv[1], 'x')) ? 0 : 1
93
+ }
94
+
95
+ if (operator === '-n' && argv[1]) {
96
+ return argv[1].length > 0 ? 0 : 1
97
+ }
98
+
99
+ if (operator === '-z' && argv[1]) {
100
+ return argv[1].length === 0 ? 0 : 1
101
+ }
102
+
103
+ if (argv.length === 3) {
104
+ const [left, op, right] = argv
105
+ if (op === '=') {
106
+ return left === right ? 0 : 1
107
+ }
108
+ if (op === '!=') {
109
+ return left !== right ? 0 : 1
110
+ }
111
+ }
112
+
113
+ return 1
114
+ }
115
+ })
116
+ }
@@ -1,7 +1,15 @@
1
1
  import path from 'path'
2
- import type { CommandLineOptions } from 'command-line-args'
3
- import type { Kernel, Shell, Terminal } from '@ecmaos/types'
2
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
4
3
  import { TerminalCommand } from '../shared/terminal-command.js'
4
+ import { writelnStdout, writelnStderr } from '../shared/helpers.js'
5
+
6
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
7
+ const usage = `Usage: touch [OPTION]... FILE...
8
+ Update the access and modification times of each FILE to the current time.
9
+
10
+ --help display this help and exit`
11
+ writelnStdout(process, terminal, usage)
12
+ }
5
13
 
6
14
  export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
7
15
  return new TerminalCommand({
@@ -10,16 +18,37 @@ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal):
10
18
  kernel,
11
19
  shell,
12
20
  terminal,
13
- options: [
14
- { name: 'help', type: Boolean, description: kernel.i18n.t('Display help') },
15
- { name: 'path', type: String, typeLabel: '{underline path}', defaultOption: true, description: 'The path to the file to create' }
16
- ],
17
- run: async (argv: CommandLineOptions) => {
18
- const target = (argv.path as string) || shell.cwd
19
- const fullPath = target ? path.resolve(shell.cwd, target) : shell.cwd
20
- await shell.context.fs.promises.appendFile(fullPath, '')
21
- return 0
21
+ run: async (pid: number, argv: string[]) => {
22
+ const process = kernel.processes.get(pid) as Process | undefined
23
+
24
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
25
+ printUsage(process, terminal)
26
+ return 0
27
+ }
28
+
29
+ if (argv.length === 0) {
30
+ await writelnStderr(process, terminal, 'touch: missing file operand')
31
+ await writelnStderr(process, terminal, "Try 'touch --help' for more information.")
32
+ return 1
33
+ }
34
+
35
+ let hasError = false
36
+
37
+ for (const target of argv) {
38
+ if (!target || target.startsWith('-')) continue
39
+
40
+ const fullPath = target ? path.resolve(shell.cwd, target) : shell.cwd
41
+
42
+ try {
43
+ await shell.context.fs.promises.appendFile(fullPath, '')
44
+ } catch (error) {
45
+ const errorMessage = error instanceof Error ? error.message : String(error)
46
+ await writelnStderr(process, terminal, `touch: ${target}: ${errorMessage}`)
47
+ hasError = true
48
+ }
49
+ }
50
+
51
+ return hasError ? 1 : 0
22
52
  }
23
53
  })
24
54
  }
25
-
@@ -0,0 +1,170 @@
1
+ import type { Kernel, Process, Shell, Terminal } from '@ecmaos/types'
2
+ import { TerminalCommand } from '../shared/terminal-command.js'
3
+ import { writelnStderr } from '../shared/helpers.js'
4
+
5
+ function printUsage(process: Process | undefined, terminal: Terminal): void {
6
+ const usage = `Usage: tr [OPTION]... SET1 [SET2]
7
+ Translate or delete characters.
8
+
9
+ -d, --delete delete characters in SET1
10
+ -s, --squeeze replace each sequence of a repeated character
11
+ --help display this help and exit`
12
+ writelnStderr(process, terminal, usage)
13
+ }
14
+
15
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
16
+ return new TerminalCommand({
17
+ command: 'tr',
18
+ description: 'Translate or delete characters',
19
+ kernel,
20
+ shell,
21
+ terminal,
22
+ run: async (pid: number, argv: string[]) => {
23
+ const process = kernel.processes.get(pid) as Process | undefined
24
+
25
+ if (!process) return 1
26
+
27
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
28
+ printUsage(process, terminal)
29
+ return 0
30
+ }
31
+
32
+ const args: string[] = []
33
+ let deleteMode = false
34
+ let squeeze = false
35
+
36
+ for (const arg of argv) {
37
+ if (arg === '--help' || arg === '-h') {
38
+ printUsage(process, terminal)
39
+ return 0
40
+ } else if (arg === '-d' || arg === '--delete') {
41
+ deleteMode = true
42
+ } else if (arg === '-s' || arg === '--squeeze') {
43
+ squeeze = true
44
+ } else if (arg.startsWith('-')) {
45
+ const flags = arg.slice(1).split('')
46
+ if (flags.includes('d')) deleteMode = true
47
+ if (flags.includes('s')) squeeze = true
48
+ const invalidFlags = flags.filter(f => !['d', 's'].includes(f))
49
+ if (invalidFlags.length > 0) {
50
+ await writelnStderr(process, terminal, `tr: invalid option -- '${invalidFlags[0]}'`)
51
+ return 1
52
+ }
53
+ } else {
54
+ args.push(arg)
55
+ }
56
+ }
57
+
58
+ if (args.length === 0) {
59
+ await writelnStderr(process, terminal, 'tr: missing operand')
60
+ return 1
61
+ }
62
+
63
+ const set1 = args[0] || ''
64
+ const set2 = args[1] || ''
65
+
66
+ if (!deleteMode && !squeeze && !set2) {
67
+ await writelnStderr(process, terminal, 'tr: missing operand after SET1')
68
+ return 1
69
+ }
70
+
71
+ const writer = process.stdout.getWriter()
72
+
73
+ const expandSet = (set: string): string => {
74
+ let result = ''
75
+ let i = 0
76
+ while (i < set.length) {
77
+ if (i < set.length - 2 && set[i + 1] === '-') {
78
+ const startChar = set[i]
79
+ const endChar = set[i + 2]
80
+ if (startChar !== undefined && endChar !== undefined) {
81
+ const start = startChar.charCodeAt(0)
82
+ const end = endChar.charCodeAt(0)
83
+ for (let j = start; j <= end; j++) {
84
+ result += String.fromCharCode(j)
85
+ }
86
+ }
87
+ i += 3
88
+ } else {
89
+ const char = set[i]
90
+ if (char !== undefined) {
91
+ result += char
92
+ }
93
+ i++
94
+ }
95
+ }
96
+ return result
97
+ }
98
+
99
+ try {
100
+ if (!process.stdin) {
101
+ return 0
102
+ }
103
+
104
+ const reader = process.stdin.getReader()
105
+ const decoder = new TextDecoder()
106
+ let content = ''
107
+
108
+ try {
109
+ while (true) {
110
+ const { done, value } = await reader.read()
111
+ if (done) break
112
+ if (value) {
113
+ content += decoder.decode(value, { stream: true })
114
+ }
115
+ }
116
+ } finally {
117
+ reader.releaseLock()
118
+ }
119
+
120
+ const expandedSet1 = expandSet(set1)
121
+ const expandedSet2 = deleteMode ? '' : expandSet(set2)
122
+
123
+ let result = ''
124
+
125
+ if (deleteMode) {
126
+ const set1Chars = new Set(expandedSet1)
127
+ for (const char of content) {
128
+ if (!set1Chars.has(char)) {
129
+ result += char
130
+ }
131
+ }
132
+ } else {
133
+ const map = new Map<string, string>()
134
+ const maxLen = Math.max(expandedSet1.length, expandedSet2.length)
135
+
136
+ for (let i = 0; i < maxLen; i++) {
137
+ const from = expandedSet1[i] || (expandedSet1.length > 0 ? expandedSet1[expandedSet1.length - 1] : '')
138
+ const to = expandedSet2[i] || (expandedSet2.length > 0 ? expandedSet2[expandedSet2.length - 1] : '')
139
+ if (from !== undefined) {
140
+ map.set(from, to || '')
141
+ }
142
+ }
143
+
144
+ for (const char of content) {
145
+ result += map.get(char) || char
146
+ }
147
+ }
148
+
149
+ if (squeeze) {
150
+ let squeezed = ''
151
+ let lastChar = ''
152
+ for (const char of result) {
153
+ if (char !== lastChar || !expandedSet1.includes(char)) {
154
+ squeezed += char
155
+ lastChar = char
156
+ } else {
157
+ lastChar = char
158
+ }
159
+ }
160
+ result = squeezed
161
+ }
162
+
163
+ await writer.write(new TextEncoder().encode(result))
164
+ return 0
165
+ } finally {
166
+ writer.releaseLock()
167
+ }
168
+ }
169
+ })
170
+ }
@@ -0,0 +1,31 @@
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: true
7
+ Return a successful exit status.
8
+
9
+ --help display this help and exit`
10
+ writelnStdout(process, terminal, usage)
11
+ }
12
+
13
+ export function createCommand(kernel: Kernel, shell: Shell, terminal: Terminal): TerminalCommand {
14
+ return new TerminalCommand({
15
+ command: 'true',
16
+ description: 'Return a successful exit status',
17
+ kernel,
18
+ shell,
19
+ terminal,
20
+ run: async (pid: number, argv: string[]) => {
21
+ const process = kernel.processes.get(pid) as Process | undefined
22
+
23
+ if (argv.length > 0 && (argv[0] === '--help' || argv[0] === '-h')) {
24
+ printUsage(process, terminal)
25
+ return 0
26
+ }
27
+
28
+ return 0
29
+ }
30
+ })
31
+ }
@@ -0,0 +1,189 @@
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: uniq [OPTION]... [INPUT [OUTPUT]]
9
+ Report or omit repeated lines.
10
+
11
+ -c, --count prefix lines by the number of occurrences
12
+ -d, --repeated only print duplicate lines
13
+ -u, --unique only print unique lines
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: 'uniq',
21
+ description: 'Report or omit repeated lines',
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 count = false
37
+ let repeated = false
38
+ let unique = 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 === '-c' || arg === '--count') {
45
+ count = true
46
+ } else if (arg === '-d' || arg === '--repeated') {
47
+ repeated = true
48
+ } else if (arg === '-u' || arg === '--unique') {
49
+ unique = true
50
+ } else if (arg.startsWith('-')) {
51
+ const flags = arg.slice(1).split('')
52
+ if (flags.includes('c')) count = true
53
+ if (flags.includes('d')) repeated = true
54
+ if (flags.includes('u')) unique = true
55
+ const invalidFlags = flags.filter(f => !['c', 'd', 'u'].includes(f))
56
+ if (invalidFlags.length > 0) {
57
+ await writelnStderr(process, terminal, `uniq: invalid option -- '${invalidFlags[0]}'`)
58
+ return 1
59
+ }
60
+ } else {
61
+ files.push(arg)
62
+ }
63
+ }
64
+
65
+ const writer = process.stdout.getWriter()
66
+
67
+ try {
68
+ let lines: string[] = []
69
+
70
+ if (files.length === 0) {
71
+ if (!process.stdin) {
72
+ return 0
73
+ }
74
+
75
+ const reader = process.stdin.getReader()
76
+ const decoder = new TextDecoder()
77
+ let buffer = ''
78
+
79
+ try {
80
+ while (true) {
81
+ const { done, value } = await reader.read()
82
+ if (done) break
83
+ if (value) {
84
+ buffer += decoder.decode(value, { stream: true })
85
+ const newLines = buffer.split('\n')
86
+ buffer = newLines.pop() || ''
87
+ lines.push(...newLines)
88
+ }
89
+ }
90
+ if (buffer) {
91
+ lines.push(buffer)
92
+ }
93
+ } finally {
94
+ reader.releaseLock()
95
+ }
96
+ } else {
97
+ for (const file of files) {
98
+ const fullPath = path.resolve(shell.cwd, file)
99
+
100
+ let interrupted = false
101
+ const interruptHandler = () => { interrupted = true }
102
+ kernel.terminal.events.on(TerminalEvents.INTERRUPT, interruptHandler)
103
+
104
+ try {
105
+ if (fullPath.startsWith('/dev')) {
106
+ await writelnStderr(process, terminal, `uniq: ${file}: cannot process device files`)
107
+ continue
108
+ }
109
+
110
+ const handle = await shell.context.fs.promises.open(fullPath, 'r')
111
+ const stat = await shell.context.fs.promises.stat(fullPath)
112
+
113
+ const decoder = new TextDecoder()
114
+ let content = ''
115
+ let bytesRead = 0
116
+ const chunkSize = 1024
117
+
118
+ while (bytesRead < stat.size) {
119
+ if (interrupted) break
120
+ const data = new Uint8Array(chunkSize)
121
+ const readSize = Math.min(chunkSize, stat.size - bytesRead)
122
+ await handle.read(data, 0, readSize, bytesRead)
123
+ const chunk = data.subarray(0, readSize)
124
+ content += decoder.decode(chunk, { stream: true })
125
+ bytesRead += readSize
126
+ }
127
+
128
+ const fileLines = content.split('\n')
129
+ if (fileLines[fileLines.length - 1] === '') {
130
+ fileLines.pop()
131
+ }
132
+ lines.push(...fileLines)
133
+ } catch (error) {
134
+ await writelnStderr(process, terminal, `uniq: ${file}: ${error instanceof Error ? error.message : 'Unknown error'}`)
135
+ } finally {
136
+ kernel.terminal.events.off(TerminalEvents.INTERRUPT, interruptHandler)
137
+ }
138
+ }
139
+ }
140
+
141
+ if (lines.length === 0) {
142
+ return 0
143
+ }
144
+
145
+ let prevLine: string | null = null
146
+ let countValue = 1
147
+
148
+ for (let i = 0; i < lines.length; i++) {
149
+ const line = lines[i]
150
+ if (line === undefined) continue
151
+
152
+ if (prevLine === null) {
153
+ prevLine = line
154
+ countValue = 1
155
+ continue
156
+ }
157
+
158
+ if (line === prevLine) {
159
+ countValue++
160
+ } else {
161
+ if (repeated && countValue === 1) {
162
+ } else if (unique && countValue > 1) {
163
+ } else {
164
+ const output = count ? `${countValue.toString().padStart(7)} ${prevLine}` : prevLine
165
+ await writer.write(new TextEncoder().encode(output + '\n'))
166
+ }
167
+ if (line !== undefined) {
168
+ prevLine = line
169
+ }
170
+ countValue = 1
171
+ }
172
+ }
173
+
174
+ if (prevLine !== null) {
175
+ if (repeated && countValue === 1) {
176
+ } else if (unique && countValue > 1) {
177
+ } else {
178
+ const output = count ? `${countValue.toString().padStart(7)} ${prevLine}` : prevLine
179
+ await writer.write(new TextEncoder().encode(output + '\n'))
180
+ }
181
+ }
182
+
183
+ return 0
184
+ } finally {
185
+ writer.releaseLock()
186
+ }
187
+ }
188
+ })
189
+ }