Rewrite zsh completion script, fix #30900

1. `devscripts/zsh-completion.py`
2. `sudo mv youtube-dl.zsh /usr/share/zsh/site-functions/_youtube-dl`
3. `compinit`
This commit is contained in:
Wu Zhenyu 2022-08-04 22:42:06 +08:00
parent adb5294177
commit 66c952916d
3 changed files with 220 additions and 62 deletions

View File

@ -1,28 +1,22 @@
#compdef youtube-dl
#compdef {{programs}}
# https://github.com/zsh-users/zsh/blob/master/Etc/completion-style-guide
__youtube_dl() {
local curcontext="$curcontext" fileopts diropts cur prev
typeset -A opt_args
fileopts="{{fileopts}}"
diropts="{{diropts}}"
cur=$words[CURRENT]
case $cur in
:)
_arguments '*: :(::ytfavorites ::ytrecommended ::ytsubscriptions ::ytwatchlater ::ythistory)'
typeset -A opt_args
_arguments -S -s \
{{flags}} \
'*:URL:_urls'
case $state in
external-downloader-args)
case "${opt_args[--external-downloader]}" in
curl) _curl
;;
*)
prev=$words[CURRENT-1]
if [[ ${prev} =~ ${fileopts} ]]; then
_path_files
elif [[ ${prev} =~ ${diropts} ]]; then
_path_files -/
elif [[ ${prev} == "--recode-video" ]]; then
_arguments '*: :(mp4 flv ogg webm mkv)'
else
_arguments '*: :({{flags}})'
fi
ffmpeg) _ffmpeg
;;
httpie) _httpie
;;
wget) _wget
;;
esac
}
__youtube_dl
;;
esac

View File

