Aded extended commands and editor
This commit is contained in:
@@ -1,3 +1,26 @@
|
|||||||
|
0.2.14
|
||||||
|
|
||||||
|
- Add `rackedit` as a runtime dependency.
|
||||||
|
- Add the Rash `edit` command, implemented by `coreutils-edit`.
|
||||||
|
- Use RackEdit's `rkdt` as the default editor and map `edit --wait FILE` to `#:wait? #t`.
|
||||||
|
- Add `current-coreutils-editor` so callers can dynamically select another editor procedure.
|
||||||
|
- Rework the Scribble manual around the commands available in Rash, with each command documenting its internal Racket implementation.
|
||||||
|
- Add non-GUI regression tests for editor dispatch and `--wait` translation.
|
||||||
|
|
||||||
|
0.2.12
|
||||||
|
|
||||||
|
- Add a portable `time` command for rash-coreutils commands and executables on PATH.
|
||||||
|
- Report real, combined CPU, and Racket GC time to stderr so command stdout remains pipeline-safe.
|
||||||
|
- Include completed subprocess CPU time in the reported CPU measurement.
|
||||||
|
- Add package-index-safe regression coverage for command timing and stderr separation.
|
||||||
|
|
||||||
|
0.2.11
|
||||||
|
|
||||||
|
- Make head and tail scan fixed-size byte blocks instead of materializing complete lines.
|
||||||
|
- Make tail scan seekable files backwards so memory use is independent of file and line size.
|
||||||
|
- Spool tail input from stdin or multiple files to a temporary file instead of retaining lines in memory.
|
||||||
|
- Add regression coverage for very long lines in head and tail.
|
||||||
|
|
||||||
0.2.9
|
0.2.9
|
||||||
|
|
||||||
- Make package-build tests independent of the directory from which raco test is invoked.
|
- Make package-build tests independent of the directory from which raco test is invoked.
|
||||||
|
|||||||
@@ -1,31 +1,63 @@
|
|||||||
# rash-coreutils
|
# rash-coreutils
|
||||||
|
|
||||||
Portable Unix-style core utilities for Rash, implemented in Racket.
|
Portable Unix-style commands for Rash, implemented in Racket.
|
||||||
|
|
||||||
Version 0.2.9 includes text/pipeline tools (`head`, `tail`, `wc`, `sort`, `uniq`,
|
Version 0.2.14 adds the `edit` command backed by the `rackedit` package and
|
||||||
`cut`, `tee`, `tr`), path and filesystem helpers (`basename`, `dirname`,
|
reworks the manual around the commands that are available directly in Rash.
|
||||||
`realpath`, `readlink`, `stat`, `du`, `df`, `mktemp`), environment commands
|
The manual now describes, per command, which internal `coreutils-*` Racket
|
||||||
(`printenv`, `env`), and `date` backed by Gregor.
|
procedure implements it.
|
||||||
|
|
||||||
Rash remains responsible for shell syntax such as globbing, pipelines and
|
Rash remains responsible for shell syntax such as globbing, pipelines and
|
||||||
redirection. `rash-coreutils` additionally accepts Racket regular expressions as
|
redirection. `rash-coreutils` additionally accepts Racket regular expressions as
|
||||||
path selectors.
|
path selectors.
|
||||||
|
|
||||||
|
## Editing
|
||||||
|
|
||||||
|
`edit` opens one file using the editor configured by `current-coreutils-editor`.
|
||||||
|
The default editor is RackEdit's `rkdt` procedure.
|
||||||
|
|
||||||
|
```text
|
||||||
|
edit notes.rkt
|
||||||
|
edit --wait notes.rkt
|
||||||
|
```
|
||||||
|
|
||||||
|
`--wait` is passed to RackEdit as `#:wait? #t`. In ordinary Racket code a
|
||||||
|
different editor can be selected dynamically by parameterizing
|
||||||
|
`current-coreutils-editor` with a procedure that accepts a file path and the
|
||||||
|
`#:wait?` keyword.
|
||||||
|
|
||||||
|
## Command timing
|
||||||
|
|
||||||
|
`time` executes one rash-coreutils command or executable and writes timing data
|
||||||
|
to standard error. Command output therefore remains suitable for pipelines and
|
||||||
|
redirection. The portable measurements are elapsed (`real`) time, combined CPU
|
||||||
|
time (`cpu`) for Racket and completed subprocesses, and Racket garbage-collection
|
||||||
|
time (`gc`).
|
||||||
|
|
||||||
|
```text
|
||||||
|
time head -n 100 large.txt
|
||||||
|
time raco test tests/coreutils.rkt
|
||||||
|
```
|
||||||
|
|
||||||
## Racket tooling
|
## Racket tooling
|
||||||
|
|
||||||
`rash-coreutils` also exposes the `raco` command from the active Racket installation, without requiring `raco` to be on PATH.
|
`rash-coreutils` also exposes the `raco` command from the active Racket
|
||||||
|
installation, without requiring `raco` to be on PATH.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
raco setup rash-coreutils
|
raco setup rash-coreutils
|
||||||
raco pkg show
|
raco pkg show
|
||||||
```
|
```
|
||||||
|
|
||||||
In Racket expression mode, use `coreutils-raco`, for example `(coreutils-raco '(setup rash-coreutils))`. The name `raco` is reserved for the Rash command binding.
|
In Racket expression mode, use `coreutils-raco`, for example
|
||||||
|
`(coreutils-raco '(setup rash-coreutils))`. The name `raco` is reserved for the
|
||||||
|
Rash command binding.
|
||||||
|
|
||||||
## Windows paths
|
## Windows paths
|
||||||
|
|
||||||
Commands that print paths use forward slashes on Windows. This keeps their textual output directly reusable in Rash line mode, where a backslash is an escape character. Internally, Racket path values remain native paths.
|
Commands that print paths use forward slashes on Windows. This keeps their
|
||||||
|
textual output directly reusable in Rash line mode, where a backslash is an
|
||||||
|
escape character. Internally, Racket path values remain native paths.
|
||||||
|
|
||||||
```text
|
```text
|
||||||
pwd
|
pwd
|
||||||
@@ -35,4 +67,5 @@ mktemp
|
|||||||
C:/Users/hans/AppData/Local/Temp/tmp123
|
C:/Users/hans/AppData/Local/Temp/tmp123
|
||||||
```
|
```
|
||||||
|
|
||||||
A native Racket path value can still be passed directly, for example `ls (values (find-system-path 'temp-dir))`.
|
A native Racket path value can still be passed directly, for example
|
||||||
|
`ls (values (find-system-path 'temp-dir))`.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#lang info
|
#lang info
|
||||||
|
|
||||||
(define pkg-authors '(hnmdijkema))
|
(define pkg-authors '(hnmdijkema))
|
||||||
(define version "0.1.6")
|
(define version "0.2.14")
|
||||||
(define license 'MIT)
|
(define license 'MIT)
|
||||||
(define collection "rash-coreutils")
|
(define collection "rash-coreutils")
|
||||||
(define pkg-desc
|
(define pkg-desc
|
||||||
@@ -11,10 +11,10 @@
|
|||||||
'(("scrbl/rash-coreutils.scrbl" () (library 0))))
|
'(("scrbl/rash-coreutils.scrbl" () (library 0))))
|
||||||
|
|
||||||
(define deps
|
(define deps
|
||||||
'("base" "rash"))
|
'("base" "rash" "gregor-lib" "rackedit"))
|
||||||
|
|
||||||
(define build-deps
|
(define build-deps
|
||||||
'("racket-doc"
|
'("racket-doc"
|
||||||
"rackunit-lib"
|
"rackunit-lib"
|
||||||
"scribble-lib"
|
"scribble-lib"
|
||||||
"rash"))
|
"linea"))
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
#lang info
|
#lang info
|
||||||
|
|
||||||
(define pkg-authors '(hnmdijkema))
|
(define pkg-authors '(hnmdijkema))
|
||||||
(define version "0.2.9")
|
(define version "0.2.14")
|
||||||
(define license 'MIT)
|
(define license 'MIT)
|
||||||
(define collection "rash-coreutils")
|
(define collection "rash-coreutils")
|
||||||
(define pkg-desc
|
(define pkg-desc
|
||||||
@@ -11,7 +11,7 @@
|
|||||||
'(("scrbl/rash-coreutils.scrbl" () (library 0))))
|
'(("scrbl/rash-coreutils.scrbl" () (library 0))))
|
||||||
|
|
||||||
(define deps
|
(define deps
|
||||||
'("base" "rash" "gregor-lib"))
|
'("base" "rash" "gregor-lib" "rackedit"))
|
||||||
|
|
||||||
(define build-deps
|
(define build-deps
|
||||||
'("racket-doc"
|
'("racket-doc"
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"private/extended.rkt"
|
"private/extended.rkt"
|
||||||
"private/commands.rkt"
|
"private/commands.rkt"
|
||||||
"private/racket-tools.rkt"
|
"private/racket-tools.rkt"
|
||||||
|
"private/editor.rkt"
|
||||||
"private/dispatcher.rkt"
|
"private/dispatcher.rkt"
|
||||||
"private/help.rkt"
|
"private/help.rkt"
|
||||||
"private/aliases.rkt")
|
"private/aliases.rkt")
|
||||||
@@ -12,6 +13,7 @@
|
|||||||
(all-from-out "private/extended.rkt")
|
(all-from-out "private/extended.rkt")
|
||||||
(all-from-out "private/commands.rkt")
|
(all-from-out "private/commands.rkt")
|
||||||
(all-from-out "private/racket-tools.rkt")
|
(all-from-out "private/racket-tools.rkt")
|
||||||
|
(all-from-out "private/editor.rkt")
|
||||||
(all-from-out "private/dispatcher.rkt")
|
(all-from-out "private/dispatcher.rkt")
|
||||||
(all-from-out "private/help.rkt")
|
(all-from-out "private/help.rkt")
|
||||||
(all-from-out "private/aliases.rkt"))
|
(all-from-out "private/aliases.rkt"))
|
||||||
|
|||||||
+5
-1
@@ -9,7 +9,7 @@
|
|||||||
|
|
||||||
(provide pwd ls mkdir rmdir rm cp mv cat touch echo which
|
(provide pwd ls mkdir rmdir rm cp mv cat touch echo which
|
||||||
head tail wc sort uniq cut tee tr basename dirname realpath readlink
|
head tail wc sort uniq cut tee tr basename dirname realpath readlink
|
||||||
stat du df mktemp printenv env date help raco)
|
stat du df mktemp printenv env date time edit help raco)
|
||||||
|
|
||||||
(define (make-command-procedure name)
|
(define (make-command-procedure name)
|
||||||
(λ args
|
(λ args
|
||||||
@@ -45,6 +45,8 @@
|
|||||||
(define printenv-command (make-command-procedure 'printenv))
|
(define printenv-command (make-command-procedure 'printenv))
|
||||||
(define env-command (make-command-procedure 'env))
|
(define env-command (make-command-procedure 'env))
|
||||||
(define date-command (make-command-procedure 'date))
|
(define date-command (make-command-procedure 'date))
|
||||||
|
(define time-command (make-command-procedure 'time))
|
||||||
|
(define edit-command (make-command-procedure 'edit))
|
||||||
(define raco-command coreutils-raco)
|
(define raco-command coreutils-raco)
|
||||||
|
|
||||||
(define (help-command . args)
|
(define (help-command . args)
|
||||||
@@ -131,5 +133,7 @@
|
|||||||
(define-pipeline-alias printenv (make-coreutils-alias #'printenv-command))
|
(define-pipeline-alias printenv (make-coreutils-alias #'printenv-command))
|
||||||
(define-pipeline-alias env (make-env-alias #'env-command))
|
(define-pipeline-alias env (make-env-alias #'env-command))
|
||||||
(define-pipeline-alias date (make-coreutils-alias #'date-command))
|
(define-pipeline-alias date (make-coreutils-alias #'date-command))
|
||||||
|
(define-pipeline-alias time (make-coreutils-alias #'time-command))
|
||||||
|
(define-pipeline-alias edit (make-coreutils-alias #'edit-command))
|
||||||
(define-pipeline-alias help (make-coreutils-alias #'help-command))
|
(define-pipeline-alias help (make-coreutils-alias #'help-command))
|
||||||
(define-pipeline-alias raco (make-coreutils-alias #'raco-command))
|
(define-pipeline-alias raco (make-coreutils-alias #'raco-command))
|
||||||
|
|||||||
@@ -2,7 +2,8 @@
|
|||||||
|
|
||||||
(require "coreutils.rkt"
|
(require "coreutils.rkt"
|
||||||
"extended.rkt"
|
"extended.rkt"
|
||||||
"racket-tools.rkt")
|
"racket-tools.rkt"
|
||||||
|
"editor.rkt")
|
||||||
|
|
||||||
(provide (struct-out coreutils-command)
|
(provide (struct-out coreutils-command)
|
||||||
coreutils-commands
|
coreutils-commands
|
||||||
@@ -42,6 +43,8 @@
|
|||||||
(coreutils-command 'printenv coreutils-printenv 'rash-coreutils-printenv)
|
(coreutils-command 'printenv coreutils-printenv 'rash-coreutils-printenv)
|
||||||
(coreutils-command 'env coreutils-env 'rash-coreutils-env)
|
(coreutils-command 'env coreutils-env 'rash-coreutils-env)
|
||||||
(coreutils-command 'date coreutils-date 'rash-coreutils-date)
|
(coreutils-command 'date coreutils-date 'rash-coreutils-date)
|
||||||
|
(coreutils-command 'time coreutils-time 'rash-coreutils-time)
|
||||||
|
(coreutils-command 'edit coreutils-edit 'rash-coreutils-edit)
|
||||||
(coreutils-command 'raco coreutils-raco 'rash-coreutils-raco)))
|
(coreutils-command 'raco coreutils-raco 'rash-coreutils-raco)))
|
||||||
|
|
||||||
(define (find-coreutils-command name)
|
(define (find-coreutils-command name)
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
#lang racket/base
|
||||||
|
|
||||||
|
(require racket/format
|
||||||
|
racket/path
|
||||||
|
racket/string
|
||||||
|
rackedit)
|
||||||
|
|
||||||
|
(provide coreutils-edit
|
||||||
|
current-coreutils-editor)
|
||||||
|
|
||||||
|
(define current-coreutils-editor
|
||||||
|
(make-parameter
|
||||||
|
rkdt
|
||||||
|
(λ (editor)
|
||||||
|
(unless (procedure? editor)
|
||||||
|
(raise-argument-error 'current-coreutils-editor "procedure?" editor))
|
||||||
|
editor)))
|
||||||
|
|
||||||
|
(define (arg->path arg)
|
||||||
|
(cond
|
||||||
|
[(path? arg) arg]
|
||||||
|
[(string? arg) (string->path arg)]
|
||||||
|
[(symbol? arg) (string->path (symbol->string arg))]
|
||||||
|
[else
|
||||||
|
(raise-argument-error
|
||||||
|
'edit
|
||||||
|
"path, string, or symbol"
|
||||||
|
arg)]))
|
||||||
|
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
; goal : Open one file in the editor configured for rash-coreutils.
|
||||||
|
; pre : args contains exactly one filename and optionally --wait.
|
||||||
|
; post : The configured editor has been invoked once. With --wait, the
|
||||||
|
; command does not return until the editor procedure returns.
|
||||||
|
; result : (void).
|
||||||
|
; internals:
|
||||||
|
; current-coreutils-editor defaults to rackedit's rkdt procedure.
|
||||||
|
; The --wait flag is translated to rkdt's #:wait? keyword.
|
||||||
|
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||||
|
(define (coreutils-edit . args)
|
||||||
|
(define wait? #f)
|
||||||
|
(define reversed-paths '())
|
||||||
|
|
||||||
|
(for ([arg (in-list args)])
|
||||||
|
(define text (~a arg))
|
||||||
|
(cond
|
||||||
|
[(string=? text "--wait")
|
||||||
|
(set! wait? #t)]
|
||||||
|
[(string-prefix? text "-")
|
||||||
|
(raise-arguments-error 'edit
|
||||||
|
"unsupported option"
|
||||||
|
"option" text)]
|
||||||
|
[else
|
||||||
|
(set! reversed-paths (cons (arg->path arg) reversed-paths))]))
|
||||||
|
|
||||||
|
(define paths (reverse reversed-paths))
|
||||||
|
(unless (or (= (length paths) 1) (= (length paths) 0))
|
||||||
|
(raise-arguments-error 'edit
|
||||||
|
"expected exactly zero or one file"
|
||||||
|
"arguments" args))
|
||||||
|
|
||||||
|
(if (null? paths)
|
||||||
|
((current-coreutils-editor) #:wait? wait?)
|
||||||
|
((current-coreutils-editor) (car paths) #:wait? wait?))
|
||||||
|
|
||||||
|
(void))
|
||||||
+827
-133
File diff suppressed because it is too large
Load Diff
+552
-103
@@ -1,6 +1,7 @@
|
|||||||
#lang scribble/manual
|
#lang scribble/manual
|
||||||
|
|
||||||
@(require (for-label racket/base
|
@(require (for-label racket/base
|
||||||
|
(only-in rackedit rkdt)
|
||||||
(only-in rash-coreutils
|
(only-in rash-coreutils
|
||||||
coreutils-pwd coreutils-ls coreutils-mkdir coreutils-rmdir
|
coreutils-pwd coreutils-ls coreutils-mkdir coreutils-rmdir
|
||||||
coreutils-rm coreutils-cp coreutils-mv coreutils-cat
|
coreutils-rm coreutils-cp coreutils-mv coreutils-cat
|
||||||
@@ -10,140 +11,506 @@
|
|||||||
coreutils-dirname coreutils-realpath coreutils-readlink
|
coreutils-dirname coreutils-realpath coreutils-readlink
|
||||||
coreutils-stat coreutils-du coreutils-df coreutils-mktemp
|
coreutils-stat coreutils-du coreutils-df coreutils-mktemp
|
||||||
coreutils-printenv coreutils-env coreutils-date
|
coreutils-printenv coreutils-env coreutils-date
|
||||||
raco
|
coreutils-time coreutils-edit current-coreutils-editor
|
||||||
coreutils-raco)))
|
coreutils-help dispatch-coreutils-command
|
||||||
|
raco coreutils-raco)))
|
||||||
|
|
||||||
@title{rash-coreutils}
|
@title{rash-coreutils}
|
||||||
@author["Hans Dijkema / hans@dijkewijk.nl"]
|
@author["Hans Dijkema / hans@dijkewijk.nl"]
|
||||||
|
|
||||||
@defmodule[rash-coreutils]
|
@defmodule[rash-coreutils]
|
||||||
|
|
||||||
@bold{rash-coreutils} provides platform-independent Unix-style shell commands
|
@bold{rash-coreutils} provides platform-independent Unix-style commands for
|
||||||
for Rash. The commands are implemented in Racket, so common shell utilities do
|
Rash. The user-facing API is the set of commands that can be written directly
|
||||||
not depend on @tt{cmd.exe}, PowerShell, or Unix executables being installed.
|
in Rash line mode. Most commands are implemented by ordinary Racket procedures,
|
||||||
|
so they do not depend on @tt{cmd.exe}, PowerShell, or Unix utility executables.
|
||||||
|
|
||||||
Rash remains responsible for shell syntax such as pipelines, globbing, tilde
|
Rash remains responsible for shell syntax such as pipelines, globbing, tilde
|
||||||
expansion, variable expansion, and redirection. @bold{rash-coreutils} supplies
|
expansion, variable expansion, and redirection. The command aliases provided by
|
||||||
the commands themselves plus Racket-specific additions such as regular-expression
|
this package preserve those Rash semantics and then dispatch to the internal
|
||||||
path selectors.
|
Racket implementation.
|
||||||
|
|
||||||
@section{Commands}
|
@section{How a Rash command is executed}
|
||||||
|
|
||||||
The package provides @tt{pwd}, @tt{ls}, @tt{mkdir}, @tt{rmdir}, @tt{rm},
|
For a normal command such as
|
||||||
@tt{cp}, @tt{mv}, @tt{cat}, @tt{touch}, @tt{echo}, @tt{which}, @tt{head},
|
|
||||||
@tt{tail}, @tt{wc}, @tt{sort}, @tt{uniq}, @tt{cut}, @tt{tee}, @tt{tr},
|
@verbatim{
|
||||||
@tt{basename}, @tt{dirname}, @tt{realpath}, @tt{readlink}, @tt{stat}, @tt{du},
|
ls -l *.rkt
|
||||||
@tt{df}, @tt{mktemp}, @tt{printenv}, @tt{env}, @tt{date}, @tt{raco}, and @tt{help}.
|
}
|
||||||
|
|
||||||
|
Rash first processes its line syntax and pipeline semantics. The @tt{ls} alias
|
||||||
|
then calls @racket[dispatch-coreutils-command], which looks up the registered
|
||||||
|
command and invokes @racket[coreutils-ls]. Arguments that are Racket regular
|
||||||
|
expressions are expanded by the rash-coreutils dispatcher before the internal
|
||||||
|
procedure is called.
|
||||||
|
|
||||||
|
This means the shell command is the primary interface, while procedures such as
|
||||||
|
@racket[coreutils-ls] are the implementation API and are also available to
|
||||||
|
ordinary Racket code.
|
||||||
|
|
||||||
The option set is deliberately smaller than GNU coreutils. Unsupported options
|
The option set is deliberately smaller than GNU coreutils. Unsupported options
|
||||||
raise an error instead of silently approximating another command's behavior.
|
raise an error instead of silently approximating behavior that is not
|
||||||
|
implemented.
|
||||||
|
|
||||||
@section{Racket procedures}
|
@section{Filesystem commands}
|
||||||
|
|
||||||
Each shell command has an explicitly named Racket procedure. All procedures
|
|
||||||
write to the normal current ports so Rash pipeline and redirection semantics
|
|
||||||
continue to work.
|
|
||||||
|
|
||||||
@index["rash-coreutils-pwd"]
|
@index["rash-coreutils-pwd"]
|
||||||
@defproc[(coreutils-pwd) void?]{Writes the current directory.}
|
@subsection{@tt{pwd}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
pwd
|
||||||
|
}
|
||||||
|
|
||||||
|
Writes the current directory. The command accepts no arguments.
|
||||||
|
|
||||||
|
Internally, @tt{pwd} dispatches to @racket[coreutils-pwd].
|
||||||
|
|
||||||
|
@defproc[(coreutils-pwd) void?]{Writes @racket[current-directory] using a Rash-friendly textual path.}
|
||||||
|
|
||||||
@index["rash-coreutils-ls"]
|
@index["rash-coreutils-ls"]
|
||||||
@defproc[(coreutils-ls [arg any/c] ...) void?]{Lists paths. Supports @tt{-a}, @tt{-l}, and their long forms.}
|
@subsection{@tt{ls}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
ls
|
||||||
|
ls -l
|
||||||
|
ls -a
|
||||||
|
ls -la
|
||||||
|
ls *.rkt
|
||||||
|
ls #px"^info[0-9]+[.]rkt$"
|
||||||
|
}
|
||||||
|
|
||||||
|
Lists files and directories. @tt{-a}/@tt{--all} includes hidden names and
|
||||||
|
@tt{-l}/@tt{--long} writes the portable long form used by this package. Rash
|
||||||
|
performs normal glob expansion. A Racket regular-expression value is an
|
||||||
|
additional rash-coreutils path selector and is matched against names in the
|
||||||
|
current directory.
|
||||||
|
|
||||||
|
Internally, @tt{ls} dispatches to @racket[coreutils-ls].
|
||||||
|
|
||||||
|
@defproc[(coreutils-ls [arg any/c] ...) void?]{Implements the listing after Rash and dispatcher argument handling.}
|
||||||
|
|
||||||
@index["rash-coreutils-mkdir"]
|
@index["rash-coreutils-mkdir"]
|
||||||
@defproc[(coreutils-mkdir [arg any/c] ...) void?]{Creates directories. Supports @tt{-p}.}
|
@subsection{@tt{mkdir}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
mkdir work
|
||||||
|
mkdir -p build/output
|
||||||
|
}
|
||||||
|
|
||||||
|
Creates one or more directories. @tt{-p}/@tt{--parents} creates missing parent
|
||||||
|
directories.
|
||||||
|
|
||||||
|
Internally, @tt{mkdir} dispatches to @racket[coreutils-mkdir].
|
||||||
|
|
||||||
|
@defproc[(coreutils-mkdir [arg any/c] ...) void?]{Creates the requested directories.}
|
||||||
|
|
||||||
@index["rash-coreutils-rmdir"]
|
@index["rash-coreutils-rmdir"]
|
||||||
|
@subsection{@tt{rmdir}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
rmdir empty-directory
|
||||||
|
}
|
||||||
|
|
||||||
|
Removes one or more empty directories. No options are currently supported.
|
||||||
|
|
||||||
|
Internally, @tt{rmdir} dispatches to @racket[coreutils-rmdir].
|
||||||
|
|
||||||
@defproc[(coreutils-rmdir [arg any/c] ...) void?]{Removes empty directories.}
|
@defproc[(coreutils-rmdir [arg any/c] ...) void?]{Removes empty directories.}
|
||||||
|
|
||||||
@index["rash-coreutils-rm"]
|
@index["rash-coreutils-rm"]
|
||||||
@defproc[(coreutils-rm [arg any/c] ...) void?]{Removes paths. Supports recursive and force options.}
|
@subsection{@tt{rm}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
rm file.txt
|
||||||
|
rm -r directory
|
||||||
|
rm -rf generated
|
||||||
|
}
|
||||||
|
|
||||||
|
Removes files or links. Directories require @tt{-r}, @tt{-R}, or
|
||||||
|
@tt{--recursive}. @tt{-f}/@tt{--force} suppresses an error for a missing path.
|
||||||
|
The combined forms @tt{-rf}, @tt{-fr}, @tt{-Rf}, and @tt{-fR} are supported.
|
||||||
|
|
||||||
|
Internally, @tt{rm} dispatches to @racket[coreutils-rm].
|
||||||
|
|
||||||
|
@defproc[(coreutils-rm [arg any/c] ...) void?]{Removes the requested paths.}
|
||||||
|
|
||||||
@index["rash-coreutils-cp"]
|
@index["rash-coreutils-cp"]
|
||||||
@defproc[(coreutils-cp [arg any/c] ...) void?]{Copies a source to a destination. Directory copies require @tt{-r}.}
|
@subsection{@tt{cp}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
cp source.txt copy.txt
|
||||||
|
cp -r source-directory copy-directory
|
||||||
|
}
|
||||||
|
|
||||||
|
Copies exactly one source to one destination. Directory copies require
|
||||||
|
@tt{-r}, @tt{-R}, or @tt{--recursive}.
|
||||||
|
|
||||||
|
Internally, @tt{cp} dispatches to @racket[coreutils-cp].
|
||||||
|
|
||||||
|
@defproc[(coreutils-cp [arg any/c] ...) void?]{Copies a file or, in recursive mode, a directory tree.}
|
||||||
|
|
||||||
@index["rash-coreutils-mv"]
|
@index["rash-coreutils-mv"]
|
||||||
|
@subsection{@tt{mv}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
mv old-name.txt new-name.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
Moves or renames exactly one source path to one destination path.
|
||||||
|
|
||||||
|
Internally, @tt{mv} dispatches to @racket[coreutils-mv].
|
||||||
|
|
||||||
@defproc[(coreutils-mv [source any/c] [destination any/c]) void?]{Moves or renames a path.}
|
@defproc[(coreutils-mv [source any/c] [destination any/c]) void?]{Moves or renames a path.}
|
||||||
|
|
||||||
@index["rash-coreutils-cat"]
|
|
||||||
@defproc[(coreutils-cat [arg any/c] ...) void?]{Copies files, or standard input, to standard output.}
|
|
||||||
|
|
||||||
@index["rash-coreutils-touch"]
|
@index["rash-coreutils-touch"]
|
||||||
@defproc[(coreutils-touch [arg any/c] ...) void?]{Updates modification times or creates empty files.}
|
@subsection{@tt{touch}}
|
||||||
|
|
||||||
@index["rash-coreutils-echo"]
|
@verbatim{
|
||||||
@defproc[(coreutils-echo [arg any/c] ...) void?]{Writes arguments separated by spaces.}
|
touch notes.txt
|
||||||
|
}
|
||||||
|
|
||||||
@index["rash-coreutils-which"]
|
Creates missing files and updates the modification time of existing files. No
|
||||||
@defproc[(coreutils-which [arg any/c] ...) void?]{Finds executables using Racket's executable search.}
|
options are currently supported.
|
||||||
|
|
||||||
@index["rash-coreutils-head"]
|
Internally, @tt{touch} dispatches to @racket[coreutils-touch].
|
||||||
@defproc[(coreutils-head [arg any/c] ...) void?]{Writes the first lines of input. Supports @tt{-n}.}
|
|
||||||
|
|
||||||
@index["rash-coreutils-tail"]
|
@defproc[(coreutils-touch [arg any/c] ...) void?]{Touches one or more files.}
|
||||||
@defproc[(coreutils-tail [arg any/c] ...) void?]{Writes the last lines of input. Supports @tt{-n}.}
|
|
||||||
|
|
||||||
@index["rash-coreutils-wc"]
|
|
||||||
@defproc[(coreutils-wc [arg any/c] ...) void?]{Counts lines, words, and bytes. Supports @tt{-l}, @tt{-w}, and @tt{-c}.}
|
|
||||||
|
|
||||||
@index["rash-coreutils-sort"]
|
|
||||||
@defproc[(coreutils-sort [arg any/c] ...) void?]{Sorts lines. Supports @tt{-r} and @tt{-n}.}
|
|
||||||
|
|
||||||
@index["rash-coreutils-uniq"]
|
|
||||||
@defproc[(coreutils-uniq [arg any/c] ...) void?]{Collapses adjacent duplicate lines. Supports @tt{-c}.}
|
|
||||||
|
|
||||||
@index["rash-coreutils-cut"]
|
|
||||||
@defproc[(coreutils-cut [arg any/c] ...) void?]{Selects one delimited field with @tt{-d} and @tt{-f}.}
|
|
||||||
|
|
||||||
@index["rash-coreutils-tee"]
|
|
||||||
@defproc[(coreutils-tee [arg any/c] ...) void?]{Copies standard input to standard output and files. Supports @tt{-a}.}
|
|
||||||
|
|
||||||
@index["rash-coreutils-tr"]
|
|
||||||
@defproc[(coreutils-tr [arg any/c] ...) void?]{Translates characters from standard input. Character ranges such as @tt{a-z}, @tt{A-Z}, and @tt{0-9} are expanded. @tt{-d} deletes characters and @tt{-s} squeezes repeated characters.}
|
|
||||||
|
|
||||||
@index["rash-coreutils-basename"]
|
@index["rash-coreutils-basename"]
|
||||||
|
@subsection{@tt{basename}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
basename a/b/file.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
Writes the final component of one path.
|
||||||
|
|
||||||
|
Internally, @tt{basename} dispatches to @racket[coreutils-basename].
|
||||||
|
|
||||||
@defproc[(coreutils-basename [path any/c]) void?]{Writes the final path component.}
|
@defproc[(coreutils-basename [path any/c]) void?]{Writes the final path component.}
|
||||||
|
|
||||||
@index["rash-coreutils-dirname"]
|
@index["rash-coreutils-dirname"]
|
||||||
|
@subsection{@tt{dirname}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
dirname a/b/file.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
Writes the directory portion of one path.
|
||||||
|
|
||||||
|
Internally, @tt{dirname} dispatches to @racket[coreutils-dirname].
|
||||||
|
|
||||||
@defproc[(coreutils-dirname [path any/c]) void?]{Writes the directory portion of a path.}
|
@defproc[(coreutils-dirname [path any/c]) void?]{Writes the directory portion of a path.}
|
||||||
|
|
||||||
@index["rash-coreutils-realpath"]
|
@index["rash-coreutils-realpath"]
|
||||||
@defproc[(coreutils-realpath [path any/c]) void?]{Writes a complete simplified path.}
|
@subsection{@tt{realpath}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
realpath relative/path
|
||||||
|
}
|
||||||
|
|
||||||
|
Writes a complete simplified path with filesystem links resolved where the
|
||||||
|
current filesystem supports that resolution.
|
||||||
|
|
||||||
|
Internally, @tt{realpath} dispatches to @racket[coreutils-realpath].
|
||||||
|
|
||||||
|
@defproc[(coreutils-realpath [path any/c]) void?]{Writes the complete simplified path.}
|
||||||
|
|
||||||
@index["rash-coreutils-readlink"]
|
@index["rash-coreutils-readlink"]
|
||||||
@defproc[(coreutils-readlink [path any/c]) void?]{Resolves a symbolic link.}
|
@subsection{@tt{readlink}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
readlink link-name
|
||||||
|
}
|
||||||
|
|
||||||
|
Writes the target of one symbolic link and reports an error when the supplied
|
||||||
|
path is not a link.
|
||||||
|
|
||||||
|
Internally, @tt{readlink} dispatches to @racket[coreutils-readlink].
|
||||||
|
|
||||||
|
@defproc[(coreutils-readlink [path any/c]) void?]{Writes the resolved symbolic-link target.}
|
||||||
|
|
||||||
@index["rash-coreutils-stat"]
|
@index["rash-coreutils-stat"]
|
||||||
|
@subsection{@tt{stat}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
stat file.txt
|
||||||
|
stat file.txt directory
|
||||||
|
}
|
||||||
|
|
||||||
|
Writes portable type, size, and modification information for one or more paths.
|
||||||
|
|
||||||
|
Internally, @tt{stat} dispatches to @racket[coreutils-stat].
|
||||||
|
|
||||||
@defproc[(coreutils-stat [path any/c] ...) void?]{Writes basic portable path metadata.}
|
@defproc[(coreutils-stat [path any/c] ...) void?]{Writes basic portable path metadata.}
|
||||||
|
|
||||||
@index["rash-coreutils-du"]
|
@index["rash-coreutils-du"]
|
||||||
@defproc[(coreutils-du [arg any/c] ...) void?]{Calculates recursive file size. Supports @tt{-h}.}
|
@subsection{@tt{du}}
|
||||||
|
|
||||||
@index["rash-coreutils-df"]
|
|
||||||
@defproc[(coreutils-df) void?]{Lists filesystem roots. Racket has no portable API for total and free filesystem capacity, so this initial implementation deliberately does not invent platform-specific subprocess fallbacks.}
|
|
||||||
|
|
||||||
@index["rash-coreutils-mktemp"]
|
|
||||||
@defproc[(coreutils-mktemp [arg any/c] ...) void?]{Creates a temporary file, or a directory with @tt{-d}, and writes its path.}
|
|
||||||
|
|
||||||
@index["rash-coreutils-printenv"]
|
|
||||||
@defproc[(coreutils-printenv [name any/c] ...) void?]{Writes environment variables.}
|
|
||||||
|
|
||||||
@index["rash-coreutils-env"]
|
|
||||||
@defproc[(coreutils-env [arg any/c] ...) void?]{Creates a copied environment, applies leading @tt{NAME=value} assignments, and either writes that environment or runs the remaining command in it. Racket values are converted to environment strings.}
|
|
||||||
|
|
||||||
The Rash form can mix shell-style assignment names with Racket expressions:
|
|
||||||
|
|
||||||
@verbatim{
|
@verbatim{
|
||||||
|
du
|
||||||
|
du -h directory
|
||||||
|
}
|
||||||
|
|
||||||
|
Writes the accumulated file size of each requested path. With no path, the
|
||||||
|
current directory is used. @tt{-h}/@tt{--human-readable} formats the size using
|
||||||
|
binary K, M, and G units. Symbolic links are not followed.
|
||||||
|
|
||||||
|
Internally, @tt{du} dispatches to @racket[coreutils-du].
|
||||||
|
|
||||||
|
@defproc[(coreutils-du [arg any/c] ...) void?]{Calculates recursive file size.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-df"]
|
||||||
|
@subsection{@tt{df}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
df
|
||||||
|
}
|
||||||
|
|
||||||
|
Lists filesystem roots visible to Racket. This portable implementation does not
|
||||||
|
invent platform-specific subprocess fallbacks for total and free capacity.
|
||||||
|
|
||||||
|
Internally, @tt{df} dispatches to @racket[coreutils-df].
|
||||||
|
|
||||||
|
@defproc[(coreutils-df) void?]{Lists filesystem roots.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-mktemp"]
|
||||||
|
@subsection{@tt{mktemp}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
mktemp
|
||||||
|
mktemp -d
|
||||||
|
mktemp "rash-coreutils-~a"
|
||||||
|
}
|
||||||
|
|
||||||
|
Creates a unique temporary file and writes its path. @tt{-d}/@tt{--directory}
|
||||||
|
creates a directory instead. An optional non-option argument is used as the
|
||||||
|
Racket temporary-file template.
|
||||||
|
|
||||||
|
Internally, @tt{mktemp} dispatches to @racket[coreutils-mktemp].
|
||||||
|
|
||||||
|
@defproc[(coreutils-mktemp [arg any/c] ...) void?]{Creates a temporary file or directory and writes its path.}
|
||||||
|
|
||||||
|
@section{Text and pipeline commands}
|
||||||
|
|
||||||
|
@index["rash-coreutils-cat"]
|
||||||
|
@subsection{@tt{cat}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
cat file.txt
|
||||||
|
cat first.txt second.txt
|
||||||
|
producer | cat
|
||||||
|
}
|
||||||
|
|
||||||
|
Copies files to standard output. With no file arguments, it copies standard
|
||||||
|
input. This makes the implementation usable in Rash pipelines without special
|
||||||
|
pipeline code.
|
||||||
|
|
||||||
|
Internally, @tt{cat} dispatches to @racket[coreutils-cat].
|
||||||
|
|
||||||
|
@defproc[(coreutils-cat [arg any/c] ...) void?]{Copies file or standard-input bytes to the current output port.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-echo"]
|
||||||
|
@subsection{@tt{echo}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
echo hello world
|
||||||
|
}
|
||||||
|
|
||||||
|
Writes its arguments separated by spaces and terminates the result with a
|
||||||
|
newline.
|
||||||
|
|
||||||
|
Internally, @tt{echo} dispatches to @racket[coreutils-echo].
|
||||||
|
|
||||||
|
@defproc[(coreutils-echo [arg any/c] ...) void?]{Writes the textual representation of the arguments.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-head"]
|
||||||
|
@subsection{@tt{head}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
head file.txt
|
||||||
|
head -n 20 file.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
Writes the first lines of files or standard input. @tt{-n}/@tt{--lines}
|
||||||
|
selects a non-negative line count; the default is 10. Input is scanned in fixed
|
||||||
|
size byte blocks so memory use does not grow with file size or an individual
|
||||||
|
line length.
|
||||||
|
|
||||||
|
Internally, @tt{head} dispatches to @racket[coreutils-head].
|
||||||
|
|
||||||
|
@defproc[(coreutils-head [arg any/c] ...) void?]{Writes the requested first lines.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-tail"]
|
||||||
|
@subsection{@tt{tail}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
tail file.txt
|
||||||
|
tail -n 20 file.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
Writes the last lines of files or standard input. @tt{-n}/@tt{--lines} selects
|
||||||
|
a non-negative line count; the default is 10. Seekable files are scanned
|
||||||
|
backwards in fixed-size byte blocks. Input that cannot be scanned backwards is
|
||||||
|
spooled to a temporary file, keeping memory use independent of input size and
|
||||||
|
line length.
|
||||||
|
|
||||||
|
Internally, @tt{tail} dispatches to @racket[coreutils-tail].
|
||||||
|
|
||||||
|
@defproc[(coreutils-tail [arg any/c] ...) void?]{Writes the requested last lines.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-wc"]
|
||||||
|
@subsection{@tt{wc}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
wc file.txt
|
||||||
|
wc -l file.txt
|
||||||
|
wc -w file.txt
|
||||||
|
wc -c file.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
Counts lines, words, and bytes. @tt{-l} selects lines, @tt{-w} words, and
|
||||||
|
@tt{-c}/@tt{--bytes} bytes. With no count options all three values are written.
|
||||||
|
|
||||||
|
Internally, @tt{wc} dispatches to @racket[coreutils-wc].
|
||||||
|
|
||||||
|
@defproc[(coreutils-wc [arg any/c] ...) void?]{Counts input using bounded streaming buffers.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-sort"]
|
||||||
|
@subsection{@tt{sort}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
sort file.txt
|
||||||
|
sort -r file.txt
|
||||||
|
sort -n numbers.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
Sorts input lines. @tt{-r}/@tt{--reverse} reverses the ordering and
|
||||||
|
@tt{-n}/@tt{--numeric-sort} uses numeric keys. Large input is divided into
|
||||||
|
bounded in-memory runs and merged through temporary files.
|
||||||
|
|
||||||
|
Internally, @tt{sort} dispatches to @racket[coreutils-sort].
|
||||||
|
|
||||||
|
@defproc[(coreutils-sort [arg any/c] ...) void?]{Sorts lines without retaining the complete input in memory.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-uniq"]
|
||||||
|
@subsection{@tt{uniq}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
uniq sorted.txt
|
||||||
|
uniq -c sorted.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
Collapses adjacent duplicate lines. @tt{-c}/@tt{--count} prefixes each output
|
||||||
|
line with its run count. As with the Unix command, input should already be
|
||||||
|
grouped when non-adjacent equal lines must also be collapsed.
|
||||||
|
|
||||||
|
Internally, @tt{uniq} dispatches to @racket[coreutils-uniq].
|
||||||
|
|
||||||
|
@defproc[(coreutils-uniq [arg any/c] ...) void?]{Collapses adjacent duplicate lines.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-cut"]
|
||||||
|
@subsection{@tt{cut}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
cut -f 2 data.txt
|
||||||
|
cut -d ";" -f 3 data.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
Writes one delimited field from each input line. @tt{-f}/@tt{--fields} is
|
||||||
|
required and currently accepts one positive field number. @tt{-d}/@tt{--delimiter}
|
||||||
|
changes the delimiter from the default tab.
|
||||||
|
|
||||||
|
Internally, @tt{cut} dispatches to @racket[coreutils-cut].
|
||||||
|
|
||||||
|
@defproc[(coreutils-cut [arg any/c] ...) void?]{Selects one field from each input line.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-tee"]
|
||||||
|
@subsection{@tt{tee}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
producer | tee output.txt
|
||||||
|
producer | tee -a output.txt
|
||||||
|
}
|
||||||
|
|
||||||
|
Copies standard input to standard output and to each named file.
|
||||||
|
@tt{-a}/@tt{--append} appends instead of replacing existing file content.
|
||||||
|
|
||||||
|
Internally, @tt{tee} dispatches to @racket[coreutils-tee].
|
||||||
|
|
||||||
|
@defproc[(coreutils-tee [arg any/c] ...) void?]{Copies the current input stream to the current output and files.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-tr"]
|
||||||
|
@subsection{@tt{tr}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
echo abc | tr a-z A-Z
|
||||||
|
echo a1b2c3 | tr -d 0-9
|
||||||
|
echo "a b" | tr -s " "
|
||||||
|
}
|
||||||
|
|
||||||
|
Translates characters from standard input. Simple ranges such as @tt{a-z},
|
||||||
|
@tt{A-Z}, and @tt{0-9} are expanded. @tt{-d}/@tt{--delete} deletes selected
|
||||||
|
characters and @tt{-s}/@tt{--squeeze-repeats} squeezes repeated selected
|
||||||
|
characters. @tt{-ds} and @tt{-sd} are also accepted.
|
||||||
|
|
||||||
|
Internally, @tt{tr} dispatches to @racket[coreutils-tr].
|
||||||
|
|
||||||
|
@defproc[(coreutils-tr [arg any/c] ...) void?]{Translates, deletes, or squeezes characters from the current input port.}
|
||||||
|
|
||||||
|
@section{Environment, time, and executable commands}
|
||||||
|
|
||||||
|
@index["rash-coreutils-which"]
|
||||||
|
@subsection{@tt{which}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
which git
|
||||||
|
which racket
|
||||||
|
}
|
||||||
|
|
||||||
|
Finds commands using Racket's executable search and writes the resolved path.
|
||||||
|
|
||||||
|
Internally, @tt{which} dispatches to @racket[coreutils-which].
|
||||||
|
|
||||||
|
@defproc[(coreutils-which [arg any/c] ...) void?]{Finds one or more executables on @tt{PATH}.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-printenv"]
|
||||||
|
@subsection{@tt{printenv}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
printenv
|
||||||
|
printenv PATH
|
||||||
|
}
|
||||||
|
|
||||||
|
Writes the complete current environment or the values of selected variables.
|
||||||
|
A missing selected variable produces no output.
|
||||||
|
|
||||||
|
Internally, @tt{printenv} dispatches to @racket[coreutils-printenv].
|
||||||
|
|
||||||
|
@defproc[(coreutils-printenv [name any/c] ...) void?]{Writes environment-variable values from @racket[current-environment-variables].}
|
||||||
|
|
||||||
|
@index["rash-coreutils-env"]
|
||||||
|
@subsection{@tt{env}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
env NAME=value printenv NAME
|
||||||
(define x 42)
|
(define x 42)
|
||||||
env ANSWER=(values x) printenv ANSWER
|
env ANSWER=(values x) printenv ANSWER
|
||||||
}
|
}
|
||||||
|
|
||||||
A parenthesized expression in Rash line mode is ordinary Racket code. Therefore
|
Creates a copy of the current environment, applies leading @tt{NAME=value}
|
||||||
the value after an empty @tt{NAME=} token can be a number, symbol, path, string,
|
assignments, and either writes the resulting environment or runs the remaining
|
||||||
or another printable Racket value.
|
command in it. A parenthesized expression in Rash line mode is ordinary Racket
|
||||||
|
code, so the value following an empty @tt{NAME=} token can be a Racket value.
|
||||||
|
Registered rash-coreutils commands are tried before an executable on @tt{PATH}.
|
||||||
|
|
||||||
|
Internally, the @tt{env} Rash alias first preserves Racket expressions in
|
||||||
|
assignment values and then dispatches to @racket[coreutils-env].
|
||||||
|
|
||||||
|
@defproc[(coreutils-env [arg any/c] ...) void?]{Applies temporary environment assignments and optionally runs a command.}
|
||||||
|
|
||||||
@index["rash-coreutils-date"]
|
@index["rash-coreutils-date"]
|
||||||
@defproc[(coreutils-date [arg any/c] ...) void?]{Writes the current date and time using Gregor. Supports @tt{-u}/@tt{--utc}, @tt{-I}/@tt{--iso}, @tt{--tz ZONE}, and @tt{--format CLDR-PATTERN}. The format pattern follows Gregor's CLDR syntax rather than pretending to implement every GNU @tt{date} format escape.}
|
@subsection{@tt{date}}
|
||||||
|
|
||||||
@verbatim{
|
@verbatim{
|
||||||
date
|
date
|
||||||
@@ -152,47 +519,90 @@ date --utc --iso
|
|||||||
date --tz Europe/Amsterdam --format "yyyy-MM-dd HH:mm:ss"
|
date --tz Europe/Amsterdam --format "yyyy-MM-dd HH:mm:ss"
|
||||||
}
|
}
|
||||||
|
|
||||||
@index["rash-coreutils-raco"]
|
Writes the current date and time using Gregor. @tt{-u}/@tt{--utc} selects UTC,
|
||||||
@defproc[(raco [arg any/c] ...) void?]{Runs @tt{raco} from the current Racket installation. The executable is resolved from the active Racket installation before PATH is considered, so it also works on Windows when @tt{raco.exe} is not on PATH. Arguments may be strings, symbols, paths, numbers, or nested lists. With no arguments, the underlying @tt{raco} program is invoked without a subcommand.}
|
@tt{-I}/@tt{--iso}/@tt{--iso-8601} selects ISO output, @tt{--tz ZONE} selects a
|
||||||
|
time zone, and @tt{--format CLDR-PATTERN} uses Gregor's CLDR formatting syntax.
|
||||||
|
|
||||||
@defproc[(coreutils-raco [arg any/c] ...) void?]{Explicit implementation name for @racket[raco].}
|
Internally, @tt{date} dispatches to @racket[coreutils-date].
|
||||||
|
|
||||||
|
@defproc[(coreutils-date [arg any/c] ...) void?]{Writes the current date and time in the requested representation.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-time"]
|
||||||
|
@subsection{@tt{time}}
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
time head -n 100 large.txt
|
||||||
|
time raco test tests/coreutils.rkt
|
||||||
|
}
|
||||||
|
|
||||||
|
Executes one registered rash-coreutils command or an executable on @tt{PATH} and
|
||||||
|
writes timing data to standard error. The portable measurements are elapsed
|
||||||
|
@tt{real} time, combined @tt{cpu} time for Racket and completed subprocesses,
|
||||||
|
and Racket @tt{gc} time. Standard output from the timed command remains usable
|
||||||
|
in pipelines and redirections.
|
||||||
|
|
||||||
|
Internally, @tt{time} dispatches to @racket[coreutils-time].
|
||||||
|
|
||||||
|
@defproc[(coreutils-time [command any/c] [arg any/c] ...) void?]{Runs and measures a command.}
|
||||||
|
|
||||||
|
@index["rash-coreutils-raco"]
|
||||||
|
@subsection{@tt{raco}}
|
||||||
|
|
||||||
@verbatim{
|
@verbatim{
|
||||||
raco setup rash-coreutils
|
raco setup rash-coreutils
|
||||||
raco pkg show
|
raco pkg show
|
||||||
}
|
}
|
||||||
|
|
||||||
The same @racket[raco] binding is a normal Racket procedure, so expression-mode code can use @racket[(raco '(setup rash-coreutils))]. The explicit implementation name @racket[coreutils-raco] is also exported.
|
Runs @tt{raco} from the active Racket installation. The executable is resolved
|
||||||
|
from that installation before @tt{PATH} is considered, which avoids accidentally
|
||||||
|
using @tt{raco} from another Racket installation and also works on Windows when
|
||||||
|
@tt{raco.exe} is not on @tt{PATH}.
|
||||||
|
|
||||||
@section{Path selection}
|
The Rash command calls @racket[coreutils-raco]. The exported @racket[raco]
|
||||||
|
binding is the Rash-facing command binding; ordinary Racket code can call
|
||||||
|
@racket[coreutils-raco] explicitly.
|
||||||
|
|
||||||
Shell-style argument expansion remains Rash's responsibility. The aliases expand
|
@defproc[(coreutils-raco [arg any/c] ...) void?]{Runs the active installation's @tt{raco} executable.}
|
||||||
back into Rash's @tt{=unix-pipe=} operator, preserving Rash globbing, tilde
|
|
||||||
expansion, and @tt{$} expansion.
|
@section{Editor command}
|
||||||
|
|
||||||
|
@index["rash-coreutils-edit"]
|
||||||
|
@subsection{@tt{edit}}
|
||||||
|
|
||||||
@verbatim{
|
@verbatim{
|
||||||
ls *.rkt
|
edit notes.rkt
|
||||||
ls -l info*
|
edit --wait notes.rkt
|
||||||
cat info*
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Regular-expression values are an additional @bold{rash-coreutils} selector. A
|
Opens exactly one file in the editor configured for rash-coreutils. By default,
|
||||||
@racket[#rx""] or @racket[#px""] value is matched against entry names in the
|
the editor is RackEdit's @racket[rkdt] procedure. Without @tt{--wait}, RackEdit
|
||||||
current directory.
|
opens the editor and the Rash command can continue. With @tt{--wait}, the flag
|
||||||
|
is translated to @racket[#:wait?] @racket[#t], so the command returns only after
|
||||||
|
the editor procedure returns.
|
||||||
|
|
||||||
|
Internally, @tt{edit} dispatches to @racket[coreutils-edit]. The implementation
|
||||||
|
calls the procedure stored in @racket[current-coreutils-editor] with the file
|
||||||
|
path and a @racket[#:wait?] keyword.
|
||||||
|
|
||||||
|
@defproc[(coreutils-edit [arg any/c] ...) void?]{Parses @tt{--wait}, validates that exactly one file was supplied, and invokes @racket[current-coreutils-editor].}
|
||||||
|
|
||||||
|
@defthing[current-coreutils-editor parameter?]{
|
||||||
|
A parameter whose default value is @racket[rkdt]. The configured procedure must
|
||||||
|
accept one file path and the optional keyword @racket[#:wait?]. A wrapper can be
|
||||||
|
used to select a different editor for a dynamic extent.
|
||||||
|
}
|
||||||
|
|
||||||
@verbatim{
|
@verbatim{
|
||||||
ls #px"^info[0-9]+[.]rkt$"
|
(parameterize
|
||||||
|
([current-coreutils-editor
|
||||||
|
(λ (filename #:wait? [wait? #f])
|
||||||
|
...)])
|
||||||
|
...)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@section{Help command}
|
||||||
|
|
||||||
@section{Windows paths}
|
@subsection{@tt{help}}
|
||||||
|
|
||||||
On Windows, commands that write path names use forward slashes in their textual output. Rash treats backslashes as escape characters in line mode, so a printed path such as @tt{C:/Users/name/AppData/Local/Temp/file} can be copied directly into another Rash command. Racket path values themselves remain native and can be passed directly to a command through a Racket expression.
|
|
||||||
|
|
||||||
@section{Help}
|
|
||||||
|
|
||||||
@tt{help} uses Racket's documentation search through @tt{raco docs}. Registered
|
|
||||||
commands use package-specific index terms such as @tt{rash-coreutils-ls}.
|
|
||||||
|
|
||||||
@verbatim{
|
@verbatim{
|
||||||
help
|
help
|
||||||
@@ -201,10 +611,49 @@ ls --help
|
|||||||
help directory-list
|
help directory-list
|
||||||
}
|
}
|
||||||
|
|
||||||
|
With no argument, @tt{help} writes the registered rash-coreutils command names.
|
||||||
|
For a registered command, it searches Racket documentation using the command's
|
||||||
|
package-specific index term, for example @tt{rash-coreutils-ls}. Other terms are
|
||||||
|
passed directly to Racket's documentation search. Supplying @tt{--help} to a
|
||||||
|
registered command uses the same mechanism.
|
||||||
|
|
||||||
|
The @tt{help} alias calls @racket[coreutils-help] directly. The normal command
|
||||||
|
dispatcher intercepts @tt{--help} before invoking the command implementation.
|
||||||
|
|
||||||
|
@defproc[(coreutils-help [arg any/c] ...) void?]{Lists commands or opens Racket documentation for one search term.}
|
||||||
|
|
||||||
|
@section{Path selection and Rash expansion}
|
||||||
|
|
||||||
|
Normal shell-style argument expansion remains Rash's responsibility. The aliases
|
||||||
|
expand back into Rash's pipeline machinery, preserving globbing, tilde expansion,
|
||||||
|
variable expansion, pipelines, and redirection.
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
ls *.rkt
|
||||||
|
ls -l info*
|
||||||
|
cat info* | wc -l
|
||||||
|
}
|
||||||
|
|
||||||
|
Racket regular-expression values are an additional rash-coreutils selector. A
|
||||||
|
@racket[#rx""] or @racket[#px""] value is matched against entry names in the
|
||||||
|
current directory before the internal command procedure is called.
|
||||||
|
|
||||||
|
@verbatim{
|
||||||
|
ls #px"^info[0-9]+[.]rkt$"
|
||||||
|
}
|
||||||
|
|
||||||
|
@section{Windows paths}
|
||||||
|
|
||||||
|
On Windows, commands that write path names use forward slashes in their textual
|
||||||
|
output. Rash treats backslashes as escape characters in line mode, so a printed
|
||||||
|
path such as @tt{C:/Users/name/AppData/Local/Temp/file} can be copied directly
|
||||||
|
into another Rash command. Racket path values themselves remain native and can
|
||||||
|
be passed directly to a command through a Racket expression.
|
||||||
|
|
||||||
@section{Capturing and redirecting output}
|
@section{Capturing and redirecting output}
|
||||||
|
|
||||||
Commands use normal ports. Rash can therefore capture or redirect them without
|
The internal Racket procedures use normal current ports. Rash can therefore
|
||||||
special support in this package.
|
capture, pipe, or redirect command output without command-specific support.
|
||||||
|
|
||||||
@verbatim{
|
@verbatim{
|
||||||
(define git-path { which git |> read-line })
|
(define git-path { which git |> read-line })
|
||||||
@@ -216,5 +665,5 @@ cat info.rkt &>! copy.rkt
|
|||||||
|
|
||||||
The command names and common options follow Unix conventions, but this package
|
The command names and common options follow Unix conventions, but this package
|
||||||
does not claim complete GNU coreutils compatibility. Platform-independent
|
does not claim complete GNU coreutils compatibility. Platform-independent
|
||||||
behavior, predictable Rash scripting, and useful integration with Racket values
|
behavior, predictable Rash scripting, and integration with Racket values are
|
||||||
are the primary goals.
|
the primary goals.
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
"../private/dispatcher.rkt"
|
"../private/dispatcher.rkt"
|
||||||
"../private/help.rkt"
|
"../private/help.rkt"
|
||||||
"../private/racket-tools.rkt"
|
"../private/racket-tools.rkt"
|
||||||
|
"../private/editor.rkt"
|
||||||
"../private/env-support.rkt")
|
"../private/env-support.rkt")
|
||||||
|
|
||||||
(define (capture-output procedure . args)
|
(define (capture-output procedure . args)
|
||||||
@@ -95,8 +96,36 @@
|
|||||||
(check-equal? (coreutils-help-search-term 'directory-list)
|
(check-equal? (coreutils-help-search-term 'directory-list)
|
||||||
"directory-list")
|
"directory-list")
|
||||||
(check-equal? (coreutils-help-search-term 'raco) "rash-coreutils-raco")
|
(check-equal? (coreutils-help-search-term 'raco) "rash-coreutils-raco")
|
||||||
|
(check-equal? (coreutils-help-search-term 'edit) "rash-coreutils-edit")
|
||||||
(check-true (path? (find-raco-executable)))
|
(check-true (path? (find-raco-executable)))
|
||||||
(check-true (procedure? coreutils-raco))
|
(check-true (procedure? coreutils-raco))
|
||||||
|
(check-true (procedure? (current-coreutils-editor)))
|
||||||
|
|
||||||
|
(define edit-call #f)
|
||||||
|
(parameterize
|
||||||
|
([current-coreutils-editor
|
||||||
|
(λ (filename #:wait? [wait? #f])
|
||||||
|
(set! edit-call (list filename wait?)))])
|
||||||
|
(dispatch-coreutils-command 'edit '--wait "one.txt"))
|
||||||
|
(check-equal? (path->string (first edit-call)) "one.txt")
|
||||||
|
(check-true (second edit-call))
|
||||||
|
|
||||||
|
(set! edit-call #f)
|
||||||
|
(parameterize
|
||||||
|
([current-coreutils-editor
|
||||||
|
(λ (filename #:wait? [wait? #f])
|
||||||
|
(set! edit-call (list filename wait?)))])
|
||||||
|
(dispatch-coreutils-command 'edit "one.txt"))
|
||||||
|
(check-equal? (path->string (first edit-call)) "one.txt")
|
||||||
|
(check-false (second edit-call))
|
||||||
|
|
||||||
|
(check-exn
|
||||||
|
exn:fail?
|
||||||
|
(λ ()
|
||||||
|
(parameterize
|
||||||
|
([current-coreutils-editor
|
||||||
|
(λ (filename #:wait? [wait? #f]) (void))])
|
||||||
|
(dispatch-coreutils-command 'edit "one.txt" "two.txt"))))
|
||||||
|
|
||||||
(call-with-output-file "lines.txt"
|
(call-with-output-file "lines.txt"
|
||||||
#:exists 'truncate
|
#:exists 'truncate
|
||||||
@@ -106,6 +135,27 @@
|
|||||||
"one\ntwo\n")
|
"one\ntwo\n")
|
||||||
(check-equal? (capture-output coreutils-tail "-n" "1" "lines.txt")
|
(check-equal? (capture-output coreutils-tail "-n" "1" "lines.txt")
|
||||||
"three\n")
|
"three\n")
|
||||||
|
|
||||||
|
;; A long physical line must not force head or tail to materialize that
|
||||||
|
;; line in memory. The 64 KiB internal block size is crossed many times.
|
||||||
|
(define long-lines-path (build-path test-root "long-lines.txt"))
|
||||||
|
(define long-chunk (make-bytes (* 64 1024) 120))
|
||||||
|
(call-with-output-file long-lines-path
|
||||||
|
#:exists 'truncate
|
||||||
|
(λ (out)
|
||||||
|
(display "first\n" out)
|
||||||
|
(for ([i (in-range 64)])
|
||||||
|
(write-bytes long-chunk out))
|
||||||
|
(display "\nlast-one\nlast-two\n" out)))
|
||||||
|
(check-equal? (capture-output coreutils-head "-n" "1" long-lines-path)
|
||||||
|
"first\n")
|
||||||
|
(check-equal? (capture-output coreutils-tail "-n" "2" long-lines-path)
|
||||||
|
"last-one\nlast-two\n")
|
||||||
|
(call-with-input-file long-lines-path
|
||||||
|
(λ (in)
|
||||||
|
(parameterize ([current-input-port in])
|
||||||
|
(check-equal? (capture-output coreutils-tail "-n" "2")
|
||||||
|
"last-one\nlast-two\n"))))
|
||||||
(check-equal? (capture-output coreutils-wc "-l" "lines.txt")
|
(check-equal? (capture-output coreutils-wc "-l" "lines.txt")
|
||||||
"3\n")
|
"3\n")
|
||||||
(check-equal? (capture-output coreutils-basename "a/b/c.txt")
|
(check-equal? (capture-output coreutils-basename "a/b/c.txt")
|
||||||
@@ -146,6 +196,23 @@
|
|||||||
(check-true (regexp-match? #px"[0-9]{4}-[0-9]{2}-[0-9]{2}T"
|
(check-true (regexp-match? #px"[0-9]{4}-[0-9]{2}-[0-9]{2}T"
|
||||||
(capture-output coreutils-date "--iso")))
|
(capture-output coreutils-date "--iso")))
|
||||||
|
|
||||||
|
|
||||||
|
;; time writes measurements to stderr without contaminating command output.
|
||||||
|
(define time-error (open-output-string))
|
||||||
|
(define timed-output
|
||||||
|
(parameterize ([current-error-port time-error])
|
||||||
|
(capture-output
|
||||||
|
dispatch-coreutils-command
|
||||||
|
'time
|
||||||
|
'echo
|
||||||
|
'timed)))
|
||||||
|
(check-equal? timed-output "timed\n")
|
||||||
|
(define time-output (get-output-string time-error))
|
||||||
|
(check-true
|
||||||
|
(regexp-match?
|
||||||
|
#px"^real\\t[0-9]+[.][0-9]{3}s\ncpu\\t[0-9]+[.][0-9]{3}s\ngc\\t[0-9]+[.][0-9]{3}s\n$"
|
||||||
|
time-output))
|
||||||
|
|
||||||
(check-exn
|
(check-exn
|
||||||
exn:fail?
|
exn:fail?
|
||||||
(λ ()
|
(λ ()
|
||||||
|
|||||||
+16
-2
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
(require rackunit
|
(require rackunit
|
||||||
racket/file
|
racket/file
|
||||||
|
racket/path
|
||||||
racket/port
|
racket/port
|
||||||
rash-coreutils)
|
rash-coreutils)
|
||||||
|
|
||||||
@@ -11,13 +12,17 @@
|
|||||||
(open-output-string))
|
(open-output-string))
|
||||||
(define errors
|
(define errors
|
||||||
(open-output-string))
|
(open-output-string))
|
||||||
|
(define edited #f)
|
||||||
|
|
||||||
(dynamic-wind
|
(dynamic-wind
|
||||||
void
|
void
|
||||||
(λ ()
|
(λ ()
|
||||||
(parameterize ([current-directory test-root]
|
(parameterize ([current-directory test-root]
|
||||||
[current-output-port output]
|
[current-output-port output]
|
||||||
[current-error-port errors])
|
[current-error-port errors]
|
||||||
|
[current-coreutils-editor
|
||||||
|
(λ (filename #:wait? [wait? #f])
|
||||||
|
(set! edited (list filename wait?)))])
|
||||||
{
|
{
|
||||||
(call-with-output-file "info.rkt"
|
(call-with-output-file "info.rkt"
|
||||||
#:exists 'truncate
|
#:exists 'truncate
|
||||||
@@ -30,21 +35,30 @@
|
|||||||
ls -l rash-coreutils-smoke
|
ls -l rash-coreutils-smoke
|
||||||
ls *.rkt
|
ls *.rkt
|
||||||
ls #px"^info[.]rkt$"
|
ls #px"^info[.]rkt$"
|
||||||
|
edit --wait info.rkt
|
||||||
|
|
||||||
(define x 42)
|
(define x 42)
|
||||||
env RASH_COREUTILS_VALUE=(values x) printenv RASH_COREUTILS_VALUE
|
env RASH_COREUTILS_VALUE=(values x) printenv RASH_COREUTILS_VALUE
|
||||||
|
|
||||||
echo one two three | wc -w
|
echo one two three | wc -w
|
||||||
|
time echo timed
|
||||||
date --iso
|
date --iso
|
||||||
rm -r rash-coreutils-smoke
|
rm -r rash-coreutils-smoke
|
||||||
|
|
||||||
(check-false (directory-exists? "rash-coreutils-smoke"))
|
(check-false (directory-exists? "rash-coreutils-smoke"))
|
||||||
})
|
})
|
||||||
(check-equal? (get-output-string errors) "")
|
(define error-text (get-output-string errors))
|
||||||
|
(check-true
|
||||||
|
(regexp-match?
|
||||||
|
#px"^real\\t[0-9]+[.][0-9]{3}s\ncpu\\t[0-9]+[.][0-9]{3}s\ngc\\t[0-9]+[.][0-9]{3}s\n$"
|
||||||
|
error-text))
|
||||||
(define text (get-output-string output))
|
(define text (get-output-string output))
|
||||||
(check-true (regexp-match? #rx"hello from rash-coreutils" text))
|
(check-true (regexp-match? #rx"hello from rash-coreutils" text))
|
||||||
(check-true (regexp-match? #rx"info[.]rkt" text))
|
(check-true (regexp-match? #rx"info[.]rkt" text))
|
||||||
(check-true (regexp-match? #rx"42" text))
|
(check-true (regexp-match? #rx"42" text))
|
||||||
|
(check-true (regexp-match? #rx"timed" text))
|
||||||
|
(check-equal? (path->string (first edited)) "info.rkt")
|
||||||
|
(check-true (second edited))
|
||||||
(check-true (regexp-match? #rx"\n3\n" text))
|
(check-true (regexp-match? #rx"\n3\n" text))
|
||||||
(check-true (regexp-match? #px"[0-9]{4}-[0-9]{2}-[0-9]{2}T" text)))
|
(check-true (regexp-match? #px"[0-9]{4}-[0-9]{2}-[0-9]{2}T" text)))
|
||||||
(λ ()
|
(λ ()
|
||||||
|
|||||||
Reference in New Issue
Block a user