diff --git a/cgi-bin/hype.cgi b/cgi-bin/hype.cgi
new file mode 100755
index 0000000..fe69a86
--- /dev/null
+++ b/cgi-bin/hype.cgi
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+
+import cgitb; cgitb.enable()
+import os, sys, cgi, mimetypes
+from settings import PROJECT_PATH, PROJECT_URL, EDITOR_URL
+from project import Project
+from urlparse import urljoin
+from urllib import urlencode
+
+
+print """Content-type: text/html; charset=utf-8"""
+print
+
+method = os.environ.get("REQUEST_METHOD", "")
+fs = cgi.FieldStorage()
+project = fs.getvalue("p", "")
+proj = Project(project)
+varz = {}
+varz['project'] = proj.path
+varz['listing_url'] = "/cgi-bin/listing.cgi?"+urlencode({'p': proj.path})
+varz['make_url'] = "/cgi-bin/make.cgi?"+urlencode({'p': proj.path})
+varz['rm_url'] = "/cgi-bin/rm.cgi?"+urlencode({'p': proj.path})
+varz['save_url'] = "/cgi-bin/save.cgi?"+urlencode({'p': proj.path})
+
+print """
+
+
+hype : {0[project]}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+""".format(varz)
+
+
+
+
+
+
diff --git a/cgi-bin/listing.cgi b/cgi-bin/listing.cgi
new file mode 100755
index 0000000..b0c8f28
--- /dev/null
+++ b/cgi-bin/listing.cgi
@@ -0,0 +1,42 @@
+#!/usr/bin/env python
+
+import cgitb; cgitb.enable()
+import os, sys, cgi, json
+from project import Project
+
+
+fs = cgi.FieldStorage()
+project = fs.getvalue("p", "")
+proj = Project(project)
+
+print "Content-Type: application/json"
+print
+print json.dumps(proj._dict())
+sys.exit(0)
+
+# varz = {}
+# varz['project'] = project
+# varz['json_url'] = "?" + urlencode({'project': proj.path, 'format': 'json'})
+
+# print """Content-Type: text/html; charset=utf-8"""
+# print
+# print """
+#
+#
+# hybrid editor: {0[project]}
+#
+#
+#
+#
+
+#
+#
+#
+#
+# """.format(varz)
+
+
+
+
+
+
diff --git a/cgi-bin/project.py b/cgi-bin/project.py
new file mode 100644
index 0000000..3ee6e31
--- /dev/null
+++ b/cgi-bin/project.py
@@ -0,0 +1,176 @@
+import os, operator, sys, re, subprocess, mimetypes, json
+from settings import PROJECT_PATH, PROJECT_URL, MAKE
+from urlparse import urljoin
+from pprint import pprint
+from itertools import tee, izip
+
+
+MIME_TYPES = {}
+MIME_TYPES['md'] = "text/markdown"
+MIME_TYPES['markdown'] = "text/markdown"
+MIME_TYPES_BYBASE = {}
+MIME_TYPES_BYBASE['makefile'] = "text/makefile"
+
+def guessmime (f):
+ if os.path.isdir(f):
+ return "inode/directory"
+ _, ext = os.path.splitext(f)
+ base = os.path.basename(f)
+ ret = MIME_TYPES_BYBASE.get(base)
+ if ret == None:
+ ext = ext.lstrip(".").lower()
+ ret = MIME_TYPES.get(ext)
+ if ret == None:
+ ret = mimetypes.guess_type(f)[0]
+ return ret or ""
+
+def pairwise(iterable):
+ "s -> (s0,s1), (s1,s2), (s2, s3), ..."
+ a, b = tee(iterable)
+ next(b, None)
+ return izip(a, b)
+
+# def grouper(iterable, n, fillvalue=None):
+# "Collect data into fixed-length chunks or blocks"
+# # grouper('ABCDEFG', 3, 'x') --> ABC DEF Gxx
+# args = [iter(iterable)] * n
+# return izip_longest(fillvalue=fillvalue, *args)
+
+textchars = bytearray([7,8,9,10,12,13,27]) + bytearray(range(0x20, 0x100))
+is_binary_string = lambda bytes: bool(bytes.translate(None, textchars))
+
+def is_binary_file (path):
+ with open(path, 'rb') as f:
+ return is_binary_string(f.read(1024))
+
+def is_filename (f):
+ base, ext = os.path.splitext(f)
+ return len(ext) > 1
+
+class Project (object):
+ def __init__(self, path, create=False):
+ self.fullpath = os.path.abspath(os.path.join(PROJECT_PATH, path))
+ if os.path.exists(self.fullpath) and not os.path.isdir(self.fullpath):
+ raise OSError("Project must be a directory ({0})".format(self.fullpath))
+ absproj = os.path.abspath(PROJECT_PATH)
+ (parent, _) = os.path.split(self.fullpath)
+ if parent != absproj:
+ raise OSError("Bad project path")
+ self.path = os.path.relpath(self.fullpath, absproj)
+ self.url = urljoin(PROJECT_URL, self.path) + "/"
+
+ if not os.path.exists(self.fullpath) and create:
+ os.mkdir(self.fullpath)
+
+ def items (self):
+ itemsByPath = {}
+ for base, dirs, files in os.walk(self.fullpath):
+ for p in files:
+ fp = os.path.join(base, p)
+ relpath = os.path.relpath(fp, self.fullpath)
+ item = ProjectItem(self, relpath)
+ itemsByPath[relpath] = item
+
+ remake, missing = self.check_makefile()
+ for i in missing:
+ item = ProjectItem(self, i, exists=False)
+ itemsByPath[i] = item
+ for i in remake:
+ item = itemsByPath[i]
+ item.remake = True
+ return [itemsByPath[x] for x in sorted(itemsByPath.keys())]
+
+ def check_makefile (self):
+ """
+ Runs make -n --debug=v on project folder and searches for patterns:
+ File `...' does not exist ==> missing
+ Must remake target `...' ==> remake
+ """
+ output = subprocess.check_output([MAKE, "-n", "--debug=v"], cwd=self.fullpath)
+ missingpat = re.compile(r"^\s*File\ \`(.+?)\'\ does\ not\ exist\.\s*$", re.M)
+ remakepat = re.compile(r"^\s*Must\ remake\ target\ \`(.+?)\'\.\s*$", re.M)
+ missing = [x for x in missingpat.findall(output) if is_filename(x)]
+ remake = [x for x in remakepat.findall(output) if is_filename(x)]
+ return remake, missing
+
+ def _dict (self):
+ ret = {}
+ ret['path'] = self.path
+ ret['url'] = self.url
+ ret['items'] = [x._dict() for x in self.items()]
+ return ret
+
+ def json (self):
+ items = self.items()
+ return json.dumps(items)
+
+
+
+class ProjectItem (object):
+ def __init__(self, project, path, exists=True, remake=False):
+ self.project = project
+ self.path = path
+ self.basename = os.path.basename(path)
+ self.base, self.ext = os.path.splitext(self.basename)
+ self.ext = self.ext.lstrip(".").lower()
+ self.exists = exists
+ self.remake = remake
+ self.fullpath = os.path.join(project.fullpath, self.path)
+ self.url = urljoin(project.url, path)
+ self.mime_type = None
+ self.size = None
+ self.binary = False
+ self.directory = False
+ if exists:
+ self.directory = os.path.isdir(self.fullpath)
+ self.mime_type = guessmime(self.fullpath)
+ self.size = os.path.getsize(self.fullpath)
+ self.binary = is_binary_file(self.fullpath)
+
+ def status (self):
+ """
+ .: exists and up to date,
+ m: exists but needs to be remade,
+ M: file doesn't yet exist, but can be made
+ """
+ status = "."
+ if self.remake:
+ if self.exists:
+ status = "m"
+ else:
+ status = "M"
+ return status
+
+ def __repr__ (self):
+ return """""".format(self.path, self.status())
+
+ def _dict (self):
+ d = {}
+ d['path'] = self.path
+ d['exists'] = self.exists
+ d['directory'] = self.directory
+ d['remake'] = self.remake
+ d['url'] = self.url
+ d['basename'] = self.basename
+ d['base'] = self.base
+ d['ext'] = self.ext
+ d['mime_type'] = self.mime_type
+ d['size'] = self.size
+ d['binary'] = self.binary
+ return d
+
+ def json (self):
+ return json.dumps(self._dict())
+
+
+
+if __name__ == "__main__":
+ p = Project(sys.argv[1])
+ # pprint (p._dict())
+
+ items = p.items()
+ for item in items:
+ print item.status(), item.path
+ # print item.json()
+
+
diff --git a/cgi-bin/rm.cgi b/cgi-bin/rm.cgi
new file mode 100755
index 0000000..5e9fa2a
--- /dev/null
+++ b/cgi-bin/rm.cgi
@@ -0,0 +1,29 @@
+#!/usr/bin/env python
+
+import cgitb; cgitb.enable()
+import os, sys, cgi, json
+from subprocess import PIPE, Popen
+from settings import PROJECT_PATH, PROJECT_URL, EDITOR_URL, MAKE
+from project import Project
+
+method = os.environ.get("REQUEST_METHOD", "")
+fs = cgi.FieldStorage()
+project = fs.getvalue("p", "")
+paths = fs.getlist("f[]")
+
+ret = {}
+ret['stdout'] = ''
+ret['stderr'] = ''
+ret['returncode'] = -1
+
+if paths:
+ proj = Project(project)
+ for p in paths:
+ fp = os.path.join(proj.fullpath, p)
+ print >> sys.stderr, "*** rm", fp
+ os.remove(fp)
+ ret['returncode'] = 0
+
+print "Content-type: application/json"
+print
+print json.dumps(ret)
diff --git a/cgi-bin/save.cgi b/cgi-bin/save.cgi
new file mode 100755
index 0000000..eab69e6
--- /dev/null
+++ b/cgi-bin/save.cgi
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+import cgitb; cgitb.enable()
+import os, sys, cgi, json
+from subprocess import PIPE, Popen
+from settings import PROJECT_PATH, PROJECT_URL, EDITOR_URL, MAKE
+from project import Project
+
+method = os.environ.get("REQUEST_METHOD", "")
+fs = cgi.FieldStorage()
+project = fs.getvalue("p", "")
+path = fs.getvalue("f")
+content = fs.getvalue("text")
+
+ret = {}
+ret['returncode'] = -1
+
+if path and content != None:
+ proj = Project(project)
+ fp = os.path.join(proj.fullpath, path)
+ with open(fp, "w") as f:
+ f.write(content)
+ ret['returncode'] = 0
+
+print "Content-type: application/json"
+print
+print json.dumps(ret)
diff --git a/cgi-bin/select.cgi b/cgi-bin/select.cgi
new file mode 100755
index 0000000..660e587
--- /dev/null
+++ b/cgi-bin/select.cgi
@@ -0,0 +1,73 @@
+#!/usr/bin/env python
+
+import cgitb; cgitb.enable()
+import os, cgi, sys, operator
+from settings import PROJECT_PATH, PROJECT_URL, EDITOR_URL
+from urlparse import urljoin
+from urllib import urlencode
+from project import Project
+
+
+method = os.environ.get("REQUEST_METHOD", "")
+
+def redirect (url):
+ print """Content-type: text/html; charset=utf-8"""
+ print
+ print """
+
+continue
+
+
+""".format(url)
+
+errormsg = ""
+if method == "POST":
+ fs = cgi.FieldStorage()
+ project = fs.getvalue("p", "").strip()
+ if len(project):
+ try:
+ proj = Project(project, create=True)
+ redirect("{0}?{1}".format(EDITOR_URL, urlencode({'p': proj.path})))
+ sys.exit(0)
+ except OSError, e:
+ errormsg = """An error occurred, check your project name (try without using special characters)
\n({0})
""".format(e)
+
+projects = []
+for p in os.listdir(PROJECT_PATH):
+ fp = os.path.join(PROJECT_PATH, p)
+ if os.path.isdir(fp) and not p.startswith("."):
+ projects.append(Project(p))
+projects.sort(key=operator.attrgetter("path"))
+
+
+print "Content-type:text/html;charset=utf-8"
+print
+print """
+
+
+step 1.
+"""
+if len(errormsg):
+ print """{0}
""".format(errormsg).encode("utf-8")
+
+if len(projects):
+ print """Select a project:
"""
+print """"""
+for p in projects:
+ print """- {2}
""".format(EDITOR_URL, urlencode({'p': p.path}), p.path)
+print """
"""
+if len(projects):
+ print """or
"""
+print """
+
Create a new project folder
+
+
+
+