@fnndsc/brasa 0.56.1 → 0.56.3
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/dist/buildinfo.json +1 -1
- package/dist/builtins/berth/berth.d.ts +90 -3
- package/dist/builtins/berth/berth.js +129 -76
- package/dist/builtins/berth/berth.js.map +1 -1
- package/dist/builtins/berth/berthClose.d.ts +25 -6
- package/dist/builtins/berth/berthClose.js +8 -64
- package/dist/builtins/berth/berthClose.js.map +1 -1
- package/dist/builtins/berth/berthCube.d.ts +3 -2
- package/dist/builtins/berth/berthCube.js +9 -16
- package/dist/builtins/berth/berthCube.js.map +1 -1
- package/dist/builtins/berth/berthProcesses.js +33 -3
- package/dist/builtins/berth/berthProcesses.js.map +1 -1
- package/dist/builtins/debug.d.ts +24 -3
- package/dist/builtins/debug.js +66 -13
- package/dist/builtins/debug.js.map +1 -1
- package/dist/builtins/fs/acl.args.d.ts +5 -38
- package/dist/builtins/fs/acl.args.js +6 -72
- package/dist/builtins/fs/acl.args.js.map +1 -1
- package/dist/builtins/fs/acl.d.ts +100 -10
- package/dist/builtins/fs/acl.js +222 -68
- package/dist/builtins/fs/acl.js.map +1 -1
- package/dist/builtins/fs/cat.args.d.ts +14 -15
- package/dist/builtins/fs/cat.args.js +13 -40
- package/dist/builtins/fs/cat.args.js.map +1 -1
- package/dist/builtins/fs/cat.d.ts +40 -3
- package/dist/builtins/fs/cat.js +92 -13
- package/dist/builtins/fs/cat.js.map +1 -1
- package/dist/builtins/fs/cd.d.ts +24 -6
- package/dist/builtins/fs/cd.js +71 -18
- package/dist/builtins/fs/cd.js.map +1 -1
- package/dist/builtins/fs/configWrite.d.ts +24 -8
- package/dist/builtins/fs/configWrite.js +77 -36
- package/dist/builtins/fs/configWrite.js.map +1 -1
- package/dist/builtins/fs/cp.d.ts +39 -4
- package/dist/builtins/fs/cp.js +122 -56
- package/dist/builtins/fs/cp.js.map +1 -1
- package/dist/builtins/fs/download.d.ts +31 -4
- package/dist/builtins/fs/download.js +75 -38
- package/dist/builtins/fs/download.js.map +1 -1
- package/dist/builtins/fs/du.d.ts +59 -33
- package/dist/builtins/fs/du.js +111 -79
- package/dist/builtins/fs/du.js.map +1 -1
- package/dist/builtins/fs/edit.d.ts +19 -4
- package/dist/builtins/fs/edit.js +77 -16
- package/dist/builtins/fs/edit.js.map +1 -1
- package/dist/builtins/fs/find.d.ts +25 -0
- package/dist/builtins/fs/find.js +76 -5
- package/dist/builtins/fs/find.js.map +1 -1
- package/dist/builtins/fs/ls.d.ts +69 -4
- package/dist/builtins/fs/ls.js +84 -45
- package/dist/builtins/fs/ls.js.map +1 -1
- package/dist/builtins/fs/mkdir.d.ts +28 -18
- package/dist/builtins/fs/mkdir.js +95 -66
- package/dist/builtins/fs/mkdir.js.map +1 -1
- package/dist/builtins/fs/mv.d.ts +34 -4
- package/dist/builtins/fs/mv.js +129 -64
- package/dist/builtins/fs/mv.js.map +1 -1
- package/dist/builtins/fs/pull.args.d.ts +56 -17
- package/dist/builtins/fs/pull.args.js +16 -66
- package/dist/builtins/fs/pull.args.js.map +1 -1
- package/dist/builtins/fs/pull.d.ts +62 -0
- package/dist/builtins/fs/pull.js +123 -31
- package/dist/builtins/fs/pull.js.map +1 -1
- package/dist/builtins/fs/pwd.d.ts +19 -5
- package/dist/builtins/fs/pwd.js +62 -8
- package/dist/builtins/fs/pwd.js.map +1 -1
- package/dist/builtins/fs/rm.d.ts +58 -21
- package/dist/builtins/fs/rm.js +99 -82
- package/dist/builtins/fs/rm.js.map +1 -1
- package/dist/builtins/fs/rmdir.d.ts +29 -11
- package/dist/builtins/fs/rmdir.js +87 -36
- package/dist/builtins/fs/rmdir.js.map +1 -1
- package/dist/builtins/fs/touch.d.ts +44 -9
- package/dist/builtins/fs/touch.js +91 -27
- package/dist/builtins/fs/touch.js.map +1 -1
- package/dist/builtins/fs/tree.d.ts +36 -9
- package/dist/builtins/fs/tree.js +126 -36
- package/dist/builtins/fs/tree.js.map +1 -1
- package/dist/builtins/fs/upload.d.ts +22 -3
- package/dist/builtins/fs/upload.js +82 -14
- package/dist/builtins/fs/upload.js.map +1 -1
- package/dist/builtins/fs/xattr.args.d.ts +12 -33
- package/dist/builtins/fs/xattr.args.js +17 -95
- package/dist/builtins/fs/xattr.args.js.map +1 -1
- package/dist/builtins/fs/xattr.d.ts +98 -8
- package/dist/builtins/fs/xattr.js +163 -24
- package/dist/builtins/fs/xattr.js.map +1 -1
- package/dist/builtins/games/bytes.d.ts +201 -5
- package/dist/builtins/games/bytes.js +255 -54
- package/dist/builtins/games/bytes.js.map +1 -1
- package/dist/builtins/games/chimes.d.ts +180 -3
- package/dist/builtins/games/chimes.js +203 -26
- package/dist/builtins/games/chimes.js.map +1 -1
- package/dist/builtins/games/chrislab.d.ts +101 -3
- package/dist/builtins/games/chrislab.js +117 -17
- package/dist/builtins/games/chrislab.js.map +1 -1
- package/dist/builtins/games/cowsay.d.ts +93 -2
- package/dist/builtins/games/cowsay.js +90 -31
- package/dist/builtins/games/cowsay.js.map +1 -1
- package/dist/builtins/games/figlet.d.ts +70 -2
- package/dist/builtins/games/figlet.js +83 -6
- package/dist/builtins/games/figlet.js.map +1 -1
- package/dist/builtins/games/lab.d.ts +104 -5
- package/dist/builtins/games/lab.js +144 -6
- package/dist/builtins/games/lab.js.map +1 -1
- package/dist/builtins/games/lolcat.d.ts +61 -1
- package/dist/builtins/games/lolcat.js +59 -24
- package/dist/builtins/games/lolcat.js.map +1 -1
- package/dist/builtins/games/morse.d.ts +36 -1
- package/dist/builtins/games/morse.js +50 -2
- package/dist/builtins/games/morse.js.map +1 -1
- package/dist/builtins/games/numbers.d.ts +241 -5
- package/dist/builtins/games/numbers.js +298 -23
- package/dist/builtins/games/numbers.js.map +1 -1
- package/dist/builtins/games/play.d.ts +96 -6
- package/dist/builtins/games/play.js +137 -6
- package/dist/builtins/games/play.js.map +1 -1
- package/dist/builtins/games/qr.d.ts +40 -1
- package/dist/builtins/games/qr.js +51 -2
- package/dist/builtins/games/qr.js.map +1 -1
- package/dist/builtins/games/show.d.ts +131 -6
- package/dist/builtins/games/show.js +208 -20
- package/dist/builtins/games/show.js.map +1 -1
- package/dist/builtins/games/sky.d.ts +178 -4
- package/dist/builtins/games/sky.js +228 -12
- package/dist/builtins/games/sky.js.map +1 -1
- package/dist/builtins/games/text.d.ts +258 -7
- package/dist/builtins/games/text.js +310 -29
- package/dist/builtins/games/text.js.map +1 -1
- package/dist/builtins/games/trek.d.ts +67 -3
- package/dist/builtins/games/trek.js +63 -10
- package/dist/builtins/games/trek.js.map +1 -1
- package/dist/builtins/games/wtf.d.ts +58 -1
- package/dist/builtins/games/wtf.js +67 -6
- package/dist/builtins/games/wtf.js.map +1 -1
- package/dist/builtins/help.d.ts +41 -3
- package/dist/builtins/help.js +92 -796
- package/dist/builtins/help.js.map +1 -1
- package/dist/builtins/net/connect.d.ts +36 -3
- package/dist/builtins/net/connect.js +84 -26
- package/dist/builtins/net/connect.js.map +1 -1
- package/dist/builtins/net/cubepath.d.ts +49 -5
- package/dist/builtins/net/cubepath.js +69 -27
- package/dist/builtins/net/cubepath.js.map +1 -1
- package/dist/builtins/net/logout.d.ts +13 -4
- package/dist/builtins/net/logout.js +49 -3
- package/dist/builtins/net/logout.js.map +1 -1
- package/dist/builtins/net/netstat.d.ts +29 -4
- package/dist/builtins/net/netstat.js +64 -23
- package/dist/builtins/net/netstat.js.map +1 -1
- package/dist/builtins/net/pacs.d.ts +169 -15
- package/dist/builtins/net/pacs.js +92 -33
- package/dist/builtins/net/pacs.js.map +1 -1
- package/dist/builtins/net/query.args.d.ts +95 -0
- package/dist/builtins/net/query.args.js +26 -0
- package/dist/builtins/net/query.args.js.map +1 -0
- package/dist/builtins/net/query.d.ts +98 -5
- package/dist/builtins/net/query.js +89 -83
- package/dist/builtins/net/query.js.map +1 -1
- package/dist/builtins/net/status.d.ts +5 -3
- package/dist/builtins/net/status.js +5 -12
- package/dist/builtins/net/status.js.map +1 -1
- package/dist/builtins/parametersofplugin.d.ts +82 -3
- package/dist/builtins/parametersofplugin.js +79 -23
- package/dist/builtins/parametersofplugin.js.map +1 -1
- package/dist/builtins/proc.d.ts +210 -3
- package/dist/builtins/proc.js +210 -119
- package/dist/builtins/proc.js.map +1 -1
- package/dist/builtins/res/compute.d.ts +67 -3
- package/dist/builtins/res/compute.js +121 -35
- package/dist/builtins/res/compute.js.map +1 -1
- package/dist/builtins/res/dicom.d.ts +55 -7
- package/dist/builtins/res/dicom.js +86 -52
- package/dist/builtins/res/dicom.js.map +1 -1
- package/dist/builtins/res/expect.d.ts +52 -3
- package/dist/builtins/res/expect.js +92 -29
- package/dist/builtins/res/expect.js.map +1 -1
- package/dist/builtins/res/feed.d.ts +475 -3
- package/dist/builtins/res/feed.js +253 -158
- package/dist/builtins/res/feed.js.map +1 -1
- package/dist/builtins/res/files.d.ts +517 -3
- package/dist/builtins/res/files.js +124 -44
- package/dist/builtins/res/files.js.map +1 -1
- package/dist/builtins/res/gather.d.ts +113 -3
- package/dist/builtins/res/gather.js +153 -44
- package/dist/builtins/res/gather.js.map +1 -1
- package/dist/builtins/res/gather.process.d.ts +27 -19
- package/dist/builtins/res/gather.process.js +29 -59
- package/dist/builtins/res/gather.process.js.map +1 -1
- package/dist/builtins/res/group.d.ts +284 -3
- package/dist/builtins/res/group.js +147 -76
- package/dist/builtins/res/group.js.map +1 -1
- package/dist/builtins/res/image.d.ts +30 -2
- package/dist/builtins/res/image.js +62 -10
- package/dist/builtins/res/image.js.map +1 -1
- package/dist/builtins/res/play.d.ts +71 -9
- package/dist/builtins/res/play.js +124 -82
- package/dist/builtins/res/play.js.map +1 -1
- package/dist/builtins/res/plugininstance.d.ts +307 -2
- package/dist/builtins/res/plugininstance.js +122 -50
- package/dist/builtins/res/plugininstance.js.map +1 -1
- package/dist/builtins/res/pluginmeta.d.ts +244 -2
- package/dist/builtins/res/pluginmeta.js +98 -40
- package/dist/builtins/res/pluginmeta.js.map +1 -1
- package/dist/builtins/res/record.d.ts +37 -3
- package/dist/builtins/res/record.js +127 -57
- package/dist/builtins/res/record.js.map +1 -1
- package/dist/builtins/res/resourceList.d.ts +136 -0
- package/dist/builtins/res/resourceList.js +51 -0
- package/dist/builtins/res/resourceList.js.map +1 -0
- package/dist/builtins/res/tag.d.ts +244 -3
- package/dist/builtins/res/tag.js +97 -41
- package/dist/builtins/res/tag.js.map +1 -1
- package/dist/builtins/res/user.d.ts +122 -3
- package/dist/builtins/res/user.js +83 -21
- package/dist/builtins/res/user.js.map +1 -1
- package/dist/builtins/res/workflow.d.ts +244 -2
- package/dist/builtins/res/workflow.js +97 -40
- package/dist/builtins/res/workflow.js.map +1 -1
- package/dist/builtins/store.d.ts +85 -3
- package/dist/builtins/store.js +135 -68
- package/dist/builtins/store.js.map +1 -1
- package/dist/builtins/sys/apropos.d.ts +80 -5
- package/dist/builtins/sys/apropos.js +121 -15
- package/dist/builtins/sys/apropos.js.map +1 -1
- package/dist/builtins/sys/cal.d.ts +44 -5
- package/dist/builtins/sys/cal.js +57 -10
- package/dist/builtins/sys/cal.js.map +1 -1
- package/dist/builtins/sys/command.d.ts +20 -0
- package/dist/builtins/sys/command.js +31 -0
- package/dist/builtins/sys/command.js.map +1 -1
- package/dist/builtins/sys/context.d.ts +13 -3
- package/dist/builtins/sys/context.js +51 -4
- package/dist/builtins/sys/context.js.map +1 -1
- package/dist/builtins/sys/date.d.ts +56 -5
- package/dist/builtins/sys/date.js +63 -9
- package/dist/builtins/sys/date.js.map +1 -1
- package/dist/builtins/sys/echo.d.ts +63 -2
- package/dist/builtins/sys/echo.js +87 -8
- package/dist/builtins/sys/echo.js.map +1 -1
- package/dist/builtins/sys/fortune.d.ts +27 -2
- package/dist/builtins/sys/fortune.js +44 -2
- package/dist/builtins/sys/fortune.js.map +1 -1
- package/dist/builtins/sys/grep.d.ts +245 -11
- package/dist/builtins/sys/grep.js +418 -131
- package/dist/builtins/sys/grep.js.map +1 -1
- package/dist/builtins/sys/joblets.d.ts +66 -7
- package/dist/builtins/sys/joblets.js +101 -14
- package/dist/builtins/sys/joblets.js.map +1 -1
- package/dist/builtins/sys/motd.d.ts +37 -3
- package/dist/builtins/sys/motd.js +49 -8
- package/dist/builtins/sys/motd.js.map +1 -1
- package/dist/builtins/sys/notes.d.ts +75 -3
- package/dist/builtins/sys/notes.js +90 -29
- package/dist/builtins/sys/notes.js.map +1 -1
- package/dist/builtins/sys/physicalmode.d.ts +24 -3
- package/dist/builtins/sys/physicalmode.js +68 -14
- package/dist/builtins/sys/physicalmode.js.map +1 -1
- package/dist/builtins/sys/tasks.d.ts +74 -5
- package/dist/builtins/sys/tasks.js +102 -14
- package/dist/builtins/sys/tasks.js.map +1 -1
- package/dist/builtins/sys/textFilters.d.ts +673 -41
- package/dist/builtins/sys/textFilters.js +719 -264
- package/dist/builtins/sys/textFilters.js.map +1 -1
- package/dist/builtins/sys/timing.d.ts +24 -3
- package/dist/builtins/sys/timing.js +67 -14
- package/dist/builtins/sys/timing.js.map +1 -1
- package/dist/builtins/sys/variables.d.ts +70 -4
- package/dist/builtins/sys/variables.js +103 -31
- package/dist/builtins/sys/variables.js.map +1 -1
- package/dist/builtins/sys/version.d.ts +27 -2
- package/dist/builtins/sys/version.js +48 -3
- package/dist/builtins/sys/version.js.map +1 -1
- package/dist/builtins/sys/weather.d.ts +92 -15
- package/dist/builtins/sys/weather.js +71 -45
- package/dist/builtins/sys/weather.js.map +1 -1
- package/dist/builtins/sys/whoami.d.ts +38 -18
- package/dist/builtins/sys/whoami.js +112 -8
- package/dist/builtins/sys/whoami.js.map +1 -1
- package/dist/builtins/utils.d.ts +1 -38
- package/dist/builtins/utils.js +1 -87
- package/dist/builtins/utils.js.map +1 -1
- package/dist/chris/backend.js +2 -0
- package/dist/chris/backend.js.map +1 -1
- package/dist/chris/chrisCommands.js +62 -159
- package/dist/chris/chrisCommands.js.map +1 -1
- package/dist/chris/commandFallback.d.ts +12 -0
- package/dist/chris/commandFallback.js +47 -0
- package/dist/chris/commandFallback.js.map +1 -1
- package/dist/chris/files.js +24 -85
- package/dist/chris/files.js.map +1 -1
- package/dist/chris/filesystem.js +7 -5
- package/dist/chris/filesystem.js.map +1 -1
- package/dist/chris/help.js +0 -1082
- package/dist/chris/help.js.map +1 -1
- package/dist/chris/kinds.d.ts +30 -0
- package/dist/chris/kinds.js +154 -0
- package/dist/chris/kinds.js.map +1 -0
- package/dist/chris/mounts.d.ts +4 -4
- package/dist/chris/mounts.js +9 -16
- package/dist/chris/mounts.js.map +1 -1
- package/dist/chris/navigation.d.ts +2 -3
- package/dist/chris/navigation.js +6 -20
- package/dist/chris/navigation.js.map +1 -1
- package/dist/chris/pacsResources.d.ts +259 -0
- package/dist/chris/pacsResources.js +168 -0
- package/dist/chris/pacsResources.js.map +1 -0
- package/dist/chris/promptContext.d.ts +6 -0
- package/dist/chris/promptContext.js +3 -1
- package/dist/chris/promptContext.js.map +1 -1
- package/dist/core/argumentKinds.d.ts +107 -0
- package/dist/core/argumentKinds.js +223 -0
- package/dist/core/argumentKinds.js.map +1 -0
- package/dist/core/backend.d.ts +4 -1
- package/dist/core/backend.js.map +1 -1
- package/dist/core/commandDeclaration.d.ts +453 -0
- package/dist/core/commandDeclaration.js +52 -0
- package/dist/core/commandDeclaration.js.map +1 -0
- package/dist/core/commandParse.d.ts +64 -0
- package/dist/core/commandParse.js +468 -0
- package/dist/core/commandParse.js.map +1 -0
- package/dist/core/commandRegistry.d.ts +37 -0
- package/dist/core/commandRegistry.js +100 -3
- package/dist/core/commandRegistry.js.map +1 -1
- package/dist/core/commandSchema.d.ts +32 -0
- package/dist/core/commandSchema.js +117 -0
- package/dist/core/commandSchema.js.map +1 -0
- package/dist/core/commandUsage.d.ts +62 -0
- package/dist/core/commandUsage.js +247 -0
- package/dist/core/commandUsage.js.map +1 -0
- package/dist/core/coreCommands.js +66 -214
- package/dist/core/coreCommands.js.map +1 -1
- package/dist/core/dispatch.js +15 -8
- package/dist/core/dispatch.js.map +1 -1
- package/dist/core/paths.d.ts +51 -0
- package/dist/core/paths.js +86 -2
- package/dist/core/paths.js.map +1 -1
- package/dist/core/preprocess.d.ts +10 -9
- package/dist/core/preprocess.js +10 -20
- package/dist/core/preprocess.js.map +1 -1
- package/dist/core/warmupFailures.d.ts +34 -2
- package/dist/core/warmupFailures.js +43 -2
- package/dist/core/warmupFailures.js.map +1 -1
- package/dist/lib/completer/cfsPaths.d.ts +14 -0
- package/dist/lib/completer/cfsPaths.js +91 -0
- package/dist/lib/completer/cfsPaths.js.map +1 -0
- package/dist/lib/completer/declared.d.ts +15 -0
- package/dist/lib/completer/declared.js +189 -0
- package/dist/lib/completer/declared.js.map +1 -0
- package/dist/lib/completer/index.js +33 -79
- package/dist/lib/completer/index.js.map +1 -1
- package/dist/lib/parser.d.ts +5 -8
- package/dist/lib/parser.js +2 -11
- package/dist/lib/parser.js.map +1 -1
- package/dist/lib/vfs/vfs.d.ts +2 -1
- package/dist/lib/vfs/vfs.js +9 -6
- package/dist/lib/vfs/vfs.js.map +1 -1
- package/dist/lib/wildcard.js +2 -3
- package/dist/lib/wildcard.js.map +1 -1
- package/dist/notes.json +1 -1
- package/package.json +6 -5
|
@@ -7,11 +7,26 @@
|
|
|
7
7
|
* runs none, so `find /proc/jobs | wc -l` had nowhere to go. As builtins
|
|
8
8
|
* these run on every surface, and none starts a process anywhere.
|
|
9
9
|
*
|
|
10
|
-
* Each reads what was piped in, else the files named (`-` is the
|
|
11
|
-
*
|
|
12
|
-
* flag it does not take is
|
|
13
|
-
*
|
|
14
|
-
*
|
|
10
|
+
* Each reads what was piped in, else the files named (`-` is the pipe), and
|
|
11
|
+
* takes the POSIX flags most lines use, with their meanings. Each is declared
|
|
12
|
+
* in one record, so a flag it does not take is refused by the parser before
|
|
13
|
+
* it runs; `command wc` skips the builtin and runs the system program.
|
|
14
|
+
*
|
|
15
|
+
* Key components:
|
|
16
|
+
* - wcCommand, headCommand, tailCommand, sortCommand, uniqCommand, cutCommand: the declarations.
|
|
17
|
+
* - builtin_wc … builtin_cut: each declaration run over words already split.
|
|
18
|
+
* - filter_run: reads the sources and answers them, for any filter; filterFile_read,
|
|
19
|
+
* filterSources_read and filterRead_answer are its parts, which grep's files use too.
|
|
20
|
+
* - wc_render, head_lines, tail_lines, sort_lines, uniq_lines, cut_lines: the typed cores.
|
|
21
|
+
*
|
|
22
|
+
* Typical usage:
|
|
23
|
+
* ```ts
|
|
24
|
+
* await builtin_wc(['-l', 'notes.txt']);
|
|
25
|
+
* await builtin_head(['-5']);
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Dependencies: the session's filesystem (through the one store-path
|
|
29
|
+
* resolver) and what was piped in.
|
|
15
30
|
*
|
|
16
31
|
* @module
|
|
17
32
|
*/
|
|
@@ -19,30 +34,105 @@ import chalk from 'chalk';
|
|
|
19
34
|
import { envelope_ok, envelope_error } from '@fnndsc/menu';
|
|
20
35
|
import { errno_words } from '@fnndsc/fond';
|
|
21
36
|
import { stdin_take } from '../games/stdin.js';
|
|
22
|
-
import { surface_get } from '../../core/surface.js';
|
|
23
37
|
import { vfsDispatcher_get } from '../../core/filesystem.js';
|
|
24
|
-
import {
|
|
38
|
+
import { storePath_resolve } from '../utils.js';
|
|
25
39
|
import { exitCode_set } from '../../core/handlerRun.js';
|
|
26
|
-
|
|
40
|
+
import { command_declare } from '../../core/commandDeclaration.js';
|
|
41
|
+
import { declaredCommand_run } from '../../core/commandParse.js';
|
|
42
|
+
/**
|
|
43
|
+
* The file operand every filter but `uniq` takes.
|
|
44
|
+
*
|
|
45
|
+
* @example
|
|
46
|
+
* ```ts
|
|
47
|
+
* const GRAMMAR = { operands: [FILES_OPERAND] } as const satisfies CommandGrammar;
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
export const FILES_OPERAND = {
|
|
51
|
+
name: 'files', kind: 'cfsPath', optional: true, repeat: true, glob: true,
|
|
52
|
+
summary: 'ChRIS files to read, in order (a wildcard matches paths; - is the pipe); the pipe when none is named',
|
|
53
|
+
};
|
|
54
|
+
/** What the posix pages say of each filter. */
|
|
55
|
+
const POSIX_GROUP = 'Text';
|
|
56
|
+
/**
|
|
57
|
+
* A whole number from a flag's argument, or null.
|
|
58
|
+
*
|
|
59
|
+
* @param word - The argument.
|
|
60
|
+
* @returns The number, or null when it is not digits.
|
|
61
|
+
*/
|
|
27
62
|
function count_parse(word) {
|
|
28
|
-
|
|
29
|
-
return null;
|
|
30
|
-
return Number(word);
|
|
63
|
+
return /^\d+$/.test(word) ? Number(word) : null;
|
|
31
64
|
}
|
|
32
|
-
/**
|
|
33
|
-
|
|
65
|
+
/**
|
|
66
|
+
* The lines of a text, without the last line's newline.
|
|
67
|
+
*
|
|
68
|
+
* @param text - The text.
|
|
69
|
+
* @returns Its lines; none for the empty text.
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```ts
|
|
73
|
+
* lines_split('a\nb\n'); // ['a', 'b']
|
|
74
|
+
* ```
|
|
75
|
+
*/
|
|
76
|
+
export function lines_split(text) {
|
|
34
77
|
if (text === '')
|
|
35
78
|
return [];
|
|
36
79
|
return (text.endsWith('\n') ? text.slice(0, -1) : text).split('\n');
|
|
37
80
|
}
|
|
38
|
-
/**
|
|
39
|
-
|
|
81
|
+
/**
|
|
82
|
+
* Lines written back as text, each ended by a newline.
|
|
83
|
+
*
|
|
84
|
+
* @param lines - The lines.
|
|
85
|
+
* @returns The text; empty for no lines.
|
|
86
|
+
*
|
|
87
|
+
* @example
|
|
88
|
+
* ```ts
|
|
89
|
+
* lines_join(['a', 'b']); // 'a\nb\n'
|
|
90
|
+
* ```
|
|
91
|
+
*/
|
|
92
|
+
export function lines_join(lines) {
|
|
40
93
|
return lines.length === 0 ? '' : `${lines.join('\n')}\n`;
|
|
41
94
|
}
|
|
42
|
-
/**
|
|
43
|
-
|
|
95
|
+
/**
|
|
96
|
+
* A word as a POSIX shell reads it back.
|
|
97
|
+
*
|
|
98
|
+
* @param word - The word.
|
|
99
|
+
* @returns The word, quoted when the shell would read it otherwise.
|
|
100
|
+
*
|
|
101
|
+
* @example
|
|
102
|
+
* ```ts
|
|
103
|
+
* word_quote("it's"); // "'it'\\''s'"
|
|
104
|
+
* ```
|
|
105
|
+
*/
|
|
106
|
+
export function word_quote(word) {
|
|
44
107
|
return /^[\w@%+=:,./-]+$/.test(word) ? word : `'${word.replace(/'/g, `'\\''`)}'`;
|
|
45
108
|
}
|
|
109
|
+
/**
|
|
110
|
+
* A filter's refusal: said on the error channel, the line's status 1.
|
|
111
|
+
*
|
|
112
|
+
* @param words - What was wrong.
|
|
113
|
+
* @returns The envelope.
|
|
114
|
+
*/
|
|
115
|
+
function filter_refuse(words) {
|
|
116
|
+
exitCode_set(1);
|
|
117
|
+
return envelope_error('', undefined, `${words}\n`);
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Reads one ChRIS file through the one store-path resolver, under its name as typed.
|
|
121
|
+
*
|
|
122
|
+
* @param command - The filter's name, for what it says.
|
|
123
|
+
* @param operand - The path as typed.
|
|
124
|
+
* @returns The source, or the line that says why it could not be read.
|
|
125
|
+
*
|
|
126
|
+
* @example
|
|
127
|
+
* ```ts
|
|
128
|
+
* await filterFile_read('wc', 'notes.txt'); // { name: 'notes.txt', text: '…' }
|
|
129
|
+
* ```
|
|
130
|
+
*/
|
|
131
|
+
export async function filterFile_read(command, operand) {
|
|
132
|
+
const target = await storePath_resolve(operand);
|
|
133
|
+
const read = await vfsDispatcher_get().read(target);
|
|
134
|
+
return read.ok ? { name: operand, text: read.value } : { error: `${command}: ${operand}: ${read.reason ?? errno_words(read.errno)}\n` };
|
|
135
|
+
}
|
|
46
136
|
/**
|
|
47
137
|
* Reads what a filter works on: the files named, in order (`-` is the
|
|
48
138
|
* pipe), else what was piped in.
|
|
@@ -50,8 +140,13 @@ function word_quote(word) {
|
|
|
50
140
|
* @param command - The filter's name, for what it says.
|
|
51
141
|
* @param operands - The file operands.
|
|
52
142
|
* @returns The sources and what could not be read, or a refusal when there is nothing at all.
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```ts
|
|
146
|
+
* await filterSources_read('wc', ['a.txt', '-']);
|
|
147
|
+
* ```
|
|
53
148
|
*/
|
|
54
|
-
async function
|
|
149
|
+
export async function filterSources_read(command, operands) {
|
|
55
150
|
const piped = stdin_take();
|
|
56
151
|
if (operands.length === 0) {
|
|
57
152
|
if (piped === null)
|
|
@@ -65,123 +160,98 @@ async function sources_read(command, operands) {
|
|
|
65
160
|
sources.push({ name: '-', text: piped ?? '' });
|
|
66
161
|
continue;
|
|
67
162
|
}
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
sources.push({ name: operand, text: read.value });
|
|
163
|
+
const read = await filterFile_read(command, operand);
|
|
164
|
+
if ('error' in read)
|
|
165
|
+
errors += read.error;
|
|
72
166
|
else
|
|
73
|
-
|
|
167
|
+
sources.push(read);
|
|
74
168
|
}
|
|
75
169
|
return { sources, errors };
|
|
76
170
|
}
|
|
77
171
|
/**
|
|
78
|
-
*
|
|
79
|
-
* the
|
|
172
|
+
* A filter's answer over what it read: the output, with what could not be
|
|
173
|
+
* read on the error channel (exit 1).
|
|
80
174
|
*
|
|
81
|
-
* @param
|
|
82
|
-
* @param args - The words after it.
|
|
83
|
-
* @param parsed - What they ask.
|
|
175
|
+
* @param read - What was read.
|
|
84
176
|
* @param answer - Turns the sources into the filter's output.
|
|
85
177
|
* @returns The envelope.
|
|
178
|
+
*
|
|
179
|
+
* @example
|
|
180
|
+
* ```ts
|
|
181
|
+
* filterRead_answer(read, (sources: FilterSource[]): string => wc_render(ask, sources));
|
|
182
|
+
* ```
|
|
86
183
|
*/
|
|
87
|
-
|
|
88
|
-
if (parsed.refused !== null) {
|
|
89
|
-
exitCode_set(1);
|
|
90
|
-
return envelope_error('', undefined, `${parsed.refused}\n`);
|
|
91
|
-
}
|
|
92
|
-
if (parsed.foreign !== null)
|
|
93
|
-
return foreign_run(command, args, parsed.foreign, parsed.operands);
|
|
94
|
-
const read = await sources_read(command, parsed.operands);
|
|
95
|
-
if ('refusal' in read) {
|
|
96
|
-
exitCode_set(1);
|
|
97
|
-
return envelope_error('', undefined, `${read.refusal}\n`);
|
|
98
|
-
}
|
|
184
|
+
export function filterRead_answer(read, answer) {
|
|
99
185
|
if (read.errors !== '')
|
|
100
186
|
exitCode_set(1);
|
|
101
|
-
const out = read.sources.length > 0 || read.errors === '' ? answer(
|
|
187
|
+
const out = read.sources.length > 0 || read.errors === '' ? answer(read.sources) : '';
|
|
102
188
|
return read.errors === '' ? envelope_ok(out) : { ...envelope_ok(out), status: out === '' ? 'error' : 'ok', renderedErr: chalk.red(read.errors) };
|
|
103
189
|
}
|
|
104
190
|
/**
|
|
105
|
-
*
|
|
106
|
-
*
|
|
191
|
+
* Runs a filter: reads its sources and answers them; a file it cannot read
|
|
192
|
+
* is said by name, and the others are still answered.
|
|
107
193
|
*
|
|
108
194
|
* @param command - The filter's name.
|
|
109
|
-
* @param
|
|
110
|
-
* @param
|
|
111
|
-
* @
|
|
195
|
+
* @param files - The file operands (`-` is the pipe); none for the pipe alone.
|
|
196
|
+
* @param answer - Turns the sources into the filter's output.
|
|
197
|
+
* @returns The envelope: the output, with what could not be read on the error channel.
|
|
198
|
+
*
|
|
199
|
+
* @example
|
|
200
|
+
* ```ts
|
|
201
|
+
* await filter_run('wc', ['notes.txt'], (sources: FilterSource[]): string => wc_render(ask, sources));
|
|
202
|
+
* ```
|
|
203
|
+
*/
|
|
204
|
+
export async function filter_run(command, files, answer) {
|
|
205
|
+
const read = await filterSources_read(command, files);
|
|
206
|
+
if ('refusal' in read)
|
|
207
|
+
return filter_refuse(read.refusal);
|
|
208
|
+
return filterRead_answer(read, answer);
|
|
209
|
+
}
|
|
210
|
+
/**
|
|
211
|
+
* Runs a filter once its options are read: a refusal is said, else the
|
|
212
|
+
* sources are answered.
|
|
213
|
+
*
|
|
214
|
+
* @param command - The filter's name.
|
|
215
|
+
* @param read - What its options ask, or why not.
|
|
216
|
+
* @param files - The file operands.
|
|
217
|
+
* @param answer - Turns what it asks and the sources into its output.
|
|
112
218
|
* @returns The envelope.
|
|
113
219
|
*/
|
|
114
|
-
async function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
exitCode_set(1);
|
|
119
|
-
const why = operands.length > 0 || text === null
|
|
120
|
-
? `the builtin does not take it, and a system ${command} cannot read the session's files`
|
|
121
|
-
: `the builtin does not take it, and this surface runs no system ${command} (command ${command} asks for one)`;
|
|
122
|
-
return envelope_error('', undefined, `${command}: ${flag}: ${why}\n`);
|
|
123
|
-
}
|
|
124
|
-
const out = await surface.pipeSegment(`${command} ${args.map(word_quote).join(' ')}`.trim(), Buffer.from(text, 'utf-8'));
|
|
125
|
-
return envelope_ok(out.toString('utf-8'));
|
|
126
|
-
}
|
|
127
|
-
/**
|
|
128
|
-
* Reads short flags (`-lw`), each taking an argument attached or next
|
|
129
|
-
* (`-n5`, `-n 5`), and operands; `--` ends the flags.
|
|
130
|
-
*
|
|
131
|
-
* @param args - The words.
|
|
132
|
-
* @param takes - The flags without an argument.
|
|
133
|
-
* @param withArg - The flags that take one.
|
|
134
|
-
* @param long - Long flags to their short letter.
|
|
135
|
-
* @returns The flags met (in order, with any argument), the operands, and a flag not taken.
|
|
136
|
-
*/
|
|
137
|
-
function flags_read(args, takes, withArg, long = {}) {
|
|
138
|
-
const flags = [];
|
|
139
|
-
const operands = [];
|
|
140
|
-
let ended = false;
|
|
141
|
-
for (let i = 0; i < args.length; i++) {
|
|
142
|
-
const word = args[i];
|
|
143
|
-
if (ended || word === '-' || !word.startsWith('-')) {
|
|
144
|
-
operands.push(word);
|
|
145
|
-
continue;
|
|
146
|
-
}
|
|
147
|
-
if (word === '--') {
|
|
148
|
-
ended = true;
|
|
149
|
-
continue;
|
|
150
|
-
}
|
|
151
|
-
if (word.startsWith('--')) {
|
|
152
|
-
const [name, value] = (word.includes('=') ? [word.slice(0, word.indexOf('=')), word.slice(word.indexOf('=') + 1)] : [word, undefined]);
|
|
153
|
-
const letter = long[name];
|
|
154
|
-
if (letter === undefined)
|
|
155
|
-
return { flags, operands, foreign: word, missing: null };
|
|
156
|
-
if (withArg.includes(letter)) {
|
|
157
|
-
const arg = value ?? args[++i];
|
|
158
|
-
if (arg === undefined)
|
|
159
|
-
return { flags, operands, foreign: null, missing: name };
|
|
160
|
-
flags.push([letter, arg]);
|
|
161
|
-
}
|
|
162
|
-
else {
|
|
163
|
-
flags.push([letter, undefined]);
|
|
164
|
-
}
|
|
165
|
-
continue;
|
|
166
|
-
}
|
|
167
|
-
for (let j = 1; j < word.length; j++) {
|
|
168
|
-
const letter = word[j];
|
|
169
|
-
if (withArg.includes(letter)) {
|
|
170
|
-
const rest = word.slice(j + 1);
|
|
171
|
-
const arg = rest !== '' ? rest : args[++i];
|
|
172
|
-
if (arg === undefined)
|
|
173
|
-
return { flags, operands, foreign: null, missing: `-${letter}` };
|
|
174
|
-
flags.push([letter, arg]);
|
|
175
|
-
break;
|
|
176
|
-
}
|
|
177
|
-
if (!takes.includes(letter))
|
|
178
|
-
return { flags, operands, foreign: `-${letter}`, missing: null };
|
|
179
|
-
flags.push([letter, undefined]);
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
return { flags, operands, foreign: null, missing: null };
|
|
220
|
+
async function askedFilter_run(command, read, files, answer) {
|
|
221
|
+
if ('refused' in read)
|
|
222
|
+
return filter_refuse(read.refused);
|
|
223
|
+
return filter_run(command, files, (sources) => answer(read.ask, sources));
|
|
183
224
|
}
|
|
184
|
-
/**
|
|
225
|
+
/**
|
|
226
|
+
* Every line of every source, in order.
|
|
227
|
+
*
|
|
228
|
+
* @param sources - The sources.
|
|
229
|
+
* @returns Their lines.
|
|
230
|
+
*/
|
|
231
|
+
function sourceLines_list(sources) {
|
|
232
|
+
return sources.flatMap((s) => lines_split(s.text));
|
|
233
|
+
}
|
|
234
|
+
/** What a `wc` line may hold. */
|
|
235
|
+
const WC_GRAMMAR = {
|
|
236
|
+
operands: [FILES_OPERAND],
|
|
237
|
+
options: {
|
|
238
|
+
lines: { short: 'l', long: 'lines', summary: 'Count lines' },
|
|
239
|
+
words: { short: 'w', long: 'words', summary: 'Count words' },
|
|
240
|
+
bytes: { short: 'c', long: 'bytes', summary: 'Count bytes' },
|
|
241
|
+
chars: { short: 'm', long: 'chars', summary: 'Count characters' },
|
|
242
|
+
},
|
|
243
|
+
};
|
|
244
|
+
/**
|
|
245
|
+
* Counts a text as `wc` does: newlines, words between blanks, bytes, characters.
|
|
246
|
+
*
|
|
247
|
+
* @param text - The text.
|
|
248
|
+
* @returns Its counts.
|
|
249
|
+
*
|
|
250
|
+
* @example
|
|
251
|
+
* ```ts
|
|
252
|
+
* wc_count('héllo\n'); // { lines: 1, words: 1, bytes: 7, chars: 6 }
|
|
253
|
+
* ```
|
|
254
|
+
*/
|
|
185
255
|
export function wc_count(text) {
|
|
186
256
|
return {
|
|
187
257
|
lines: (text.match(/\n/g) ?? []).length,
|
|
@@ -190,28 +260,18 @@ export function wc_count(text) {
|
|
|
190
260
|
chars: [...text].length,
|
|
191
261
|
};
|
|
192
262
|
}
|
|
193
|
-
/**
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
ask.chars = true;
|
|
206
|
-
}
|
|
207
|
-
if (!ask.lines && !ask.words && !ask.bytes && !ask.chars) {
|
|
208
|
-
ask.lines = true;
|
|
209
|
-
ask.words = true;
|
|
210
|
-
ask.bytes = true;
|
|
211
|
-
}
|
|
212
|
-
return { ask, operands: read.operands, foreign: read.foreign, refused: null };
|
|
213
|
-
}
|
|
214
|
-
/** `wc`'s answer: a row per source (and a total for more than one), counts in POSIX order. */
|
|
263
|
+
/**
|
|
264
|
+
* `wc`'s answer: a row per source (and a total for more than one), counts in POSIX order.
|
|
265
|
+
*
|
|
266
|
+
* @param ask - What to count.
|
|
267
|
+
* @param sources - The sources.
|
|
268
|
+
* @returns The rows.
|
|
269
|
+
*
|
|
270
|
+
* @example
|
|
271
|
+
* ```ts
|
|
272
|
+
* wc_render({ lines: true, words: false, bytes: false, chars: false }, [{ name: null, text: 'a\n' }]); // '1\n'
|
|
273
|
+
* ```
|
|
274
|
+
*/
|
|
215
275
|
export function wc_render(ask, sources) {
|
|
216
276
|
const rows = sources.map((s) => ({ counts: wc_count(s.text), name: s.name }));
|
|
217
277
|
if (rows.length > 1) {
|
|
@@ -228,92 +288,304 @@ export function wc_render(ask, sources) {
|
|
|
228
288
|
const width = single ? 1 : Math.max(...rows.flatMap((r) => pick(r.counts).map((n) => String(n).length)));
|
|
229
289
|
return lines_join(rows.map((r) => [...pick(r.counts).map((n) => String(n).padStart(width)), ...(r.name === null ? [] : [r.name])].join(' ')));
|
|
230
290
|
}
|
|
231
|
-
/**
|
|
291
|
+
/**
|
|
292
|
+
* `wc [-lwcm] [FILE]...`: the declaration.
|
|
293
|
+
*
|
|
294
|
+
* @example
|
|
295
|
+
* ```ts
|
|
296
|
+
* commands_register({ declarations: [wcCommand] });
|
|
297
|
+
* ```
|
|
298
|
+
*/
|
|
299
|
+
export const wcCommand = command_declare({
|
|
300
|
+
name: 'wc',
|
|
301
|
+
grammar: WC_GRAMMAR,
|
|
302
|
+
help: {
|
|
303
|
+
summary: 'Count lines, words and bytes',
|
|
304
|
+
description: 'Counts what is piped in, else each file named (- is the pipe), with a total for more than one. With no flag it counts lines, words and bytes; one count of the pipe prints the number alone. It runs in the session, on every surface; a flag it does not take is refused, and command wc runs the system wc.',
|
|
305
|
+
category: 'Files',
|
|
306
|
+
posix: { group: POSIX_GROUP, differs: 'as wc; a FILE is a ChRIS path' },
|
|
307
|
+
examples: ['find /proc/jobs -plugin pl-dylld | wc -l', 'wc -l notes.txt', 'cat a.csv | wc'],
|
|
308
|
+
},
|
|
309
|
+
holds: [],
|
|
310
|
+
handler: async (args) => {
|
|
311
|
+
const { lines, words, bytes, chars } = args.options;
|
|
312
|
+
const none = !lines && !words && !bytes && !chars;
|
|
313
|
+
const ask = none ? { lines: true, words: true, bytes: true, chars: false } : { lines, words, bytes, chars };
|
|
314
|
+
return filter_run('wc', args.operands.files, (sources) => wc_render(ask, sources));
|
|
315
|
+
},
|
|
316
|
+
});
|
|
317
|
+
/**
|
|
318
|
+
* `wc` over words already split: the declaration's parse and handler.
|
|
319
|
+
*
|
|
320
|
+
* @param args - The words after `wc`.
|
|
321
|
+
* @returns The counts, or the refusal of a bad line.
|
|
322
|
+
*
|
|
323
|
+
* @example
|
|
324
|
+
* ```ts
|
|
325
|
+
* await builtin_wc(['-l', 'notes.txt']);
|
|
326
|
+
* ```
|
|
327
|
+
*/
|
|
232
328
|
export async function builtin_wc(args) {
|
|
233
|
-
return
|
|
234
|
-
}
|
|
235
|
-
/**
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
329
|
+
return declaredCommand_run(wcCommand, args);
|
|
330
|
+
}
|
|
331
|
+
/** What a `head` line may hold. */
|
|
332
|
+
const HEAD_GRAMMAR = {
|
|
333
|
+
operands: [FILES_OPERAND],
|
|
334
|
+
options: {
|
|
335
|
+
lines: {
|
|
336
|
+
short: 'n', long: 'lines', numeral: true, value: { name: 'count', kind: 'word' },
|
|
337
|
+
summary: 'The first N lines (-N says the same); -n -N: all but the last N',
|
|
338
|
+
},
|
|
339
|
+
bytes: { short: 'c', long: 'bytes', value: { name: 'count', kind: 'word' }, summary: 'The first N bytes; -c -N: all but the last N' },
|
|
340
|
+
},
|
|
341
|
+
};
|
|
342
|
+
/** What a `tail` line may hold. */
|
|
343
|
+
const TAIL_GRAMMAR = {
|
|
344
|
+
operands: [FILES_OPERAND],
|
|
345
|
+
options: {
|
|
346
|
+
lines: {
|
|
347
|
+
short: 'n', long: 'lines', numeral: true, value: { name: 'count', kind: 'word' },
|
|
348
|
+
summary: 'The last N lines (-N says the same); -n +N: from line N to the end',
|
|
349
|
+
},
|
|
350
|
+
bytes: { short: 'c', long: 'bytes', value: { name: 'count', kind: 'word' }, summary: 'The last N bytes; -c +N: from byte N to the end' },
|
|
351
|
+
follow: { short: 'f', long: 'follow', summary: 'Taken: the text is read once and its last lines shown; a ChRIS file or a pipe is not followed' },
|
|
352
|
+
},
|
|
353
|
+
};
|
|
354
|
+
/** What tail says of -f, which it takes but cannot do. */
|
|
355
|
+
const FOLLOW_NOTE = 'tail: -f: a ChRIS file or a pipe is not followed; its last lines are shown once';
|
|
356
|
+
/**
|
|
357
|
+
* Reads `head`'s or `tail`'s count: N, and for head -N, for tail +N, of
|
|
358
|
+
* lines (-n) or bytes (-c).
|
|
359
|
+
*
|
|
360
|
+
* @param command - Which of the two.
|
|
361
|
+
* @param lines - The -n count as given, or undefined.
|
|
362
|
+
* @param bytes - The -c count as given, or undefined.
|
|
363
|
+
* @returns What it asks (ten lines when neither is given), or the refusal of a count that is not one.
|
|
364
|
+
*/
|
|
365
|
+
function endsAsk_read(command, lines, bytes) {
|
|
366
|
+
if (lines !== undefined && bytes !== undefined)
|
|
367
|
+
return { refused: `${command}: -n and -c do not mix; take one` };
|
|
368
|
+
const raw = bytes ?? lines;
|
|
369
|
+
const flag = bytes !== undefined ? '-c' : '-n';
|
|
370
|
+
if (raw === undefined)
|
|
371
|
+
return { ask: { count: 10, butLast: false, fromLine: false, bytes: false } };
|
|
372
|
+
const sign = raw.startsWith('-') || raw.startsWith('+') ? raw[0] : '';
|
|
373
|
+
const n = count_parse(sign === '' ? raw : raw.slice(1));
|
|
374
|
+
const signTaken = sign === '' || (command === 'head' && sign === '-') || (command === 'tail' && sign === '+');
|
|
375
|
+
if (n === null || !signTaken) {
|
|
376
|
+
const unit = bytes !== undefined ? 'byte' : 'line';
|
|
377
|
+
return { refused: `${command}: ${flag} takes a number${command === 'head' ? ' (-N: all but the last N)' : ` (+N: from ${unit} N)`}, not '${raw}'` };
|
|
253
378
|
}
|
|
254
|
-
return { ask,
|
|
379
|
+
return { ask: { count: n, butLast: command === 'head' && sign === '-', fromLine: command === 'tail' && sign === '+', bytes: bytes !== undefined } };
|
|
255
380
|
}
|
|
256
|
-
/**
|
|
381
|
+
/**
|
|
382
|
+
* The lines `head` keeps of a text.
|
|
383
|
+
*
|
|
384
|
+
* @param ask - How many.
|
|
385
|
+
* @param text - The text.
|
|
386
|
+
* @returns The kept lines.
|
|
387
|
+
*
|
|
388
|
+
* @example
|
|
389
|
+
* ```ts
|
|
390
|
+
* head_lines({ count: 1, butLast: false, fromLine: false, bytes: false }, 'a\nb\n'); // ['a']
|
|
391
|
+
* ```
|
|
392
|
+
*/
|
|
257
393
|
export function head_lines(ask, text) {
|
|
258
394
|
const lines = lines_split(text);
|
|
259
395
|
return ask.butLast ? lines.slice(0, Math.max(0, lines.length - ask.count)) : lines.slice(0, ask.count);
|
|
260
396
|
}
|
|
261
|
-
/**
|
|
397
|
+
/**
|
|
398
|
+
* The lines `tail` keeps of a text.
|
|
399
|
+
*
|
|
400
|
+
* @param ask - How many.
|
|
401
|
+
* @param text - The text.
|
|
402
|
+
* @returns The kept lines.
|
|
403
|
+
*
|
|
404
|
+
* @example
|
|
405
|
+
* ```ts
|
|
406
|
+
* tail_lines({ count: 1, butLast: false, fromLine: false, bytes: false }, 'a\nb\n'); // ['b']
|
|
407
|
+
* ```
|
|
408
|
+
*/
|
|
262
409
|
export function tail_lines(ask, text) {
|
|
263
410
|
const lines = lines_split(text);
|
|
264
411
|
if (ask.fromLine)
|
|
265
412
|
return lines.slice(Math.max(0, ask.count - 1));
|
|
266
413
|
return ask.count === 0 ? [] : lines.slice(-ask.count);
|
|
267
414
|
}
|
|
268
|
-
/**
|
|
269
|
-
|
|
415
|
+
/**
|
|
416
|
+
* The bytes `head -c` keeps of a text, as text.
|
|
417
|
+
*
|
|
418
|
+
* @param ask - How many.
|
|
419
|
+
* @param text - The text.
|
|
420
|
+
* @returns The kept bytes.
|
|
421
|
+
*
|
|
422
|
+
* @example
|
|
423
|
+
* ```ts
|
|
424
|
+
* head_bytes({ count: 3, butLast: false, fromLine: false, bytes: true }, 'hello\n'); // 'hel'
|
|
425
|
+
* ```
|
|
426
|
+
*/
|
|
427
|
+
export function head_bytes(ask, text) {
|
|
428
|
+
const all = Buffer.from(text, 'utf-8');
|
|
429
|
+
return (ask.butLast ? all.subarray(0, Math.max(0, all.length - ask.count)) : all.subarray(0, ask.count)).toString('utf-8');
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* The bytes `tail -c` keeps of a text, as text.
|
|
433
|
+
*
|
|
434
|
+
* @param ask - How many.
|
|
435
|
+
* @param text - The text.
|
|
436
|
+
* @returns The kept bytes.
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* ```ts
|
|
440
|
+
* tail_bytes({ count: 3, butLast: false, fromLine: false, bytes: true }, 'hello\n'); // 'lo\n'
|
|
441
|
+
* ```
|
|
442
|
+
*/
|
|
443
|
+
export function tail_bytes(ask, text) {
|
|
444
|
+
const all = Buffer.from(text, 'utf-8');
|
|
445
|
+
if (ask.fromLine)
|
|
446
|
+
return all.subarray(Math.max(0, ask.count - 1)).toString('utf-8');
|
|
447
|
+
return (ask.count === 0 ? all.subarray(all.length) : all.subarray(-ask.count)).toString('utf-8');
|
|
448
|
+
}
|
|
449
|
+
/**
|
|
450
|
+
* Each source's kept part, under `==> name <==` when there is more than one.
|
|
451
|
+
*
|
|
452
|
+
* @param keepLines - The lines one source keeps.
|
|
453
|
+
* @param keepBytes - The bytes one source keeps.
|
|
454
|
+
* @returns The answer over the sources.
|
|
455
|
+
*/
|
|
456
|
+
function ends_render(keepLines, keepBytes) {
|
|
457
|
+
const keep = (ask, text) => (ask.bytes ? keepBytes(ask, text) : lines_join(keepLines(ask, text)));
|
|
270
458
|
return (ask, sources) => {
|
|
271
459
|
if (sources.length === 1)
|
|
272
|
-
return
|
|
273
|
-
return sources.map((s) => `==> ${s.name === '-' ? 'standard input' : s.name} <==\n${
|
|
460
|
+
return keep(ask, sources[0].text);
|
|
461
|
+
return sources.map((s) => `==> ${s.name === '-' ? 'standard input' : s.name ?? ''} <==\n${keep(ask, s.text)}`).join('\n');
|
|
274
462
|
};
|
|
275
463
|
}
|
|
276
|
-
/**
|
|
464
|
+
/**
|
|
465
|
+
* `head [-n N | -N | -c N] [FILE]...`: the declaration.
|
|
466
|
+
*
|
|
467
|
+
* @example
|
|
468
|
+
* ```ts
|
|
469
|
+
* commands_register({ declarations: [headCommand] });
|
|
470
|
+
* ```
|
|
471
|
+
*/
|
|
472
|
+
export const headCommand = command_declare({
|
|
473
|
+
name: 'head',
|
|
474
|
+
grammar: HEAD_GRAMMAR,
|
|
475
|
+
help: {
|
|
476
|
+
summary: 'The first lines',
|
|
477
|
+
description: 'Prints the first ten lines of what is piped in, else of each file named (under ==> name <== for more than one). -N is -n N; -n -N prints all but the last N; -c N prints the first N bytes. It runs in the session, on every surface; a flag it does not take is refused, and command head runs the system head.',
|
|
478
|
+
category: 'Files',
|
|
479
|
+
posix: { group: POSIX_GROUP, differs: 'as head; a FILE is a ChRIS path' },
|
|
480
|
+
examples: ['ls -l | head -n 5', 'head -3 notes.txt', 'head -c 100 notes.txt'],
|
|
481
|
+
},
|
|
482
|
+
holds: [],
|
|
483
|
+
handler: async (args) => askedFilter_run('head', endsAsk_read('head', args.options.lines, args.options.bytes), args.operands.files, ends_render(head_lines, head_bytes)),
|
|
484
|
+
});
|
|
485
|
+
/**
|
|
486
|
+
* `tail [-n N | -N | -n +N | -c N] [-f] [FILE]...`: the declaration.
|
|
487
|
+
*
|
|
488
|
+
* @example
|
|
489
|
+
* ```ts
|
|
490
|
+
* commands_register({ declarations: [tailCommand] });
|
|
491
|
+
* ```
|
|
492
|
+
*/
|
|
493
|
+
export const tailCommand = command_declare({
|
|
494
|
+
name: 'tail',
|
|
495
|
+
grammar: TAIL_GRAMMAR,
|
|
496
|
+
help: {
|
|
497
|
+
summary: 'The last lines',
|
|
498
|
+
description: 'Prints the last ten lines of what is piped in, else of each file named (under ==> name <== for more than one). -N is -n N; -n +N prints from line N on; -c N prints the last N bytes, -c +N from byte N on. -f is taken but cannot follow: a ChRIS file or a pipe is read once and its last lines shown, and tail says so. It runs in the session, on every surface; a flag it does not take is refused, and command tail runs the system tail.',
|
|
499
|
+
category: 'Files',
|
|
500
|
+
posix: { group: POSIX_GROUP, differs: 'as tail; -f shows the last lines once, never follows; a FILE is a ChRIS path' },
|
|
501
|
+
examples: ['cat /proc/jobs/feed_12/pl-dircopy_40/log | tail -n 20', 'tail -n +2 a.csv', 'tail -c 64 notes.txt'],
|
|
502
|
+
},
|
|
503
|
+
holds: [],
|
|
504
|
+
handler: async (args) => {
|
|
505
|
+
const envelope = await askedFilter_run('tail', endsAsk_read('tail', args.options.lines, args.options.bytes), args.operands.files, ends_render(tail_lines, tail_bytes));
|
|
506
|
+
if (!args.options.follow || envelope.status !== 'ok')
|
|
507
|
+
return envelope;
|
|
508
|
+
return { ...envelope, renderedErr: `${envelope.renderedErr ?? ''}${chalk.gray(FOLLOW_NOTE)}\n` };
|
|
509
|
+
},
|
|
510
|
+
});
|
|
511
|
+
/**
|
|
512
|
+
* `head` over words already split: the declaration's parse and handler.
|
|
513
|
+
*
|
|
514
|
+
* @param args - The words after `head`.
|
|
515
|
+
* @returns The lines, or the refusal of a bad line.
|
|
516
|
+
*
|
|
517
|
+
* @example
|
|
518
|
+
* ```ts
|
|
519
|
+
* await builtin_head(['-5', 'notes.txt']);
|
|
520
|
+
* ```
|
|
521
|
+
*/
|
|
277
522
|
export async function builtin_head(args) {
|
|
278
|
-
return
|
|
523
|
+
return declaredCommand_run(headCommand, args);
|
|
279
524
|
}
|
|
280
|
-
/**
|
|
525
|
+
/**
|
|
526
|
+
* `tail` over words already split: the declaration's parse and handler.
|
|
527
|
+
*
|
|
528
|
+
* @param args - The words after `tail`.
|
|
529
|
+
* @returns The lines, or the refusal of a bad line.
|
|
530
|
+
*
|
|
531
|
+
* @example
|
|
532
|
+
* ```ts
|
|
533
|
+
* await builtin_tail(['-n', '+2', 'a.csv']);
|
|
534
|
+
* ```
|
|
535
|
+
*/
|
|
281
536
|
export async function builtin_tail(args) {
|
|
282
|
-
return
|
|
537
|
+
return declaredCommand_run(tailCommand, args);
|
|
283
538
|
}
|
|
284
|
-
/**
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
539
|
+
/** What a `sort` line may hold. */
|
|
540
|
+
const SORT_GRAMMAR = {
|
|
541
|
+
operands: [FILES_OPERAND],
|
|
542
|
+
options: {
|
|
543
|
+
reverse: { short: 'r', long: 'reverse', summary: 'Reverse the order' },
|
|
544
|
+
numeric: { short: 'n', long: 'numeric-sort', summary: 'Compare by number' },
|
|
545
|
+
human: { short: 'h', long: 'human-numeric-sort', summary: 'Compare human sizes: 2K < 1M < 3G' },
|
|
546
|
+
version: { short: 'V', long: 'version-sort', summary: 'Compare versions: runs of digits as numbers (v2 < v10)' },
|
|
547
|
+
fold: { short: 'f', long: 'ignore-case', summary: 'Ignore case' },
|
|
548
|
+
unique: { short: 'u', long: 'unique', summary: 'One of each equal line' },
|
|
549
|
+
key: { short: 'k', long: 'key', value: { name: 'N[,M]', kind: 'word' }, summary: 'Compare fields N to M (to the end without M)' },
|
|
550
|
+
separator: { short: 't', long: 'field-separator', value: { name: 'C', kind: 'word' }, summary: 'Fields split at C, not at runs of blanks' },
|
|
551
|
+
},
|
|
552
|
+
};
|
|
553
|
+
/** The size suffixes `sort -h` reads, smallest first. */
|
|
554
|
+
const HUMAN_SUFFIXES = 'KMGTPEZY';
|
|
555
|
+
/**
|
|
556
|
+
* Reads `sort`'s options: its switches, a key of whole fields, a one-character separator.
|
|
557
|
+
*
|
|
558
|
+
* @param options - The typed options.
|
|
559
|
+
* @returns What they ask, or the refusal of a key, a separator or a mix of orders it does not take.
|
|
560
|
+
*/
|
|
561
|
+
function sortAsk_read(options) {
|
|
562
|
+
const orders = [
|
|
563
|
+
...(options.numeric ? ['numeric'] : []), ...(options.human ? ['human'] : []), ...(options.version ? ['version'] : []),
|
|
564
|
+
];
|
|
565
|
+
if (orders.length > 1)
|
|
566
|
+
return { refused: 'sort: -n, -h and -V do not mix; take one' };
|
|
567
|
+
const ask = { reverse: options.reverse, compare: orders[0] ?? 'text', fold: options.fold, unique: options.unique, key: null, separator: null };
|
|
568
|
+
if (options.separator !== undefined) {
|
|
569
|
+
if (options.separator.length !== 1)
|
|
570
|
+
return { refused: `sort: -t takes one character, not '${options.separator}'` };
|
|
571
|
+
ask.separator = options.separator;
|
|
572
|
+
}
|
|
573
|
+
if (options.key !== undefined) {
|
|
574
|
+
const key = /^(\d+)(?:,(\d+))?$/.exec(options.key);
|
|
575
|
+
if (key === null) {
|
|
576
|
+
return { refused: `sort: -k takes whole fields, N or N,M, not '${options.key}'; a key with its own options is the system sort's: command sort …` };
|
|
312
577
|
}
|
|
578
|
+
ask.key = { from: Number(key[1]), to: key[2] === undefined ? null : Number(key[2]) };
|
|
313
579
|
}
|
|
314
|
-
return { ask
|
|
580
|
+
return { ask };
|
|
315
581
|
}
|
|
316
|
-
/**
|
|
582
|
+
/**
|
|
583
|
+
* The part of a line a sort compares.
|
|
584
|
+
*
|
|
585
|
+
* @param ask - How it orders.
|
|
586
|
+
* @param line - The line.
|
|
587
|
+
* @returns The key's text.
|
|
588
|
+
*/
|
|
317
589
|
function sortKey_extract(ask, line) {
|
|
318
590
|
if (ask.key === null)
|
|
319
591
|
return line;
|
|
@@ -321,18 +593,68 @@ function sortKey_extract(ask, line) {
|
|
|
321
593
|
const to = ask.key.to ?? fields.length;
|
|
322
594
|
return fields.slice(ask.key.from - 1, to).join(ask.separator ?? ' ');
|
|
323
595
|
}
|
|
324
|
-
/**
|
|
596
|
+
/**
|
|
597
|
+
* Compares two keys by version: runs of digits as numbers, the rest as text.
|
|
598
|
+
*
|
|
599
|
+
* @param a - One key.
|
|
600
|
+
* @param b - The other.
|
|
601
|
+
* @returns Negative, zero or positive.
|
|
602
|
+
*/
|
|
603
|
+
function version_compare(a, b) {
|
|
604
|
+
const runsA = a.match(/\d+|\D+/g) ?? [];
|
|
605
|
+
const runsB = b.match(/\d+|\D+/g) ?? [];
|
|
606
|
+
for (let i = 0; i < Math.min(runsA.length, runsB.length); i++) {
|
|
607
|
+
const x = runsA[i];
|
|
608
|
+
const y = runsB[i];
|
|
609
|
+
if (x === y)
|
|
610
|
+
continue;
|
|
611
|
+
const digits = /^\d/.test(x) && /^\d/.test(y);
|
|
612
|
+
if (digits) {
|
|
613
|
+
const delta = BigInt(x) - BigInt(y);
|
|
614
|
+
if (delta !== 0n)
|
|
615
|
+
return delta < 0n ? -1 : 1;
|
|
616
|
+
}
|
|
617
|
+
return x < y ? -1 : 1;
|
|
618
|
+
}
|
|
619
|
+
return runsA.length - runsB.length;
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Compares two keys as the sort asks.
|
|
623
|
+
*
|
|
624
|
+
* @param ask - How it orders.
|
|
625
|
+
* @param a - One key.
|
|
626
|
+
* @param b - The other.
|
|
627
|
+
* @returns Negative, zero or positive.
|
|
628
|
+
*/
|
|
629
|
+
function sortKeys_compare(ask, a, b) {
|
|
630
|
+
if (ask.compare === 'version')
|
|
631
|
+
return version_compare(a.key, b.key);
|
|
632
|
+
if (ask.compare === 'human' && a.rank !== b.rank)
|
|
633
|
+
return a.rank - b.rank;
|
|
634
|
+
if (ask.compare !== 'text' && a.num !== b.num)
|
|
635
|
+
return a.num - b.num;
|
|
636
|
+
return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* The lines in `sort`'s order: stable, by key, as text, number, size or version, then reversed, then unique by key.
|
|
640
|
+
*
|
|
641
|
+
* @param ask - How it orders.
|
|
642
|
+
* @param lines - The lines.
|
|
643
|
+
* @returns The lines in order.
|
|
644
|
+
*
|
|
645
|
+
* @example
|
|
646
|
+
* ```ts
|
|
647
|
+
* sort_lines({ reverse: false, compare: 'human', fold: false, unique: false, key: null, separator: null }, ['1M', '2K']); // ['2K', '1M']
|
|
648
|
+
* ```
|
|
649
|
+
*/
|
|
325
650
|
export function sort_lines(ask, lines) {
|
|
326
651
|
const keyed = lines.map((line) => {
|
|
327
652
|
const key = sortKey_extract(ask, line);
|
|
328
|
-
const
|
|
329
|
-
|
|
653
|
+
const number = /^\s*(-?\d+(?:\.\d+)?)([a-zA-Z]?)/.exec(key);
|
|
654
|
+
const rank = HUMAN_SUFFIXES.indexOf((number?.[2] ?? '').toUpperCase()) + 1;
|
|
655
|
+
return { line, key: ask.fold ? key.toLowerCase() : key, num: Number(number?.[1] ?? 0), rank: (number?.[2] ?? '') === '' ? 0 : rank };
|
|
330
656
|
});
|
|
331
|
-
const compare = (a, b) =>
|
|
332
|
-
if (ask.numeric && a.num !== b.num)
|
|
333
|
-
return a.num - b.num;
|
|
334
|
-
return a.key < b.key ? -1 : a.key > b.key ? 1 : 0;
|
|
335
|
-
};
|
|
657
|
+
const compare = (a, b) => sortKeys_compare(ask, a, b);
|
|
336
658
|
keyed.sort((a, b) => (ask.reverse ? compare(b, a) : compare(a, b)));
|
|
337
659
|
if (!ask.unique)
|
|
338
660
|
return keyed.map((k) => k.line);
|
|
@@ -345,29 +667,63 @@ export function sort_lines(ask, lines) {
|
|
|
345
667
|
}
|
|
346
668
|
return out;
|
|
347
669
|
}
|
|
348
|
-
/**
|
|
670
|
+
/**
|
|
671
|
+
* `sort [-rnhVfu] [-k N[,M]] [-t C] [FILE]...`: the declaration.
|
|
672
|
+
*
|
|
673
|
+
* @example
|
|
674
|
+
* ```ts
|
|
675
|
+
* commands_register({ declarations: [sortCommand] });
|
|
676
|
+
* ```
|
|
677
|
+
*/
|
|
678
|
+
export const sortCommand = command_declare({
|
|
679
|
+
name: 'sort',
|
|
680
|
+
grammar: SORT_GRAMMAR,
|
|
681
|
+
help: {
|
|
682
|
+
summary: 'Lines in order',
|
|
683
|
+
description: 'Sorts the lines of what is piped in, else of the files named, stably: as text, or by number (-n), by human size (-h: 2K < 1M < 3G) or by version (-V: v2 < v10). -k compares fields N to M (to the end without M), split at -t C or at runs of blanks. It runs in the session, on every surface; a flag it does not take, or a key with its own options (-k2n), is refused, and command sort runs the system sort.',
|
|
684
|
+
category: 'Files',
|
|
685
|
+
posix: { group: POSIX_GROUP, differs: 'as sort, a key by whole fields; a FILE is a ChRIS path' },
|
|
686
|
+
examples: ['find /proc/jobs -status finishedWithError | sort', 'cat a.csv | sort -t , -k 2 -n', 'du -h | sort -h', 'ls | sort -V'],
|
|
687
|
+
},
|
|
688
|
+
holds: [],
|
|
689
|
+
handler: async (args) => askedFilter_run('sort', sortAsk_read(args.options), args.operands.files, (ask, sources) => lines_join(sort_lines(ask, sourceLines_list(sources)))),
|
|
690
|
+
});
|
|
691
|
+
/**
|
|
692
|
+
* `sort` over words already split: the declaration's parse and handler.
|
|
693
|
+
*
|
|
694
|
+
* @param args - The words after `sort`.
|
|
695
|
+
* @returns The lines in order, or the refusal of a bad line.
|
|
696
|
+
*
|
|
697
|
+
* @example
|
|
698
|
+
* ```ts
|
|
699
|
+
* await builtin_sort(['-t', ',', '-k', '2', 'a.csv']);
|
|
700
|
+
* ```
|
|
701
|
+
*/
|
|
349
702
|
export async function builtin_sort(args) {
|
|
350
|
-
return
|
|
351
|
-
}
|
|
352
|
-
/** Reads `uniq`'s words; one input at most, as uniq takes. */
|
|
353
|
-
export function uniqArgs_parse(args) {
|
|
354
|
-
const ask = { count: false, repeated: false, unique: false, fold: false };
|
|
355
|
-
const read = flags_read(args, 'cdui', '', { '--count': 'c', '--repeated': 'd', '--unique': 'u', '--ignore-case': 'i' });
|
|
356
|
-
for (const [flag] of read.flags) {
|
|
357
|
-
if (flag === 'c')
|
|
358
|
-
ask.count = true;
|
|
359
|
-
if (flag === 'd')
|
|
360
|
-
ask.repeated = true;
|
|
361
|
-
if (flag === 'u')
|
|
362
|
-
ask.unique = true;
|
|
363
|
-
if (flag === 'i')
|
|
364
|
-
ask.fold = true;
|
|
365
|
-
}
|
|
366
|
-
// A second operand is uniq's output file, written on the host: the system uniq's.
|
|
367
|
-
const foreign = read.foreign ?? (read.operands.length > 1 ? read.operands[1] : null);
|
|
368
|
-
return { ask, operands: read.operands, foreign, refused: null };
|
|
703
|
+
return declaredCommand_run(sortCommand, args);
|
|
369
704
|
}
|
|
370
|
-
/**
|
|
705
|
+
/** What a `uniq` line may hold: one input at most; a second operand, uniq's output file, is the system uniq's. */
|
|
706
|
+
const UNIQ_GRAMMAR = {
|
|
707
|
+
operands: [{ name: 'file', kind: 'cfsPath', optional: true, glob: true, summary: 'A ChRIS file to read (- is the pipe); the pipe when none is named' }],
|
|
708
|
+
options: {
|
|
709
|
+
count: { short: 'c', long: 'count', summary: 'Prefix each with its count' },
|
|
710
|
+
repeated: { short: 'd', long: 'repeated', summary: 'Only repeated lines' },
|
|
711
|
+
unique: { short: 'u', long: 'unique', summary: 'Only lines never repeated' },
|
|
712
|
+
fold: { short: 'i', long: 'ignore-case', summary: 'Ignore case' },
|
|
713
|
+
},
|
|
714
|
+
};
|
|
715
|
+
/**
|
|
716
|
+
* Adjacent equal lines folded to one, as `uniq` does.
|
|
717
|
+
*
|
|
718
|
+
* @param ask - What it keeps.
|
|
719
|
+
* @param lines - The lines.
|
|
720
|
+
* @returns The kept lines.
|
|
721
|
+
*
|
|
722
|
+
* @example
|
|
723
|
+
* ```ts
|
|
724
|
+
* uniq_lines({ count: false, repeated: false, unique: false, fold: false }, ['a', 'a', 'b']); // ['a', 'b']
|
|
725
|
+
* ```
|
|
726
|
+
*/
|
|
371
727
|
export function uniq_lines(ask, lines) {
|
|
372
728
|
const out = [];
|
|
373
729
|
const same = (a, b) => (ask.fold ? a.toLowerCase() === b.toLowerCase() : a === b);
|
|
@@ -383,11 +739,62 @@ export function uniq_lines(ask, lines) {
|
|
|
383
739
|
}
|
|
384
740
|
return out;
|
|
385
741
|
}
|
|
386
|
-
/**
|
|
742
|
+
/**
|
|
743
|
+
* `uniq [-cdui] [FILE]`: the declaration.
|
|
744
|
+
*
|
|
745
|
+
* @example
|
|
746
|
+
* ```ts
|
|
747
|
+
* commands_register({ declarations: [uniqCommand] });
|
|
748
|
+
* ```
|
|
749
|
+
*/
|
|
750
|
+
export const uniqCommand = command_declare({
|
|
751
|
+
name: 'uniq',
|
|
752
|
+
grammar: UNIQ_GRAMMAR,
|
|
753
|
+
help: {
|
|
754
|
+
summary: 'Fold adjacent equal lines to one',
|
|
755
|
+
description: 'Folds each run of equal adjacent lines to one, of what is piped in or of one file. Sort first to fold every equal line. It runs in the session, on every surface; a flag it does not take, or an output file, is refused, and command uniq runs the system uniq.',
|
|
756
|
+
category: 'Files',
|
|
757
|
+
posix: { group: POSIX_GROUP, differs: 'as uniq, without an output file; a FILE is a ChRIS path' },
|
|
758
|
+
examples: ['cut -d , -f 1 a.csv | sort | uniq -c', 'ls | uniq'],
|
|
759
|
+
},
|
|
760
|
+
holds: [],
|
|
761
|
+
handler: async (args) => {
|
|
762
|
+
const ask = { count: args.options.count, repeated: args.options.repeated, unique: args.options.unique, fold: args.options.fold };
|
|
763
|
+
const files = args.operands.file === undefined ? [] : [args.operands.file];
|
|
764
|
+
return filter_run('uniq', files, (sources) => lines_join(uniq_lines(ask, sourceLines_list(sources))));
|
|
765
|
+
},
|
|
766
|
+
});
|
|
767
|
+
/**
|
|
768
|
+
* `uniq` over words already split: the declaration's parse and handler.
|
|
769
|
+
*
|
|
770
|
+
* @param args - The words after `uniq`.
|
|
771
|
+
* @returns The folded lines, or the refusal of a bad line.
|
|
772
|
+
*
|
|
773
|
+
* @example
|
|
774
|
+
* ```ts
|
|
775
|
+
* await builtin_uniq(['-c']);
|
|
776
|
+
* ```
|
|
777
|
+
*/
|
|
387
778
|
export async function builtin_uniq(args) {
|
|
388
|
-
return
|
|
779
|
+
return declaredCommand_run(uniqCommand, args);
|
|
389
780
|
}
|
|
390
|
-
/**
|
|
781
|
+
/** What a `cut` line may hold. */
|
|
782
|
+
const CUT_GRAMMAR = {
|
|
783
|
+
operands: [FILES_OPERAND],
|
|
784
|
+
options: {
|
|
785
|
+
fields: { short: 'f', long: 'fields', value: { name: 'LIST', kind: 'word' }, summary: 'Keep these fields' },
|
|
786
|
+
chars: { short: 'c', long: 'characters', value: { name: 'LIST', kind: 'word' }, summary: 'Keep these characters' },
|
|
787
|
+
bytes: { short: 'b', long: 'bytes', value: { name: 'LIST', kind: 'word' }, summary: 'Keep these characters (as -c)' },
|
|
788
|
+
delimiter: { short: 'd', long: 'delimiter', value: { name: 'C', kind: 'word' }, summary: 'The field delimiter (a tab when not said)' },
|
|
789
|
+
onlyDelimited: { short: 's', long: 'only-delimited', summary: 'Only lines that hold the delimiter' },
|
|
790
|
+
},
|
|
791
|
+
};
|
|
792
|
+
/**
|
|
793
|
+
* Reads a cut LIST: `1,3`, `2-4`, `3-`, `-2`.
|
|
794
|
+
*
|
|
795
|
+
* @param list - The LIST.
|
|
796
|
+
* @returns Its ranges, or null when it is not one.
|
|
797
|
+
*/
|
|
391
798
|
function cutList_parse(list) {
|
|
392
799
|
const ranges = [];
|
|
393
800
|
for (const part of list.split(',')) {
|
|
@@ -402,37 +809,43 @@ function cutList_parse(list) {
|
|
|
402
809
|
}
|
|
403
810
|
return ranges;
|
|
404
811
|
}
|
|
405
|
-
/**
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
return { ask, operands: read.operands, foreign: null, refused: `cut: -d takes one character, not '${value ?? ''}'` };
|
|
418
|
-
ask.delimiter = value;
|
|
419
|
-
}
|
|
420
|
-
if (flag === 'f' || flag === 'c' || flag === 'b') {
|
|
421
|
-
if (listed)
|
|
422
|
-
return { ask, operands: read.operands, foreign: null, refused: 'cut: only one of -f, -c or -b' };
|
|
423
|
-
const ranges = cutList_parse(value ?? '');
|
|
424
|
-
if (ranges === null)
|
|
425
|
-
return { ask, operands: read.operands, foreign: null, refused: `cut: a list is N, N-M, N- or -M joined by commas, not '${value ?? ''}'` };
|
|
426
|
-
ask.by = flag === 'f' ? 'fields' : 'chars';
|
|
427
|
-
ask.ranges = ranges;
|
|
428
|
-
listed = true;
|
|
429
|
-
}
|
|
812
|
+
/**
|
|
813
|
+
* Reads `cut`'s options: one LIST, of fields or characters, and a one-character delimiter.
|
|
814
|
+
*
|
|
815
|
+
* @param options - The typed options.
|
|
816
|
+
* @returns What they ask, or the refusal of a list or delimiter it does not take.
|
|
817
|
+
*/
|
|
818
|
+
function cutAsk_read(options) {
|
|
819
|
+
const ask = { by: 'fields', ranges: [], delimiter: '\t', onlyDelimited: options.onlyDelimited };
|
|
820
|
+
if (options.delimiter !== undefined) {
|
|
821
|
+
if (options.delimiter.length !== 1)
|
|
822
|
+
return { refused: `cut: -d takes one character, not '${options.delimiter}'` };
|
|
823
|
+
ask.delimiter = options.delimiter;
|
|
430
824
|
}
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
825
|
+
const lists = [
|
|
826
|
+
...(options.fields !== undefined ? [{ by: 'fields', list: options.fields }] : []),
|
|
827
|
+
...(options.chars !== undefined ? [{ by: 'chars', list: options.chars }] : []),
|
|
828
|
+
...(options.bytes !== undefined ? [{ by: 'chars', list: options.bytes }] : []),
|
|
829
|
+
];
|
|
830
|
+
if (lists.length > 1)
|
|
831
|
+
return { refused: 'cut: only one of -f, -c or -b' };
|
|
832
|
+
const chosen = lists[0];
|
|
833
|
+
if (chosen === undefined)
|
|
834
|
+
return { refused: 'cut: say what to keep: -f LIST (fields) or -c LIST (characters)' };
|
|
835
|
+
const ranges = cutList_parse(chosen.list);
|
|
836
|
+
if (ranges === null)
|
|
837
|
+
return { refused: `cut: a list is N, N-M, N- or -M joined by commas, not '${chosen.list}'` };
|
|
838
|
+
ask.by = chosen.by;
|
|
839
|
+
ask.ranges = ranges;
|
|
840
|
+
return { ask };
|
|
434
841
|
}
|
|
435
|
-
/**
|
|
842
|
+
/**
|
|
843
|
+
* The positions a cut keeps of `n`, in order, each once.
|
|
844
|
+
*
|
|
845
|
+
* @param ask - What it keeps.
|
|
846
|
+
* @param n - How many fields or characters the line has.
|
|
847
|
+
* @returns The positions, 1-based.
|
|
848
|
+
*/
|
|
436
849
|
function cutPositions_derive(ask, n) {
|
|
437
850
|
const kept = new Set();
|
|
438
851
|
for (const range of ask.ranges)
|
|
@@ -440,7 +853,18 @@ function cutPositions_derive(ask, n) {
|
|
|
440
853
|
kept.add(i);
|
|
441
854
|
return [...kept].sort((a, b) => a - b);
|
|
442
855
|
}
|
|
443
|
-
/**
|
|
856
|
+
/**
|
|
857
|
+
* What `cut` keeps of each line.
|
|
858
|
+
*
|
|
859
|
+
* @param ask - What it keeps.
|
|
860
|
+
* @param lines - The lines.
|
|
861
|
+
* @returns The kept parts, a line each (a line with no delimiter dropped under -s).
|
|
862
|
+
*
|
|
863
|
+
* @example
|
|
864
|
+
* ```ts
|
|
865
|
+
* cut_lines({ by: 'fields', ranges: [{ from: 2, to: 2 }], delimiter: ',', onlyDelimited: false }, ['a,b']); // ['b']
|
|
866
|
+
* ```
|
|
867
|
+
*/
|
|
444
868
|
export function cut_lines(ask, lines) {
|
|
445
869
|
const out = [];
|
|
446
870
|
for (const line of lines) {
|
|
@@ -459,8 +883,39 @@ export function cut_lines(ask, lines) {
|
|
|
459
883
|
}
|
|
460
884
|
return out;
|
|
461
885
|
}
|
|
462
|
-
/**
|
|
886
|
+
/**
|
|
887
|
+
* `cut -f LIST [-d C] [-s] | -c LIST [FILE]...`: the declaration.
|
|
888
|
+
*
|
|
889
|
+
* @example
|
|
890
|
+
* ```ts
|
|
891
|
+
* commands_register({ declarations: [cutCommand] });
|
|
892
|
+
* ```
|
|
893
|
+
*/
|
|
894
|
+
export const cutCommand = command_declare({
|
|
895
|
+
name: 'cut',
|
|
896
|
+
grammar: CUT_GRAMMAR,
|
|
897
|
+
help: {
|
|
898
|
+
summary: 'Keep fields or characters of each line',
|
|
899
|
+
description: 'Keeps the fields (split at -d C, a tab when not said) or the characters LIST names, of each line piped in or of the files named. A LIST is N, N-M, N- or -M, joined by commas; -b is -c. -s drops a line with no delimiter. It runs in the session, on every surface; a flag it does not take is refused, and command cut runs the system cut.',
|
|
900
|
+
category: 'Files',
|
|
901
|
+
posix: { group: POSIX_GROUP, differs: 'as cut, -b as -c; a FILE is a ChRIS path' },
|
|
902
|
+
examples: ['cut -d , -f 1,3 a.csv', 'ls | cut -c 1-8'],
|
|
903
|
+
},
|
|
904
|
+
holds: [],
|
|
905
|
+
handler: async (args) => askedFilter_run('cut', cutAsk_read(args.options), args.operands.files, (ask, sources) => lines_join(cut_lines(ask, sourceLines_list(sources)))),
|
|
906
|
+
});
|
|
907
|
+
/**
|
|
908
|
+
* `cut` over words already split: the declaration's parse and handler.
|
|
909
|
+
*
|
|
910
|
+
* @param args - The words after `cut`.
|
|
911
|
+
* @returns The kept parts, or the refusal of a bad line.
|
|
912
|
+
*
|
|
913
|
+
* @example
|
|
914
|
+
* ```ts
|
|
915
|
+
* await builtin_cut(['-d', ',', '-f', '1', 'a.csv']);
|
|
916
|
+
* ```
|
|
917
|
+
*/
|
|
463
918
|
export async function builtin_cut(args) {
|
|
464
|
-
return
|
|
919
|
+
return declaredCommand_run(cutCommand, args);
|
|
465
920
|
}
|
|
466
921
|
//# sourceMappingURL=textFilters.js.map
|