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