#lang scribble/manual @(require (for-label racket/base (only-in rackedit rkdt) (only-in rash-coreutils coreutils-pwd coreutils-ls coreutils-mkdir coreutils-rmdir coreutils-rm coreutils-cp coreutils-mv coreutils-cat coreutils-touch coreutils-echo coreutils-which coreutils-head coreutils-tail coreutils-wc coreutils-sort coreutils-uniq coreutils-cut coreutils-tee coreutils-tr coreutils-basename coreutils-dirname coreutils-realpath coreutils-readlink coreutils-stat coreutils-du coreutils-df coreutils-mktemp coreutils-printenv coreutils-env coreutils-date coreutils-time coreutils-edit current-coreutils-editor coreutils-help dispatch-coreutils-command raco coreutils-raco))) @title{rash-coreutils} @author["Hans Dijkema / hans@dijkewijk.nl"] @defmodule[rash-coreutils] @bold{rash-coreutils} provides platform-independent Unix-style commands for Rash. The user-facing API is the set of commands that can be written directly 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 expansion, variable expansion, and redirection. The command aliases provided by this package preserve those Rash semantics and then dispatch to the internal Racket implementation. @section{How a Rash command is executed} For a normal command such as @verbatim{ ls -l *.rkt } 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 raise an error instead of silently approximating behavior that is not implemented. @section{Filesystem commands} @index["rash-coreutils-pwd"] @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"] @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"] @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"] @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.} @index["rash-coreutils-rm"] @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"] @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"] @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.} @index["rash-coreutils-touch"] @subsection{@tt{touch}} @verbatim{ touch notes.txt } Creates missing files and updates the modification time of existing files. No options are currently supported. Internally, @tt{touch} dispatches to @racket[coreutils-touch]. @defproc[(coreutils-touch [arg any/c] ...) void?]{Touches one or more files.} @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.} @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.} @index["rash-coreutils-realpath"] @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"] @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"] @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.} @index["rash-coreutils-du"] @subsection{@tt{du}} @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) env ANSWER=(values x) printenv ANSWER } Creates a copy of the current environment, applies leading @tt{NAME=value} assignments, and either writes the resulting environment or runs the remaining 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"] @subsection{@tt{date}} @verbatim{ date date --iso date --utc --iso date --tz Europe/Amsterdam --format "yyyy-MM-dd HH:mm:ss" } Writes the current date and time using Gregor. @tt{-u}/@tt{--utc} selects UTC, @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. 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{ raco setup rash-coreutils raco pkg show } 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}. 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. @defproc[(coreutils-raco [arg any/c] ...) void?]{Runs the active installation's @tt{raco} executable.} @section{Editor command} @index["rash-coreutils-edit"] @subsection{@tt{edit}} @verbatim{ edit notes.rkt edit --wait notes.rkt } Opens exactly one file in the editor configured for rash-coreutils. By default, the editor is RackEdit's @racket[rkdt] procedure. Without @tt{--wait}, RackEdit 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{ (parameterize ([current-coreutils-editor (λ (filename #:wait? [wait? #f]) ...)]) ...) } @section{Help command} @subsection{@tt{help}} @verbatim{ help help ls ls --help 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} The internal Racket procedures use normal current ports. Rash can therefore capture, pipe, or redirect command output without command-specific support. @verbatim{ (define git-path { which git |> read-line }) ls &> listing.txt cat info.rkt &>! copy.rkt } @section{Compatibility scope} The command names and common options follow Unix conventions, but this package does not claim complete GNU coreutils compatibility. Platform-independent behavior, predictable Rash scripting, and integration with Racket values are the primary goals.