8000 Undeterministic zsh completion script output (depending on terminal size) · Issue #16072 · curl/curl · GitHub
[go: up one dir, main page]

Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Undeterministic zsh completion script output (depending on terminal size) #16072

Open
kpcyrd opened this issue Jan 22, 2025 · 1 comment
Open

Comments

@kpcyrd
Copy link
Contributor
kpcyrd commented Jan 22, 2025

I did this

I tried to reproduce the Arch Linux curl package by repeating the build in the documented build environment and noticed the zsh completions do not match:

https://web.archive.org/web/20250121190753/https://reproducible.archlinux.org/api/v0/builds/722464/diffoscope

@@ -13,19 +13,17 @@
   {-D,--dump-header}'[Write the received headers to <filename>]':'<filename>':_files \
   {-y,--speed-time}'[Trigger '\''speed-limit'\'' abort after this time]':'<seconds>' \
   --abstract-unix-socket'[Connect via abstract Unix domain socket]':'<path>':_files \
   --connect-to'[Connect to host2 instead of host1]':'<HOST1\:PORT1\:HOST2\:PORT2>' \
   {-E,--cert}'[Client certificate file and password]':'<certificate[\:password]>' \
   --proxy-capath'[CA directory to verify proxy against]':'<dir>':'_path_files -/' \
   --proxy-pinnedpubkey'[FILE/HASHES public key to verify proxy with]':'<hashes>' \
-  --preproxy'[\[protocol\://\]host\[\:port\]           Use this proxy first]' \
   --resolve'[Resolve host+port to address]':'<[+]host\:port\:addr[,addr]...>' \
   --aws-sigv4'[AWS V4 signature auth]':'<provider1[\:prvdr2[\:reg[\:srv]]]>' \
   {-b,--cookie}'[Send cookies from string/load from file]':'<data|filename>' \
-  {-x,--proxy}'[\[protocol\://\]host\[\:port\]              Use this proxy]' \
   --socks5-hostname'[SOCKS5 proxy, pass hostname to proxy]':'<host[\:port]>' \
   --ftp-method'[Control CWD usage]':'<method>':'(multicwd nocwd singlecwd)' \
   --proto-default'[Use PROTOCOL for any URL missing a scheme]':'<protocol>' \
   --proxy-cacert'[CA certificates to verify proxy against]':'<file>':_files \
   --socks5-gssapi-service'[SOCKS5 proxy service name for GSS-API]':'<name>' \
   --capath'[CA directory to verify peer against]':'<dir>':'_path_files -/' \
   --ftp-alternative-to-user'[String to replace USER \[name\]]':'<command>' \
@@ -56,14 +54,15 @@
   --proto-redir'[Enable/disable PROTOCOLS on redirect]':'<protocols>' \
   --proxy-cert'[Set client certificate for proxy]':'<cert[\:passwd]>' \
   --proxy-ssl-allow-beast'[Allow this security flaw for HTTPS proxy]' \
   --trace-config'[Details to log in trace/verbose output]':'<string>' \
   --dns-interface'[Interface to use for DNS requests]':'<interface>' \
   --dump-ca-embed'[Write the embedded CA bundle to standard output]' \
   --keepalive-time'[Interval time for keepalive probes]':'<seconds>' \
+  --preproxy'[\[protocol\://\]host\[\:port\]  Use this proxy first]' \
   --random-file'[File for reading random data from]':'<file>':_files \
   --cacert'[CA certificate to verify peer against]':'<file>':_files \
   {-H,--header}'[Pass custom header(s) to server]':'<header/@file>' \
   --ignore-content-length'[Ignore the size of the remote resource]' \
   --keepalive-cnt'[Maximum number of keepalive probes]':'<integer>' \
   --proxy-header'[Pass custom header(s) to proxy]':'<header/@file>' \
   --proxy-ssl-auto-client-cert'[Auto client certificate for proxy]' \
@@ -88,14 +87,15 @@
   --ftp-ssl-control'[Require TLS for login, clear for transfer]' \
   --hostpubmd5'[Acceptable MD5 hash of host public key]':'<md5>' \
   --hsts'[Enable HSTS with this cache file]':'<filename>':_files \
   --http2-prior-knowledge'[Use HTTP 2 without HTTP/1.1 Upgrade]' \
   --ip-tos'[Set IP Type of Service or Traffic Class]':'<string>' \
   --local-port'[Use a local port number within RANGE]':'<range>' \
   --pinnedpubkey'[Public key to verify peer against]':'<hashes>' \
+  {-x,--proxy}'[\[protocol\://\]host\[\:port\]  Use this proxy]' \
   --proxy-ca-native'[Load CA certs from the OS to verify proxy]' \
   --proxy-tlspassword'[TLS password for HTTPS proxy]':'<string>' \
   {-r,--range}'[Retrieve only the bytes within RANGE]':'<range>' \
   --socks4'[SOCKS4 proxy on given host + port]':'<host[\:port]>' \
   --socks5'[SOCKS5 proxy on given host + port]':'<host[\:port]>' \
   {-A,--user-agent}'[Send User-Agent <name> to server]':'<name>' \
   --egd-file'[EGD socket path for random data]':'<file>':_files \

I expected the following

I expected the zsh completions to be deterministic when building identical source code.

I found the perl script generating the completions (but have trouble reading it). It seems to parse curl --help all output and sort the generated lines by length, but the number of spaces differs across builds, causing some lines to rank higher/lower.

https://github.com/curl/curl/blob/master/scripts/completion.pl

Running curl --help all with strace I noticed the terminal window size is being queried:

ioctl(0, TIOCGWINSZ, {ws_row=29, ws_col=117, ws_xpixel=0, ws_ypixel=0}) = 0

This is indeed coming from a function related to printing --help output:

curl/src/tool_help.c

Lines 233 to 307 in 96843f4

8000
unsigned int cols = get_terminal_columns();
/* If no category was provided */
if(!category) {
const char *category_note = "\nThis is not the full help; this "
"menu is split into categories.\nUse \"--help category\" to get "
"an overview of all categories, which are:";
const char *category_note2 =
"Use \"--help all\" to list all options"
#ifdef USE_MANUAL
"\nUse \"--help [option]\" to view documentation for a given option"
#endif
;
puts("Usage: curl [options...] <url>");
print_category(CURLHELP_IMPORTANT, cols);
puts(category_note);
get_categories_list(cols);
puts(category_note2);
}
/* Lets print everything if "all" was provided */
else if(curl_strequal(category, "all"))
/* Print everything */
print_category(CURLHELP_ALL, cols);
/* Lets handle the string "category" differently to not print an errormsg */
else if(curl_strequal(category, "category"))
get_categories();
else if(category[0] == '-') {
#ifdef USE_MANUAL
/* command line option help */
const struct LongShort *a = NULL;
if(category[1] == '-') {
char *lookup = &category[2];
bool noflagged = FALSE;
if(!strncmp(lookup, "no-", 3)) {
lookup += 3;
noflagged = TRUE;
}
a = findlongopt(lookup);
if(a && noflagged && (ARGTYPE(a->desc) != ARG_BOOL))
/* a --no- prefix for a non-boolean is not specifying a proper
option */
a = NULL;
}
else if(!category[2])
a = findshortopt(category[1]);
if(!a) {
fprintf(tool_stderr, "Incorrect option name to show help for,"
" see curl -h\n");
}
else {
char cmdbuf[80];
if(a->letter != ' ')
msnprintf(cmdbuf, sizeof(cmdbuf), "\n -%c, --", a->letter);
else if(a->desc & ARG_NO)
msnprintf(cmdbuf, sizeof(cmdbuf), "\n --no-%s", a->lname);
else
msnprintf(cmdbuf, sizeof(cmdbuf), "\n %s", category);
#ifndef UNITTESTS
if(a->cmd == C_XATTR)
/* this is the last option, which then ends when FILES starts */
showhelp("\nALL OPTIONS\n", cmdbuf, "\nFILES");
else
showhelp("\nALL OPTIONS\n", cmdbuf, "\n -");
#endif
}
#else
fprintf(tool_stderr, "Cannot comply. "
"This curl was built without built-in manual\n");
#endif
}
/* Otherwise print category and handle the case if the cat was not found */
else if(get_category_content(category, cols)) {
puts("Unknown category provided, here is a list of all categories:\n");
get_categories();
}
free(category);

I suspect the zsh completions indirectly record the terminal width and the extra padding needs to be removed with trim(...) in the zsh completion script.

Thanks!

curl/libcurl version

curl 8.11.1

operating system

Arch Linux

@bagder
Copy link
Member
bagder commented Jan 22, 2025

This script should be fixed to instead use the source code as input instead of the dynamic --help output. That would not only solve this problem but also make it work for cross-compiles etc...

vishwamartur added a commit to vishwamartur/curl that referenced this issue Feb 7, 2025
Related to curl#16072

Modify the zsh completion script to use source code as input instead of dynamic `--help` output.

* **scripts/completion.pl**
  - Change `parse_main_opts` to read source code files instead of calling `curl --help all`.
  - Update the regex pattern to match option definitions in the source code files.
  - Add a function `read_source_files` to read and parse source code files for options and descriptions.

* **scripts/Makefile.am**
  - Update the `completion.pl` target to use the new source code parsing function.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

3 participants
0