wiki

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

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