"""argument parser for bilby_pipe, adapted from configargparse.ArgParser."""importosimportreimportsysimportconfigargparsefrom.utilsimportDuplicateErrorDict,get_version_information,logger
[docs]defparse_known_args(self,args=None,namespace=None,config_file_contents=None,env_vars=None,**kwargs,):""" Supports all the same args as the ArgumentParser.parse_args(..), as well as the following additional args. Parameters ---------- args: (None, str, List[str]) List of strings to parse. The default is taken from sys.argv Can also be a string :code:`"-x -y bla"` namespace: argparse.Namespace The Namespace object that will be returned by parse_args(). config_file_contents: None Present because inherited from abstract method. The config_file_contents are read from the config_file passed in the args. env_vars: dict Dictionary of environment variables Returns ------- namespace: argparse.Namespace An object to take the attributes parsed. unknown_args: List[str] List of the args unrecognised by the parser """ifenv_varsisNone:env_vars=os.environnamespace,unknown_args=super(BilbyArgParser,self).parse_known_args(args=self._preprocess_args(args),namespace=namespace,config_file_contents=self._preprocess_config_file_contents(args),env_vars=env_vars,)returnnamespace,unknown_args
[docs]def_preprocess_config_file_contents(self,args):"""Reads config file into string and formats it for ArgParser Parameters ---------- args: None, list, str The input args to be parsed. Creates config_file_content only if one of the args is a config_file Returns ------- file_contents: str The content of the config files, correctly formatted file_contents: None If no config file specified """file_contents=Noneconfig_stream=self._open_config_files(args)ifconfig_stream:config_file_parser=BilbyConfigFileParser()ini_stream=config_stream.pop()# get ini file's steamini_items,numbers,comments,inline_comments=config_file_parser.parse(ini_stream)ini_stream.close()self.numbers=numbersself.comments=commentsself.inline_comments=inline_commentscorrected_items=dict((key.replace("_","-"),val)forkey,valinini_items.items())file_contents=config_file_parser.serialize(corrected_items)returnfile_contents
[docs]def_preprocess_args(self,args):"""Processes args into correct format for ArgParser Parameters ---------- args: None, list, str The input args to be parsed. Returns ------- normalized_args: List[string] args list normalised to "--key value" """# format args into listifargsisNone:args=sys.argv[1:]elifisinstance(args,str):args=args.split()else:args=list(args)# normalize args by converting args like --key=value to --key valuenormalized_args=list()forarginargs:ifargandarg[0]inself.prefix_charsand"="inarg:key,value=arg.split("=",1)key=HyphenStr(key)normalized_args.append(key)normalized_args.append(value)else:ifarg.startswith("--"):arg.replace("_","-")normalized_args.append(arg)returnnormalized_args
[docs]defwrite_to_file(self,filename,args=None,overwrite=False,include_description=False,exclude_default=False,comment=None,):ifos.path.isfile(filename)andnotoverwrite:logger.warning(f"File {filename} already exists, not writing to file.")withopen(filename,"w")asff:__version__=get_version_information()ifinclude_description:print(f"## This file was written with bilby_pipe version {__version__}\n",file=ff,)ifisinstance(comment,str):print("#"+comment+"\n",file=ff)forgroupinself._action_groups[2:]:print("#"*80,file=ff)print(f"## {group.title}",file=ff)ifinclude_description:print(f"# {group.description}",file=ff)print("#"*80+"\n",file=ff)foractioningroup._group_actions:ifinclude_description:print(f"# {action.help}",file=ff)dest=action.desthyphen_dest=HyphenStr(dest)ifisinstance(args,dict):ifaction.destinargs:value=args[dest]elifhyphen_destinargs:value=args[hyphen_dest]else:value=action.defaultelse:value=getattr(args,dest,action.default)ifexclude_defaultandvalue==action.default:continueself.write_comment_if_needed(hyphen_dest,ff)self.write_line(hyphen_dest,value,ff)print("",file=ff)
[docs]defwrite_comment_if_needed(self,hyphen_dest,ff):"""Determine if the line is associated with a comment"""ifhyphen_destinself.numbers:i=1whileTrue:previous_line=self.numbers[hyphen_dest]-iifprevious_lineinself.comments:print(self.comments[previous_line],file=ff)i+=1else:break
[docs]defparse(self,stream):"""Parses the keys + values from a config file."""# Pre-process lines to put multi-line dicts on single linesjlines=list(stream)# Turn into a listlines=[line.strip(" ")forlineinlines]# Strip trailing white spacefirst_chars=[line[0]forlineinlines]# Pull out the first characterii=0lines_repacked=[]whileii<len(lines):iffirst_chars[ii]=="#":lines_repacked.append(lines[ii])ii+=1else:# Find the next commentjj=ii+1whilejj<len(first_chars)andfirst_chars[jj]!="#":jj+=1int_lines="".join(lines[ii:jj])# Form single stringint_lines=int_lines.replace(",\n#",", \n#")#int_lines=int_lines.replace(",\n",", ")# Multiline args on single linesint_lines=int_lines.replace("\n}\n","}\n")# Trailing } on single linesint_lines=int_lines.split("\n")lines_repacked+=int_linesii=jj# items is where we store the key-value pairs read in from the config# we use a DuplicateErrorDict so that an error is raised on duplicate# entries (e.g., if the user has two identical keys)items=DuplicateErrorDict()numbers=dict()comments=dict()inline_comments=dict()forii,lineinenumerate(lines_repacked):line=line.strip()ifnotline:continueifline[0]in["#",";","["]orline.startswith("---"):comments[ii]=linecontinueiflen(line.split("#"))>1:inline_comments[ii]=" #"+"#".join(line.split("#")[1:])line=line.split("#")[0]white_space="\\s*"key=r"(?P<key>[^:=;#\s]+?)"value=white_space+r"[:=\s]"+white_space+"(?P<value>.+?)"comment=white_space+"(?P<comment>\\s[;#].*)?"key_only_match=re.match("^"+key+comment+"$",line)ifkey_only_match:key=HyphenStr(key_only_match.group("key"))items[key]="true"numbers[key]=iicontinuekey_value_match=re.match("^"+key+value+comment+"$",line)ifkey_value_match:key=HyphenStr(key_value_match.group("key"))value=key_value_match.group("value")ifvalue.startswith("[")andvalue.endswith("]"):# handle special case of listsvalue=[elem.strip()foreleminvalue[1:-1].split(",")]items[key]=valuenumbers[key]=iicontinueraiseconfigargparse.ConfigFileParserException(f"Unexpected line {ii} in {getattr(stream,'name','stream')}: {line}")items=self.reconstruct_multiline_dictionary(items)returnitems,numbers,comments,inline_comments
[docs]defreconstruct_multiline_dictionary(self,items):# Convert items into a dictionary to avoid duplicate-line errorsitems=dict(items)keys=list(items.keys())vals=list(items.values())forii,valinenumerate(vals):if"{"invaland"}"notinval:sub_ii=1sub_dict_vals=[]ifval!="{":sub_dict_vals.append(val.rstrip("{"))whileTrue:next_line=f"{keys[ii+sub_ii]}: {vals[ii+sub_ii]}"items.pop(keys[ii+sub_ii])if"}"notinnext_line:if"{"innext_line:raiseValueError("Unable to pass multi-line config file")sub_dict_vals.append(next_line)sub_ii+=1continueelifnext_line=="}: true":sub_dict_vals.append("}")breakelse:sub_dict_vals.append(next_line)breaksub_dict_vals_with_comma=[vv.rstrip(",").lstrip(",")forvvinsub_dict_vals]items[keys[ii]]="{"+(", ".join(sub_dict_vals_with_comma)).lstrip("{")returnitems