wiki

A personal commandline wiki.
git clone git://r-36.net/wiki
Log | Files | Refs | README | LICENSE

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