@ -1,49 +1,203 @@
#!/usr/bin/env python
from __future__ import unicode_literals
"""Generate zsh completion script.
Usage
-----
.. code-block:: zsh
devscripts/zsh-completion.py
sudo mv youtube-dl.zsh /usr/share/zsh/site-functions/_youtube-dl
rm -f ~/.zcompdump # optional
compinit # regenerate ~/.zcompdump
Debug
-----
.. code-block:: zsh
devscripts/zsh-completion.py MODULE_NAME - # will output to stdout
Refer
-----
- https://github.com/ytdl-org/youtube-dl/blob/master/devscripts/zsh-completion.py
- https://github.com/zsh-users/zsh/blob/master/Etc/completion-style-guide
Examples
--------
.. code-block::
'(- *)'{-h,--help}'[show this help message and exit]'
|<-1->||<---2--->||<---------------3--------------->|
.. code-block:: console
% foo --<TAB>
option
--help show this help message and exit
% foo --help <TAB>
no more arguments
.. code-block::
--color'[When to show color. Default: auto. Support: auto, always, never]:when:(auto always never)'
|<-2->||<------------------------------3------------------------------->||<4>||<--------5-------->|
.. code-block:: console
% foo --color <TAB>
when
always
auto
never
.. code-block::
--color'[When to show color. Default: auto. Support: auto, always, never]:when:((auto\:"only when output is stdout" always\:always never\:never))'
|<-2->||<------------------------------3------------------------------->||<4>||<--------------------------------5------------------------------->|
.. code-block:: console
% foo --color <TAB>
when
always always
auto only when output is stdout
never never
.. code-block::
--config='[Config file. Default: ~/.config/foo/foo.toml]:config file:_files -g *.toml'
|<--2-->||<---------------------3--------------------->||<---4---->||<------5------->|
.. code-block:: console
% foo --config <TAB>
config file
a.toml b/ ...
...
.. code-block::
{1,2}'::_command_names -e'
|<2->|4|<-------5------->|
.. code-block:: console
% foo help<TAB>
_command_names -e
help2man generate a simple manual page
helpviewer
...
% foo hello hello <TAB>
no more arguments
.. code-block::
'*: :_command_names -e'
2|4||<-------5------->|
.. code-block:: console
% foo help<TAB>
external command
help2man generate a simple manual page
helpviewer
...
% foo hello hello help<TAB>
external command
help2man generate a simple manual page
helpviewer
...
+----+------------+----------+------+
| id | variable | required | expr |
+====+============+==========+======+
| 1 | prefix | F | (.*) |
| 2 | optionstr | T | .* |
| 3 | helpstr | F | [.*] |
| 4 | metavar | F | :.* |
| 5 | completion | F | :.* |
+----+------------+----------+------+
"""
from __future__ import unicode_literals
from optparse import SUPPRESS_HELP
import os
from os.path import dirname as dirn
import sys
from typing import Final
sys.path.insert(0, dirn(dirn((os.path.abspath(__file__)))))
import youtube_dl
from setuptools import find_packages
ZSH_COMPLETION_FILE = "youtube-dl.zsh"
rootpath = dirn(dirn(os.path.abspath(__file__)))
path = os.path.join(rootpath, "src")
packages = find_packages(path)
if packages == []:
path = rootpath
sys.path.insert(0, path)
PACKAGE: Final = "youtube_dl" if sys.argv[1:2] == [] else sys.argv[1]
parser = __import__(PACKAGE).parseOpts()[0]
BINNAME: Final = PACKAGE.replace("_", "-")
BINNAMES: Final = [BINNAME]
ZSH_COMPLETION_FILE: Final = (
"youtube-dl.zsh" if sys.argv[2:3] == [] else sys.argv[2]
)
ZSH_COMPLETION_TEMPLATE = "devscripts/zsh-completion.in"
opts = parser._get_all_options()
def build_completion(opt_parser):
opts = [opt for group in opt_parser.option_groups
for opt in group.option_list]
opts_file = [opt for opt in opts if opt.metavar == "FILE"]
opts_dir = [opt for opt in opts if opt.metavar == "DIR"]
flags = []
for opt in opts:
optionstrs = opt._long_opts + opt._short_opts
if len(optionstrs) == 1:
optionstr = optionstrs[0]
else:
optionstr = "'{" + ",".join(optionstrs) + "}'"
fileopts = []
for opt in opts_file:
if opt._short_opts:
fileopts.extend(opt._short_opts)
if opt._long_opts:
fileopts.extend(opt._long_opts)
if opt.action in ["help", "version"]:
prefix = "- *"
else:
prefix = " ".join(optionstrs)
prefix = "'(" + prefix + ")"
diropts = []
for opt in opts_dir:
if opt._short_opts:
diropts.extend(opt._short_opts)
if opt._long_opts:
diropts.extend(opt._long_opts)
if opt.help == SUPPRESS_HELP:
helpstr = ""
else:
helpstr = opt.help.replace("'", "'\\''").replace("]", "\\]")
helpstr = "[" + helpstr + "]"
flags = [opt.get_opt_string() for opt in opts]
if isinstance(opt.metavar, str):
metavar = opt.metavar
elif optionstr == "--external-downloader-args":
metavar = " " # use default
else: # opt.metavar is None
metavar = ""
if metavar != "":
# use lowcase conventionally
metavar = metavar.lower().replace(":", "\\:")
with open(ZSH_COMPLETION_TEMPLATE) as f:
template = f.read()
if opt.choices:
completion = "(" + " ".join(opt.choices) + ")"
elif optionstr == "--external-downloader-args":
completion = "->external-downloader-args"
elif metavar == "file":
completion = "_files"
metavar = " "
elif metavar == "dir":
completion = "_dirs"
metavar = " "
elif metavar == "url":
completion = "_urls"
metavar = " "
elif metavar == "command":
completion = "_command_names -e"
metavar = " "
else:
completion = ""
template = template.replace("{{fileopts}}", "|".join(fileopts))
template = template.replace("{{diropts}}", "|".join(diropts))
template = template.replace("{{flags}}", " ".join(flags))
if metavar != "":
metavar = ":" + metavar
if completion != "":
completion = ":" + completion
with open(ZSH_COMPLETION_FILE, "w") as f:
f.write(template)
flag = "{0}{1}{2}{3}{4}'".format(
prefix, optionstr, helpstr, metavar, completion
)
flags += [flag]
parser = youtube_dl.parseOpts()[0]
build_completion(parser)
with open(ZSH_COMPLETION_TEMPLATE) as f:
template = f.read()
template = template.replace("{{programs}}", " ".join(BINNAMES))
template = template.replace("{{flags}}", " \\\n ".join(flags))
with (
open(ZSH_COMPLETION_FILE, "w")
if ZSH_COMPLETION_FILE != "-"
else sys.stdout
) as f:
f.write(template)

