diff options
Diffstat (limited to 'wiki')
| -rwxr-xr-x | wiki | 341 |
1 files changed, 341 insertions, 0 deletions
@@ -0,0 +1,341 @@ +#!/usr/bin/python3 +# coding=utf-8 +# +# See LICENSE. +# +# Copy me if you can. +# by 20h +# + +import os +import sys +import re +import getopt +import click +import codecs +from subprocess import Popen, PIPE + +DEFAULTBASE = "~/.psw" + +class git(object): + def __init__(self, base=None): + if base == None: + base = DEFAULTBASE + self.base = os.path.expanduser(base) + if not os.path.exists(self.base): + os.makedirs(self.base, 0o750) + + if self.base[-1] != os.sep: + self.pages = "%s%s" % (self.base, os.sep) + else: + self.pages = self.base + + self.keycache = None + + """ I/O helper functions. """ + def getfile(self, fi): + fd = codecs.open(fi, mode="r", encoding="utf-8") + text = fd.read() + fd.close() + return text + + def putfile(self, fi, content): + os.makedirs(os.path.dirname(fi), exist_ok=True) + fd = codecs.open(fi, mode="w+", encoding="utf-8") + fd.write(content) + fd.close() + return None + + def mkpath(self, path, fi=None): + if path[-1] != os.sep: + path = "%s%s" % (path, os.sep) + if fi != None: + path = "%s%s" % (path, fi) + + return path + + def recursedir(self, path): + subdirs = [] + files = os.listdir(path) + files.sort() + for f in files: + npath = self.mkpath(path, f) + if os.path.isfile(npath) or \ + os.path.islink(npath): + yield npath + else: + subdirs.append(f) + + for s in subdirs: + npath = self.mkpath(path, s) + if s == ".git": + continue + for j in self.recursedir(npath): + yield j + + def makesearchlist(self, fun, path): + li = [] + npath = self.mkpath(path) + for i in fun(path): + i = i.replace(npath, "") + i = i.removesuffix(".md") + li.append(i) + return li + + """ Git command functions. """ + def git(self, args): + gitdir = "%s.git" % (self.pages) + workdir = self.pages + cmd = ["git", "--git-dir=%s" % gitdir, "--work-tree=%s" % workdir] + args + + p = Popen(cmd, stdout=PIPE, stderr=PIPE) + result = p.stdout.read().decode("utf-8", errors="replace") + + return [result, p.wait()] + + def gitpath(self, file): + return "%s%s.md" % (self.pages, file) + + def init(self): + self.git(["init"]) + + def add(self, file): + self.git(["add", file]) + + def rm(self, file): + self.git(["rm", file]) + + def mv(self, old, new): + self.git(["mv", old, new]) + + def commit(self, msg): + self.git(["commit", "--allow-empty", "--no-verify", + "--message=%s" % msg, "--author=psw <psw@psw>"]) + self.git(["gc"]) + + def log(self, page): + changes = [] + if page == "": + extra = [] + else: + extra = ["--", self.gitpath(page)] + sep = "\x01" + (result, status) = self.git( + ["log", "--pretty=format:%%H%s%%T%s%%an%s%%ae%s%%aD%s%%s" % ((sep,) * 5)] + extra + ) + for line in result.splitlines(): + if not line.strip(): + continue + entries = line.split(sep) + if len(entries) < 6: + continue + change = {} + change["commit"] = entries[0] + change["author"] = entries[2] + change["email"] = entries[3] + change["date"] = entries[4] + change["message"] = entries[5] + try: + (task, cpage) = entries[5].split(" ", 1) + except ValueError: + cpage = page + change["page"] = cpage + changes.append(change) + + return changes + + def showcommit(self, page, commit): + (file, status) = self.git(["cat-file", "-p", "%s:%s.md" % (commit, page)]) + return file + + """ Dictionary abstraction functions. """ + def __setitem__(self, page, value): + file = self.gitpath(page) + needadd = False + if os.path.exists(file) == False: + needadd = True + self.putfile(file, value) + + self.add(file) + if needadd: + self.commit("Added: %s" % (page)) + else: + self.commit("Changed: %s" % (page)) + + def __delitem__(self, page): + file = self.gitpath(page) + os.remove(file) + + self.rm(file) + self.commit("Deleted: %s" % (page)) + + def __getitem__(self, page): + try: + return self.getfile(self.gitpath(page)) + except IOError as err: + return "" + + def getlog(self, page): + log = self.log(page) + ret = "" + for i in log: + ret += "%s %s %s %s %s %s\n" % (i["commit"], + i["author"], + i["email"], i["date"], i["page"], + i["message"]) + return ret + + def mkkeycache(self): + if self.keycache == None: + self.keycache = self.makesearchlist(self.recursedir, + self.pages) + + def keys(self): + self.mkkeycache() + return self.keycache + + def __contains__(self, item): + return item in list(self.keys()) + + def search(self, query): + try: + pattern = re.compile(query) + except re.error as e: + sys.stderr.write("Invalid regex: %s\n" % e) + return [] + + name_hits = [] + content_hits = [] + seen = set() + + for page in list(self.keys()): + if pattern.search(page): + name_hits.append(page) + seen.add(page) + + for page in list(self.keys()): + if page in seen: + continue + try: + content = self.getfile(self.gitpath(page)) + except OSError: + continue + for line in content.splitlines(): + if pattern.search(line): + content_hits.append("%s: %s" % (page, line.strip())) + seen.add(page) + break + + return name_hits + content_hits + + def move(self, old, new): + opath = self.gitpath(old) + npath = self.gitpath(new) + self.mv(opath, npath) + self.commit("Moved: %s -> %s" % (old, new)) + +def editor(content): + try: + data = click.edit(content, require_save=True, extension='.md') + except click.UsageError: + return (1, content) + if data == None: + return (1, content) + + return (0, data) + +def usage(app): + app = os.path.basename(app) + sys.stderr.write("usage: %s [-oh] [-b base] [[-d|-e|-c|-s|-p] item" \ + "|-l|-r old new]\n" % (app)) + sys.exit(1) + +def main(args): + try: + opts, largs = getopt.getopt(args[1:], "hosplb:sdecr") + except getopt.GetoptError as err: + print(str(err)) + usage(args[0]) + + dorm = False + doedit = False + docommit = False + dosearch = False + dolist = False + dorename = False + tostdout = False + base = DEFAULTBASE + for o, a in opts: + if o == "-h": + usage(args[0]) + elif o == "-b": + base = a + elif o == "-c": + docommit = True + elif o == "-d": + dorm = True + elif o == "-e": + doedit = True + elif o == "-l": + dolist = True + elif o == "-o": + doedit = True + tostdout = True + elif o == "-p": + doedit = True + tostdout = True + elif o == "-r": + dorename = True + elif o == "-s": + dosearch = True + else: + assert False, "unhandled option" + + val = "" + if doedit == True or dosearch == True or dorm == True or docommit == True: + if len(largs) < 1 and not docommit: + usage(args[0]) + val = "_".join(largs) + elif dorename == True: + if len(largs) < 2: + usage(args[0]) + + lgit = git(base) + + if doedit == True: + content = lgit[val] + if tostdout == True: + sys.stdout.write(content) + else: + (sts, data) = editor(content) + if data == content: + print("No changes made. Quitting.") + else: + print("Some changes made. Committing.") + lgit[val] = data + elif dorename == True: + lgit.move(largs[0], largs[1]) + elif dorm == True: + del lgit[val] + elif docommit == True: + commits = lgit.getlog(val) + if tostdout == True: + sys.stdout.write(commits) + else: + p = Popen(["less"], stdin=PIPE) + p.communicate(input=commits.encode("utf-8")) + elif dosearch == True: + results = lgit.search(val) + for r in results: + print(r) + elif dolist == True: + for k in list(lgit.keys()): + print(k) + else: + usage(args[0]) + + return 0 + +if __name__ == "__main__": + sys.exit(main(sys.argv)) + |
