summaryrefslogtreecommitdiffhomepage
path: root/src/prj/mmv
diff options
context:
space:
mode:
authorThomas Voss <mail@thomasvoss.com> 2023-09-11 05:15:20 +0200
committerThomas Voss <mail@thomasvoss.com> 2023-09-11 05:15:20 +0200
commitbda44e93541fa478abf3ce4b3461f026a90fa8cb (patch)
treea62a7e1d456effe914a77b45f66485c3e8bfd92d /src/prj/mmv
parentced3ed9ddde25614bbc9777a5d546eee2a44a2e0 (diff)
Move the site from HTML to GSP
Diffstat (limited to 'src/prj/mmv')
-rw-r--r--src/prj/mmv/camel-to-snake-naïve.sh.gsp2
-rw-r--r--src/prj/mmv/camel-to-snake-naïve.sh.html2
-rw-r--r--src/prj/mmv/camel-to-snake-smart.sh.gsp1
-rw-r--r--src/prj/mmv/camel-to-snake-smart.sh.html1
-rw-r--r--src/prj/mmv/examples/camel-to-snake.sh.gsp1
-rw-r--r--src/prj/mmv/examples/camel-to-snake.sh.html1
-rw-r--r--src/prj/mmv/examples/hyphens.sh.gsp1
-rw-r--r--src/prj/mmv/examples/hyphens.sh.html1
-rw-r--r--src/prj/mmv/examples/i-flag.sh.gsp1
-rw-r--r--src/prj/mmv/examples/i-flag.sh.html1
-rw-r--r--src/prj/mmv/examples/lowercase.sh.gsp1
-rw-r--r--src/prj/mmv/examples/lowercase.sh.html1
-rw-r--r--src/prj/mmv/examples/number.sh.gsp2
-rw-r--r--src/prj/mmv/examples/number.sh.html2
-rw-r--r--src/prj/mmv/examples/swap.sh.gsp1
-rw-r--r--src/prj/mmv/examples/swap.sh.html1
-rw-r--r--src/prj/mmv/examples/vipe.sh.gsp1
-rw-r--r--src/prj/mmv/examples/vipe.sh.html1
-rw-r--r--src/prj/mmv/index.gsp645
-rw-r--r--src/prj/mmv/index.html667
-rw-r--r--src/prj/mmv/ls-files.sh.gsp (renamed from src/prj/mmv/ls-files.sh.html)2
-rw-r--r--src/prj/mmv/manual-mv.sh.gsp5
-rw-r--r--src/prj/mmv/manual-mv.sh.html5
-rw-r--r--src/prj/mmv/mmv-rev-zero.sh.gsp2
-rw-r--r--src/prj/mmv/mmv-rev-zero.sh.html2
-rw-r--r--src/prj/mmv/mmv-rev.sh.gsp2
-rw-r--r--src/prj/mmv/mmv-rev.sh.html2
-rw-r--r--src/prj/mmv/mmv-tr.sh.gsp1
-rw-r--r--src/prj/mmv/mmv-tr.sh.html1
-rw-r--r--src/prj/mmv/mmv-verbose.sh.gsp (renamed from src/prj/mmv/mmv-verbose.sh.html)2
-rw-r--r--src/prj/mmv/reverse-embedded-newline.sh.gsp3
-rw-r--r--src/prj/mmv/reverse-embedded-newline.sh.html3
-rw-r--r--src/prj/mmv/sed-debugging-rev.sh.gsp3
-rw-r--r--src/prj/mmv/sed-debugging-rev.sh.html3
-rw-r--r--src/prj/mmv/sed-debugging.sh.gsp3
-rw-r--r--src/prj/mmv/sed-debugging.sh.html3
-rw-r--r--src/prj/mmv/sha1sum-long-example.sh.gsp11
-rw-r--r--src/prj/mmv/sha1sum-long-example.sh.html11
-rw-r--r--src/prj/mmv/sha1sum-short-example.sh.gsp6
-rw-r--r--src/prj/mmv/sha1sum-short-example.sh.html6
-rw-r--r--src/prj/mmv/tr.sh.gsp3
-rw-r--r--src/prj/mmv/tr.sh.html3
-rw-r--r--src/prj/mmv/vim.gsp2
-rw-r--r--src/prj/mmv/vim.html2
-rw-r--r--src/prj/mmv/vipe.sh.gsp1
-rw-r--r--src/prj/mmv/vipe.sh.html1
46 files changed, 700 insertions, 722 deletions
diff --git a/src/prj/mmv/camel-to-snake-naïve.sh.gsp b/src/prj/mmv/camel-to-snake-naïve.sh.gsp
new file mode 100644
index 0000000..cd0d156
--- /dev/null
+++ b/src/prj/mmv/camel-to-snake-naïve.sh.gsp
@@ -0,0 +1,2 @@
+@span .sh-cmt {-# If you aren’t a shell-guru, take a moment to figure out how this works!}
+$ @span .sh-fn {-ls} *.[ch] | @span .sh-fn {-sed} @span .sh-str {-'p; s/[A-Z]/\\L_&/g'} | @span .sh-fn {-xargs} -L2 mv
diff --git a/src/prj/mmv/camel-to-snake-naïve.sh.html b/src/prj/mmv/camel-to-snake-naïve.sh.html
deleted file mode 100644
index 573da41..0000000
--- a/src/prj/mmv/camel-to-snake-naïve.sh.html
+++ /dev/null
@@ -1,2 +0,0 @@
-<span class="sh-cmt"># If you aren’t a shell-guru, take a moment to figure out how this works!</span>
-$ <span class="sh-fn">ls</span> *.[ch] | <span class="sh-fn">sed</span> <span class="sh-str">'p; s/[A-Z]/\L_&/g'</span> | <span class="sh-fn">xargs</span> -L2 mv
diff --git a/src/prj/mmv/camel-to-snake-smart.sh.gsp b/src/prj/mmv/camel-to-snake-smart.sh.gsp
new file mode 100644
index 0000000..9ce6123
--- /dev/null
+++ b/src/prj/mmv/camel-to-snake-smart.sh.gsp
@@ -0,0 +1 @@
+$ @span .sh-fn {-ls} *.[ch] | @span .sh-fn {-mmv} sed @span .sh-str {-'s/[A-Z]/\\L_&/g'}
diff --git a/src/prj/mmv/camel-to-snake-smart.sh.html b/src/prj/mmv/camel-to-snake-smart.sh.html
deleted file mode 100644
index 191e87f..0000000
--- a/src/prj/mmv/camel-to-snake-smart.sh.html
+++ /dev/null
@@ -1 +0,0 @@
-$ <span class="sh-fn">ls</span> *.[ch] | <span class="sh-fn">mmv</span> sed <span class="sh-str">'s/[A-Z]/\L_&/g'</span>
diff --git a/src/prj/mmv/examples/camel-to-snake.sh.gsp b/src/prj/mmv/examples/camel-to-snake.sh.gsp
new file mode 100644
index 0000000..7948861
--- /dev/null
+++ b/src/prj/mmv/examples/camel-to-snake.sh.gsp
@@ -0,0 +1 @@
+$ @span .sh-fn {-git} ls-files @span .sh-str {-'*.[ch]'} | @span .sh-fn {-mmv} sed @span .sh-str {-'s/[A-Z]/\\L_&/g'}
diff --git a/src/prj/mmv/examples/camel-to-snake.sh.html b/src/prj/mmv/examples/camel-to-snake.sh.html
deleted file mode 100644
index bd67492..0000000
--- a/src/prj/mmv/examples/camel-to-snake.sh.html
+++ /dev/null
@@ -1 +0,0 @@
-$ <span class="sh-fn">git</span> ls-files <span class="sh-str">'*.[ch]'</span> | <span class="sh-fn">mmv</span> sed <span class="sh-str">'s/[A-Z]/\L_&/g'</span>
diff --git a/src/prj/mmv/examples/hyphens.sh.gsp b/src/prj/mmv/examples/hyphens.sh.gsp
new file mode 100644
index 0000000..8f9cc76
--- /dev/null
+++ b/src/prj/mmv/examples/hyphens.sh.gsp
@@ -0,0 +1 @@
+$ @span .sh-fn {-ls} | @span .sh-fn {-mmv} tr ' ' '-'
diff --git a/src/prj/mmv/examples/hyphens.sh.html b/src/prj/mmv/examples/hyphens.sh.html
deleted file mode 100644
index ca49946..0000000
--- a/src/prj/mmv/examples/hyphens.sh.html
+++ /dev/null
@@ -1 +0,0 @@
-$ <span class="sh-fn">ls</span> | <span class="sh-fn">mmv</span> tr ' ' '-'
diff --git a/src/prj/mmv/examples/i-flag.sh.gsp b/src/prj/mmv/examples/i-flag.sh.gsp
new file mode 100644
index 0000000..6977635
--- /dev/null
+++ b/src/prj/mmv/examples/i-flag.sh.gsp
@@ -0,0 +1 @@
+$ @span .sh-fn {-ls} --zero | @span .sh-fn {-mmv} -0i cmd
diff --git a/src/prj/mmv/examples/i-flag.sh.html b/src/prj/mmv/examples/i-flag.sh.html
deleted file mode 100644
index c22c7c9..0000000
--- a/src/prj/mmv/examples/i-flag.sh.html
+++ /dev/null
@@ -1 +0,0 @@
-$ <span class="sh-fn">ls</span> --zero | <span class="sh-fn">mmv</span> -0i cmd
diff --git a/src/prj/mmv/examples/lowercase.sh.gsp b/src/prj/mmv/examples/lowercase.sh.gsp
new file mode 100644
index 0000000..72a8e4c
--- /dev/null
+++ b/src/prj/mmv/examples/lowercase.sh.gsp
@@ -0,0 +1 @@
+$ @span .sh-fn {-find} . -print0 | @span .sh-fn {-mmv} -0 tr A-Z a-z
diff --git a/src/prj/mmv/examples/lowercase.sh.html b/src/prj/mmv/examples/lowercase.sh.html
deleted file mode 100644
index 84abb92..0000000
--- a/src/prj/mmv/examples/lowercase.sh.html
+++ /dev/null
@@ -1 +0,0 @@
-$ <span class="sh-fn">find</span> . -print0 | <span class="sh-fn">mmv</span> -0 tr A-Z a-z
diff --git a/src/prj/mmv/examples/number.sh.gsp b/src/prj/mmv/examples/number.sh.gsp
new file mode 100644
index 0000000..ce82f24
--- /dev/null
+++ b/src/prj/mmv/examples/number.sh.gsp
@@ -0,0 +1,2 @@
+$ @span .sh-fn {-ls} @span .sh-str {-'The Fellowship of the Ring.mp4'} … @span .sh-str {-'The Two Towers.mp4'} | \\
+ @span .sh-fn {-mmv} awk @span .sh-str {-'{ gsub(" ", "-"); printf "%02d-%s", NR, tolower($0) \}'}
diff --git a/src/prj/mmv/examples/number.sh.html b/src/prj/mmv/examples/number.sh.html
deleted file mode 100644
index 5e8e74a..0000000
--- a/src/prj/mmv/examples/number.sh.html
+++ /dev/null
@@ -1,2 +0,0 @@
-$ <span class="sh-fn">ls</span> <span class="sh-str">'The Fellowship of the Ring.mp4'</span> … <span class="sh-str">'The Two Towers.mp4'</span> | \
- <span class="sh-fn">mmv</span> awk <span class="sh-str">'{ gsub(" ", "-"); printf "%02d-%s", NR, tolower($0) }'</span>
diff --git a/src/prj/mmv/examples/swap.sh.gsp b/src/prj/mmv/examples/swap.sh.gsp
new file mode 100644
index 0000000..0249751
--- /dev/null
+++ b/src/prj/mmv/examples/swap.sh.gsp
@@ -0,0 +1 @@
+$ @span .sh-fn {-ls} foo bar | @span .sh-fn {-mmv} tac
diff --git a/src/prj/mmv/examples/swap.sh.html b/src/prj/mmv/examples/swap.sh.html
deleted file mode 100644
index 02c9c28..0000000
--- a/src/prj/mmv/examples/swap.sh.html
+++ /dev/null
@@ -1 +0,0 @@
-$ <span class="sh-fn">ls</span> foo bar | <span class="sh-fn">mmv</span> tac
diff --git a/src/prj/mmv/examples/vipe.sh.gsp b/src/prj/mmv/examples/vipe.sh.gsp
new file mode 100644
index 0000000..b738ae1
--- /dev/null
+++ b/src/prj/mmv/examples/vipe.sh.gsp
@@ -0,0 +1 @@
+$ @span .sh-fn {-ls} | @span .sh-fn {-mmv} -0e vipe
diff --git a/src/prj/mmv/examples/vipe.sh.html b/src/prj/mmv/examples/vipe.sh.html
deleted file mode 100644
index 933039a..0000000
--- a/src/prj/mmv/examples/vipe.sh.html
+++ /dev/null
@@ -1 +0,0 @@
-$ <span class="sh-fn">ls</span> | <span class="sh-fn">mmv</span> -0e vipe
diff --git a/src/prj/mmv/index.gsp b/src/prj/mmv/index.gsp
new file mode 100644
index 0000000..3c2fd97
--- /dev/null
+++ b/src/prj/mmv/index.gsp
@@ -0,0 +1,645 @@
+html lang="en" {
+ head { m4_include(head.gsp) }
+ body {
+ header {
+ div {
+ h1 {-Moving Files the Right Way}
+ m4_include(nav.gsp)
+ }
+
+ figure .quote {
+ blockquote {
+ p {=
+ I think the OpenBSD crowd is a bunch of masturbating monkeys, in
+ that they make such a big deal about concentrating on security to
+ the point where they pretty much admit that nothing else matters to
+ them.
+ }
+ }
+ figcaption {-Linus Torvalds}
+ }
+ }
+
+ main {
+ p {
+ em {-
+ You can find the @code{-mmv} git repository over at
+ @a
+ href="https://git.sr.ht/~mango/mmv"
+ target="_blank"
+ {-sourcehut}
+ or
+ @a
+ href="https://github.com/Mango0x45/mmv"
+ target="_blank"
+ {-GitHub}.
+ }
+ }
+
+ p {-
+ NOTE: As of the
+ @a href="https://git.sr.ht/~mango/mmv/refs/v1.2.0" {-v1.2.0}
+ release there is now also the @code{-mcp} utility. It behaves the same
+ as the @code{-mmv} utility but it copies files instead of moving them.
+ It also doesn’t support the ‘@code{--n}’ flag as it doesn’t need to deal
+ with backups.
+ }
+
+ h2 {-Table of Contents}
+
+ ul {
+ li {a href="#prologue" {-Prologue}}
+ li {a href="#moving" {-Advanced Moving and Pitfalls}}
+ li {a href="#mapping" {-Name Mapping with @code{-mmv}}}
+ li {a href="#newlines" {-Filenames with Embedded Newlines}}
+ ul {
+ li {a href="0-flag" {-The Simple Case}}
+ li {a href="#e-flag" {-Encoding Newlines}}
+ }
+ li {a href="#i-flag" {-Individual Execution}}
+ li {a href="#safety" {-Safety}}
+ li {a href="#examples" {-Examples}}
+ }
+
+ h2 #prologue {-Prologue}
+ p {-
+ File moving and renaming is one of the most common tasks we undertake on
+ the command-line. We basically always do this with the @code{-mv}
+ utility, and it gets the job done most of the time. Want to rename one
+ file? Use @code{-mv}! Want to move a bunch of files into a directory?
+ Use @code{-mv}! How could mv ever go wrong? Well I’m glad you asked!
+ }
+
+ h2 #moving {-Advanced Moving and Pitfalls}
+ p {-
+ Let’s start off nice and simple. You just inherited a C project that
+ uses the sacrilegious
+ @a
+ href="https://en.wikipedia.org/wiki/Camel_case"
+ target="_blank"
+ {-camelCase}
+ naming convention for its files:
+ }
+
+ figure {
+ pre { m4_fmt_code(ls-files.sh.gsp) }
+ }
+
+ p {-
+ This deeply upsets you, as it upsets me. So you decide you want to
+ switch all these files to use
+ @a
+ href="https://en.wikipedia.org/wiki/Snake_case"
+ target="_blank"
+ {-snake_case},
+ like a normal person. Well how would you do this? You use @code{-mv}!
+ This is what you might end up doing:
+ }
+
+ figure {
+ pre { m4_fmt_code(manual-mv.sh.gsp) }
+ }
+
+ p {-
+ Well… it works I guess, but it’s a pretty shitty way of renaming these
+ files. Luckily we only had 5, but what if this was a much larger
+ project with many more files to rename? Things would get tedious. So
+ instead we can use a pipeline for this:
+ }
+
+ figure {
+ pre { m4_fmt_code(camel-to-snake-naïve.sh.gsp) }
+ }
+
+ aside {
+ p {-
+ The given example assumes your @code{-sed} implementation supports
+ ‘@code{-\\L}’ which is a non-standard m4_abbr(GNU) extension.
+ }
+ }
+
+ p {-
+ That works and it gets the job done, but it’s not really ideal is
+ it? There are a couple of issues with this.
+ }
+
+ ol {
+ li {
+ p {-
+ You’re writing more complicated code. This has the obvious drawback
+ of potentially being more error-prone, but also risks taking more
+ time to write than you’d like as you might have forgotten if
+ @code{-xargs} actually has an ‘@code{--L}’ option or not (which
+ would require reading the
+ @a
+ href="https://www.man7.org/linux/man-pages/man1/xargs.1.html"
+ target="_blank"
+ {
+ code {-xargs(1)}
+ }
+ manual).
+ }
+ }
+ li {
+ p {-
+ If you try to rename the file @em{-foo} to @em{-bar} but @em{-bar}
+ already exists, you end up deleting a file you may not have wanted
+ to.
+ }
+ }
+ li {
+ p {-
+ In a similar vein to the previous point, you need to be very careful
+ about schemes like renaming the file @em{-a} to @em{-b} and @em{-b}
+ to @em{-c}. You run the risk of turning @em{-a} into @em{-c} and
+ losing the file @em{-b} entirely.
+ }
+ }
+ li {
+ p {-
+ Moving symbolic links is its own whole can of worms. If a symlink
+ points to a relative location then you need to make sure you keep
+ pointing to the right place. If the symlink is absolute however
+ then you can leave it untouched. But what if the symlink points to
+ a file that you’re moving as part of your batch move operation? Now
+ you need to handle that too.
+ }
+ }
+ }
+
+ h2 #mapping {-Name Mapping with @code{-mmv}}
+
+ p {-
+ What is @code{-mmv}? It’s the solution to all your problems, that’s
+ what it is! @code{-mmv} takes as its argument(s) a utility and that
+ utilities arguments and uses that to create a mapping between old and
+ new filenames — similar to the @code{-map()} function found in many
+ programming languages. I think to best convey how the tool functions, I
+ should provide an example. Let’s try to do the same thing we did
+ previously where we tried to turn camelCase files to snake_case, but
+ using @code{-mmv}:
+ }
+
+ figure {
+ pre { m4_fmt_code(camel-to-snake-smart.sh.gsp) }
+ }
+
+ p {-Let me break down how this works.}
+
+ p {-
+ @code{-mmv} starts by reading a series of filenames separated by
+ newlines from the standard input. Yes, sometimes filenames have
+ newlines in them and yes there is a way to handle them but I shall get
+ to that later. The filenames that @code{-mmv} reads from the standard
+ input will be referred to as the @em{-input files}. Once all the input
+ files have been read, the utility specified by the arguments is spawned;
+ in this case that would be @code{-sed} with the argument
+ @code{-'s/[A-Z]/\\L_&/g'}. The input files are then piped into
+ @code{-sed} the exact same way that they would have been if we ran the
+ above commands without @code{-mmv}, and the output of @code{-sed} then
+ forms what will be referred to as the @em{-output files}. Once a
+ complete list of output files is accumulated, each input file gets
+ renamed to its corresponding output file.
+ }
+
+ p {-
+ Let’s look at a simpler example. Say we want to rename 2 files in the
+ current directory to use lowercase letters, we could use the following
+ command:
+ }
+
+ figure {
+ pre { m4_fmt_code(mmv-tr.sh.gsp) }
+ }
+
+ p {-
+ In the above example @code{-mmv} reads 2 lines from standard input,
+ those being @em{-LICENSE} and @em{-README}. Those are our 2 input files
+ now. The @code{-tr} utility is then spawned and the input files are
+ piped into it. We can simulate this in the shell:
+ }
+
+ figure {
+ pre { m4_fmt_code(tr.sh.gsp) }
+ }
+
+ p {-
+ As you can see above, @code{-tr} has produced 2 lines of output; these
+ are our 2 output files. Since we now have our 2 input files and 2
+ output files, @code{-mmv} can go ahead and rename the files. In this
+ case it will rename @em{-LICENSE} to @em{-license} and @em{-README} to
+ @em{-readme}. For some examples, check the @a href="#examples"
+ {-examples} section of this page down below.
+ }
+
+ h2 #newlines {-Filenames with Embedded Newlines}
+
+ p {-
+ People are retarded, and as a result we have filenames with newlines in
+ them. All it would have taken to solve this issue for everyone was for
+ literally @strong{-anybody} during the early UNIX days to go “@em{-hey,
+ this is a bad idea!}”, but alas, we must deal with this. Newlines are
+ of course not the only special characters filenames can contain, but
+ they are the single most infuriating to deal with; the UNIX utilities
+ all being line-oriented really doesn’t work well with these files.
+ }
+
+ p {-
+ So how does @code{-mmv} deal with special characters, and newlines in
+ particular? Well it does so by providing the user with the @code{--0}
+ and @code{--e} flags:
+ }
+
+ dl {
+ dt { code{--0} }
+ dd {
+ p {-
+ Tell @code{-mmv} to expect its input to not be separated by newlines
+ (‘@code{-\\n}’), but by NUL bytes (‘@code{-\\0}’). NUL bytes are
+ the only characters not allowed in filenames besides forward
+ slashes, so they are an obvious choice for an alternative separator.
+ }
+ }
+ dt { code{--e} }
+ dd {
+ p {-
+ Encode newlines in filenames before passing them to the provided
+ utility. Newline characters are replaced by the literal string
+ ‘@code{-\\n}’ and backslashes by the literal string ‘@code{-\\\\}’.
+ After processing, the resulting output is decoded again.
+ }
+ p {-
+ If combined with the @code{--0} flag, then while input will be read
+ assuming a NUL-byte input-seperator, the encoded input files will be
+ written to the spawned process newline-seperated.
+ }
+ }
+ }
+
+ h3 id="0-flag" {-The Simple Case}
+
+ p {-
+ In order to better understand these flags and how they work let’s go
+ though another example. We have 2 files — one with and one without an
+ embedded newline — and our goal is to simply reverse these filenames.
+ In this example I am going to be displaying newlines in filenames with
+ the “@code{-$'\\n'}” syntax as this is how my shell displays embedded
+ newlines.
+ }
+
+ p {-
+ We can start by just trying to naïvely pass these 2 files to @code{-mmv}
+ and use @code{-rev} to reverse the names, but this doesn’t work:
+ }
+
+ figure {
+ pre { m4_fmt_code(mmv-rev.sh.gsp) }
+ }
+
+ p {-
+ The reason this doesn’t work is because due to the line-oriented nature
+ of @code{-ls} and @code{-rev}, we are actually trying to rename the
+ files @em{-foo}, @em{-bar}, and @em{-baz} to the new filenames
+ @em{-zab}, @em{-rab}, and @em{-oof}. As can be seen in the following
+ diagram, the embedded newline is causing our input to be ambiguous and
+ @code{-mmv} can’t reliably proceed anymore @x-ref{-1}:
+ }
+
+ figure {
+ object data="conflict.svg" type="image/svg+xml" {-}
+ }
+
+ aside {
+ p data-ref="1" {-
+ The reason you get a cryptic “file not found” error message is because
+ @code{-mmv} tries to assert that all the input files actually exist
+ before doing anything. Since “foo” isn’t a real file, we error out.
+ }
+ }
+
+ p {-
+ The first thing we need to do in order to proceed is to pass the
+ @code{--0} flag to @code{-mmv}. This will tell @code{-mmv} that we want
+ to use the NUL-byte as our input separator and not the newline. We also
+ need @code{-ls} to actually provide us with the filenames delimited by
+ NUL-bytes. Luckily m4_abbr(GNU) @code{-ls} gives us the @code{---zero}
+ flag to do just that:
+ }
+
+ figure {
+ pre { m4_fmt_code(mmv-rev-zero.sh.gsp) }
+ }
+
+ p {-
+ So we’re getting places, but we aren’t quite there yet. The issue we’re
+ getting now is that @code{-mmv} recieved 2 input files from the standard
+ input, but @code{-rev} produced 3 output files. Why is that? Well
+ let’s try our hand at a little bit of command-line debugging with
+ @code{-sed}:
+ }
+
+ figure {
+ pre { m4_fmt_code(sed-debugging.sh.gsp) }
+ }
+
+ p {-
+ If you aren’t quite sure what the above is doing, here’s a quick
+ summary:
+ }
+
+ ul {
+ li {-
+ The @code{--U} flag given to @code{-ls} tells it not to sort our
+ output. This is purely just to keep this example clear to the reader.
+ }
+ li {-
+ The @code{--n} flag given to @code{-sed} tells it not to print the
+ input line automatically at the end of the provided script.
+ }
+ li {-
+ The @code{-l} command in @code{-sed} prints the current input in a
+ “visually unambiguous form”.
+ }
+ }
+
+ p {-
+ In the @code{-sed} output, we can see that @samp{-$} represents the end
+ of a line, and @samp{-\\000} represents the NUL-byte. All looks good
+ here, we have two inputs seperated by NUL-bytes. Now let’s try to throw
+ in @code{-rev}:
+ }
+
+ figure {
+ pre { m4_fmt_code(sed-debugging-rev.sh.gsp) }
+ }
+
+ p {-
+ Well wouldn’t you know it? Since @code{-rev} @em{-also} works with
+ newline-seperated input, it reversed out NUL-byte seperators and now
+ gives us 3 outputs. Luckily the folks over at @em{-util-linux} provided
+ us with the @code{--0} flag here too, so that we can properly handle
+ NUL-delimited input. Combining all of this together we get a final
+ working product:
+ }
+
+ figure {
+ pre { m4_fmt_code(reverse-embedded-newline.sh.gsp) }
+ }
+
+ h3 #e-flag {-Encoding Newlines}
+
+ p {-
+ Sometimes we want to rename a bunch of files, but the command we want to
+ use doesn’t support NUL-bytes as nicely as we would like. In these
+ cases, you may want to consider encoding your newline characters into
+ the literal string ‘@code{-\\n}’ and then passing your input
+ newline-seperated to your given command with the @code{--e} flag.
+ }
+
+ p {-
+ For a real-world example, perhaps you want to edit some filenames in
+ vim, or whatever other editor you use. Well we can do this incredibly
+ easily with the @code{-vipe} utility from the
+ @a href="https://joeyh.name/code/moreutils/" {-moreutils}
+ collection. The @code{-vipe} command simply reads input from the
+ standard input, opens it up in your editor, and then prints the
+ resulting output to the standard output; perfect for @code{-mmv}! We do
+ not really want to deal with NUL-bytes in our text-editor though, so
+ let’s just encode our newlines:
+ }
+
+ figure {
+ pre { m4_fmt_code(vipe.sh.gsp) }
+ }
+
+ aside {
+ p {-
+ Notice how you still need to pass the @code{--0} flag to @code{-mmv}
+ know that our inputfiles may have embedded newlines.
+ }
+ }
+
+ p {-
+ When running the above code example, you will see the following in your
+ editor:
+ }
+
+ figure {
+ pre { m4_fmt_code(vim.gsp) }
+ }
+
+ p {-
+ After you exit your editor, @code{-mmv} will decode all occurances of
+ ‘@code{-\\n}’ back into a newline, and all occurances of ‘@code{-\\\\}’
+ back into a backslash:
+ }
+
+ figure {
+ object data="e-flag.svg" type="image/svg+xml" {-}
+ }
+
+ h2 #i-flag {-Individual Execution}
+ p {-
+ The previous examples are great and all, but what do you do if your
+ mapping command doesn’t have the concept of an input seperator at all?
+ This is where the @code{--i} flag comes into play. With the @code{--i}
+ flag we can get @code{-mmv} to execute our mapping command for every
+ input filename. This means that as long as we can work with a complete
+ buffer, we don’t need to worry about seperators.
+ }
+
+ p {-
+ To be honest, I cannot really think of any situation where you might
+ actually need to do this. If you can think of one, please @a
+ href="mailto:mail@thomasvoss.com" {-email me} and I’ll update the
+ example on this page. Regardless, let’s imagine that we wanted to
+ rename some files so that their filenames are replaced with their
+ filename
+ @a
+ href="https://en.wikipedia.org/wiki/SHA-1"
+ target="_blank"
+ {-m4_abbr(SHA)-1 hash}.
+ On Linux we have the @code{-sha1sum} program which reads input from the
+ standard input and outputs the m4_abbr(SHA)-1 hash. This is how we
+ would use it with @code{-mmv}:
+ }
+
+ figure {
+ pre { m4_fmt_code(sha1sum-long-example.sh.gsp) }
+ }
+
+ p {-
+ Another approach is to invoke @code{-mmv} twice:
+ }
+
+ figure {
+ pre { m4_fmt_code(sha1sum-short-example.sh.gsp) }
+ }
+
+ p {-
+ If you are confused about why we need to make a call to @code{-awk},
+ it’s because the @code{-sha1sum} program outputs 2 columns of data. The
+ first column is our hash and the second column is the filename where the
+ to-be-hashed data was read from. We don’t want the second column.
+ }
+
+ p {-
+ Unlike in previous examples where one process was spawned to map all our
+ filenames, with the @code{--i} flag we are spawning a new instance for
+ each filename. If you struggle to visualize this, perhaps the following
+ diagrams help:
+ }
+
+ figure {
+ figcaption {-Invoking @code{-mmv} without @code{--i}}
+ object data="without-i-flag.svg" type="image/svg+xml" {-}
+ }
+
+ figure {
+ figcaption {-Invoking @code{-mmv} with @code{--i}}
+ object data="with-i-flag.svg" type="image/svg+xml" {-}
+ }
+
+ h2 #safety {-Safety}
+ p {-
+ When compared to the standard @code{-for f in *; do mv $f …; done} or
+ @code{-ls | … | xargs -L2 mv} constructs, @code{-mmv} is significantly
+ more safe to use. These are some of the safety features that are built
+ into the tool:
+ }
+
+ ol {
+ li {-
+ If the number of input- and output files differs, execution is aborted
+ before making any changes.
+ }
+ li {-
+ If an input file is renamed to the name of another input file, the
+ second input file is not lost (i.e. you can rename @em{-a} to @em{-b}
+ and @em{-b} to @em{-a} with no problem).
+ }
+ li {-
+ All input files must be unique and all output files must be unique.
+ Otherwise execution is aborted before making any changes.
+ }
+ li {-
+ In the case that something goes wrong during execution (perhaps you
+ tried to move a file to a non-existant directory, or a syscall
+ failed), a backup of your input files is saved automatically by
+ @code{-mmv} for recovery.
+ }
+ }
+
+ p {-
+ Due to the way @code{-mmv} handles #2, when things do go wrong you may
+ find that all of your input files have disappeared. Don’t worry though,
+ @code{-mmv} takes a backup of your code before doing anything. If you
+ run @code{-mmv} with the @code{--v} option for verbose output, you’ll
+ notice it backing up your stuff in the @code{-$XDG_CACHE_DIR} directory:
+ }
+
+ figure {
+ pre { m4_fmt_code(mmv-verbose.sh.gsp) }
+ }
+
+ p {-
+ Upon successful execution the @code{-$XDG_CACHE_DIR/mmv/TIMESTAMP}
+ directory will be automatically removed, but it remains when things go
+ wrong so that you can recover any missing data. The names of the
+ backup-subdirectories in the @code{-$XDG_CACHE_DIR/mmv} directory are
+ timestamps of when the directories were created. This should make it
+ easier for you to figure out which directory you need to recover if you
+ happen to have multiple of these.
+ }
+
+ h2 #examples {-Examples}
+
+ aside {
+ p {-
+ All of these examples are ripped straight from the @code{-mmv(1)}
+ manual page. If you installed @code{-mmv} through a package manager or
+ via @code{-make install} then you should have the manual installed on
+ your system.
+ }
+ }
+
+ p {-Swap the files @em{-foo} and @em{-bar}:}
+ figure {
+ pre { m4_fmt_code(examples/swap.sh.gsp) }
+ }
+
+ p {-
+ Rename all files in the current directory to use hyphens (‘-’) instead
+ of spaces:
+ }
+ figure {
+ pre { m4_fmt_code(examples/hyphens.sh.gsp) }
+ }
+
+ p {-
+ Rename a given list of movies to use lowercase letters and hyphens
+ instead of uppercase letters and spaces, and number them so that they’re
+ properly ordered in globs (e.g. rename @em{-The Return of the King.mp4}
+ to @em{-02-the-return-of-the-king.mp4}):
+ }
+ figure {
+ pre { m4_fmt_code(examples/number.sh.gsp) }
+ }
+
+ p {-
+ Rename files interactively in your editor while encoding newline into
+ the literal string ‘@code{-\\n}’, making use of
+ @code {
+ a
+ href="https://linux.die.net/man/1/vipe"
+ target="_blank"
+ {-vipe(1)}
+ }
+ from @em{-moreutils}:
+ }
+ figure {
+ pre { m4_fmt_code(examples/vipe.sh.gsp) }
+ }
+
+ p {-
+ Rename all C source code- and header files in a git repository
+ to use snake_case instead of camelCase using
+ the m4_abbr(GNU)
+ @code {
+ a
+ href="https://www.man7.org/linux/man-pages/man1/sed.1.html"
+ target="_blank"
+ {-sed(1)}
+ }
+ ‘@code{-\\n}’ extension:
+ }
+ figure {
+ pre { m4_fmt_code(examples/camel-to-snake.sh.gsp) }
+ }
+
+ p {-
+ Lowercase all filenames within a directory hierarchy which may contain
+ newline characters:
+ }
+ figure {
+ pre { m4_fmt_code(examples/lowercase.sh.gsp) }
+ }
+
+ p {-
+ Map filenames which may contain newlines in the current directory with
+ the command ‘@code{-cmd}’, which itself does not support nul-byte
+ separated entries. This only works assuming your mapping doesn’t
+ require any context outside of the given input filename (for example,
+ you would not be able to number your files as this requires knowledge of
+ the input files position in the input list):
+ }
+ figure {
+ pre { m4_fmt_code(examples/i-flag.sh.gsp) }
+ }
+ }
+
+ hr{}
+
+ footer { m4_footer }
+ }
+}
diff --git a/src/prj/mmv/index.html b/src/prj/mmv/index.html
deleted file mode 100644
index 09aadb1..0000000
--- a/src/prj/mmv/index.html
+++ /dev/null
@@ -1,667 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
- <head>
- m4_include(head.html)
- </head>
- <body>
- <header>
- <div>
- <h1>Moving Files the Right Way</h1>
- m4_include(nav.html)
- </div>
-
- <figure class="quote">
- <blockquote>
- <p>I think the OpenBSD crowd is a bunch of masturbating
- monkeys, in that they make such a big deal about
- concentrating on security to the point where they pretty much
- admit that nothing else matters to them.</p>
- </blockquote>
- <figcaption>
- Linux Torvalds
- </figcaption>
- </figure>
- </header>
-
- <main>
- <p>
- <em>
- You can find the <code>mmv</code> git repository over at
- <a href="https://git.sr.ht/~mango/mmv" target="_blank">sourcehut</a>
- or <a href="https://github.com/Mango0x45/mmv"
- target="_blank">GitHub</a>.
- </em>
- </p>
-
- <p>
- NOTE: As of the
- <a href="https://git.sr.ht/~mango/mmv/refs/v1.2.0">v1.2.0</a> release
- there is now also the <code>mcp</code> utility. It behaves the same as
- the <code>mmv</code> utility but it copies files instead of moving them.
- It also doesn’t support the ‘<code>-n</code>’ flag as it doesn’t need to
- deal with backups.
- </p>
-
- <h2>Table of Contents</h2>
-
- <ul>
- <li><a href="#prologue">Prologue</a></li>
- <li><a href="#moving">Advanced Moving and Pitfalls</a></li>
- <li><a href="#mapping">Name Mapping with <code>mmv</code></a></li>
- <li><a href="#newlines">Filenames with Embedded Newlines</a></li>
- <ul>
- <li><a href="#0-flag">The Simple Case</a></li>
- <li><a href="#e-flag">Encoding Newlines</a></li>
- </ul>
- <li><a href="#i-flag">Individual Execution</a></li>
- <li><a href="#safety">Safety</a></li>
- <li><a href="#examples">Examples</a></li>
- </ul>
-
- <h2 id="prologue">Prologue</h2>
- <p>
- File moving and renaming is one of the most common tasks we
- undertake on the command-line. We basically always do this with
- the <code>mv</code> utility, and it gets the job done most of the
- time. Want to rename one file? Use <code>mv</code>! Want to
- move a bunch of files into a directory? Use <code>mv</code>!
- How could mv ever go wrong? Well I’m glad you asked!
- </p>
-
- <h2 id="moving">Advanced Moving and Pitfalls</h2>
- <p>
- Let’s start off nice and simple. You just inherited a C project
- that uses the sacrilegious
- <a
- href="https://en.wikipedia.org/wiki/Camel_case"
- target="_blank"
- >camelCase</a>
- naming convention for its files:
- </p>
-
- <figure>
- <pre>m4_fmt_code(ls-files.sh.html)</pre>
- </figure>
-
- <p>
- This deeply upsets you, as it upsets me. So you decide you want
- to switch all these files to use
- <a
- href="https://en.wikipedia.org/wiki/Snake_case"
- target="_blank"
- >snake_case</a>,
- like a normal person. Well how would you do this? You use
- <code>mv</code>! This is what you might end up doing:
- </p>
-
- <figure>
- <pre>m4_fmt_code(manual-mv.sh.html)</pre>
- </figure>
-
- <p>
- Well… it works I guess, but it’s a pretty shitty way of renaming
- these files. Luckily we only had 5, but what if this was a much
- larger project with many more files to rename? Things would get
- tedious. So instead we can use a pipeline for
- this:
- </p>
-
- <figure>
- <pre>m4_fmt_code(camel-to-snake-naïve.sh.html)</pre>
- </figure>
-
- <aside>
- <p>
- The given example assumes your <code>sed</code>
- implementation supports ‘<code>\L</code>’ which is a
- non-standard <abbr class="gnu">GNU</abbr> extension.
- </p>
- </aside>
-
- <p>
- That works and it gets the job done, but it’s not really ideal is
- it? There are a couple of issues with this.
- </p>
-
- <ol>
- <li>
- <p>
- You’re writing more complicated code. This has the
- obvious drawback of potentially being more error-prone,
- but also risks taking more time to write than you’d like
- as you might have forgotten if <code>xargs</code>
- actually has an ‘<code>-L</code>’ option or not (which
- would require reading the
- <a href="https://www.man7.org/linux/man-pages/man1/xargs.1.html"
- target="_blank" ><code>xargs(1)</code></a> manual).
- </p>
- </li>
- <li>
- <p>
- If you try to rename the file <em>foo</em>
- to <em>bar</em> but <em>bar</em> already exists, you end
- up deleting a file you may not have wanted to.
- </p>
- </li>
- <li>
- <p>
- In a similar vein to the previous point, you need to be
- very careful about schemes like renaming the
- file <em>a</em> to <em>b</em> and <em>b</em>
- to <em>c</em>. You run the risk of turning <em>a</em>
- into <em>c</em> and losing the file <em>b</em> entirely.
- </p>
- </li>
- <li>
- <p>
- Moving symbolic links is its own whole can of worms. If
- a symlink points to a relative location then you need to
- make sure you keep pointing to the right place. If the
- symlink is absolute however then you can leave it
- untouched. But what if the symlink points to a file
- that you’re moving as part of your batch move operation?
- Now you need to handle that too.
- </p>
- </li>
- </ol>
-
- <h2 id="mapping">Name Mapping with <code>mmv</code></h2>
-
- <p>
- What is <code>mmv</code>? It’s the solution to all your
- problems, that’s what it is! <code>mmv</code> takes as its
- argument(s) a utility and that utilities arguments and uses that
- to create a mapping between old and new filenames — similar to
- the <code>map()</code> function found in many programming
- languages. I think to best convey how the tool functions, I
- should provide an example. Let’s try to do the same thing we did
- previously where we tried to turn camelCase files to snake_case,
- but using <code>mmv</code>:
- </p>
-
- <figure>
- <pre>m4_fmt_code(camel-to-snake-smart.sh.html)</pre>
- </figure>
-
- <p>Let me break down how this works.</p>
-
- <p>
- <code>mmv</code> starts by reading a series of filenames
- separated by newlines from the standard input. Yes, sometimes
- filenames have newlines in them and yes there is a way to handle
- them but I shall get to that later. The filenames that
- <code>mmv</code> reads from the standard input will be referred
- to as the <em>input files</em>. Once all the input files have
- been read, the utility specified by the arguments is spawned; in
- this case that would be <code>sed</code> with the argument
- <code>'s/[A-Z]/\L_&/g'</code>. The input files are then piped
- into <code>sed</code> the exact same way that they would have
- been if we ran the above commands without <code>mmv</code>, and
- the output of <code>sed</code> then forms what will be referred
- to as the <em>output files</em>. Once a complete list of output
- files is accumulated, each input file gets renamed to its
- corresponding output file.
- </p>
-
- <p>
- Let’s look at a simpler example. Say we want to rename 2 files
- in the current directory to use lowercase letters, we could use
- the following command:
- </p>
-
- <figure>
- <pre>m4_fmt_code(mmv-tr.sh.html)</pre>
- </figure>
-
- <p>
- In the above example <code>mmv</code> reads 2 lines from
- standard input, those being <em>LICENSE</em>
- and <em>README</em>. Those are our 2 input files now.
- The <code>tr</code> utility is then spawned and the input files
- are piped into it. We can simulate this in the shell:
- </p>
-
- <figure>
- <pre>m4_fmt_code(tr.sh.html)</pre>
- </figure>
-
- <p>
- As you can see above, <code>tr</code> has produced 2 lines of
- output; these are our 2 output files. Since we now have our 2
- input files and 2 output files, <code>mmv</code> can go ahead
- and rename the files. In this case it will rename
- <em>LICENSE</em> to <em>license</em> and
- <em>README</em> to <em>readme</em>. For some examples, check
- the <a href="#examples">examples</a> section of this page down
- below.
- </p>
-
- <h2 id="newlines">Filenames with Embedded Newlines</h2>
-
- <p>
- People are retarded, and as a result we have filenames with
- newlines in them. All it would have taken to solve this issue
- for everyone was for literally <strong>anybody</strong> during
- the early UNIX days to go “<em>hey, this is a bad idea!</em>”,
- but alas, we must deal with this. Newlines are of course not
- the only special characters filenames can contain, but they are
- the single most infuriating to deal with; the UNIX utilities all
- being line-oriented really doesn’t work well with these files.
- </p>
-
- <p>
- So how does <code>mmv</code> deal with special characters, and
- newlines in particular? Well it does so by providing the user
- with the <code>-0</code> and <code>-e</code> flags:
- </p>
-
- <dl>
- <dt><code>-0</code></dt>
- <dd>
- <p>
- Tell <code>mmv</code> to expect its input to not be
- separated by newlines (‘<code>\n</code>’), but by NUL
- bytes (‘<code>\0</code>’). NUL bytes are the only
- characters not allowed in filenames besides forward
- slashes, so they are an obvious choice for an
- alternative separator.
- </p>
- </dd>
- <dt><code>-e</code></dt>
- <dd>
- <p>
- Encode newlines in filenames before passing them to the
- provided utility. Newline characters are replaced by the
- literal string ‘<code>\n</code>’ and backslashes by the
- literal string ‘<code>\\</code>’. After processing, the
- resulting output is decoded again.
- </p>
- <p>
- If combined with the <code>-0</code> flag, then while
- input will be read assuming a NUL-byte input-seperator,
- the encoded input files will be written to the spawned
- process newline-seperated.
- </p>
- </dd>
- </dl>
-
- <h3 id="0-flag">The Simple Case</h3>
-
- <p>
- In order to better understand these flags and how they work
- let’s go though another example. We have 2 files — one with and
- one without an embedded newline — and our goal is to simply
- reverse these filenames. In this example I am going to be
- displaying newlines in filenames with the “<code>$'\n'</code>”
- syntax as this is how my shell displays embedded newlines.
- </p>
-
- <p>
- We can start by just trying to naïvely pass these 2 files
- to <code>mmv</code> and use <code>rev</code> to reverse the
- names, but this doesn’t work:
- </p>
-
- <figure>
- <pre>m4_fmt_code(mmv-rev.sh.html)</pre>
- </figure>
-
- <p>
- The reason this doesn’t work is because due to the line-oriented
- nature of <code>ls</code> and <code>rev</code>, we are actually
- trying to rename the files <em>foo</em>, <em>bar</em>, and
- <em>baz</em> to the new filenames <em>zab</em>,
- <em>rab</em>, and <em>oof</em>. As can be seen in the following
- diagram, the embedded newline is causing our input to be ambiguous
- and <code>mmv</code> can’t reliably proceed
- anymore <x-ref>1</x-ref>:
- </p>
-
- <figure>
- <object data="conflict.svg" type="image/svg+xml"></object>
- </figure>
-
- <aside>
- <p data-ref="1">
- The reason you get a cryptic “file not found” error message
- is because <code>mmv</code> tries to assert that all the
- input files actually exist before doing anything. Since
- “foo” isn’t a real file, we error out.
- </p>
- </aside>
-
- <p>
- The first thing we need to do in order to proceed is to pass
- the <code>-0</code> flag to <code>mmv</code>. This will
- tell <code>mmv</code> that we want to use the NUL-byte as our
- input separator and not the newline. We also need <code>ls</code>
- to actually provide us with the filenames delimited by NUL-bytes.
- Luckily <abbr class="gnu">GNU</abbr> <code>ls</code> gives us the
- <code>--zero</code> flag to do just that:
- </p>
-
- <figure>
- <pre>m4_fmt_code(mmv-rev-zero.sh.html)</pre>
- </figure>
-
- <p>
- So we’re getting places, but we aren’t quite there yet. The
- issue we’re getting now is that <code>mmv</code> recieved 2
- input files from the standard input, but <code>rev</code>
- produced 3 output files. Why is that? Well let’s try our hand
- at a little bit of command-line debugging with <code>sed</code>:
- </p>
-
- <figure>
- <pre>m4_fmt_code(sed-debugging.sh.html)</pre>
- </figure>
-
- <p>
- If you aren’t quite sure what the above is doing, here’s a quick
- summary:
- </p>
-
- <ul>
- <li>
- The <code>-U</code> flag given to <code>ls</code> tells it
- not to sort our output. This is purely just to keep this
- example clear to the reader.
- </li>
- <li>
- The <code>-n</code> flag given to <code>sed</code> tells it
- not to print the input line automatically at the end of the
- provided script.
- </li>
- <li>
- The <code>l</code> command in <code>sed</code> prints the
- current input in a “visually unambiguous form”.
- </li>
- </ul>
-
- <p>
- In the <code>sed</code> output, we can see that <samp>$</samp>
- represents the end of a line, and <samp>\000</samp> represents
- the NUL-byte. All looks good here, we have two inputs seperated
- by NUL-bytes. Now let’s try to throw in <code>rev</code>:
- </p>
-
- <figure>
- <pre>m4_fmt_code(sed-debugging-rev.sh.html)</pre>
- </figure>
-
- <p>
- Well wouldn’t you know it? Since <code>rev</code> <em>also</em>
- works with newline-seperated input, it reversed out NUL-byte
- seperators and now gives us 3 outputs. Luckily the folks over
- at <em>util-linux</em> provided us with the <code>-0</code> flag
- here too, so that we can properly handle NUL-delimited input.
- Combining all of this together we get a final working product:
- </p>
-
- <figure>
- <pre>m4_fmt_code(reverse-embedded-newline.sh.html)</pre>
- </figure>
-
- <h3 id="e-flag">Encoding Newlines</h3>
-
- <p>
- Sometimes we want to rename a bunch of files, but the command we
- want to use doesn’t support NUL-bytes as nicely as we would
- like. In these cases, you may want to consider encoding your
- newline characters into the literal string ‘<code>\n</code>’ and
- then passing your input newline-seperated to your given command
- with the <code>-e</code> flag.
- </p>
-
- <p>
- For a real-world example, perhaps you want to edit some
- filenames in vim, or whatever other editor you use. Well we can
- do this incredibly easily with the <code>vipe</code> utility
- from
- the <a href="https://joeyh.name/code/moreutils/">moreutils</a>
- collection. The <code>vipe</code> command simply reads input
- from the standard input, opens it up in your editor, and then
- prints the resulting output to the standard output; perfect
- for <code>mmv</code>! We do not really want to deal with
- NUL-bytes in our text-editor though, so let’s just encode our
- newlines:
- </p>
-
- <figure>
- <pre>m4_fmt_code(vipe.sh.html)</pre>
- </figure>
-
- <aside>
- <p>
- Notice how you still need to pass the <code>-0</code> flag
- to <code>mmv</code> know that our inputfiles may have
- embedded newlines.
- </p>
- </aside>
-
- <p>
- When running the above code example, you will see the following
- in your editor:
- </p>
-
- <figure>
- <pre>m4_fmt_code(vim.html)</pre>
- </figure>
-
- <p>
- After you exit your editor, <code>mmv</code> will decode all
- occurances of ‘<code>\n</code>’ back into a newline, and all
- occurances of ‘<code>\\</code>’ back into a backslash:
- </p>
-
- <figure>
- <object data="e-flag.svg" type="image/svg+xml"></object>
- </figure>
-
- <h2 id="i-flag">Individual Execution</h2>
- <p>
- The previous examples are great and all, but what do you do if
- your mapping command doesn’t have the concept of an input
- seperator at all? This is where the <code>-i</code> flag comes
- into play. With the <code>-i</code> flag we can
- get <code>mmv</code> to execute our mapping command for every
- input filename. This means that as long as we can work with a
- complete buffer, we don’t need to worry about seperators.
- </p>
-
- <p>
- To be honest, I cannot really think of any situation where you
- might actually need to do this. If you can think of one,
- please <a href="mailto:mail@thomasvoss.com">email me</a> and
- I’ll update the example on this page. Regardless, let’s imagine
- that we wanted to rename some files so that their filenames are
- replaced with their filename
- <a href="https://en.wikipedia.org/wiki/SHA-1" target="_blank">
- SHA-1 hash</a>.
- On Linux we have the <code>sha1sum</code> program which reads
- input from the standard input and outputs the SHA-1 hash. This
- is how we would use it with <code>mmv</code>:
- </p>
-
- <figure>
- <pre>m4_fmt_code(sha1sum-long-example.sh.html)</pre>
- </figure>
-
- <p>
- Another approach is to invoke <code>mmv</code> twice:
- </p>
-
- <figure>
- <pre>m4_fmt_code(sha1sum-short-example.sh.html)</pre>
- </figure>
-
- <p>
- If you are confused about why we need to make a call
- to <code>awk</code>, it’s because the <code>sha1sum</code>
- program outputs 2 columns of data. The first column is our hash
- and the second column is the filename where the to-be-hashed
- data was read from. We don’t want the second column.
- </p>
-
- <p>
- Unlike in previous examples where one process was spawned to map
- all our filenames, with the <code>-i</code> flag we are spawning
- a new instance for each filename. If you struggle to visualize
- this, perhaps the following diagrams help:
- </p>
-
- <figure>
- <figcaption>Invoking <code>mmv</code> without <code>-i</code></figcaption>
- <object data="without-i-flag.svg" type="image/svg+xml"></object>
- </figure>
-
- <figure>
- <figcaption>Invoking <code>mmv</code> with <code>-i</code></figcaption>
- <object data="with-i-flag.svg" type="image/svg+xml"></object>
- </figure>
-
- <h2 id="safety">Safety</h2>
- <p>
- When compared to the standard <code>for f in *; do mv $f …;
- done</code> or <code>ls | … | xargs -L2 mv</code>
- constructs, <code>mmv</code> is significantly more safe to use.
- These are some of the safety features that are built into the
- tool:
- </p>
-
- <ol>
- <li>
- If the number of input- and output files differs, execution
- is aborted before making any changes.
- </li>
- <li>
- If an input file is renamed to the name of another input
- file, the second input file is not lost (i.e. you can rename
- <em>a</em> to <em>b</em> and <em>b</em> to <em>a</em> with
- no problem).
- </li>
- <li>
- All input files must be unique and all output files must be
- unique. Otherwise execution is aborted before making any
- changes.
- </li>
- <li>
- In the case that something goes wrong during execution
- (perhaps you tried to move a file to a non-existant
- directory, or a syscall failed), a backup of your input
- files is saved automatically by <code>mmv</code> for
- recovery.
- </li>
- </ol>
-
- <p>
- Due to the way <code>mmv</code> handles #2, when things do go
- wrong you may find that all of your input files have
- disappeared. Don’t worry though, <code>mmv</code> takes a
- backup of your code before doing anything. If you
- run <code>mmv</code> with the <code>-v</code> option for verbose
- output, you’ll notice it backing up your stuff in
- the <code>$XDG_CACHE_DIR</code> directory:
- </p>
-
- <figure>
- <pre>m4_fmt_code(mmv-verbose.sh.html)</pre>
- </figure>
-
- <p>
- Upon successful execution
- the <code>$XDG_CACHE_DIR/mmv/TIMESTAMP</code> directory will be
- automatically removed, but it remains when things go wrong so
- that you can recover any missing data. The names of the
- backup-subdirectories in the <code>$XDG_CACHE_DIR/mmv</code>
- directory are timestamps of when the directories were created.
- This should make it easier for you to figure out which directory
- you need to recover if you happen to have multiple of these.
- </p>
-
- <h2 id="examples">Examples</h2>
-
- <aside>
- <p>
- All of these examples are ripped straight from
- the <code>mmv(1)</code> manual page. If you
- installed <code>mmv</code> through a package manager or
- via <code>make install</code> then you should have the
- manual installed on your system.
- </p>
- </aside>
-
- <p>Swap the files <em>foo</em> and <em>bar</em>:</p>
- <figure>
- <pre>m4_fmt_code(examples/swap.sh.html)</pre>
- </figure>
-
- <p>
- Rename all files in the current directory to use hyphens (‘-’)
- instead of spaces:
- </p>
- <figure>
- <pre>m4_fmt_code(examples/hyphens.sh.html)</pre>
- </figure>
-
- <p>
- Rename a given list of movies to use lowercase letters and
- hyphens instead of uppercase letters and spaces, and number them
- so that they’re properly ordered in globs (e.g. rename <em>The
- Return of the King.mp4</em> to
- <em>02-the-return-of-the-king.mp4</em>):
- </p>
- <figure>
- <pre>m4_fmt_code(examples/number.sh.html)</pre>
- </figure>
-
- <p>
- Rename files interactively in your editor while encoding newline
- into the literal string ‘<code>\n</code>’, making use
- of <code><a href="https://linux.die.net/man/1/vipe"
- target="_blank">vipe(1)</a></code> from <em>moreutils</em>:
- </p>
- <figure>
- <pre>m4_fmt_code(examples/vipe.sh.html)</pre>
- </figure>
-
- <p>
- Rename all C source code- and header files in a git repository
- to use snake_case instead of camelCase using
- the <abbr class="gnu">GNU</abbr>
- <code><a href="https://www.man7.org/linux/man-pages/man1/sed.1.html"
- target="_blank">sed(1)</a></code> ‘<code>\n</code>’ extension:
- </p>
- <figure>
- <pre>m4_fmt_code(examples/camel-to-snake.sh.html)</pre>
- </figure>
-
- <p>
- Lowercase all filenames within a directory hierarchy which may
- contain newline characters:
- </p>
- <figure>
- <pre>m4_fmt_code(examples/lowercase.sh.html)</pre>
- </figure>
-
- <p>
- Map filenames which may contain newlines in the current
- directory with the command ‘<code>cmd</code>’, which itself does
- not support nul-byte separated entries. This only works
- assuming your mapping doesn’t require any context outside of the
- given input filename (for example, you would not be able to
- number your files as this requires knowledge of the input files
- position in the input list):
- </p>
- <figure>
- <pre>m4_fmt_code(examples/i-flag.sh.html)</pre>
- </figure>
- </main>
-
- <hr>
-
- <footer>
- m4_footer
- </footer>
- </body>
-</html>
diff --git a/src/prj/mmv/ls-files.sh.html b/src/prj/mmv/ls-files.sh.gsp
index d24b5af..95e6af3 100644
--- a/src/prj/mmv/ls-files.sh.html
+++ b/src/prj/mmv/ls-files.sh.gsp
@@ -1,2 +1,2 @@
-$ <span class="sh-fn">ls</span>
+$ @span .sh-fn {-ls}
bytecodeVm.c fastLexer.c fastLexer.h slowParser.c slowParser.h
diff --git a/src/prj/mmv/manual-mv.sh.gsp b/src/prj/mmv/manual-mv.sh.gsp
new file mode 100644
index 0000000..0dbc3e5
--- /dev/null
+++ b/src/prj/mmv/manual-mv.sh.gsp
@@ -0,0 +1,5 @@
+$ @span .sh-fn {-mv} bytecodeVm.c bytecode_vm.c
+$ @span .sh-fn {-mv} fastLexer.c fast_lexer.c
+$ @span .sh-fn {-mv} fastLexer.h fast_lexer.h
+$ @span .sh-fn {-mv} slowParser.c slow_parser.c
+$ @span .sh-fn {-mv} slowParser.h slow_parser.h
diff --git a/src/prj/mmv/manual-mv.sh.html b/src/prj/mmv/manual-mv.sh.html
deleted file mode 100644
index 2484d9f..0000000
--- a/src/prj/mmv/manual-mv.sh.html
+++ /dev/null
@@ -1,5 +0,0 @@
-$ <span class="sh-fn">mv</span> bytecodeVm.c bytecode_vm.c
-$ <span class="sh-fn">mv</span> fastLexer.c fast_lexer.c
-$ <span class="sh-fn">mv</span> fastLexer.h fast_lexer.h
-$ <span class="sh-fn">mv</span> slowParser.c slow_parser.c
-$ <span class="sh-fn">mv</span> slowParser.h slow_parser.h
diff --git a/src/prj/mmv/mmv-rev-zero.sh.gsp b/src/prj/mmv/mmv-rev-zero.sh.gsp
new file mode 100644
index 0000000..ef8d158
--- /dev/null
+++ b/src/prj/mmv/mmv-rev-zero.sh.gsp
@@ -0,0 +1,2 @@
+$ @span .sh-fn {-ls} --zero foo$'\\n'bar baz | @span .sh-fn {-mmv} -0 rev
+mmv: Files have been added or removed during editing
diff --git a/src/prj/mmv/mmv-rev-zero.sh.html b/src/prj/mmv/mmv-rev-zero.sh.html
deleted file mode 100644
index 4be992e..0000000
--- a/src/prj/mmv/mmv-rev-zero.sh.html
+++ /dev/null
@@ -1,2 +0,0 @@
-$ <span class="sh-fn">ls</span> --zero foo$'\n'bar baz | <span class="sh-fn">mmv</span> -0 rev
-mmv: Files have been added or removed during editing
diff --git a/src/prj/mmv/mmv-rev.sh.gsp b/src/prj/mmv/mmv-rev.sh.gsp
new file mode 100644
index 0000000..e2e6a1c
--- /dev/null
+++ b/src/prj/mmv/mmv-rev.sh.gsp
@@ -0,0 +1,2 @@
+$ @span .sh-fn {-ls} foo$'\\n'bar baz | @span .sh-fn {-mmv} rev
+mmv: No such file or directory (os error 2)
diff --git a/src/prj/mmv/mmv-rev.sh.html b/src/prj/mmv/mmv-rev.sh.html
deleted file mode 100644
index 3d2e683..0000000
--- a/src/prj/mmv/mmv-rev.sh.html
+++ /dev/null
@@ -1,2 +0,0 @@
-$ <span class="sh-fn">ls</span> foo$'\n'bar baz | <span class="sh-fn">mmv</span> rev
-mmv: No such file or directory (os error 2)
diff --git a/src/prj/mmv/mmv-tr.sh.gsp b/src/prj/mmv/mmv-tr.sh.gsp
new file mode 100644
index 0000000..83a728b
--- /dev/null
+++ b/src/prj/mmv/mmv-tr.sh.gsp
@@ -0,0 +1 @@
+$ @span .sh-fn {-ls} LICENSE README | @span .sh-fn {-mmv} tr A-Z a-z
diff --git a/src/prj/mmv/mmv-tr.sh.html b/src/prj/mmv/mmv-tr.sh.html
deleted file mode 100644
index 4d8773a..0000000
--- a/src/prj/mmv/mmv-tr.sh.html
+++ /dev/null
@@ -1 +0,0 @@
-$ <span class="sh-fn">ls</span> LICENSE README | <span class="sh-fn">mmv</span> tr A-Z a-z
diff --git a/src/prj/mmv/mmv-verbose.sh.html b/src/prj/mmv/mmv-verbose.sh.gsp
index 3767416..dc4dfa0 100644
--- a/src/prj/mmv/mmv-verbose.sh.html
+++ b/src/prj/mmv/mmv-verbose.sh.gsp
@@ -1,4 +1,4 @@
-$ <span class="sh-fn">ls</span> foo bar | <span class="sh-fn">mmv</span> -v awk <span class="sh-str">'{ printf "%d-%s\n", NR, $0 }'</span>
+$ @span .sh-fn {-ls} foo bar | @span .sh-fn {-mmv} -v awk @span .sh-str {-'{ printf "%d-%s\\n", NR, $0 \}'}
created directory ‘/home/thomas/.cache/mmv/1692102229’
created directory ‘/home/thomas/.cache/mmv/1692102229/home/thomas/code/repo/Mango0x45/mmv’
diff --git a/src/prj/mmv/reverse-embedded-newline.sh.gsp b/src/prj/mmv/reverse-embedded-newline.sh.gsp
new file mode 100644
index 0000000..3a91d08
--- /dev/null
+++ b/src/prj/mmv/reverse-embedded-newline.sh.gsp
@@ -0,0 +1,3 @@
+$ @span .sh-fn {-ls} --zero foo$'\\n'bar baz | @span .sh-fn {-mmv} -0 rev -0
+$ @span .sh-fn {-ls}
+'rab'$'\\n''oof' zab
diff --git a/src/prj/mmv/reverse-embedded-newline.sh.html b/src/prj/mmv/reverse-embedded-newline.sh.html
deleted file mode 100644
index ff84d5c..0000000
--- a/src/prj/mmv/reverse-embedded-newline.sh.html
+++ /dev/null
@@ -1,3 +0,0 @@
-$ <span class="sh-fn">ls</span> --zero foo$'\n'bar baz | <span class="sh-fn">mmv</span> -0 rev -0
-$ <span class="sh-fn">ls</span>
-'rab'$'\n''oof' zab
diff --git a/src/prj/mmv/sed-debugging-rev.sh.gsp b/src/prj/mmv/sed-debugging-rev.sh.gsp
new file mode 100644
index 0000000..39b7c5a
--- /dev/null
+++ b/src/prj/mmv/sed-debugging-rev.sh.gsp
@@ -0,0 +1,3 @@
+$ @span .sh-fn {-ls} -U --zero foo$'\\n'bar baz | @span .sh-fn {-rev} | @span .sh-fn {-sed} -n l
+oof$
+\\000zab\\000rab$
diff --git a/src/prj/mmv/sed-debugging-rev.sh.html b/src/prj/mmv/sed-debugging-rev.sh.html
deleted file mode 100644
index f1fddb1..0000000
--- a/src/prj/mmv/sed-debugging-rev.sh.html
+++ /dev/null
@@ -1,3 +0,0 @@
-$ <span class="sh-fn">ls</span> -U --zero foo$'\n'bar baz | <span class="sh-fn">rev</span> | <span class="sh-fn">sed</span> -n l
-oof$
-\000zab\000rab$
diff --git a/src/prj/mmv/sed-debugging.sh.gsp b/src/prj/mmv/sed-debugging.sh.gsp
new file mode 100644
index 0000000..3db277f
--- /dev/null
+++ b/src/prj/mmv/sed-debugging.sh.gsp
@@ -0,0 +1,3 @@
+$ @span .sh-fn {-ls} -U --zero foo$'\\n'bar baz | @span .sh-fn {-sed} -n l
+foo$
+bar\\000baz\\000$
diff --git a/src/prj/mmv/sed-debugging.sh.html b/src/prj/mmv/sed-debugging.sh.html
deleted file mode 100644
index e61cde4..0000000
--- a/src/prj/mmv/sed-debugging.sh.html
+++ /dev/null
@@ -1,3 +0,0 @@
-$ <span class="sh-fn">ls</span> -U --zero foo$'\n'bar baz | <span class="sh-fn">sed</span> -n l
-foo$
-bar\000baz\000$
diff --git a/src/prj/mmv/sha1sum-long-example.sh.gsp b/src/prj/mmv/sha1sum-long-example.sh.gsp
new file mode 100644
index 0000000..f997509
--- /dev/null
+++ b/src/prj/mmv/sha1sum-long-example.sh.gsp
@@ -0,0 +1,11 @@
+$ @span .sh-fn {-touch} foo bar
+$ @span .sh-fn {-cat} <<@span .sh-hd {-EOF} >hash-filename
+@span .sh-hd {-#!/bin/sh}
+
+@span .sh-hd {-sha1sum | awk '{ print \\$1 \}'}
+@span .sh-hd {-EOF}
+$ @span .sh-fn {-chmod} +x hash-filename
+$ @span .sh-fn {-ls} foo bar | @span .sh-fn {-mmv} -i ./hash-filename
+$ @span .sh-fn {-ls}
+e242ed3bffccdf271b7fbaf34ed72d089537b42f hash-filename
+f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
diff --git a/src/prj/mmv/sha1sum-long-example.sh.html b/src/prj/mmv/sha1sum-long-example.sh.html
deleted file mode 100644
index ddbda86..0000000
--- a/src/prj/mmv/sha1sum-long-example.sh.html
+++ /dev/null
@@ -1,11 +0,0 @@
-$ <span class="sh-fn">touch</span> foo bar
-$ <span class="sh-fn">cat</span> &lt;&lt;<span class="sh-hd">EOF</span> &gt;hash-filename
-<span class="sh-hd">#!/bin/sh</span>
-
-<span class="sh-hd">sha1sum | awk '{ print \$1 }'</span>
-<span class="sh-hd">EOF</span>
-$ <span class="sh-fn">chmod</span> +x hash-filename
-$ <span class="sh-fn">ls</span> foo bar | <span class="sh-fn">mmv</span> -i ./hash-filename
-$ <span class="sh-fn">ls</span>
-e242ed3bffccdf271b7fbaf34ed72d089537b42f hash-filename
-f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
diff --git a/src/prj/mmv/sha1sum-short-example.sh.gsp b/src/prj/mmv/sha1sum-short-example.sh.gsp
new file mode 100644
index 0000000..0811dde
--- /dev/null
+++ b/src/prj/mmv/sha1sum-short-example.sh.gsp
@@ -0,0 +1,6 @@
+$ @span .sh-fn {-touch} foo bar
+$ @span .sh-fn {-ls} | @span .sh-fn {-mmv} -i sha1sum
+$ @span .sh-fn {-ls} | @span .sh-fn {-mmv} awk @span .sh-str {-'{ print $1 \}'}
+$ @span .sh-fn {-ls}
+e242ed3bffccdf271b7fbaf34ed72d089537b42f
+f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
diff --git a/src/prj/mmv/sha1sum-short-example.sh.html b/src/prj/mmv/sha1sum-short-example.sh.html
deleted file mode 100644
index 99e781c..0000000
--- a/src/prj/mmv/sha1sum-short-example.sh.html
+++ /dev/null
@@ -1,6 +0,0 @@
-$ <span class="sh-fn">touch</span> foo bar
-$ <span class="sh-fn">ls</span> | <span class="sh-fn">mmv</span> -i sha1sum
-$ <span class="sh-fn">ls</span> | <span class="sh-fn">mmv</span> awk <span class="sh-str">'{ print $1 }'</span>
-$ <span class="sh-fn">ls</span>
-e242ed3bffccdf271b7fbaf34ed72d089537b42f
-f1d2d2f924e986ac86fdf7b36c94bcdf32beec15
diff --git a/src/prj/mmv/tr.sh.gsp b/src/prj/mmv/tr.sh.gsp
new file mode 100644
index 0000000..e357de8
--- /dev/null
+++ b/src/prj/mmv/tr.sh.gsp
@@ -0,0 +1,3 @@
+$ @span .sh-fn {-ls} LICENSE README | @span .sh-fn {-tr} A-Z a-z
+license
+readme
diff --git a/src/prj/mmv/tr.sh.html b/src/prj/mmv/tr.sh.html
deleted file mode 100644
index db08c38..0000000
--- a/src/prj/mmv/tr.sh.html
+++ /dev/null
@@ -1,3 +0,0 @@
-$ <span class="sh-fn">ls</span> LICENSE README | <span class="sh-fn">tr</span> A-Z a-z
-license
-readme
diff --git a/src/prj/mmv/vim.gsp b/src/prj/mmv/vim.gsp
new file mode 100644
index 0000000..1c20dca
--- /dev/null
+++ b/src/prj/mmv/vim.gsp
@@ -0,0 +1,2 @@
+foo\\nbar
+baz
diff --git a/src/prj/mmv/vim.html b/src/prj/mmv/vim.html
deleted file mode 100644
index cd89cd8..0000000
--- a/src/prj/mmv/vim.html
+++ /dev/null
@@ -1,2 +0,0 @@
-foo\nbar
-baz
diff --git a/src/prj/mmv/vipe.sh.gsp b/src/prj/mmv/vipe.sh.gsp
new file mode 100644
index 0000000..690db73
--- /dev/null
+++ b/src/prj/mmv/vipe.sh.gsp
@@ -0,0 +1 @@
+$ @span .sh-fn {-ls} --zero foo$'\\n'bar baz | @span .sh-fn {-mmv} -0e vipe
diff --git a/src/prj/mmv/vipe.sh.html b/src/prj/mmv/vipe.sh.html
deleted file mode 100644
index c56ff08..0000000
--- a/src/prj/mmv/vipe.sh.html
+++ /dev/null
@@ -1 +0,0 @@
-$ <span class="sh-fn">ls</span> --zero foo$'\n'bar baz | <span class="sh-fn">mmv</span> -0e vipe