I want to write zsh completions for a program with the following calling convention:
program [generaloptions] operation [operationoptions]
where operation is one of --install, --upgrade...
What I have so far, are the general options and the operation options. My code looks something like this:
local generaloptions; generaloptions=(...)
local installoptions; installoptions=(...)
local upgradeoptions; upgradeoptions=(...)
case "$state" in
(install)
_arguments -s \
"$installoptions[#]" \
&& ret=0
(upgrade)
_arguments -s \
"$upgradeoption[#]" \
&& ret=0
*)
_arguments -s \
"$generaloptions[#]" \
'--install[...]: :->install' \
'--upgrade[...]: :->upgrade' \
&& ret=0
The problem is, after I type the operation and the first operation option, the state gets reset to the *) case.
Example
$ program --install --installoption --<tab>
list of general options
How can I set the next state to be the same as the old? Which command has similar calling conventions, so I can look at the code of the completion for this command?
The main problem is that the operations start with a --, so it is harder to find them in the arguments. In git for example all subcommands are only a word without dashes. So git solves this problem something like this:
Find the first argument without dashes because this must be the subcommand
Dispatch based on the subcommand to the commandline arguments for that subcommand.
So git dispatches in every call to the completion function (this was what I meant with "holding the state").
The way I solved this problem was by looking through many completion functions and finding a command that had a similar calling convention. The command that I found the most useful is pacman. Here is what I extracted from that:
# This somehow disassembles the commandline options
args=( ${${${(M)words:#-*}#-}:#-*}
case $args in
*i)
_arguments -s \
${installoptions} \
'(-i[...]' \
&& ret=0
;;
*u)
_arguments -s \
${upgradeoption} \
'-u[...]' \
&& ret=0
;;
*)
case ${(M)words:#--*} in
*--install*)
_arguments -s \
${installoptions} \
'--install[...]' \
&& ret=0
;;
*--upgrade*)
_arguments -s \
${upgradeoption} \
'--upgrade[...]' \
&& ret=0
;;
*)
_arguments -s \
{generaloptions} \
&& ret=0
;;
esac
esac
I know, there is a lot of dublication, but I think you get the point. Also notice, I moved the --install and --upgrade options from the general case to the operation case. If you don't do that, you loose the argument if you want complete after --install or --upgrade
Related
I'm trying to bootstrap some installation automation of a freshly downloaded ISO in QEMU. I create a clean img to install to and kick off QEMU like this:
$ qemu-img create -f qcow2 out/main.img 15G
$ qemu-system-x86_64
-m 8G \
-serial stdio \
-cdrom out/linux.iso \
-drive file=out/main.img,if=virtio \
-netdev user,id=net0 \
-device e1000,netdev=net0
and I can see Arch boot up. At first both the display and the terminal are in sync, but they soon diverge after this the GRUB boot up screen.
I'm not sure what piece I'm missing to get this to work. I've seen some people suggest adding -append "root=/dev/sda console=ttyS0" to your QEMU arguments, but (from what I can tell) while it requires you to extract the kernel and the initram from the ISO (which should be easy enough as mounting and copy/pasting the correct files) but it also expects you to already have an installed system on /dev/sda (which is what I'm trying to bootstrap).
At this point I don't know what to search for next, how do I get the full terminal session in my current terminal and not just in my display?
In this case, it was as #Peter Maydell commented; this is not a QEMU question. QEMU was doing exactly what it was supposed to do, but Arch had to be told to utilize the serial console as its primary means of communication.
Two samples of how this can be done
bash via console
pipe_dir="$(mktemp -d)"
mkfifo "${pipe_dir}/pipe.in" "${pipe_dir}/pipe.out"
function cleanup {
rm -rfv "${pipe_dir}"
}
trap cleanup EXIT
qemu-system-x86_64 \
-m 8G \
-display none \
-serial stdio \
-drive file=./out/linux.iso,index=0,media=cdrom \
-drive file=./out/main.img,if=virtio &
sleep 2s
printf "\t" > "${pipe_dir}/pipe.in"
sleep 2s
printf " console=ttyS0,115200" > "${pipe_dir}/pipe.in"
sleep 2s
echo > "${pipe_dir}/pipe.in"
# Whatever other interactions you want go here...
wait
expect via console
set timeout -1
spawn qemu-system-x86_64 \
-m 8G \
-display none \
-serial stdio \
-drive file=./out/linux.iso,index=0,media=cdrom \
-drive file=./out/main.img,if=virtio
sleep 1
send \t
sleep 1
send " console=ttyS0,115200"
sleep 1
send \n
In theory this should be fine, but in practice I still had difficulty interacting with the console and sending characters over to login correctly. I'm sure there is probably more user-error on my part than not.
A better solution (again contextual to Arch and not QEMU specifically) was to use a cloud-init script that included my SSH public key. Interactions with the VM were stable, reliable, and easily reproducible.
bash with cloud-init/ssh
$ touch ./out/meta-data
$ cat > ./out/user-data <<EOF
#cloud-config
users:
- name: root
ssh_authorized_keys:
- $(cat ${HOME}/.ssh/id_ed25519.pub)
EOF
$ xorriso -as genisoimage -output ./out/cloud-init.iso \
-volid CIDATA -joliet -rock ./out/meta-data ./out/user-data
$ qemu-system-x86_64 \
-m 8G \
-drive file=./out/linux.iso,index=0,media=cdrom \
-drive file=./out/cloud-init.iso,index=1,media=cdrom \
-drive file=./out/main.img,if=virtio \
-net user,hostfwd=tcp::10022-:22 \
-net nic &
$ function qemu-ssh {
ssh -q -o ConnectTimeout=5 -o StrictHostKeyChecking=no -o "UserKnownHostsFile /dev/null" -p 10022 root#localhost ${#}
}
$ printf 'Waiting for SSH to go live (this will take a while)...'
$ until qemu-ssh exit; do
printf '.'
done
# This convenience function starts an interactive
# session when supplied with no additional arguments
# but your automation can go here
$ qemu-ssh
I am trying to write a tab-completion script for borg.
So far, I have managed to define completions for borg itself, as well as borg key with its subcommands and borg benchmark with its singular subcommand. However, I am now trying to define completion for borg init and I am having trouble.
The issue presents itself only when I define two arguments under the borg init command to use the same description text; i.e. both -e and --encryption should use the same description, as they are practically the same argument. This has worked fine for borg's arguments, but now it breaks.
This is my code, slightly redacted to spare you the redundancy:
compdef _borg borg
function _borg {
local line ret=1
local -a argus
local logs="--critical --error --warning --debug --info -v --verbose"
argus+=(
"(*)"{-h,--help}"[Show help and exit]"
"(*)-V[Show Borg version and exit]"
"($logs)--critical[Work on log level CRITICAL]"
"($logs)--error[Work on log level ERROR]"
"($logs)--warning[Work on log level WARNING (default)]"
"($logs)"{--info,-v,--verbose}"[Work on log level INFO]"
"($logs)--debug[Enable debug output; log level DEBUG]"
{-p,--progress}"[Show progress]"
"--log-json[Output one JSON object per log line instead of formatted text]"
"--show-version[Show/log borg version]"
"--show-rc[Show/log returncode]"
"--consider-part-files[treat part files like normal files (e.g. to list/extract them)]"
"--lock-wait[Wait at most SECONDS for acquiring a repository/cache lock (default 1)]:SECONDS:()"
"--umask[Set umask to M (local and remote; default 0077)]:M (umask value, e.g. 0077):()"
"--remote-path[Use PATH as borg executable on the remote (default: \"borg\")]:PATH:()"
"--remote-ratelimit[Set remote network upload rate limit in kiByte/s (default: 0=unlimited)]:RATE:()"
"--debug-profile[Write execution profile in Borg format into FILE.]:FILE:_files"
"--rsh[Use this command to connect to the \"borg serve\" process (default: \"ssh\")]:RSH:()"
"1: :((init\:\"Initialize a new repository\" \
create\:\"Create a new archive\" \
extract\:\"Extract the contents of an archive\" \
check\:\"Verifies consistency of a repository and its archives\" \
rename\:\"Renames an archive in a repository\" \
list\:\"Lists contents of a repository or archive\" \
diff\:\"Finds differences between archives\" \
delete\:\"Deletes an archive or an entire repository (and its cache)\" \
prune\:\"Prunes a repository\" \
info\:\"Shows info about a repository or archive\" \
mount\:\"Mounts an archive as a FUSE filesystem\" \
unmount\:\"Unmounts a FUSE filesystem mounted with \\\"borg mount\\\"\" \
key\:\"Keyword for key-related functions\" \
upgrade\:\"Upgrade a local Borg repository\" \
recreate\:\"EXPERIMENTAL: Recreates contents of existing archives\" \
export-tar\:\"Creates a tarball from an archive\" \
serve\:\"Starts repository server process. Not usually used manually.\" \
config\:\"Gets and sets options in local repository and cache config files\" \
with-lock\:\"Executes another command with the repository lock held\" \
break-lock\:\"Breaks the repository and cache locks\" \
benchmark\:\"Keyword for the benchmark function\"))" \
"*::arg:->args"
)
_arguments -w -s -S -C $argus[#] && ret=0
case $line[1] in
benchmark)
_borg_benchmark
;;
init)
_borg_init
;;
key)
_borg_key
;;
esac
return ret
}
function _borg_benchmark {
# stuff
}
function _borg_benchmark_crud {
# stuff again
}
function _borg_init {
local line ret=1
local -a argus
argus+=(
"-t[This is a test]"
"--test[This is a test]"
"(--append-only)--append-only[Create an append-only mode repository]"
"*::arg:->args"
)
_arguments -w -s -S -C $argus[#] && ret=0
return ret
}
function _borg_key {
# key stuff
}
function _borg_key_changepassphrase {
# stuff
}
function _borg_key_export {
# more stuff
}
function _borg_key_import {
# other stuff
}
If I try to tab-complete borg init - using this setup, I get the following output:
$ borg init -
Completing option
--append-only
--test
-t
-- Create an append-only mode repository
-- This is a test
--append-only
--test
-t
-- Create an append-only mode repository
-- This is a test
--append-only
--test
-t
-- Create an append-only mode repository
-- This is a test
--append-only
--test
-t
-- Create an append-only mode repository
-- This is a test
The completion appears to forget what tabs are and repeats itself four times. If I change --test[This is a test] to --test[This is another test] in _borg_init, I instead get the following completion:
$ borg init -
Completing option
--append-only -- Create an append-only mode repository
--test -- This is another test
-t -- This is a test
The above is "correct", in the sense that it's not broken, but I cannot seem to define arguments that share a description in a subcommand. How should I do that? And, more generally, how are you supposed to define completions for commands with subcommands (which may, in turn, have more arguments)?
I know that is possible to use && (and) statement to go running multiple commands for a same alias. However for long combinations it loses in readability. For example:
save = !git status && git add -A && git commit -m \"$1\" && git push --force && git log && :
Is there a multi-line way to write it?
Maybe wrapping it with {} for example?
You can use a line escape (\) to break lines like this:
[alias]
save = !git status \
&& git add -A \
&& git commit -m \"$1\" \
&& git push -f \
&& git log -1 \
&& : # Used to distinguish last command from arguments
You can also put multiple statements inside a function like this:
[alias]
save = "!f() { \
git status; \
git add -A; \
git commit -m "$1"; \
git push -f; \
git log -1; \
}; \
f; \
unset f"
See Also: Git Alias - Multiple Commands and Parameters
I'd refrain from writing such extensive aliases in the config file. You can also add new commands by adding an executable file named git-newcommand to your PATH. This could be a Bash script, Python script or even a binary as long as it's executable and named with the prefix "git-".
In case of scripts you've to add the proper Hashbang:
#!/usr/bin/env python
Export the PATH, for example in your home:
export PATH="${PATH}:${HOME}/bin"
This is more modular, portable and easier debuggable.
I have a Makefile which creates build a programme called monitor:
fo/monitor: fo/monitor.c fo/inotify.c
(cd fo ; $(MAKE) monitor)
I have two types of system that I can run my Make on, and only wish to have have one installer.
So I would like to add an IF statement to this to check for a file, and if it exists, then to build the monitor.
fo/monitor:
if [ -f path/to/file/exists ]; \
then \
fo/monitor.c fo/inotify.c \
(cd fo ; $(MAKE) monitor) \
else \
echo "" >/dev/null \
fi \
The problem is, when I attempt to run the Makefile - it falls over becuase it does not like this code - can anyone point me in the right direction please?
The fo/monitor.c and fo/inotify.c have to be added to the targets dependencies, and not in the if statement. You can also use the -C option of make instead of using a subshell. And you do have to echo nothing in nothing.
This should be good:
fo/monitor: fo/monitor.c fo/inotify.c
if [ -f path/to/file/exists ]; then \
$(MAKE) -C fo monitor; \
fi
Another way is to depend on that target only if path/to/file/exists exists:
# add fo/monitor dependency only if path/to/file/exists exists
all : $(shell test -e path/to/file/exists && echo "fo/monitor")
fo/monitor: fo/monitor.c fo/inotify.c
${MAKE} -C ${#D}
I have a build tool that runs a patch command and if the patch command returns non-zero, it will cause the build to fail. I am applying a patch that may or may not already be applied, so I use the -N option to patch, which skips as it should. However, when it does skip, patch is returning non-zero. Is there a way to force it to return 0 even if it skips applying patches? I couldn't find any such capability from the man page.
Accepted answer did not work for me because patch was returning 1 also on other types of errors (maybe different version or sth).
So instead, in case of error I am checking output for "Skipping patch" message to ignore such but return error on other issues.
OUT="$(patch -p0 --forward < FILENAME)" || echo "${OUT}" | grep "Skipping patch" -q || (echo "$OUT" && false);
I believe that the following recipe should do the trick, it is what I am using in the same situation;
patches: $(wildcard $(SOMEWHERE)/patches/*.patch)
for patch_file in $^; do \
patch --strip=2 --unified --backup --forward --directory=<somewhere> --input=$$patch_file; \
retCode=$$?; \
[[ $$retCode -gt 1 ]] && exit $$retCode; \
done; \
exit 0
This recipe loops over the dependencies (in my case the patch files) and calls patch for each one. The "trick" on which I am relying is that patch returns 1 if the patch has already been applied and other higher numbers for other errors (such as a non existent patch file). The DIAGNOSTICS section of the patch manual entry describes the return code situation. YMMV
You can also do that as a one line only
patch -p0 --forward < patches/patch-babylonjs.diff || true
So if you want to apply the patch and make sure that's it's working:
(patch -p0 --forward < patches/patch-babylonjs.diff || true) && echo OK
No matter whether the patch has already been applied or not, you'll always get "OK" displayed here.
Below is a script that iterates on the above idea from #fsw and handles removal of .rej files as necessary.
#! /bin/sh
set +x
set -euo pipefail
bn=$(basename "$0")
patch="$1"; shift
r=$(mktemp /tmp/"$bn".XXXX)
if ! out=$(patch -p1 -N -r "$r" < "$patch")
then
echo "$out" | grep -q "Reversed (or previously applied) patch detected! Skipping patch."
test -s "$r" # Make sure we have rejects.
else
test -f "$r" && ! test -s "$r" # Make sure we have no rejects.
fi
rm -f "$r"