Skip to content

Commit 910d0d3

Browse files
committed
Add a few scripts to examples/.
These are of the same "form" as the canonical extensions, such that sourcing them from .esrc will modify the shell in some more-or-less useful way. However, they don't have the same historical pedigree or universally acknowledged usefulness as the canonical extensions, so instead we put them in examples/ until people decide they should be installed with every copy of es.
1 parent 7295635 commit 910d0d3

File tree

3 files changed

+153
-0
lines changed

3 files changed

+153
-0
lines changed

examples/noclobber.es

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
# noclobber.es -- do not accidentally clobber files
2+
3+
# Sloppy demo of an imitation of the "noclobber" option in some shells -- simply
4+
# throws an error if the file exists. Not 100% confident this plays nice with
5+
# other spoofs to these hooks, though I don't actually know of any spoofs which
6+
# change the files or pre-create them (in theory, something dwimming would.)
7+
# Note also that this has TOCTTOU risks.
8+
#
9+
# %clobbers can be overwritten to modify our idea of what counts as clobbering:
10+
# for example, perhaps we don't mind clobbering an empty file.
11+
12+
fn %clobbers file {
13+
access -f $file
14+
15+
# simple alternate empty-file check might look like the following;
16+
# this may act unexpectedly for unreadable files:
17+
# access -f $file && !~ <={%read < $file} ()
18+
}
19+
20+
let (o = $fn-%create)
21+
fn %create fd file cmd {
22+
if {%clobbers $file} {
23+
throw error %create $cmd would clobber $file
24+
} {
25+
$o $fd $file $cmd
26+
}
27+
}
28+
29+
# This function lets someone override noclobber. Note that it needs to be run
30+
# "around" the redirection, like
31+
#
32+
# clobber {command > file}
33+
#
34+
# as opposed to
35+
#
36+
# clobber command > file
37+
#
38+
# Why this is the case is left as an exercise for the reader.
39+
40+
fn clobber cmd {
41+
local (fn-%clobbers = false) $cmd
42+
}
43+
44+
# Imitates the APPEND_CREATE option in zsh, where we only successfully append to
45+
# files which already exist.
46+
# TODO: figure out a nicer way to toggle this on or off.
47+
48+
let (o = $fn-%append)
49+
fn %append fd file cmd {
50+
if {!access $file} {
51+
throw error %append file $file does not exist
52+
} {
53+
$o $fd $file $cmd
54+
}
55+
}

examples/nullcmd.es

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
#!/usr/local/bin/es
2+
3+
# Implementation of zsh's customizable NULLCMD behavior for es (see the
4+
# "REDIRECTIONS WITH NO COMMAND" section in zshmisc(1) for what that means).
5+
# If a redirection with an empty command is run, like
6+
#
7+
# > file
8+
#
9+
# then with these hooks, that empty command is replaced with a call to the
10+
# %nullcmd function, which is called with the "mode" of the redirection as its
11+
# only argument, which will be one of the list (r w a r+ w+ a+ here).
12+
#
13+
# Like in zsh, %nullcmd is initially defined as `cat`, but csh
14+
# or sh behavior can be implemented by setting %nullcmd to an error-throwing
15+
# command or {}, respectively. Note that since es' default behavior here is
16+
# already like sh's, there's probably no reason to source this file and then set
17+
# fn-%nullcmd = {}.
18+
#
19+
# Note that some scripts may expect `> file` to be a quick way to create/empty a
20+
# file, and those scripts may break with these hooks. To avoid that, you may
21+
# want to define your %nullcmd to only do something special if %is-interactive.
22+
23+
fn %nullcmd {cat}
24+
25+
let (of = $fn-%openfile)
26+
fn %openfile mode fd file cmd {
27+
if {!~ $#fn-%nullcmd 0 && ~ $cmd *'{}'} {
28+
$of $mode $fd $file {%nullcmd $mode}
29+
} {
30+
$of $mode $fd $file $cmd
31+
}
32+
}
33+
34+
35+
# Try to unset `%nullcmd` in `exec {> foo}` cases.
36+
# FIXME: This is reeeally ugly.
37+
38+
let (e = $fn-exec)
39+
fn exec cmd {
40+
if {~ $cmd *'{'^(%create %append %open-create)^*'{}}'} {
41+
$e {local (fn-%nullcmd = ()) $cmd}
42+
} {
43+
$e $cmd
44+
}
45+
}
46+
47+
48+
# %here doesn't use %openfile, so we override it as well so that we can do neat
49+
# things like `> file << EOF`.
50+
51+
let (h = $fn-%here)
52+
fn %here fd str cmd {
53+
if {!~ $#fn-%nullcmd 0 && ~ $cmd *'{}'} {
54+
$h $fd $str {%nullcmd here}
55+
} {
56+
$h $fd $str $cmd
57+
}
58+
}

examples/preparse.es

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
# preparse.es -- parse scripts strictly before evaluating
2+
3+
# Sourcing this script redefines %batch-loop such that it reads and parses the
4+
# entire input, saving the commands in the input to $cmds, before evaluating the
5+
# whole script at once. This allows the shell to catch any syntax errors before
6+
# evaluating any part of a potentially-buggy script, which is a nice "sanity"
7+
# feature. In theory, buffering all the commands has an impact on memory use as
8+
# well as script "start-up" time, but in practice these impacts are quite small.
9+
#
10+
# It's nice that %batch-loop was made hookable despite the original authors'
11+
# reservations on whether hooking it would ever be a good idea :) It's also
12+
# impressive how relatively simple this is to do. However, note the couple of
13+
# "gotchas" in the function, each of which represent potential issues in the
14+
# shell. (Crashing while printing circular bindings is a known problem; the
15+
# other note indicates more marginal issues.)
16+
17+
fn %batch-loop {
18+
let (cmds = ()) {
19+
# Read and buffer the commands until we get an eof.
20+
catch @ e rest {
21+
if {!~ $e eof} {
22+
throw $e $rest
23+
}
24+
} {
25+
forever {
26+
# NOTE: Don't echo $cmds or the shell will die.
27+
let (cmd = <=%parse)
28+
if {!~ $#cmd 0} {
29+
cmds = $cmds {$fn-%dispatch $cmd}
30+
}
31+
}
32+
}
33+
# Evaluate the commands.
34+
# NOTE: using a for loop here catches the `break` exception,
35+
# which we want to avoid. Also, due to some particular details
36+
# about how %seq is parsed, we need the {} around this
37+
# invocation or else $cmds won't get called at all.
38+
{%seq $cmds}
39+
}
40+
}

0 commit comments

Comments
 (0)