wiki (6405B)
1 #!/usr/bin/python 2 # coding=utf-8 3 # 4 # Copy me if you can. 5 # by 20h 6 # 7 8 import os 9 import sys 10 import re 11 import glob 12 import getopt 13 import click 14 import codecs 15 from subprocess import Popen, PIPE 16 17 DEFAULTBASE = "~/.psw" 18 19 class git(object): 20 def __init__(self, base=None): 21 if base == None: 22 base = DEFAULTBASE 23 self.base = os.path.expanduser(base) 24 if not os.path.exists(self.base): 25 os.makedirs(self.base, 0o750) 26 27 if self.base[-1] != os.sep: 28 self.pages = "%s%s" % (self.base, os.sep) 29 else: 30 self.pages = path 31 32 self.keycache = None 33 34 """ I/O helper functions. """ 35 def getfile(self, fi): 36 fd = open(fi, mode="r", encoding="utf-8") 37 text = fd.read() 38 fd.close() 39 return text 40 41 def putfile(self, fi, content): 42 #content = content.decode("utf-8") 43 fd = open(fi, mode="w+", encoding="utf-8") 44 fd.write(content) 45 fd.close() 46 return None 47 48 def mkpath(self, path, fi=None): 49 if path[-1] != os.sep: 50 path = "%s%s" % (path, os.sep) 51 if fi != None: 52 path = "%s%s" % (path, fi) 53 54 return path 55 56 def recursedir(self, path): 57 subdirs = [] 58 files = os.listdir(path) 59 files.sort() 60 for f in files: 61 npath = self.mkpath(path, f) 62 if os.path.isfile(npath) or \ 63 os.path.islink(npath): 64 yield npath 65 else: 66 subdirs.append(f) 67 68 for s in subdirs: 69 npath = self.mkpath(path, s) 70 if s == ".git": 71 continue 72 for j in self.recursedir(npath): 73 yield j 74 75 def makesearchlist(self, fun, path): 76 li = [] 77 npath = self.mkpath(path) 78 for i in fun(path): 79 i = i.replace(npath, "") 80 i = i.replace(".md", "") 81 li.append(i) 82 return li 83 84 """ Git command functions. """ 85 def git(self, cmd): 86 gitdir = "%s.git" % (self.pages) 87 workdir = self.pages 88 gitcmd = "git --git-dir=%s --work-tree=%s %s" % (gitdir, 89 workdir, cmd) 90 91 p = Popen(gitcmd, stdout=PIPE, shell=True) 92 result = p.stdout.read() 93 94 return [result, p.wait()] 95 96 def gitpath(self, file): 97 return "%s%s.md" % (self.pages, file) 98 99 def init(self): 100 self.git("init") 101 102 def add(self, file): 103 self.git("add %s" % (file)) 104 105 def rm(self, file): 106 self.git("rm %s" % (file)) 107 108 def mv(self, old, new): 109 self.git("mv %s %s" % (old, new)) 110 111 def commit(self, msg): 112 self.git("commit --allow-empty --no-verify --message=\"%s\"" \ 113 " --author=\"psw <psw@psw>\"" % (msg)) 114 self.git("gc") 115 116 def log(self, page): 117 changes = [] 118 if page == "": 119 file = "" 120 else: 121 file = self.gitpath(page) 122 (result, status) = self.git("log" \ 123 " --pretty=format:'%%H>%%T>%%an>%%ae>%%aD>%%s'" \ 124 " -- %s" % (file)) 125 for line in result.split("\n"): 126 change = {} 127 entries = line.split(">") 128 change["author"] = entries[2] 129 change["email"] = entries[3] 130 change["date"] = entries[4] 131 change["message"] = entries[5] 132 change["commit"] = entries[0] 133 try: 134 (task, cpage) = entries[5].split(" ") 135 except: 136 cpage = page 137 change["page"] = cpage 138 changes.append(change) 139 140 return changes 141 142 def showcommit(self, page, commit): 143 (file, status) = self.git("cat-file -p %s:%s.md" \ 144 % (commit, page)) 145 return file 146 147 """ Dictionary abstraction functions. """ 148 def __setitem__(self, page, value): 149 file = self.gitpath(page) 150 needadd = False 151 if os.path.exists(file) == False: 152 needadd = True 153 self.putfile(file, value) 154 155 self.add(file) 156 if needadd: 157 self.commit("Added: %s" % (page)) 158 else: 159 self.commit("Changed: %s" % (page)) 160 161 def __delitem__(self, page): 162 file = self.gitpath(page) 163 os.remove(file) 164 165 self.rm(file) 166 self.commit("Deleted: %s" % (page)) 167 168 def __getitem__(self, page): 169 try: 170 return self.getfile(self.gitpath(page)) 171 except IOError as err: 172 return "" 173 174 def getlog(self, page): 175 log = self.log(page) 176 ret = "" 177 for i in log: 178 ret += "%s %s %s %s %s %s\n" % (i["commit"], 179 i["author"], 180 i["email"], i["date"], i["page"], 181 i["message"]) 182 return ret 183 184 def mkkeycache(self): 185 if self.keycache == None: 186 self.keycache = self.makesearchlist(self.recursedir, 187 self.pages) 188 189 def keys(self): 190 self.mkkeycache() 191 return self.keycache 192 193 def __contains__(self, item): 194 return item in list(self.keys()) 195 196 def search(self, query): 197 results = [] 198 for i in list(self.keys()): 199 m = re.search(query, i) 200 if m != None: 201 results.append(i) 202 return results 203 204 def move(self, old, new): 205 opath = self.gitpath(old) 206 npath = self.gitpath(new) 207 self.mv(opath, npath) 208 self.commit("Moved: %s -> %s" % (old, new)) 209 210 def editor(content): 211 try: 212 data = click.edit(content, require_save=False, extension='.md') 213 except click.UsageError: 214 return (1, content) 215 if data == None: 216 return (1, content) 217 218 return (0, data) 219 220 def usage(app): 221 app = os.path.basename(app) 222 sys.stderr.write("usage: %s [-oh] [-b base] [[-d|-e|-c|-s|-p] item" \ 223 "|-l|-r old new]\n" % (app)) 224 sys.exit(1) 225 226 def main(args): 227 try: 228 opts, largs = getopt.getopt(args[1:], "hosplb:sdecr") 229 except getopt.GetoptError as err: 230 print(str(err)) 231 usage(args[0]) 232 233 dorm = False 234 doedit = False 235 docommit = False 236 dosearch = False 237 dolist = False 238 dorename = False 239 tostdout = False 240 base = DEFAULTBASE 241 for o, a in opts: 242 if o == "-h": 243 usage(args[0]) 244 elif o == "-b": 245 base = a 246 elif o == "-c": 247 docommit = True 248 elif o == "-d": 249 dorm = True 250 elif o == "-e": 251 doedit = True 252 elif o == "-l": 253 dolist = True 254 elif o == "-o": 255 tostdout = True 256 elif o == "-p": 257 doedit = True 258 tostdout = True 259 elif o == "-r": 260 dorename = True 261 elif o == "-s": 262 dosearch = True 263 else: 264 assert False, "unhandled option" 265 266 val = "" 267 if doedit == True or dosearch == True or dorm == True: 268 if len(largs) < 1: 269 usage(args[0]) 270 val = "_".join(largs) 271 elif dorename == True: 272 if len(largs) < 2: 273 usage(args[0]) 274 275 lgit = git(base) 276 277 if doedit == True: 278 content = lgit[val] 279 if tostdout == True: 280 sys.stdout.write(content) 281 else: 282 (sts, data) = editor(content) 283 if data == content: 284 print("No changes made. Quitting.") 285 else: 286 print("Some changes made. Committing.") 287 lgit[val] = data 288 elif dorename == True: 289 lgit.move(largs[0], largs[1]) 290 elif dorm == True: 291 del lgit[val] 292 elif docommit == True: 293 commits = lgit.getlog(val) 294 if tostdout == True: 295 sys.stdout.write(commits) 296 else: 297 editor(commits) 298 elif dosearch == True: 299 results = lgit.search(val) 300 for r in results: 301 print(r) 302 elif dolist == True: 303 for k in list(lgit.keys()): 304 print(k) 305 else: 306 usage(args[0]) 307 308 return 0 309 310 if __name__ == "__main__": 311 sys.exit(main(sys.argv)) 312