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