tf.py 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  1. # tf Text File manipulations
  2. # for micropython and other tiny environments
  3. # (c) Pat Beirne patb@pbeirne.com
  4. import re,os,sys,gc
  5. # ====legend====
  6. # e=end
  7. # f=input file
  8. # g=output file
  9. # i=iteration variable
  10. # m=line range
  11. # r=replace text
  12. # s=start/search/string
  13. # sp=search regex
  14. def transfer(src,dest,first=1,last=0xFFFFFFFF,numbers=False,grep_func=None):
  15. #src is a filename, dst is a handle
  16. i=0
  17. with open(src) as f:
  18. for lin in f:
  19. i=i+1
  20. if i<first or i>last:
  21. continue
  22. if grep_func and not grep_func(lin):
  23. continue
  24. if numbers:
  25. dest.write(str(i)+' ')
  26. dest.write(lin)
  27. def cp(src,dest):
  28. with open(dest,'w') as g:
  29. transfer(src,g)
  30. def cat(filename, first=1, last=1000000, numbers=False, title=True):
  31. if title:
  32. print("===={}====".format(filename))
  33. transfer(filename,sys.stdout,first,last,numbers=numbers)
  34. def grep(filename, pattern, numbers=False):
  35. m=re.compile(pattern)
  36. transfer(filename,sys.stdout,numbers=numbers,grep_func=(lambda x:m.search(x[:-1])))
  37. def sed(filename, sed_cmd, bak_ext=".bak"):
  38. # parse the sed_cmd
  39. # group 1,3 are the n-start, n-end group 4 is command: aidsxX
  40. a=re.search("^(\d*)([,-](\d+|\$))?\s*([sdaixX])(.*)",sed_cmd)
  41. if not a:
  42. raise ValueError("sed() failed; pattern must be a number-range followed by one of sdaixX; no changes applied")
  43. op,args=a.group(4),a.group(5)
  44. s,e=1,1000000
  45. if a.group(1):
  46. s=e=int(a.group(1))
  47. if a.group(3):
  48. e=1000000 if a.group(3)=='$' else int(a.group(3))
  49. if op in "aid" and e-s==1000000:
  50. raise ValueError("sed(a/i/d) should have a line number")
  51. #print("sed command parser of <{}> returned {} {} {}".format(op, cmd, a.group(1), a.group(3)))
  52. if op in "sxX":
  53. if len(args)<2 or args[0] in "\^$()[]":
  54. raise ValueError("invalid sed argument: "+op+args)
  55. dl=args[0]
  56. if op=='s':
  57. gs=re.search(dl+"([^"+dl+"]*)"+dl+"([^"+dl+"]*)"+dl+"(g)?",args)
  58. else:
  59. gs=re.search(dl+"([^"+dl+"]*)"+dl,args)
  60. if not gs:
  61. raise ValueError("invalid sed search pattern: "+op+args)
  62. ss=gs.group(1)
  63. if op=='s':
  64. r=gs.group(2)
  65. rm=0 if gs.group(3) else 1
  66. sp=re.compile(ss)
  67. args=args + '\n'
  68. try:
  69. os.rename(filename,filename+bak_ext)
  70. except:
  71. raise OSError("problem with filename",filename," backup failed; no changes made")
  72. i=h=0
  73. with open(filename+bak_ext) as f:
  74. with open(filename,'w') as g:
  75. for lin in f:
  76. i=i+1
  77. m=(s <= i <= e)
  78. if op=='s' and m:
  79. lin=lin[:-1]
  80. if sp.search(lin): h+=1
  81. lin=sp.sub(r,lin,rm)+'\n'
  82. if op=='d' and m:
  83. h+=1
  84. continue # delete line
  85. if op=='i' and m:
  86. #print("insert a line before {} <{}>".format(i,extra))
  87. g.write(args)
  88. h+=1
  89. if op in "aids":
  90. g.write(lin)
  91. elif m and (op=='x' if sp.search(lin) else op=='X'):
  92. g.write(lin)
  93. h+=1
  94. if op=='a' and m:
  95. #print("append a line after {} <{}>".format(i,extra))
  96. g.write(args)
  97. h+=1
  98. #f.write("--file modifed by sed()--\n")
  99. return (i, h)
  100. def _dir(d='.'):
  101. for f in os.listdir(d):
  102. s=os.stat(d+'/'+f)
  103. print("{}rwx all {:9d} {}".format('d' if (s[0] & 0x4000) else '-',s[6],f ))
  104. s=os.statvfs('/')
  105. print("disk size:{:8d} KB disk free: {} KB".format(s[0]*s[2]//1024,s[0]*s[3]//1024))
  106. '''-----cut here if you only need the functions-----'''
  107. def ext_cmd(a):
  108. return
  109. if 'tf_extend.py' in os.listdir():
  110. import tf_extend
  111. ext_cmd=tf_extend.cmd
  112. def _help():
  113. print("==Simple shell v1.2 for Text Files")
  114. print(" cp/copy <src-file> <dest-file>")
  115. print(" mv/move/rename <src-file> <dest-file> \trm/del <file>")
  116. print(" cd [<folder>]\t\tmkdir <folder>\t\trmdir <folder>")
  117. print(" dir/ls [<folder>]")
  118. print(" cat/list [-n] [-l <n>,<m>] <file>")
  119. print(" grep <pattern> <file>")
  120. print(" sed <pattern> <file>")
  121. print(" pattern is <line-range><op><extra> eg: 's/search/replace/' 'x!TODO:!' '43,49d' '8itext'")
  122. print(" patterns with spaces require '-quotes\tsed ops are one of s/d/i/a/x/X")
  123. print(" sed cannot cross line boundaries\t\tsed s/x/X-patterns: non-/ delimiters are ok")
  124. print("file names must NOT have embedded spaces\toptions must be early on the command line")
  125. ext_cmd('help')
  126. def parseQuotedArgs(st):
  127. #returns (pattern,file)
  128. st=st.strip()
  129. if st[0]=="'":
  130. p=re.search("'(.*?[^\\\\])'\s*(\S*)",st)
  131. if not p:
  132. print("error in quoted pattern:",st)
  133. return
  134. return p.group(1),p.group(2)
  135. else:
  136. return st.split(None,1)
  137. def main():
  138. print("Simple shell: cp/copy mv/move rm/del cat/list cd dir/ls mkdir rmdir grep sed help")
  139. while 1:
  140. numbers=False
  141. r=input(os.getcwd()+"$ ")
  142. rp=r.split()
  143. if not len(rp): continue
  144. op=rp[0]
  145. if op in ('dir','ls'):
  146. try:
  147. _dir(rp[1] if len(rp)>1 else '.')
  148. except:
  149. print("directory not found")
  150. elif op in ('cat','list'):
  151. n=(" -n " in r) #print line-nums
  152. s,e=1,1000000 #start/end
  153. g=re.search("\s(-l\s*(\d+)([-,](\d+|\$)?)?)\s+",r[3:])
  154. if g:
  155. s=e=int(g.group(2))
  156. if g.group(3):
  157. e=int(g.group(4)) if g.group(4) and g.group(4).isdigit() else 1000000
  158. try:
  159. cat(rp[-1],s,e,numbers=n)
  160. except:
  161. print("file not found",rp[-1])
  162. elif op in('grep','sed'):
  163. if len(rp)<3:
  164. print(op,"pattern filename")
  165. continue
  166. p=parseQuotedArgs(r[4:])
  167. if not p:
  168. continue
  169. try:
  170. if op=='grep':
  171. grep(p[1],p[0],numbers=True)
  172. else:
  173. r=sed(p[1],p[0])
  174. if r:
  175. print("Lines processed: {} Lines modifed: {}".format(*r))
  176. except (ValueError, OSError) as e:
  177. print(e)
  178. except RuntimeError:
  179. print("problem with the regex; try a different pattern")
  180. elif op=='help':
  181. _help()
  182. ext_cmd(rp)
  183. elif ext_cmd(rp):
  184. pass
  185. else:
  186. try:
  187. if op in ('cp','copy'):
  188. cp(rp[1],rp[2])
  189. elif op=='cd':
  190. os.chdir(rp[1] if len(rp)>1 else '/')
  191. elif op=='mkdir':
  192. os.mkdir(rp[1])
  193. elif op=='rmdir':
  194. os.rmdir(rp[1])
  195. elif op in('mv','move','rename'):
  196. os.rename(rp[1],rp[2])
  197. elif op in('rm','del'):
  198. os.remove(rp[1])
  199. else:
  200. print("command not implemented:",op)
  201. except IndexError:
  202. print("not enough argments; check syntax with 'help'")
  203. except OSError:
  204. print("file/folder not found or cannot be written")
  205. gc.collect()
  206. if __name__=="tf":
  207. print("tf module loaded; members cp(), cat(), _dir(), grep() and sed()")
  208. main()