1#!/usr/local/bin/python3.8 2 3# python3 status: started 4 5import os, sys, glob, operator, string, re 6 7valid_afni_views = ['+orig', '+acpc', '+tlrc'] 8valid_new_views = ['+orig', '+acpc', '+tlrc', ''] 9 10# limits for shell_com history 11SAVE_SHELL_HISTORY = 400 12MAX_SHELL_HISTORY = 600 13 14class afni_name(object): 15 def __init__(self, name="", do_sel=1, view=None): 16 """do_sel : apply selectors (col, row, range)""" 17 self.initname = name 18 self.do_sel = do_sel 19 res = parse_afni_name(name, do_sel=self.do_sel) 20 self.path = res['path'] 21 self.prefix = res['prefix'] 22 self.view = res['view'] 23 self.extension = res['extension'] 24 self.type = res['type'] 25 self.colsel = res['col'] 26 self.nodesel = res['node'] 27 self.rowsel = res['row'] 28 self.rangesel = res['range'] 29 self.selquote = '"' # selector quote 30 if view in valid_new_views: self.new_view(view) 31 return 32 33 def p(self): #Full path 34 """show path only, no dataset name""" 35 pp = "%s/" % os.path.abspath('./') #full path at this location 36 fn = pp.find(self.path) #is path at end of abspath? 37 if (fn > 0 and fn+len(self.path) == len(pp)): #path is at end of abs path 38 return pp 39 else: 40 return "%s/" % os.path.abspath(self.path) 41 42 def realp(self): #Full path following symbolic links 43 """show path only, no dataset name""" 44 pp = "%s/" % os.path.realpath('./') #full path at this location 45 fn = str.find(pp,self.path) #is path at end of abspath? 46 if (fn > 0 and fn+len(self.path) == len(pp)): #path is at end of abs path 47 return pp 48 else: 49 return "%s/" % os.path.realpath(self.path) 50 51 def ppve(self, sel=0): 52 """show path, prefix, view and extension""" 53 s = "%s%s" % (self.p(), self.pve(sel=sel)) 54 return s 55 56 def rppve(self, sel=0): 57 """show path, prefix, view and extension""" 58 s = "%s%s" % (self.realp(), self.pve(sel=sel)) 59 return s 60 61 # selectors, along with many sel=0 function parameters 7 Jun 2016 [rickr] 62 def selectors(self): 63 """return all selectors, usually in double quotes 64 (colsel, rowsel, nodesel, rangesel)""" 65 66 sstuff = '%s%s%s%s' % (self.colsel, self.rowsel, self.nodesel, 67 self.rangesel) 68 69 if sstuff == '': return sstuff 70 71 return "%s%s%s" % (self.selquote, sstuff, self.selquote) 72 73 def ppves(self, quotes=1): 74 """show path, prefix, view, extension and all selectors 75 (colsel, rowsel, nodesel, rangesel) 76 77 this is identically ppve(sel=1), but maybe without quotes 78 """ 79 80 # if no selectors, do not incude quotes 7 Apr 2015 [rickr] 81 82 # if no quotes, clear and reset internal selqute 83 if not quotes: 84 qstr = self.selquote 85 self.selquote = '' 86 87 pstuff = self.ppve(sel=1) 88 89 if not quotes: 90 self.selquote = qstr 91 92 return pstuff 93 94 # s = "%s%s%s%s'%s%s%s%s'" % (self.p(), self.prefix, \ 95 # self.view, self.extension,\ 96 # self.colsel, self.rowsel,\ 97 # self.nodesel, self.rangesel) 98 # 99 # return s 100 101 def input(self): 102 """full path to dataset in 'input' format 103 e.g. +orig, but no .HEAD 104 e.g. would include .nii""" 105 if self.type == 'BRIK': 106 return self.ppv() 107 else: 108 return self.ppve() 109 110 def real_input(self): 111 """full path to dataset in 'input' format 112 follow symbolic links!!!! 113 e.g. +orig, but no .HEAD 114 e.g. would include .nii""" 115 if self.type == 'BRIK': 116 return self.rppv() 117 else: 118 return self.rppve() 119 120 def nice_input(self, head=0, sel=0): 121 """return a path to the input, where rel/abs path matches original 122 - the path being relative or absolute will match self.initname 123 14 Apr 2020 [rickr] 124 """ 125 # if relative path, rel_input does the job 126 if not self.initname.startswith('/'): 127 return self.rel_input(head=head, sel=sel) 128 129 # absolute path 130 131 # if not head or not BRIK type, easy return 132 if not head or self.type != 'BRIK': 133 return self.ppv(sel=sel) 134 135 # have head AND type==BRIK, 136 # temporarily alter the extension and use it 137 138 save_ext = self.extension 139 self.extension = '.HEAD' 140 141 # get return value, including extension, without expanding links 142 retval = self.ppve(sel=sel) 143 144 # and restore previous extension before return 145 self.extension = save_ext 146 147 return retval 148 149 def rel_input(self, head=0, sel=0): 150 """relative path to dataset in 'input' format 151 e.g. +orig, but no .HEAD 152 e.g. would include .nii""" 153 if self.type == 'BRIK': 154 # separate selectors for HEAD case 155 if sel: sstr = self.selectors() 156 else: sstr = '' 157 name = self.rpv() 158 if head: return '%s%s%s' % (name, '.HEAD', sstr) 159 else: return '%s%s' % (name, sstr) 160 else: 161 return self.rpve(sel=sel) 162 163 def shortinput(self, head=0, sel=0): 164 """dataset name in 'input' format 165 - no directory prefix 166 - include extension if non-BRIK format 167 - if head: include .HEAD suffix 168 - if sel: include selectors 169 """ 170 if self.type == 'BRIK': 171 # separate selectors for HEAD case 172 if sel: sstr = self.selectors() 173 else: sstr = '' 174 name = self.pv() 175 if head: return '%s%s%s' % (name, '.HEAD', sstr) 176 else: return '%s%s' % (name, sstr) 177 else: 178 return self.pve(sel=sel) 179 180 def out_prefix(self): 181 """dataset name in 'output' format 182 - no directory 183 - include extension if non-BRIK format""" 184 if self.type == 'BRIK': 185 return self.prefix 186 else: 187 return self.pve() 188 def ppv(self, sel=0): 189 """return path, prefix, view formatted name""" 190 s = "%s%s" % (self.p(), self.pv(sel=sel)) 191 return s 192 def rppv(self, sel=0): 193 """return path, prefix, view formatted name resolving symbolic links""" 194 s = "%s%s" % (self.realp(), self.pv(sel=sel)) 195 return s 196 def rel_dir(self, sel=0): 197 """return relative directory of an object 198 - this will be empty (instead of "./") if in current directory 199 """ 200 cwd = os.path.abspath(os.curdir) 201 # if not at root (not mentioning any names, Dylan), append '/' 202 # to remove current directory from prefix 30 Nov 2017 [rickr] 203 if cwd != '/': cwd += '/' 204 # check if equal, where staring from len() would fail 205 if self.path == cwd: 206 pp = '' 207 elif self.path.startswith(cwd): 208 pp = self.path[len(cwd):] 209 else: 210 pp = self.path 211 return pp 212 def rpv(self, sel=0): 213 """return relative path, prefix, view formatted name 214 - do not include ./ as relative path""" 215 # rp = str.replace(self.path, "%s/" % os.path.abspath(os.curdir), '') 216 rp = self.rel_dir() 217 s = "%s%s" % (rp, self.pv(sel=sel)) 218 return s 219 def rpve(self, sel=0): 220 """return relative path, prefix, view, extension formatted name 221 - do not include ./ as relative path""" 222 rp = self.rel_dir() 223 s = "%s%s" % (rp, self.pve(sel=sel)) 224 return s 225 def pp(self): 226 """return path, prefix formatted name""" 227 return "%s%s" % (self.p(), self.prefix) 228 def pv(self, sel=0): 229 """return prefix, view formatted name""" 230 if sel: sstr = self.selectors() 231 else: sstr = '' 232 if self.type == 'BRIK': 233 return "%s%s%s" % (self.prefix, self.view, sstr) 234 else: 235 return self.pve(sel=sel) 236 def pve(self, sel=0): 237 """return prefix, view, extension formatted name""" 238 if sel: sstr = self.selectors() 239 else: sstr = '' 240 return "%s%s%s%s" % (self.prefix, self.view, self.extension, sstr) 241 def dims(self, quotes=1): 242 """return xyzt dimensions, as a list of ints""" 243 return dset_dims(self.ppves(quotes=quotes)) 244 def exist(self): 245 """return whether the dataset seems to exist on disk""" 246 locppv = self.ppv() 247 if (self.type == 'NIFTI'): 248 if ( os.path.isfile("%s" % locppv) or \ 249 os.path.isfile("%s.gz" % locppv) \ 250 ): 251 return 1 252 else: return 0 253 elif (self.type == 'BRIK'): 254 if ( os.path.isfile("%s.HEAD" % locppv) \ 255 and \ 256 ( os.path.isfile("%s.BRIK" % locppv) or \ 257 os.path.isfile("%s.BRIK.gz" % locppv) or \ 258 os.path.isfile("%s.BRIK.bz2" % locppv) or \ 259 os.path.isfile("%s.BRIK.Z" % locppv) ) \ 260 ): 261 return 1 262 else: return 0 263 elif (self.type == 'NIML'): 264 # ppv leads down to pve for non-BRIK 265 if ( os.path.isfile(locppv) or \ 266 os.path.isfile("%s.niml.dset" % locppv) \ 267 ): 268 return 1 269 else: return 0 270 else: 271 if ( os.path.isfile(locppv) ): 272 return 1 273 else: return 0 274 275 def locate(self, oexec=""): 276 """Attempt to locate the file and if found, update its info""" 277 if (self.exist()): 278 return 1 279 else: 280 #could it be in abin, etc. 281 cmd = '@FindAfniDsetPath %s' % self.pv() 282 com=shell_com(cmd,oexec, capture=1) 283 com.run() 284 285 if com.status or not com.so or len(com.so[0]) < 2: 286 # call this a non-fatal error for now 287 if 0: 288 print(' status = %s' % com.status) 289 print(' stdout = %s' % com.so) 290 print(' stderr = %s' % com.se) 291 return 0 292 293 # self.path = com.so[0].decode() 294 self.path = com.so[0] 295 # nuke any newline character 296 newline = self.path.find('\n') 297 if newline > 1: self.path = self.path[0:newline] 298 return 0 299 300 def delete(self, oexec=""): #delete files on disk! 301 """delete the files via a shell command""" 302 if (self.type == 'BRIK'): 303 if os.path.isfile("%s.HEAD" % self.ppv()): 304 shell_com("rm %s.HEAD" % self.ppv(), oexec).run() 305 if os.path.isfile("%s.BRIK" % self.ppv()): 306 shell_com("rm %s.BRIK" % self.ppv(), oexec).run() 307 if os.path.isfile("%s.BRIK.gz" % self.ppv()): 308 shell_com("rm %s.BRIK.gz" % self.ppv(), oexec).run() 309 if os.path.isfile("%s.BRIK.bz2" % self.ppv()): 310 shell_com("rm %s.BRIK.bz2" % self.ppv(), oexec).run() 311 if os.path.isfile("%s.BRIK.Z" % self.ppv()): 312 shell_com("rm %s.BRIK.Z" % self.ppv(), oexec).run() 313 else: 314 if os.path.isfile(self.ppve()): 315 shell_com("rm %s" % self.ppve(), oexec).run() 316 return 317 def move_to_dir(self, path="", oexec=""): 318 #self.show() 319 #print path 320 found = 0 321 if os.path.isdir(path): 322 if (self.type == 'BRIK'): 323 if os.path.isfile("%s.HEAD" % self.ppv()): 324 sv = shell_com("mv %s %s/" % (self.head(), path), oexec).run() 325 found = found + 1 326 if os.path.isfile("%s.BRIK" % self.ppv()): 327 sv = shell_com("mv %s %s/" % (self.brick(), path), oexec).run() 328 found = found + 1 329 if os.path.isfile("%s.BRIK.gz" % self.ppv()): 330 sv = shell_com("mv %s %s/" % (self.brickgz(), path), oexec).run() 331 found = found + 1 332 if os.path.isfile("%s.BRIK.bz2" % self.ppv()): 333 sv = shell_com("mv %s %s/" % (self.brickbz2(), path), oexec).run() 334 found = found + 1 335 if os.path.isfile("%s.BRIK.Z" % self.ppv()): 336 sv = shell_com("mv %s %s/" % (self.brickZ(), path), oexec).run() 337 found = found + 1 338 if (found > 0): 339 self.new_path(path) 340 if ( not self.exist() and oexec != "dry_run"): 341 print("Error: Move to %s failed" % (self.ppv())) 342 return 0 343 else: 344 print("Error: Found no .HEAD or .BRIK or .BRIK.gz (or .bz2 or .Z) of %s" % (self.ppv())) 345 return 0 346 else: 347 if os.path.isfile("%s" % self.ppve()): 348 sv = shell_com("mv %s %s/" % (self.ppve(), path), oexec).run() 349 found = found + 1 350 if (found > 0): 351 self.new_path(path) 352 if ( not self.exist() and oexec != "dry_run"): 353 print("Error: Move to %s failed" % (self.ppv())) 354 return 0 355 else: 356 print("Error: Found no file %s to move." % self.ppve()) 357 return 0 358 else: 359 print("Error: Path %s not found for moving %s." % (path, self.ppv())) 360 return 0 361 return 1 362 363 def head(self): 364 return "%s.HEAD" % self.ppv() 365 def brick(self): 366 return "%s.BRIK" % self.ppv() 367 def brickgz(self): 368 return "%s.BRIK.gz" % self.ppv() 369 def brickbz2(self): 370 return "%s.BRIK.bz2" % self.ppv() 371 def brickZ(self): 372 return "%s.BRIK.Z" % self.ppv() 373 def new_path(self,path=""): 374 #give name a new path (check for root) 375 if len(path) == 0: pp = "./" 376 else: pp = path 377 ap = os.path.abspath(pp) 378 # require this to end in a '/' 379 if ap[-1] != '/': ap += '/' 380 self.path = ap 381 def new_prefix(self, prefix=""): 382 self.prefix = prefix 383 def new_view(self,view=""): 384 self.view = view 385 def show(self, mesg='', verb=1): 386 """options to see the path require verb>1""" 387 if mesg: mesg = ' (%s)' % mesg 388 print("AFNI filename%s:" % mesg) 389 if verb > 1: print(" curdir : %s" % os.path.abspath(os.curdir)) 390 391 print(" initial : %s" % self.initname) 392 if verb > 1: print(" name : %s" % self.ppve()) 393 if verb > 1: print(" path : %s" % self.path) 394 395 print(" prefix : %s" % self.prefix) 396 print(" view : %s" % self.view) 397 print(" exten. : %s" % self.extension) 398 print(" type : %s" % self.type) 399 print(" On Disk : %d" % self.exist()) 400 print(" Row Sel : %s" % self.rowsel) 401 print(" Col Sel : %s" % self.colsel) 402 print(" Node Sel: %s" % self.nodesel) 403 print(" RangeSel: %s" % self.rangesel) 404 405 def new(self, new_pref='', new_view='', parse_pref=0): 406 """return a copy with optional new_prefix and new_view 407 if parse_pref, parse prefix as afni_name 408 """ 409 an = afni_name() 410 an.path = self.path 411 if len(new_pref): 412 # maybe parse prefix as afni_name 413 if parse_pref: 414 ant = parse_afni_name(new_pref, do_sel=self.do_sel) 415 an.prefix = ant['prefix'] 416 else: an.prefix = new_pref 417 else: 418 an.prefix = self.prefix 419 if len(new_view): 420 an.view = new_view 421 else: 422 an.view = self.view 423 an.extension = self.extension 424 an.type = self.type 425 return an 426 427 def initial_view(self): 428 """return any initial view (e.g. +tlrc) from self.initial""" 429 pdict = parse_afni_name(self.initname, do_sel=self.do_sel) 430 view = pdict['view'] 431 if view in ['+orig', '+acpc', '+tlrc']: return view 432 return '' 433 434 def to_afni(self, new_view=''): 435 """modify to be BRIK type, with possible new_view (default is +orig)""" 436 437 # be sure there is some view 438 if new_view in valid_afni_views: self.view = new_view 439 elif self.view not in valid_afni_views: self.view = '+orig' 440 441 if self.type == 'BRIK': return 442 443 self.type = 'BRIK' 444 self.extension = '' # clear 445 return 446 447class comopt(object): 448 def __init__(self, name, npar, defpar, acplist=[], helpstr=""): 449 self.name = name 450 self.i_name = -1 # index of option name in argv 451 self.n_exp = npar # Number of expected params, 0 if no params, 452 # -1 if any number > 0 is OK. 453 # N if exactly N numbers are expected 454 self.n_found = -1 # Number of parameters found after parsing 455 # 0 means was on command line but had no params 456 self.parlist = None # parameter strings list following option 457 self.deflist = defpar # default parameter list,if any 458 self.acceptlist = acplist # acceptable values if any 459 self.required = 0 # is the argument required? 460 self.helpstr = helpstr # The help string 461 return 462 463 def show(self, mesg = '', short = 0): 464 print("%sComopt: %s" % (mesg, self.name)) 465 if short: return # 22 Jan 2008 [rickr] 466 467 print(" (i_name, n_exp, n_found) = (%d, %d, %d)" % \ 468 (self.i_name, self.n_exp, self.n_found)) 469 print(" parlist = %s" % self.parlist) 470 print(" deflist = %s" % self.deflist) 471 print(" acceptlist = %s" % self.acceptlist) 472 473 def test(self): 474 if (len(self.deflist) != 0 and self.parlist == None): 475 # some checks possible, parlist not set yet 476 if self.n_exp >= 0: 477 if len(self.deflist) != self.n_exp: 478 print("Error: Option %s needs %d parameters\n" \ 479 "Default list has %d parameters." \ 480 % (self.name, self.n_exp, len(self.deflist))) 481 return None 482 else: 483 if len(self.deflist) < -self.n_exp: 484 print("Error: Option %s needs at least %d parameters\n" \ 485 "Default list has %d parameters."\ 486 % (self.name, -self.n_exp, len(self.deflist))) 487 return None 488 else : 489 if self.n_exp >= 0: 490 #print "option %s n_exp = %d, len(parlist)=%d" % (self.name, self.n_exp, len(self.parlist)) 491 #self.show() 492 if len(self.parlist) != self.n_exp: 493 print("Error: Option %s needs %d parameters\n" \ 494 "Parameter list has %d parameters." \ 495 % (self.name, self.n_exp, len(self.parlist))) 496 return None 497 else: 498 if len(self.parlist) < -self.n_exp: 499 print("Error: Option %s needs at least %d parameters\n" \ 500 "Parameter list has %d parameters."\ 501 % (self.name, -self.n_exp, len(self.parlist))) 502 return None 503 return 1 504 505class shell_com(object): 506 history = [] # shell_com history 507 save_hist = 1 # whether to record as we go 508 509 def __init__(self, com, eo="", capture=0, save_hist=1): 510 """create instance of shell command class 511 512 com command to execute (or echo, etc) 513 eo echo mode string: echo/dry_run/script/"" 514 capture flag: store output from command? 515 save_hist flag: store history of commands across class instances? 516 """ 517 518 self.com = com # command string to be executed 519 self.eo = eo # echo mode (echo/dry_run/script/"") 520 # note: getcwdu() would be an option, but not needed 521 self.dir = os.getcwd() 522 self.exc = 0 #command not executed yet 523 self.so = '' 524 self.se = '' 525 if (self.eo == "quiet"): 526 self.capture = 1 527 else: 528 self.capture = capture; #Want stdout and stderr captured? 529 self.save_hist = save_hist 530 531 # check if user has requested an overwrite of trimming behavior. If the 532 # variable is set and is not set to a false value then default trimming 533 # behavior is not performed. 534 false_vals = ['no','n','f','false',"0"] 535 mod_request = os.environ.get("NO_CMD_MOD") 536 no_cmd_modiifcation_requested = bool( 537 mod_request and mod_request.lower() not in false_vals 538 ) 539 #If command line is long, trim it, if possible 540 l1 = len(self.com) 541 if no_cmd_modiifcation_requested: 542 # does not modify command to help provide more predictable output 543 self.trimcom = self.com 544 elif (l1 > 80): 545 self.trimcom = self.trim() 546 #if (len(self.com) < l1): 547 #print "Command trimmed to: %s" % (self.com) 548 else: 549 self.trimcom = re.sub(r"[ ]{2,}", ' ', self.com) 550 # string.join(string.split(self.com)) is bad for commands like 3dNotes 551 def trim(self): 552 #try to remove absolute path and numerous blanks 553 if self.dir[-1] != '/': 554 tcom = re.sub(r"[ ]{2,}", ' ', self.com).replace("%s/" % (self.dir), './') 555 else: 556 tcom = re.sub(r"[ ]{2,}", ' ', self.com).replace(self.dir, './') 557 return tcom 558 def echo(self): 559 if (len(self.trimcom) < len(self.com)): 560 ms = " (command trimmed)" 561 else: 562 ms = "" 563 if self.eo == "echo": 564 print("#Now running%s:\n cd %s\n %s" % (ms, self.dir, self.trimcom)) 565 sys.stdout.flush() 566 elif self.eo == "dry_run": 567 print("#Would be running%s:\n %s" % (ms, self.trimcom)) 568 sys.stdout.flush() 569 elif (self.eo == "script"): 570 print("#Script is running%s:\n %s" % (ms, self.trimcom)) 571 sys.stdout.flush() 572 elif (self.eo == "quiet"): 573 pass 574 575 if self.exc==1: 576 print("# WARNING: that command has been executed already! ") 577 sys.stdout.flush() 578 else: self.add_to_history() 579 580 return 581 582 def run(self): 583 self.echo() 584 if(self.exc==1): 585 return 0 586 if(self.eo=="dry_run"): 587 self.status = 0 588 self.exc = 1 589 return 0 590 self.status, self.so, self.se = shell_exec2(self.trimcom, self.capture) 591 self.exc = 1 592 return self.status 593 594 def run_echo(self,eo=""): 595 self.eo = eo; 596 self.run() 597 598 def add_to_history(self): 599 """append the current command (trimcom) to the history, truncating 600 if it is too long""" 601 if not self.save_hist: return 602 if len(self.history) >= MAX_SHELL_HISTORY: 603 self.history = self.history[-SAVE_SHELL_HISTORY:] 604 self.history.append(self.trimcom) 605 606 def shell_history(self, nhist=0): 607 if nhist == 0 or nhist > len(self.history): return self.history 608 else: return self.history[-nhist] 609 610 def stdout(self): 611 if (len(self.so)): 612 print("++++++++++ stdout:") 613 sys.stdout.flush() 614 for ln in self.so: 615 print(" %s" % ln) 616 sys.stdout.flush() 617 def stderr(self): 618 if (len(self.se)): 619 print("---------- stderr:") 620 sys.stdout.flush() 621 for ln in self.se: 622 print(" %s" % ln) 623 def out(self): 624 if self.exc: 625 self.stdout() 626 self.stderr() 627 else: 628 print("#............. not executed.") 629 sys.stdout.flush() 630 631 def val(self, i, j=-1): #return the jth string from the ith line of output. if j=-1, return all ith line 632 if not self.exc: 633 print("Error: Command not executed") 634 return None 635 elif self.eo == "dry_run": 636 return "0" #Just something that won't cause trouble for places expecting numbers 637 elif len(self.so) == 0: 638 print("Error: Empty output.") 639 self.stderr() 640 return None 641 elif len(self.so) <= i: 642 print("Error: First index i=%d >= to number of elements (%d) in output " % \ 643 (i, len(self.so))) 644 return None 645 646 if j>= 0: 647 l = self.so[i].split() 648 if len(l) <= j: 649 print("Error: Second index j=%d is >= to number of elements (%d) in %dth line of output" % \ 650 (j, len(l), i)) 651 return None 652 else: 653 # return l[j].decode() 654 return l[j] 655 else: 656 # return self.so[i].decode() 657 return self.so[i] 658 659# return the attribute list for the given dataset and attribute 660def read_attribute(dset, atr, verb=1): 661 [so, se] = shell_exec('3dAttribute %s %s' % (atr, dset)) 662 if len(so) == 0: 663 if verb > 0: 664 print('** 3dAttribute exec failure for "%s %s"' % (atr, dset)) 665 if len(se) > 0: print("shell error:\n %s\n" % '\n '.join(se)) 666 return None 667 list = so[0].split() 668 if len(list) > 0: return list 669 else: 670 if verb > 0: print('** 3dAttribute failure for "%s %s":' % (atr, dset)) 671 return None 672 673# return dimensions of dset, 4th dimension included 674def dset_dims(dset): 675 # always return 4 values, trap some errors 7 Apr 2015 [rickr] 676 dl = [-1, -1, -1, -1] 677 if 0: #This approach fails with selectors! 678 ld = read_attribute(dset, 'DATASET_DIMENSIONS') 679 lr = read_attribute(dset, 'DATASET_RANK') 680 dl = [] 681 for dd in ld[0:3]: 682 dl.append(int(dd)) 683 dl.append(int(lr[1])) 684 else: 685 cstr = '3dnvals -all %s' % dset 686 com = shell_com(cstr, capture=1); 687 rv = com.run() 688 if rv: 689 print('** Failed "%s"' % cstr) 690 return dl 691 if len(com.so) < 1: 692 print('** no stdout from "%s"' % cstr) 693 return dl 694 vlist = com.so[0].split() 695 if len(vlist) != 4: 696 print('** failed "%s"' % cstr) 697 return dl 698 try: 699 vl = [int(val) for val in vlist] 700 # so only if success 701 dl = vl 702 except: 703 print('** could not convert output to int:\n' \ 704 ' command: %s\n' \ 705 ' output: %s' % (cstr, com.so)) 706 # dl = [int(com.val(0,0)), int(com.val(0,1)), 707 # int(com.val(0,2)), int(com.val(0,3))] 708 return dl 709 710 711#transform a list of afni names to one string for shell script usage 712def anlist(vlst, sb=''): 713 namelst = [] 714 if len(sb): 715 sbs = "'%s'" % sb 716 else: 717 sbs = '' 718 for an in vlst: 719 namelst.append("%s%s" % (an.ppv(), sbs)) 720 return str.join(' ',namelst) 721 722 723#parse options, put into dictionary 724def getopts(argv): 725 opts = {} 726 while argv: 727 if argv[0][0] == '-': 728 opts[argv[0]] = argv[1] 729 argv = argv[2:] 730 else: 731 argv = argv[1:] 732 return opts 733 734def show_opts2(opts): 735 if opts == None: 736 print("Option dictionary is None\n") 737 return 738 print(opts) 739 for key in list(opts.keys()): 740 print("Option Name: %s" % key) 741 print(" Found: %d" % opts[key].n_found) 742 print(" User Parameter List: %s" % opts[key].parlist) 743 print(" Default Parameter List: %s\n" % opts[key].deflist) 744 return 745 746def getopts2(argv,oplist): 747 """ A function to parse command line arguments. 748 to use it, you need to set up the options list. 749 So, from a main you can do the following: 750 751 oplist = [] 752 # an option that needs no params 753 oplist.append(afni_base.comopt('-dicom', 0, [])) 754 # an option that needs 2 params, with 2 options, defaulting to 2 and 10.0 755 oplist.append(afni_base.comopt('-clust', 2, ['2', '10.0'])) 756 # an option that needs an undetermined number of parameters 757 # (-1 for 1 or more, -2 for 2 or more) 758 oplist.append(afni_base.comopt('-dsets', -1, [])) 759 760 once the list is made, you call getopts2 with argv and oplist 761 762 opts = afni_base.getopts2(sys.argv, oplist) 763 764 opts is a dictionary with the name of oplist elements as keys 765 766 to get a quick look at it use: 767 afni_base.show_opts2(opts) """ 768 opts = {} 769 if len(argv) == 0: 770 return opts 771 #Add the program name 772 op = comopt('basename',0, []) 773 opts['basename'] = op 774 argv.remove( argv[0] ) 775 776 #form a list of the known options 777 optnames = [] 778 for op in oplist: 779 optnames.append(op.name) 780 781 #find those options in oplist 782 for op in oplist: 783 if op.name in argv: 784 op.n_found = 0 #found that argument 785 op.iname = argv.index(op.name) #copy index into list 786 argv.remove(op.name) #remove this option from list 787 op.parlist = [] 788 if op.n_exp < 0 or op.n_exp > 0: #parameters expected, get them 789 while ((op.n_exp < 0 and op.iname < len(argv)) or \ 790 (op.n_exp > 0 and len(op.parlist) < op.n_exp and len(argv) > 0))\ 791 and argv[op.iname] not in optnames: 792 if len(op.acceptlist): 793 if argv[op.iname] not in op.acceptlist: 794 print("Error: parameter value %s for %s is not " \ 795 "acceptable\nChoose from %s" % \ 796 (argv[op.iname], op.name, \ 797 str.join(' , ',op.acceptlist))) 798 op.parlist.append(argv[op.iname]) #string added 799 argv.remove(argv[op.iname]) #remove this string from list 800 op.n_found = len(op.parlist) 801 802 else : #No option in argv, just copy option 803 op.parlist = op.deflist 804 805 #Now copy results to dictionary 806 opts[op.name] = op #a bit of redundancy, but I don't care 807 808 if (op.test() == None): 809 afni_base.show_opts2(opts) 810 return None 811 812 813 #Any remaining? 814 for op in oplist: 815 if op.name == 'loose': #Expecting loose params 816 if op.n_exp < 0 or op.n_exp > 0: #parameters expected, get them 817 op.parlist.extend(argv) #stick'em all in 818 opts[op.name] = op 819 if op.n_exp > 0 and len(op.parlist) != op.n_exp: 820 print("Error: Expecting %d parameters\n" \ 821 "Have %d on command line (%s).\n" % \ 822 (op.n_exp, len(op.parlist), op.parlist)) 823 return None 824 elif len(argv) > 0: 825 print("Error: Expecting no loose parameters.\n" \ 826 "Have %d loose parameters (or bad option) on " \ 827 "command line (%s).\n" % (len(argv), argv)) 828 return None 829 830 #go west young man 831 return opts 832 833#Strip the extensions from extlist out of name 834#returns name without the extension and the extension 835#found, if any. 836#Example: 837# res = strip_extension('Hello.Jim', ['.paul', '.Jim']) 838# --> res[0] = 'Hello' 839# --> res[1] = '.Jim' 840# 841# To remove anything after the last 'dot' 842# res = strip_extension('Hello.Jim', []) 843# 844def strip_extension(name, extlist): 845 res = {} 846 nle = len(name) 847 if len(extlist): 848 while extlist: 849 xle = len(extlist[0]) 850 if nle > xle: 851 if name[-xle:] == extlist[0]: 852 res[0] = name[:-xle] 853 res[1] = extlist[0] 854 return res 855 #else: 856 #nada 857 #print name 858 #Go to next element 859 extlist = extlist[1:] 860 else: #Nothing specified, work the dot 861 spl = name.split('.') 862 if len(spl) > 1: 863 res[0] = str.join('.',spl[0:-1]) 864 res[1] = '.'+spl[-1] 865 return res 866 867 #defaults 868 res[0] = name 869 res[1] = '' 870 return res 871 872 873#parse an afni name 874def parse_afni_name(name, aname=None, do_sel=1): 875 """do_sel : apply afni_selectors""" 876 res = {} 877 #get the path #Can also use os.path.split 878 rp = os.path.dirname(name) #relative path 879 ap = os.path.abspath(rp) #absolute path 880 fn = os.path.basename(name) 881 #Get selectors out of the way: 882 if do_sel: 883 res['col'], res['row'], res['node'], res['range'], fn = afni_selectors(fn) 884 else: 885 res['col'] = res['row'] = res['node'] = res['range'] = '' 886 887 #is this a .nii volume? 888 rni = strip_extension(fn,['.nii', '.nii.gz']) 889 if (len(rni[1]) > 0): 890 vi = '' #No view 891 ex = rni[1] 892 pr = rni[0] 893 tp = 'NIFTI' 894 else: 895 rni = strip_extension(fn,['.HEAD','.BRIK','.BRIK.gz','.BRIK.bz2', 896 '.BRIK.Z','.1D', '.', '.1D.dset', '.niml.dset']) 897 ex = rni[1] 898 if (ex == '.1D' or ex == '.1D.dset'): 899 tp = "1D" 900 elif (ex == '.niml.dset'): 901 tp = "NIML" 902 else: 903 tp = 'BRIK' 904 if (ex == '.'): 905 ex = '' #dump the dot 906 rni = strip_extension(rni[0], ['+orig','+tlrc','+acpc']) 907 vi = rni[1] 908 pr = rni[0] 909 #get selectors 910 911 #Build the dictionary result 912 if len(rp) == 0: 913 rp = '.' 914 # A world of trouble when relative path is used. So use ap instead April 08 915 # (do not append '/' to root) 916 if ap == '/': res['path'] = ap 917 else: res['path'] = "%s/" % ap 918 res['prefix'] = pr 919 res['view'] = vi 920 res['extension'] = ex 921 res['type'] = tp 922 return res 923 924#utilitiarian laziness 925def afni_prefix(names): 926 pref = [] 927 for run in range(0, len(names)): 928 res = parse_afni_name(names[run]) 929 pref.append(res['prefix']) 930 return pref 931 932def afni_view(names): 933 pref = [] 934 for run in range(0, len(names)): 935 res = parse_afni_name(names[run]) 936 pref.append(res['view']) 937 return pref 938 939def afni_selectors(names): 940 sqsel = "" 941 cusel = "" 942 pnsel = "" 943 ltsel = "" 944 namestr = names 945 946 # Use modified namestr for continued processing, as no nesting should 947 # be considered here, and '#' can be in [] selectors. 948 949 nse = namestr.count('[') 950 if (nse == 1 and nse == namestr.count(']')): 951 sqsel = namestr[namestr.find('['):names.find(']')+1] 952 namestr = namestr.replace(sqsel,'') 953 954 # handle enclosed shell variables, like "${subj}" 13 Jun 2019 [rickr] 955 clist = find_all_non_var_curlys(namestr) 956 if len(clist) == 1: # maybe > 0 is okay, should we really distinguish? 957 if namestr.count('}', clist[-1]) == 1: 958 cend = namestr.find('}',clist[-1]) # for clarity 959 cusel = namestr[clist[-1]:cend+1] 960 namestr = namestr.replace(cusel,'') 961 962 nse = namestr.count('<') 963 if (nse == 1 and nse == namestr.count('>')): 964 ltsel = namestr[namestr.find('<'):namestr.find('>')+1] 965 namestr = namestr.replace(ltsel,'') 966 967 nse = namestr.count('#') 968 if (nse == 2): 969 nf = namestr.find('#') 970 pnsel = namestr[nf[0]:nf[1]+1] 971 namestr = namestr.replace(pnsel,'') 972 973 return sqsel, cusel, pnsel, ltsel, namestr 974 975def find_all_non_var_curlys(stext): 976 """return a 977 """ 978 clist = [] 979 start = 0 980 cind = stext.find('{', start) 981 while cind >= start: 982 # if preceded by '$', just ignore (okay if cind-1 == -1) 983 if cind == 0 or stext[cind-1] != '$': 984 # have found '{', and not preceded by '$' 985 clist.append(cind) 986 # and look for next 987 start = cind + 1 988 cind = stext.find('{', start) 989 return clist 990 991#execute a shell command and when capture is 1 returns results in: 992# so (stdout) and se (stderr) and status 993#status is only reliable with python versions 2.5 and above 994def shell_exec(s,opt="",capture=1): 995 #opt is left here for backwards compatibility. 996 #no echoing should be done here. It is better 997 #to use the shell_com objects 998 if opt == "dry_run": 999 print("#In %s, would execute:\n %s" % (os.getcwd(), s)) 1000 sys.stdout.flush() 1001 elif opt == "echo": 1002 print("#In %s, about to execute:\n %s" % (os.getcwd(), s)) 1003 sys.stdout.flush() 1004 1005 status, so, se = shell_exec2(s,capture) 1006 1007 return so, se 1008 1009def shell_exec2(s, capture=0): 1010 1011 # moved to python_ver_float() 16 May 2011 [rickr] 1012 if (python_ver_float() < 2.5): #Use old version and pray 1013 #if there is no capture in option: run os.system 1014 if(not capture): 1015 os.system("%s"%s) 1016 status = 0; #Don't got status here 1017 so = [] # should return arrays 24 Apr, 2014 [rickr] 1018 se = [] 1019 else: 1020 i,o,e = os.popen3(s) #captures stdout in o, stderr in e and stdin in i 1021 # The readlines seems to hang below despite all the attempts at 1022 # limiting the size and flushing, etc. The hangup happens when a 1023 # program spews out a lot to stdout. So when that is expected, 1024 # redirect output to a file at the command. 1025 # Or use the "script" execution mode 1026 1027 # Forget readlines() and just use readline(). From that, 1028 # construct the desired lists. 12 Mar 2015 [rickr] 1029 1030 so = [] 1031 ll = o.readline() 1032 while len(ll) > 0: 1033 so.append(ll) 1034 ll = o.readline() 1035 1036 se = [] 1037 ll = e.readline() 1038 while len(ll) > 0: 1039 se.append(ll) 1040 ll = e.readline() 1041 1042 #so = o.readlines(64) - read till EOF, but python might hang 1043 #se = e.readlines(64) - if output to stdout and stderr is too large. 1044 #Have tried readlines(1024) and (256) to little effect 1045 1046 o.close 1047 e.close # 1048 status = 0; #Don't got status here 1049 else: 1050 import subprocess as SP 1051 if(not capture): 1052 pipe = SP.Popen(s,shell=True, stdout=None, stderr=None, close_fds=True) 1053# pipe = SP.Popen(s,shell=True, executable='/bin/tcsh', stdout=None, stderr=None, close_fds=True) 1054 status = pipe.wait() #Wait till it is over and store returncode 1055 so = [] 1056 se = [] 1057 else: 1058 pipe = SP.Popen(s,shell=True, stdout=SP.PIPE, stderr=SP.PIPE, close_fds=True) 1059 o,e = pipe.communicate() #This won't return until command is over 1060 status = pipe.returncode #NOw get returncode 1061 1062 # for python3, convert bytes to unicode (note type is bytes, but 1063 # that matches str in p2), just use stupid python version 1064 if python_ver_float() >= 3.0: 1065 o = o.decode() 1066 e = e.decode() 1067 1068 so = o.splitlines() 1069 se = e.splitlines() 1070 1071 #if decode: 1072 # so = [l.decode() for l in so] 1073 # se = [l.decode() for l in se] 1074 1075 return status, so, se 1076 1077# basically shell_exec2, but no splitlines() 16 May 2011 [rickr] 1078def simple_shell_exec(command, capture=0): 1079 """return status, so, se (without any splitlines)""" 1080 1081 if (python_ver_float() < 2.5): 1082 # abuse old version, re-join split lines 1083 status, so, se = shell_exec2(command, capture=capture) 1084 return status, '\n'.join(so), '\n'.join(se) 1085 1086 import subprocess as SP 1087 1088 if capture: 1089 pipe = SP.Popen(command,shell=True, stdout=SP.PIPE, stderr=SP.PIPE, 1090 close_fds=True) 1091 so, se = pipe.communicate() # returns after command is done 1092 status = pipe.returncode 1093 1094 # for python3, convert bytes to unicode (cannot use type(so) == bytes) 1095 if python_ver_float() >= 3.0: 1096 so = so.decode() 1097 se = se.decode() 1098 1099 else: 1100# pipe = SP.Popen(command,shell=True, executable='/bin/tcsh', 1101 pipe = SP.Popen(command,shell=True, 1102 stdout=None, stderr=None, close_fds=True) 1103 status = pipe.wait() #Wait till it is over and store returncode 1104 so, se = "", "" 1105 1106 return status, so, se 1107 1108# we may want this in more than one location 16 May 2011 [rickr] 1109def python_ver_float(): 1110 """return the python version, as a float""" 1111 vs = sys.version.split()[0] 1112 vlist = vs.split('.') 1113 if len(vlist) > 1: 1114 vs = "%s.%s" % (vlist[0], vlist[1]) 1115 else: 1116 vs = vlist[0] 1117 1118 return float(vs) 1119 1120#generic unique function, from: 1121# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560/index_txt 1122def unique(s): 1123 """Return a list of the elements in s, but without duplicates. 1124 1125 For example, unique([1,2,3,1,2,3]) is some permutation of [1,2,3], 1126 unique("abcabc") some permutation of ["a", "b", "c"], and 1127 unique(([1, 2], [2, 3], [1, 2])) some permutation of 1128 [[2, 3], [1, 2]]. 1129 1130 For best speed, all sequence elements should be hashable. Then 1131 unique() will usually work in linear time. 1132 1133 If not possible, the sequence elements should enjoy a total 1134 ordering, and if list(s).sort() doesn't raise TypeError it's 1135 assumed that they do enjoy a total ordering. Then unique() will 1136 usually work in O(N*log2(N)) time. 1137 1138 If that's not possible either, the sequence elements must support 1139 equality-testing. Then unique() will usually work in quadratic 1140 time. 1141 """ 1142 1143 n = len(s) 1144 if n == 0: 1145 return [] 1146 1147 # Try using a dict first, as that's the fastest and will usually 1148 # work. If it doesn't work, it will usually fail quickly, so it 1149 # usually doesn't cost much to *try* it. It requires that all the 1150 # sequence elements be hashable, and support equality comparison. 1151 u = {} 1152 try: 1153 for x in s: 1154 u[x] = 1 1155 except TypeError: 1156 del u # move on to the next method 1157 else: 1158 return list(u.keys()) 1159 1160 # We can't hash all the elements. Second fastest is to sort, 1161 # which brings the equal elements together; then duplicates are 1162 # easy to weed out in a single pass. 1163 # NOTE: Python's list.sort() was designed to be efficient in the 1164 # presence of many duplicate elements. This isn't true of all 1165 # sort functions in all languages or libraries, so this approach 1166 # is more effective in Python than it may be elsewhere. 1167 try: 1168 t = list(s) 1169 t.sort() 1170 except TypeError: 1171 del t # move on to the next method 1172 else: 1173 assert n > 0 1174 last = t[0] 1175 lasti = i = 1 1176 while i < n: 1177 if t[i] != last: 1178 t[lasti] = last = t[i] 1179 lasti += 1 1180 i += 1 1181 return t[:lasti] 1182 1183 # Brute force is all that's left. 1184 u = [] 1185 for x in s: 1186 if x not in u: 1187 u.append(x) 1188 return u 1189 1190 1191#Get files from a wild card list 1192#e.g: GetFiles(["*.HEAD", "*.1D"]) 1193def GetFiles(wild): 1194 # was reduce(operator.add, list(map(glob.glob, wild))), but be simple 1195 rl = [] 1196 for mstr in wild: 1197 rl.extend(glob.glob(mstr)) 1198 return rl 1199 1200def PrintIndexedList(l): 1201 cnt = 0 1202 for il in l: 1203 print("%d- %s" % (cnt, il)) 1204 cnt += 1 1205 print("") 1206 1207def match(txt, l): 1208 lm = [] 1209 for il in l: 1210 fnd = il.find(txt) 1211 if fnd >= 0: 1212 lm.append(il) 1213 return lm 1214 1215def unique_match(txt, l): 1216 lm = match(txt,l) 1217 if len(lm) == 1: 1218 return lm[0] 1219 else: 1220 return None 1221 1222 1223def GetSelectionFromList(l, prmpt = ""): 1224 PrintIndexedList(l) 1225 if len(prmpt)==0: 1226 prmpt = 'Enter Selection by number or name: ' 1227 cnt = 0 1228 while cnt < 10: 1229 name = input(prmpt) 1230 if not name: 1231 return None 1232 if name.isdigit(): 1233 if int(name) < len(l) and int(name) >= 0: 1234 return l[int(name)] 1235 else: 1236 print("Input error: number must be between 0 and %d" % (len(l)-1)) 1237 else: 1238 if name in l: 1239 return name 1240 nameg = unique_match(name, l) 1241 if nameg: 1242 return nameg 1243 else: 1244 print("Input error: selection %s has %d matches in list." % \ 1245 ( name, len(match(name, l)))) 1246 cnt += 1 1247 print("Vous ne comprenez pas l'anglais?") 1248 print("Ciao") 1249 1250# determine if a string is a valid floating point number 1251# from http://mail.python.org/pipermail/python-list/2002-September/164892.html 1252# used like isnum() or isalpha() built-in string methods 1253def isFloat(s): 1254 try: 1255 float(s) 1256 return True 1257 except: 1258 return False 1259 1260def list_count_float_not_int(ll): 1261 """see if list of numbers can be treated like ints; returns number of 1262floats. 1263 1264 Note: for tiny decimals, this can be wrong: 1265 1266In [7]: ab.listContainsFloatVsInt([1,2,3,4.0000000001,5.0]) 1267Out[7]: 1 1268 1269In [8]: ab.listContainsFloatVsInt([1,2,3,4.000000000000000001,5.0]) 1270Out[8]: 0 1271 1272 """ 1273 1274 if type(ll) != list : EP("Input is not a list") 1275 N = len(ll) 1276 1277 count = 0 1278 1279 for x in ll: 1280 a = float(x) 1281 b = float(int(x)) 1282 if b-a : 1283 count+=1 1284 return count 1285 1286# ----------------------------------------------------------------------- 1287 1288afni_sel_list = [ '{', '[', '<' ] # list of left-sided selectors 1289 1290def glob_with_afni_selectors(ilist, verb=0): 1291 """Input: ilist is an input list (or str) of input names to be interpreted. 1292 1293 The ilist entries can contain wildcards and/or AFNI selectors, 1294 such as: 1295 1296 sub_*.dat 1297 sub_*.dat{0..5,7,9,12.$} 1298 sub_*.dat{0..5,7,9,12.$}[1..3] 1299 sub_*.dat[1..3]{0..5,7,9,12.$} 1300 1301 Return: a list of glob-expanded entries, with selectors (if 1302 present) applied to all appropriate ones, e.g.: 1303 1304 sub_*.dat{0..5,7,9,12.$} 1305 -> 1306 ['sub_000.dat{0..5,7,9,12.$}', 'sub_001.dat{0..5,7,9,12.$}', ...] 1307 1308 """ 1309 1310 if type(ilist) == str : ilist = [ilist] 1311 1312 N = len(ilist) 1313 olist = [] 1314 1315 no_hits = [] 1316 1317 for ii in range(N): 1318 # split at spaces first 1319 lspaced = ilist[ii].split() 1320 for word in lspaced: 1321 lll = [] 1322 # check if any selectors are present; should only be one 1323 # of each kind at most 1324 for sel in afni_sel_list: 1325 if word.__contains__(sel) : 1326 split = word.split(sel) 1327 lll = glob.glob(split[0]) 1328 if len(lll) > 0 : 1329 lll = [x + sel + ''.join(split[1:]) for x in lll] 1330 break 1331 if lll : 1332 olist.extend(lll) 1333 else: 1334 lll = glob.glob(word) 1335 if lll : 1336 olist.extend(lll) 1337 if not(lll) : 1338 no_hits.append(word) 1339 1340 if verb : 1341 IP("Found these from glob list: {}".format(' '.join(ilist))) 1342 BP('-'*40) 1343 for x in olist: 1344 BP(' ' + x) 1345 BP('-'*40) 1346 1347 if no_hits : 1348 WP("This part of the glob list contained no matches:") 1349 for x in no_hits: 1350 BP(' ' + x) 1351 BP('-'*40) 1352 1353 return olist 1354 1355# ----------------------------------------------------------------------- 1356# [PT: Jun 1, 2020] simple functions to stylize printing of messages 1357# in The AFNI Way. APRINT() is the main workhorse; BP(), IP(), EP() and 1358# WP() are just short/convenient wrappers 1359 1360def BP( S, indent=True): 1361 '''Warning print string S''' 1362 APRINT(S, ptype='BLANK', indent=indent) 1363 1364def IP( S, indent=True): 1365 '''Info print string S''' 1366 APRINT(S, ptype='INFO', indent=indent) 1367 1368def WP( S, indent=True): 1369 '''Warning print string S''' 1370 APRINT(S, ptype='WARNING', indent=indent) 1371 1372def EP( S, indent=True, end_exit=True): 1373 '''Error print string S 1374 1375 By default, exit after printing 1376 1377 ''' 1378 APRINT(S, ptype='ERROR', indent=indent) 1379 1380 if end_exit : 1381 sys.exit(1) 1382 1383def APRINT( S, ptype=None, indent=True): 1384 '''Print Error/Warn/Info for string S 1385 1386 This function is not meant to be used directly, in general; use 1387 {W|E|I}PRINT(), instead. 1388 1389 ''' 1390 1391 if ptype == 'WARNING' : 1392 ptype_str = "+* WARNING:" 1393 elif ptype == 'ERROR' : 1394 ptype_str = "** ERROR:" 1395 elif ptype == 'INFO' : 1396 ptype_str = "++" 1397 elif ptype == 'BLANK' : 1398 ptype_str = " " 1399 else: 1400 print("**** Unrecognized print type '{}'. So, error about\n" 1401 " a warning, error or info message!\n".format(ptype)) 1402 sys.exit(1) 1403 1404 if indent : 1405 S = S.replace( "\n", "\n ") 1406 1407 out = "{} ".format(ptype_str) 1408 out+= S 1409 1410 if ptype == 'ERROR' : 1411 out+= "\n" 1412 1413 print(out) 1414 1415 1416def ARG_missing_arg(arg): 1417 EP("missing argument after option flag: {}".format(arg)) 1418 1419def parse_help_text_for_opts( H ): 1420 """Input: help string H (likely, a multilined one). 1421 1422 This program will go through and try to find all option names, 1423 basically by finding leftmost strings starting with a single '-' 1424 char (like apsearch, I believe). 1425 1426 Output: list of option names, to be used in parsing user input 1427 argv. 1428 1429 """ 1430 1431 opts = [] 1432 1433 hsplit = H.split('\n') 1434 for x in hsplit: 1435 if x.strip() : 1436 start = x.split()[0].strip() 1437 if start[0] == '-' : 1438 if len(start) > 1 : 1439 if start[1] != '-': 1440 opts.append(start) 1441 1442 return opts 1443 1444