import os, operator, sys, re, subprocess, mimetypes, json from settings import PROJECT_PATH, PROJECT_URL, MAKE, MAKEFILE 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: args = [MAKE] if MAKEFILE: args.append("-f") args.append(MAKEFILE) args.append("-n") args.append("--debug=v") output = subprocess.check_output(args, 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 """""".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()