You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

180 lines
5.7 KiB
Python

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
"""
try:
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
except subprocess.CalledProcessError, e:
return [], []
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 """<ProjectItem {1} {0}>""".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()