"""Module for helper functions in the parser"""importargparseimportosfromtypingimportTuple_SHOW_ALL_ARGS='JINA_FULL_CLI'inos.environ
[docs]defadd_arg_group(parser,title):"""Add the arguments for a specific group to the parser :param parser: the parser configure :param title: the group name :return: the new parser """returnparser.add_argument_group(f'{title} arguments')
[docs]classKVAppendAction(argparse.Action):"""argparse action to split an argument into KEY=VALUE form on the first = and append to a dictionary. This is used for setting up --env """def__call__(self,parser,args,values,option_string=None):""" call the KVAppendAction .. # noqa: DAR401 :param parser: the parser :param args: args to initialize the values :param values: the values to add to the parser :param option_string: inherited, not used """importjsonimportrefromjina.helperimportparse_argd=getattr(args,self.dest)or{}forvalueinvalues:try:d.update(json.loads(value))exceptjson.JSONDecodeError:try:k,v=re.split(r'[:=]\s*',value,maxsplit=1)exceptValueError:raiseargparse.ArgumentTypeError(f'could not parse argument \"{values[0]}\" as k=v format')d[k]=parse_arg(v)setattr(args,self.dest,d)
class_ColoredHelpFormatter(argparse.ArgumentDefaultsHelpFormatter):class_Section(object):def__init__(self,formatter,parent,heading=None):self.formatter=formatterself.parent=parentself.heading=headingself.items=[]defformat_help(self):# format the indented sectionifself.parentisnotNone:self.formatter._indent()join=self.formatter._join_partsitem_help=join([func(*args)forfunc,argsinself.items])ifself.parentisnotNone:self.formatter._dedent()# return nothing if the section was emptyifnotitem_help.strip():return''# add the heading if the section was non-emptyifself.headingisnotargparse.SUPPRESSandself.headingisnotNone:fromjina.helperimportcoloredcurrent_indent=self.formatter._current_indentcaptial_heading=' '.join(v[0].upper()+v[1:]forvinself.heading.split(' '))heading='%*s%s\n'%(current_indent,'',colored(f'▮ {captial_heading}','cyan',attrs=['bold']),)else:heading=''# join the section-initial newline, the heading and the helpreturnjoin(['\n',heading,item_help,'\n'])defstart_section(self,heading):self._indent()section=self._Section(self,self._current_section,heading)self._add_item(section.format_help,[])self._current_section=sectiondef_get_help_string(self,action):help_string=''if'%(default)'notinaction.help:ifaction.defaultisnotargparse.SUPPRESS:fromjina.helperimportcoloreddefaulting_nargs=[argparse.OPTIONAL,argparse.ZERO_OR_MORE]ifisinstance(action,argparse._StoreTrueAction):help_string=colored('default: %s'%('enabled'ifaction.defaultelsef'disabled, use "{action.option_strings[0]}" to enable it'),attrs=['dark'],)elifaction.choices:choices_str=f'{{{", ".join([str(c)forcinaction.choices])}}}'help_string=colored('choose from: '+choices_str+'; default: %(default)s',attrs=['dark'],)elifaction.option_stringsoraction.nargsindefaulting_nargs:help_string=colored('type: %(type)s; default: %(default)s',attrs=['dark'])returnf'''{help_string}{action.help} '''def_join_parts(self,part_strings):return'\n'+''.join([partforpartinpart_stringsifpartandpartisnotargparse.SUPPRESS])def_get_default_metavar_for_optional(self,action):return''def_expand_help(self,action):params=dict(vars(action),prog=self._prog)fornameinlist(params):ifparams[name]isargparse.SUPPRESS:delparams[name]fornameinlist(params):ifhasattr(params[name],'__name__'):params[name]=params[name].__name__returnself._get_help_string(action)%paramsdef_metavar_formatter(self,action,default_metavar):ifaction.metavarisnotNone:result=action.metavarelifaction.choicesisnotNone:iflen(action.choices)>4:choice_strs=', '.join([str(c)forcinaction.choices][:4])result=f'{{{choice_strs} ... {len(action.choices)-4} more choices}}'else:choice_strs=', '.join([str(c)forcinaction.choices])result=f'{{{choice_strs}}}'else:result=default_metavardefformatter(tuple_size):ifisinstance(result,tuple):returnresultelse:return(result,)*tuple_sizereturnformatterdef_split_lines(self,text,width):returnself._para_reformat(text,width)def_fill_text(self,text,width,indent):lines=self._para_reformat(text,width)return'\n'.join(lines)def_indents(self,line)->Tuple[int,int]:"""Return line indent level and "sub_indent" for bullet list text. :param line: the line to check :return: indentation of line and indentation of sub-items """importreindent=len(re.match(r'( *)',line).group(1))list_match=re.match(r'( *)(([*\-+>]+|\w+\)|\w+\.) +)',line)iflist_match:sub_indent=indent+len(list_match.group(2))else:sub_indent=indentreturnindent,sub_indentdef_split_paragraphs(self,text):"""Split text into paragraphs of like-indented lines. :param text: the text input :return: list of paragraphs """importreimporttextwraptext=textwrap.dedent(text).strip()text=re.sub('\n\n[\n]+','\n\n',text)last_sub_indent=Noneparagraphs=list()forlineintext.splitlines():(indent,sub_indent)=self._indents(line)is_text=len(line.strip())>0ifis_textandindent==sub_indent==last_sub_indent:paragraphs[-1]+=' '+lineelse:paragraphs.append(line)ifis_text:last_sub_indent=sub_indentelse:last_sub_indent=Nonereturnparagraphsdef_para_reformat(self,text,width):"""Format text, by paragraph. :param text: the text to format :param width: the width to apply :return: the new text """importtextwraplines=list()forparagraphinself._split_paragraphs(text):(indent,sub_indent)=self._indents(paragraph)paragraph=self._whitespace_matcher.sub(' ',paragraph).strip()new_lines=textwrap.wrap(text=paragraph,width=width,initial_indent=' '*indent,subsequent_indent=' '*sub_indent,)# Blank lines get eaten by textwrap, put it backlines.extend(new_linesor[''])returnlines_chf=_ColoredHelpFormatter