|  | @@ -1,122 +1,128 @@
 | 
	
		
			
				|  |  |  # tf  Text File manipulations
 | 
	
		
			
				|  |  |  #   for micropython and other tiny environments 
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -#NOTE: the ESP8266 port cannot to \1,\2 type replacements in the s/search/replace/ operator
 | 
	
		
			
				|  |  | +# (c) Pat Beirne patb@pbeirne.com
 | 
	
		
			
				|  |  |  import re,os,sys,gc
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -def _file_scan(src,dest,start=1,end=0xFFFFFFFF,numbers=False,grep_func=None):
 | 
	
		
			
				|  |  | -  #src is a filename, dst is an open handle
 | 
	
		
			
				|  |  | +# ====legend====
 | 
	
		
			
				|  |  | +# e=end
 | 
	
		
			
				|  |  | +# f=input file
 | 
	
		
			
				|  |  | +# g=output file
 | 
	
		
			
				|  |  | +# i=iteration variable
 | 
	
		
			
				|  |  | +# m=line range
 | 
	
		
			
				|  |  | +# r=replace text
 | 
	
		
			
				|  |  | +# s=start/search/string
 | 
	
		
			
				|  |  | +# sp=search regex
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +def transfer(src,dest,first=1,last=0xFFFFFFFF,numbers=False,grep_func=None):
 | 
	
		
			
				|  |  | +  #src is a filename, dst is a handle
 | 
	
		
			
				|  |  |    i=0
 | 
	
		
			
				|  |  |    try:
 | 
	
		
			
				|  |  |      with open(src) as f:
 | 
	
		
			
				|  |  | -      for line in f:
 | 
	
		
			
				|  |  | +      for lin in f:
 | 
	
		
			
				|  |  |          i=i+1
 | 
	
		
			
				|  |  | -        if i<start or i>end:
 | 
	
		
			
				|  |  | +        if i<first or i>last:
 | 
	
		
			
				|  |  |            continue
 | 
	
		
			
				|  |  | -        if grep_func and not grep_func(line):
 | 
	
		
			
				|  |  | +        if grep_func and not grep_func(lin):
 | 
	
		
			
				|  |  |            continue
 | 
	
		
			
				|  |  |          if numbers:
 | 
	
		
			
				|  |  |            dest.write(str(i)+' ')
 | 
	
		
			
				|  |  | -        dest.write(line)
 | 
	
		
			
				|  |  | +        dest.write(lin)
 | 
	
		
			
				|  |  |    except:
 | 
	
		
			
				|  |  |      print("could not open file {}".format(src))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -def cp(src_f, dst_f):
 | 
	
		
			
				|  |  | +def cp(src,dest):
 | 
	
		
			
				|  |  |    try:
 | 
	
		
			
				|  |  | -    with open(dst_f,'w') as g:
 | 
	
		
			
				|  |  | -      _file_scan(src_f,g)
 | 
	
		
			
				|  |  | +    with open(dest,'w') as g:
 | 
	
		
			
				|  |  | +      transfer(src,g)
 | 
	
		
			
				|  |  |    except:
 | 
	
		
			
				|  |  | -    print("could not write to file {}".format(dst_f))
 | 
	
		
			
				|  |  | +    print("could not write to file {}".format(dest))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def grep(filename, pattern, numbers=False):
 | 
	
		
			
				|  |  |    m=re.compile(pattern)
 | 
	
		
			
				|  |  |    if not m:
 | 
	
		
			
				|  |  |      print("grep() called with invalid pattern")
 | 
	
		
			
				|  |  | -    return None
 | 
	
		
			
				|  |  | -  _file_scan(filename,sys.stdout,numbers=numbers,grep_func=(lambda x:m.search(x)))
 | 
	
		
			
				|  |  | -  print()
 | 
	
		
			
				|  |  | +    return 
 | 
	
		
			
				|  |  | +  transfer(filename,sys.stdout,numbers=numbers,grep_func=(lambda x:m.search(x)))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def cat(filename, first=1, last=1000000, numbers=False, title=True):
 | 
	
		
			
				|  |  |    if title:
 | 
	
		
			
				|  |  |      print("===={}=====".format(filename))
 | 
	
		
			
				|  |  | -  _file_scan(filename,sys.stdout,first,last,numbers=numbers)
 | 
	
		
			
				|  |  | -  print()
 | 
	
		
			
				|  |  | +  transfer(filename,sys.stdout,first,last,numbers=numbers)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -def sed(filename, sed_cmd, bak_ext="bak"):
 | 
	
		
			
				|  |  | -  #print("sed() called with sed_cmd=<{}>".format(sed_cmd))
 | 
	
		
			
				|  |  | +def sed(filename, sed_cmd, bak_ext=".bak"):
 | 
	
		
			
				|  |  |    # parse the sed_cmd
 | 
	
		
			
				|  |  | -  # group 1,3 are the n-start, n-end    group 4 is command
 | 
	
		
			
				|  |  | -  g=re.search("^(\d*)([,-](\d+|\$))?\s*([sdaixX].*)",sed_cmd)
 | 
	
		
			
				|  |  | -  if not g:
 | 
	
		
			
				|  |  | -    print("sed() failed; 2nd argument must be a number followed by one of sdaixX; no changes applied")
 | 
	
		
			
				|  |  | -    return 0,0
 | 
	
		
			
				|  |  | -  cmd=g.group(4)
 | 
	
		
			
				|  |  | -  #print("sed() cmd parsed into <{}>,<{}> and <{}>".format(g.group(1),g.group(3),g.group(4)))
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  | -  start,end=(1,1000000)
 | 
	
		
			
				|  |  | -  if g.group(1):
 | 
	
		
			
				|  |  | -    start=end=int(g.group(1))
 | 
	
		
			
				|  |  | -  if g.group(3):
 | 
	
		
			
				|  |  | -    end=1000000 if g.group(3)=='$' else int(g.group(3))
 | 
	
		
			
				|  |  | +  # group 1,3 are the n-start, n-end    group 4 is command: aidsxX
 | 
	
		
			
				|  |  | +  a=re.search("^(\d*)([,-](\d+|\$))?\s*([sdaixX].*)",sed_cmd)
 | 
	
		
			
				|  |  | +  if not a:
 | 
	
		
			
				|  |  | +    print("sed() failed; 2nd argument must be a number-range followed by one of sdaixX; no changes applied")
 | 
	
		
			
				|  |  | +    return
 | 
	
		
			
				|  |  | +  cmd=a.group(4)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +  s,e=(1,1000000)
 | 
	
		
			
				|  |  | +  if a.group(1):
 | 
	
		
			
				|  |  | +    s=e=int(a.group(1))
 | 
	
		
			
				|  |  | +  if a.group(3):
 | 
	
		
			
				|  |  | +    e=1000000 if a.group(3)=='$' else int(a.group(3))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    op=cmd[0]
 | 
	
		
			
				|  |  |    if op not in "sdiaxX":
 | 
	
		
			
				|  |  |      print("sed requires an operation, one of 's,d,i,a,x or X'")
 | 
	
		
			
				|  |  | -    return 0,0
 | 
	
		
			
				|  |  | +    return
 | 
	
		
			
				|  |  |    #print("sed command parser of <{}> returned {} {} {} {}".format(cmd,sr,de,ins,add))
 | 
	
		
			
				|  |  | -  if op in "sxX" and len(cmd)<2: 
 | 
	
		
			
				|  |  | -    print("invalid sed argument")
 | 
	
		
			
				|  |  | -    return (0,0)
 | 
	
		
			
				|  |  | -  if op=='s':
 | 
	
		
			
				|  |  | -    dl=cmd[1]
 | 
	
		
			
				|  |  | -    gs=re.search("s"+dl+"([^"+dl+"]*)"+dl+"([^"+dl+"]*)"+dl,cmd)
 | 
	
		
			
				|  |  | -    if not gs:
 | 
	
		
			
				|  |  | -      print("invalid sed search-and-replace pattern")
 | 
	
		
			
				|  |  | -      return (0,0)
 | 
	
		
			
				|  |  | -    s,r = gs.group(1),gs.group(2)
 | 
	
		
			
				|  |  | -    #print("search <{}> and replace <{}>".format(s,r))  
 | 
	
		
			
				|  |  | -    sp=re.compile(s) 
 | 
	
		
			
				|  |  | -  if op=='X' or op=='x':
 | 
	
		
			
				|  |  | +  if op in "sxX":
 | 
	
		
			
				|  |  | +    if len(cmd)<2: 
 | 
	
		
			
				|  |  | +      print("invalid sed argument")
 | 
	
		
			
				|  |  | +      return
 | 
	
		
			
				|  |  |      dl=cmd[1]
 | 
	
		
			
				|  |  | -    gs=re.search("[xX]"+dl+"([^"+dl+"]*)"+dl,cmd)
 | 
	
		
			
				|  |  | +    if op=='s':
 | 
	
		
			
				|  |  | +      gs=re.search("s"+dl+"([^"+dl+"]*)"+dl+"([^"+dl+"]*)"+dl,cmd)
 | 
	
		
			
				|  |  | +    else:
 | 
	
		
			
				|  |  | +      gs=re.search("[xX]"+dl+"([^"+dl+"]*)"+dl,cmd)
 | 
	
		
			
				|  |  |      if not gs:
 | 
	
		
			
				|  |  |        print("invalid sed search pattern")
 | 
	
		
			
				|  |  | -      return (0,0)
 | 
	
		
			
				|  |  | -    sp=re.compile(gs.group(1)) 
 | 
	
		
			
				|  |  | +      return 0,0
 | 
	
		
			
				|  |  | +    if op=='s':
 | 
	
		
			
				|  |  | +      ss,r = gs.group(1),gs.group(2)
 | 
	
		
			
				|  |  | +      #print("search <{}> and replace <{}>".format(s,r))  
 | 
	
		
			
				|  |  | +    else:
 | 
	
		
			
				|  |  | +      ss=gs.group(1) 
 | 
	
		
			
				|  |  | +      #print("search <{}>".format(s))  
 | 
	
		
			
				|  |  | +    sp=re.compile(ss) 
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -  extra=g.group(4)[1:] + '\n' 
 | 
	
		
			
				|  |  | +  extra=a.group(4)[1:] + '\n' 
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    try:
 | 
	
		
			
				|  |  | -    os.rename(filename,filename+'.'+bak_ext)
 | 
	
		
			
				|  |  | +    os.rename(filename,filename+bak_ext)
 | 
	
		
			
				|  |  |    except:
 | 
	
		
			
				|  |  |      print("problem with filename; backup failed; no changes made")
 | 
	
		
			
				|  |  | -    return (0,0)
 | 
	
		
			
				|  |  | +    return
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |    i=h=0
 | 
	
		
			
				|  |  |    try: 
 | 
	
		
			
				|  |  | -    with open(filename+'.'+bak_ext) as d:
 | 
	
		
			
				|  |  | -      with open(filename,'w') as f:
 | 
	
		
			
				|  |  | -        for lin in d:
 | 
	
		
			
				|  |  | +    with open(filename+bak_ext) as f:
 | 
	
		
			
				|  |  | +      with open(filename,'w') as g:
 | 
	
		
			
				|  |  | +        for lin in f:
 | 
	
		
			
				|  |  |            i=i+1
 | 
	
		
			
				|  |  | -          m=(i>=start and i<=end)
 | 
	
		
			
				|  |  | +          m=(i>=s and i<=e)
 | 
	
		
			
				|  |  |            if op=='s' and m:
 | 
	
		
			
				|  |  | +            lin=lin[:-1]
 | 
	
		
			
				|  |  |              if sp.search(lin): h+=1
 | 
	
		
			
				|  |  | -            lin=sp.sub(r,lin)
 | 
	
		
			
				|  |  | +            lin=sp.sub(r,lin)+'\n'
 | 
	
		
			
				|  |  |            if op=='d' and m:
 | 
	
		
			
				|  |  |              h+=1
 | 
	
		
			
				|  |  |              continue   # delete line
 | 
	
		
			
				|  |  |            if op=='i' and m:
 | 
	
		
			
				|  |  |              #print("insert a line before {} <{}>".format(i,extra))
 | 
	
		
			
				|  |  | -            f.write(extra)
 | 
	
		
			
				|  |  | +            g.write(extra)
 | 
	
		
			
				|  |  |              h+=1
 | 
	
		
			
				|  |  |            if op in "aids":
 | 
	
		
			
				|  |  | -            f.write(lin)
 | 
	
		
			
				|  |  | -          elif (m and (op=='x' and sp.search(lin)) or (op=='X' and not sp.search(lin))):
 | 
	
		
			
				|  |  | -            f.write(lin)
 | 
	
		
			
				|  |  | +            g.write(lin)
 | 
	
		
			
				|  |  | +          elif m and (op=='x' if sp.search(lin) else op=='X'):
 | 
	
		
			
				|  |  | +            g.write(lin)
 | 
	
		
			
				|  |  |              h+=1
 | 
	
		
			
				|  |  |            if op=='a' and m:
 | 
	
		
			
				|  |  |              #print("append a line after {} <{}>".format(i,extra))       
 | 
	
		
			
				|  |  | -            f.write(extra)
 | 
	
		
			
				|  |  | +            g.write(extra)
 | 
	
		
			
				|  |  |              h+=1
 | 
	
		
			
				|  |  |          #f.write("--file modifed by sed()--\n")
 | 
	
		
			
				|  |  |    except OSError:
 | 
	
	
		
			
				|  | @@ -125,31 +131,37 @@ def sed(filename, sed_cmd, bak_ext="bak"):
 | 
	
		
			
				|  |  |      print("problem with the regex; try a different pattern")
 | 
	
		
			
				|  |  |    return (i, h)
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -def _dir(d=''):
 | 
	
		
			
				|  |  | +def _dir(d='.'):
 | 
	
		
			
				|  |  |    try:  
 | 
	
		
			
				|  |  | -    for g in os.listdir(d):
 | 
	
		
			
				|  |  | -      s=os.stat(d+'/'+g)
 | 
	
		
			
				|  |  | -      print("{}rwx all {:9d} {}".format('d' if (s[0] & 0x4000) else '-',s[6],g))
 | 
	
		
			
				|  |  | +    for f in os.listdir(d):
 | 
	
		
			
				|  |  | +      s=os.stat(d+'/'+f)
 | 
	
		
			
				|  |  | +      print("{}rwx all {:9d} {}".format('d' if (s[0] & 0x4000) else '-',s[6],f))
 | 
	
		
			
				|  |  |    except:
 | 
	
		
			
				|  |  |      print("not a valid directory")
 | 
	
		
			
				|  |  |    s=os.statvfs('/')
 | 
	
		
			
				|  |  | -  print("disk size:{:8d} KB   disk free: {} KB\n".format(s[0]*s[2]//1024,s[0]*s[3]//1024))
 | 
	
		
			
				|  |  | +  print("disk size:{:8d} KB   disk free: {} KB".format(s[0]*s[2]//1024,s[0]*s[3]//1024))
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +'''-----cut here if you only need the functions-----'''
 | 
	
		
			
				|  |  | +def ext_cmd(a):
 | 
	
		
			
				|  |  | +  return
 | 
	
		
			
				|  |  | +if 'tf_extend.py' in os.listdir():
 | 
	
		
			
				|  |  | +  import tf_extend
 | 
	
		
			
				|  |  | +  ext_cmd=tf_extend.cmd
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -'''-----cut here if you only need the above functions-----'''
 | 
	
		
			
				|  |  |  def _help():
 | 
	
		
			
				|  |  | -  print("simple shell v1.0")
 | 
	
		
			
				|  |  | +  print("==Simple shell v1.1")
 | 
	
		
			
				|  |  |    print("  cp/copy <src-file> <dest-file>")
 | 
	
		
			
				|  |  | -  print("  mv/move <src-file> <dest-file>           rm/del <file>")
 | 
	
		
			
				|  |  | -  print("  cd [<folder>]       mkdir <folder>       rmdir <folder>")
 | 
	
		
			
				|  |  | +  print("  mv/move <src-file> <dest-file>    \t\trm/del <file>")
 | 
	
		
			
				|  |  | +  print("  cd [<folder>]       mkdir <folder>\t\trmdir <folder>")
 | 
	
		
			
				|  |  |    print("  dir/ls [<folder>]")
 | 
	
		
			
				|  |  |    print("  cat/list [-n] [-l <n>,<m>] <file>")
 | 
	
		
			
				|  |  |    print("  grep <pattern> <file>")
 | 
	
		
			
				|  |  |    print("  sed <pattern> <file>")
 | 
	
		
			
				|  |  | -  print("          where <pattern> is '[<n>,<m>] s/search/replace/' or '<n>[,<m>]d' or '<n>i<text>' or '<n>a<text' ")
 | 
	
		
			
				|  |  | -  print("file names must NOT have embedded spaces               options must be early on the command line")
 | 
	
		
			
				|  |  | -  print("search patterns with spaces require single-quotes      sed implements s/d/i/a/x/X")
 | 
	
		
			
				|  |  | -  print("sed does not work across line boundaries               sed s-patterns: non-/ delimiters are allowed")
 | 
	
		
			
				|  |  | +  print("      pattern is '<line-range><op><extra>'   e.g'a/search/replace/', 'x!TODO:!', '43,49d', '8itext'")
 | 
	
		
			
				|  |  | +  print("      patterns with spaces require single-quotes   sed ops are one of s/d/i/a/x/X")
 | 
	
		
			
				|  |  | +  print("      sed does not work across line boundaries     sed s/x/X-patterns: non-/ delimiters are allowed")
 | 
	
		
			
				|  |  | +  print("file names must NOT have embedded spaces           options must be early on the command line")
 | 
	
		
			
				|  |  | +  ext_cmd('help')
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def parseQuotedArgs(st):
 | 
	
		
			
				|  |  |    if st[0]=="'":
 | 
	
	
		
			
				|  | @@ -162,23 +174,23 @@ def parseQuotedArgs(st):
 | 
	
		
			
				|  |  |      return st.split()[0]
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  def main():
 | 
	
		
			
				|  |  | -  print("simple shell: cp/copy mv/move rm/del cat/list cd dir/ls mkdir rmdir grep sed help")
 | 
	
		
			
				|  |  | +  print("Simple shell: cp/copy mv/move rm/del cat/list cd dir/ls mkdir rmdir grep sed help")
 | 
	
		
			
				|  |  |    while 1:
 | 
	
		
			
				|  |  |      numbers=False
 | 
	
		
			
				|  |  |      r=input(os.getcwd()+"$ ")
 | 
	
		
			
				|  |  |      rp=r.split()
 | 
	
		
			
				|  |  |      if not len(rp): continue
 | 
	
		
			
				|  |  |      op=rp[0]
 | 
	
		
			
				|  |  | -    if op=='dir' or op=='ls':
 | 
	
		
			
				|  |  | -      _dir(rp[1] if len(rp)>1 else '')
 | 
	
		
			
				|  |  | -    elif op=='cat' or op=='list':
 | 
	
		
			
				|  |  | +    if op in ('dir','ls'):
 | 
	
		
			
				|  |  | +      _dir(rp[1] if len(rp)>1 else '.')
 | 
	
		
			
				|  |  | +    elif op in ('cat','list'):
 | 
	
		
			
				|  |  |        n=(" -n " in r) #print line-nums
 | 
	
		
			
				|  |  | -      s,e=(1,1000000) #start/end
 | 
	
		
			
				|  |  | -      g=re.search("\s+(-l\s*(\d+)([-,](\d+|\$)?)?)\s+",r[3:])
 | 
	
		
			
				|  |  | +      s,e=1,1000000 #start/end
 | 
	
		
			
				|  |  | +      g=re.search("\s(-l\s*(\d+)([-,](\d+|\$)?)?)\s+",r[3:])
 | 
	
		
			
				|  |  |        if g:
 | 
	
		
			
				|  |  |          s=e=int(g.group(2))
 | 
	
		
			
				|  |  |          if g.group(3):
 | 
	
		
			
				|  |  | -	      e=int(g.group(4)) if g.group(4) and g.group(4).isdigit() else 1000000
 | 
	
		
			
				|  |  | +          e=int(g.group(4)) if g.group(4) and g.group(4).isdigit() else 1000000
 | 
	
		
			
				|  |  |        cat(rp[-1],s,e,numbers=n)
 | 
	
		
			
				|  |  |      elif op=='grep':
 | 
	
		
			
				|  |  |        if len(rp)<3:
 | 
	
	
		
			
				|  | @@ -189,28 +201,32 @@ def main():
 | 
	
		
			
				|  |  |        if len(rp)<3:
 | 
	
		
			
				|  |  |          print("sed pattern filename")
 | 
	
		
			
				|  |  |          continue
 | 
	
		
			
				|  |  | -      lines, hits = sed(rp[-1],parseQuotedArgs(r[4:]))
 | 
	
		
			
				|  |  | -      print("Lines processed: {}  Lines modifed: {}".format(lines, hits))
 | 
	
		
			
				|  |  | +      r=sed(rp[-1],parseQuotedArgs(r[4:]))
 | 
	
		
			
				|  |  | +      if r:
 | 
	
		
			
				|  |  | +        print("Lines processed: {}  Lines modifed: {}".format(*r))
 | 
	
		
			
				|  |  |      elif op=='cd':
 | 
	
		
			
				|  |  |        os.chdir(rp[1] if len(rp)>1 else '/')
 | 
	
		
			
				|  |  |      elif op=='help':
 | 
	
		
			
				|  |  |        _help()
 | 
	
		
			
				|  |  | +      ext_cmd(rp)
 | 
	
		
			
				|  |  | +    elif ext_cmd(rp):
 | 
	
		
			
				|  |  | +      pass
 | 
	
		
			
				|  |  |      else:
 | 
	
		
			
				|  |  |        try:
 | 
	
		
			
				|  |  | -        if op=='cp' or op=='copy':
 | 
	
		
			
				|  |  | +        if op in ('cp','copy'):
 | 
	
		
			
				|  |  |            cp(rp[1],rp[2])
 | 
	
		
			
				|  |  |          elif op=='mkdir':
 | 
	
		
			
				|  |  |            os.mkdir(rp[1])
 | 
	
		
			
				|  |  |          elif op=='rmdir':
 | 
	
		
			
				|  |  |            os.rmdir(rp[1])
 | 
	
		
			
				|  |  | -        elif op=='mv' or op=='move':
 | 
	
		
			
				|  |  | +        elif op in('mv','move'):
 | 
	
		
			
				|  |  |            os.rename(rp[1],rp[2])
 | 
	
		
			
				|  |  | -        elif op=='rm' or op=='del':
 | 
	
		
			
				|  |  | +        elif op in('rm','del'):
 | 
	
		
			
				|  |  |            os.remove(rp[1])
 | 
	
		
			
				|  |  |          else:
 | 
	
		
			
				|  |  |            print("command not implemented")
 | 
	
		
			
				|  |  |        except IndexError:
 | 
	
		
			
				|  |  | -        print("not enough argments; check syntax")
 | 
	
		
			
				|  |  | +        print("not enough argments; check syntax with 'help'")
 | 
	
		
			
				|  |  |        except OSError:
 | 
	
		
			
				|  |  |          print("file not found")
 | 
	
		
			
				|  |  |      gc.collect()
 |