View File

@ -178,7 +178,7 @@ def parseOpts(overrideArguments=None):
'(%APPDATA%/youtube-dl/config.txt on Windows)')
general.add_option(
'--config-location',
dest='config_location', metavar='PATH',
dest='config_location', metavar='FILE',
help='Location of the configuration file; either the path to the config or its containing directory.')
general.add_option(
'--flat-playlist',
@ -414,13 +414,15 @@ def parseOpts(overrideArguments=None):
'--youtube-skip-dash-manifest',
action='store_false', dest='youtube_include_dash_manifest',
help='Do not download the DASH manifests and related data on YouTube videos')
choices = ['mkv', 'mp4', 'ogg', 'webm', 'flv']
video_format.add_option(
'--merge-output-format',
action='store', dest='merge_output_format', metavar='FORMAT', default=None,
choices=choices,
help=(
'If a merge is required (e.g. bestvideo+bestaudio), '
'output to given container format. One of mkv, mp4, ogg, webm, flv. '
'Ignored if no merge is required'))
'output to given container format. (currently supported: {0})'
'Ignored if no merge is required'.format(' '.join(choices))))
subtitles = optparse.OptionGroup(parser, 'Subtitle Options')
subtitles.add_option(
@ -519,8 +521,9 @@ def parseOpts(overrideArguments=None):
downloader.add_option(
'--external-downloader',
dest='external_downloader', metavar='COMMAND',
choices=list_external_downloaders(),
help='Use the specified external downloader. '
'Currently supports %s' % ','.join(list_external_downloaders()))
'(currently supported: %s)' % ' '.join(list_external_downloaders()))
downloader.add_option(
'--external-downloader-args',
dest='external_downloader_args', metavar='ARGS',
@ -787,17 +790,22 @@ def parseOpts(overrideArguments=None):
'-x', '--extract-audio',
action='store_true', dest='extractaudio', default=False,
help='Convert video files to audio-only files (requires ffmpeg/avconv and ffprobe/avprobe)')
choices = ["best", "aac", "flac", "mp3", "m4a", "opus", "vorbis", "wav"]
postproc.add_option(
'--audio-format', metavar='FORMAT', dest='audioformat', default='best',
help='Specify audio format: "best", "aac", "flac", "mp3", "m4a", "opus", "vorbis", or "wav"; "%default" by default; No effect without -x')
choices=choices,
help='Specify audio format. (currently supported: {0}); %default by default; No effect without -x'.format(" ".join(choices)))
postproc.add_option(
'--audio-quality', metavar='QUALITY',
dest='audioquality', default='5',
choices=list(map(str, range(10))),
help='Specify ffmpeg/avconv audio quality, insert a value between 0 (better) and 9 (worse) for VBR or a specific bitrate like 128K (default %default)')
choices = ["mp4", "flv", "ogg", "webm", "mkv", "avi"]
postproc.add_option(
'--recode-video',
metavar='FORMAT', dest='recodevideo', default=None,
help='Encode the video to another format if necessary (currently supported: mp4|flv|ogg|webm|mkv|avi)')
choices=choices,
help='Encode the video to another format if necessary (currently supported: {0})'.format(" ".join(choices)))
postproc.add_option(
'--postprocessor-args',
dest='postprocessor_args', metavar='ARGS',
@ -858,10 +866,12 @@ def parseOpts(overrideArguments=None):
'--exec',
metavar='CMD', dest='exec_cmd',
help='Execute a command on the file after downloading and post-processing, similar to find\'s -exec syntax. Example: --exec \'adb push {} /sdcard/Music/ && rm {}\'')
choices = ["srt", "ass", "vtt", "lrc"]
postproc.add_option(
'--convert-subs', '--convert-subtitles',
metavar='FORMAT', dest='convertsubtitles', default=None,
help='Convert the subtitles to other format (currently supported: srt|ass|vtt|lrc)')
choices=choices,
help='Convert the subtitles to other format (currently supported: {0})'.format(" ".join(choices)))
parser.add_option_group(general)
parser.add_option_group(network)