def_get_man_page(cmd:str):"""without control characters"""env=XSH.env.detype()manpage=subprocess.Popen(["man",cmd],stdout=subprocess.PIPE,stderr=subprocess.DEVNULL,env=env)# This is a trick to get rid of reverse line feedsreturnsubprocess.check_output(["col","-b"],stdin=manpage.stdout,env=env)@functools.cachedef_man_option_string_regex():returnre.compile(r"(?:(,\s?)|^|(\sor\s))(?P<option>-[\w]|--[\w-]+)(?=\[?(\s|,|=\w+|$))")
[docs]defgenerate_options_of(cmd:str):out=_get_man_page(cmd)ifnotout:returndefget_headers(text:str):"""split as header-body based on indent"""ifnottext:returnheader=""body=[]forlineintextwrap.dedent(text.replace("\n\t","\n ")).splitlines():ifnotline.strip():continueifline.startswith((" ","\t")):body.append(line)else:ifheaderorbody:yieldheader,body# found new sectionheader=line.strip()body=[]ifheaderorbody:yieldheader,bodydefsplit_options_string(text:str):text=text.strip()regex=_man_option_string_regex()regex.findall(text)options=[]formatchinregex.finditer(text):option=match.groupdict().pop("option",None)ifoption:options.append(option)text=text[match.end():]returnoptions,text.strip()defget_option_section():option_sect=dict(get_headers(out.decode()))small_names={k.lower():kforkinoption_sect}forheadin("options","command options","description",):# prefer sections in this orderifheadinsmall_names:title=small_names[head]return"\n".join(option_sect[title])defget_options(text):"""finally get the options"""# return old section ifforopt,linesinget_headers(text):# todo: some have [+-] or such vague notationsifopt.startswith("-"):# sometime a single line will have both desc and optionsoption_strings,rest=split_options_string(opt)descs=[]ifrest:descs.append(rest)iflines:descs.append(textwrap.dedent("\n".join(lines)))ifoption_strings:yield". ".join(descs),tuple(option_strings)eliflines:# sometimes the options are nested inside subheadersyield fromget_options("\n".join(lines))yield fromget_options(get_option_section())
[docs]@contextual_command_completerdefcomplete_from_man(context:CommandContext):""" Completes an option name, based on the contents of the associated man page. """ifcontext.arg_index==0ornotcontext.prefix.startswith("-"):returncmd=context.args[0].valuedefcompletions():fordesc,optsin_parse_man_page_options(cmd).items():yieldRichCompletion(value=opts[-1],display=", ".join(opts),description=desc)returncompletions(),False