def write(self, filename, debug=False): if Image is None: raise UserError("Missing Python PIL which is required to create sprite sheets!") img = Image.new('RGBA', (self.width, self.height)) draw = ImageDraw.Draw(img) #draw.rectangle((0, 0, self.width, self.height), fill=(255, 255, 0, 255)) # Load images and pack them in for block in self.blocks: res = Image.open(block.image.src) x, y = block.fit.x, block.fit.y if block.rotated: Console.debug('%s is rotated' % block.image.src) res = res.rotate(90) img.paste(res, (x, y)) del res if debug: x, y, w, h = block.fit.x, block.fit.y, block.w, block.h draw.rectangle((x, y , x + w , y + h), outline=(0, 0, 255, 255) if block.rotated else (255, 0, 0, 255)) if debug: for i, block in enumerate(self.packer.getUnused()): x, y, w, h = block.x, block.y, block.w, block.h draw.rectangle((x, y , x + w , y + h), fill=(255, 255, 0, 255)) img.save(filename)
def cleanup(node): """""" if not hasattr(node, "variables"): ScopeScanner.scan(node) # Re cleanup until nothing to remove is found iteration = 0 cleaned = False Console.debug("Removing unused variables...") Console.indent() while True: iteration += 1 modified = __cleanup(node) if modified > 0: Console.debug("Removed %s unused variables", modified) ScopeScanner.scan(node) cleaned = True else: break Console.outdent() return cleaned
def patch(node, permutation): """ Replaces all occourences with incoming values """ modified = False if node.type == "identifier" or node.type == "dot": if node.type == "dot": name = assembleDot(node) else: name = node.value replacement = __translateToValue(permutation.get(name)) if replacement: Console.debug("Found permutation query (%s => %s) in line %s", name, replacement, node.line) replacementNode = Parser.parseExpression(replacement) node.parent.replace(node, replacementNode) modified = True # Process children for child in reversed(node): if child != None: if patch(child, permutation): modified = True return modified
def __addDir(self, directory, regex, type, package): check = re.compile(regex) path = os.path.join(self.__path, directory) if not os.path.exists(path): return Console.debug("Scanning directory: %s" % directory) Console.indent() for dirPath, dirNames, fileNames in os.walk(path): for dirName in dirNames: # Filter dotted directories like .git, .bzr, .hg, .svn, etc. if dirName.startswith("."): dirNames.remove(dirName) relDirPath = os.path.relpath(dirPath, path) for fileName in fileNames: if fileName[0] == ".": continue relPath = os.path.normpath(os.path.join(relDirPath, fileName)).replace( os.sep, "/") if not check.match( os.path.join(directory, relPath).replace(os.sep, "/")): continue fullPath = os.path.join(dirPath, fileName) self.addFile(relPath, fullPath, type, package) Console.outdent()
def cleanup(node): """""" if not hasattr(node, "variables"): ScopeScanner.scan(node) # Re cleanup until nothing to remove is found x = 0 cleaned = False Console.debug("Removing unused variables...") while True: x = x + 1 #debug("Removing unused variables [Iteration: %s]...", x) Console.indent() if __cleanup(node): ScopeScanner.scan(node) cleaned = True Console.outdent() else: Console.outdent() break return cleaned
def __structurize(self, data): """ This method structurizes the incoming data into a cascaded structure representing the file system location (aka file IDs) as a tree. It further extracts the extensions and merges files with the same name (but different extensions) into the same entry. This is especially useful for alternative formats like audio files, videos and fonts. It only respects the data of the first entry! So it is not a good idea to have different files with different content stored with the same name e.g. content.css and content.png. """ root = {} # Easier to debug and understand when sorted for fileId in sorted(data): current = root splits = fileId.split("/") # Extract the last item aka the filename itself basename = splits.pop() # Find the current node to store info on for split in splits: if not split in current: current[split] = {} elif type(current[split]) != dict: raise UserError("Invalid asset structure. Folder names must not be identical to any filename without extension: \"%s\" in %s" % (split, fileId)) current = current[split] # Create entry Console.debug("Adding %s..." % fileId) current[basename] = data[fileId] return root
def __combineSiblings(node): """Backwards processing and insertion into previous sibling if both are declarations""" length = len(node) pos = length - 1 while pos > 0: child = node[pos] prevChild = node[pos - 1] # Special FOR loop optimization, emulate faked VAR if child.type == "for" and prevChild.type == "var": setup = getattr(child, "setup", None) if setup and setup.type == "var": Console.debug("Removing for-loop setup section at line %s" % setup.line) child.remove(setup) child = setup # Combine declarations of VAR statements if child.type == "var" and prevChild.type == "var": # debug("Combining var statement at line %s" % child.line) # Fix loop through casting node to list() for variable in list(child): prevChild.append(variable) if child in node: node.remove(child) pos -= 1
def __addDir(self, directory, regex, type, package): check = re.compile(regex) path = os.path.join(self.__path, directory) if not os.path.exists(path): return Console.debug("Scanning directory: %s" % directory) Console.indent() for dirPath, dirNames, fileNames in os.walk(path): for dirName in dirNames: # Filter dotted directories like .git, .bzr, .hg, .svn, etc. if dirName.startswith("."): dirNames.remove(dirName) relDirPath = os.path.relpath(dirPath, path) for fileName in fileNames: if fileName[0] == ".": continue relPath = os.path.normpath(os.path.join(relDirPath, fileName)).replace(os.sep, "/") if not check.match(os.path.join(directory, relPath).replace(os.sep, "/")): continue fullPath = os.path.join(dirPath, fileName) self.addFile(relPath, fullPath, type, package) Console.outdent()
def __addContent(self, content): Console.info("Adding manual content") Console.indent() for fileId in content: entry = content[fileId] if not isinstance(entry, dict): raise UserError("Invalid manual content section for file %s. Requires a dict with type and source definition!" % fileId) itemType = entry["type"] fileContent = entry["source"] if len(fileContent) == 0: raise UserError("Empty content!") # Support for joining text content if len(fileContent) == 1: filePath = os.path.join(self.__path, fileContent[0]) else: filePath = [os.path.join(self.__path, filePart) for filePart in fileContent] name, construct = self.__resolveConstructor(itemType) item = construct(self, fileId).attach(filePath) Console.debug("Registering %s %s" % (item.kind, fileId)) if not itemType in self.items: self.items[itemType] = {} # Check for duplication if fileId in self.items[itemType]: raise UserError("Item ID was registered before: %s" % fileId) self.items[itemType][fileId] = item Console.outdent()
def __getOptimizedTree(self, permutation=None, context=None): """Returns an optimized tree with permutations applied""" field = "opt-tree[%s]-%s" % (self.id, permutation) tree = self.project.getCache().read(field, self.mtime) if not tree: tree = copy.deepcopy(self.__getTree("%s:plain" % context)) # Logging msg = "Processing class %s" % Console.colorize(self.id, "bold") if permutation: msg += Console.colorize(" (%s)" % permutation, "grey") if context: msg += Console.colorize(" [%s]" % context, "cyan") Console.info("%s..." % msg) Console.indent() # Apply permutation if permutation: Console.debug("Patching tree with permutation: %s", permutation) Console.indent() jasy.js.clean.Permutate.patch(tree, permutation) Console.outdent() # Cleanups jasy.js.clean.DeadCode.cleanup(tree) ScopeScanner.scan(tree) jasy.js.clean.Unused.cleanup(tree) self.project.getCache().store(field, tree, self.mtime, True) Console.outdent() return tree
def __clean(node): """ Removes all empty rules. Starting from inside out for a deep cleanup. This is a required step for the next one where we combine media queries and selectors and need to have an easy reference point to the previous node. """ # Process children first for child in reversed(node): if child is not None: __clean(child) if hasattr(node, "rules") and len(node.rules) == 0: Console.debug("Cleaning up empty selector/mixin/@media/@supports at line %s" % node.line) node.parent.remove(node) elif node.type == "content": Console.debug("Cleaning up left over @content at line %s" % node.line) node.parent.remove(node) elif node.type == "meta": Console.debug("Cleaning up left over @meta at line %s" % node.line) node.parent.remove(node) elif node.type == "block" and node.parent.type in ("sheet", "block"): Console.debug("Inlining content of unnecessary block node at line %s" % node.line) node.parent.insertAllReplace(node, node) elif node.type == "root" and len(node) == 0: Console.debug("Cleaning up left over @root at line %s" % node.line) node.parent.remove(node)
def __generateTranslationBundle(self): """ Returns a translation object for the given language containing all relevant translation files for the current project set. """ language = self.getCurrentPermutation().get("locale") if language is None: return None if language in self.__translationBundles: return self.__translationBundles[language] Console.info("Creating translation bundle: %s", language) Console.indent() # Initialize new Translation object with no project assigned # This object is used to merge all seperate translation instances later on. combined = jasy.item.Translation.TranslationItem(None, id=language) relevantLanguages = self.__expandLanguage(language) # Loop structure is build to prefer finer language matching over project priority for currentLanguage in reversed(relevantLanguages): for project in self.__projects: for translation in project.getTranslations().values(): if translation.getLanguage() == currentLanguage: Console.debug("Adding %s entries from %s @ %s...", len(translation.getTable()), currentLanguage, project.getName()) combined += translation Console.debug("Combined number of translations: %s", len(combined.getTable())) Console.outdent() self.__translationBundles[language] = combined return combined
def getTranslationBundle(self, language=None): """Returns a translation object for the given language containing all relevant translation files for the current project set.""" if language is None: return None if language in self.__translationBundles: return self.__translationBundles[language] Console.info("Creating translation bundle: %s", language) Console.indent() # Initialize new Translation object with no project assigned # This object is used to merge all seperate translation instances later on. combined = jasy.item.Translation.TranslationItem(None, id=language) relevantLanguages = self.__expandLanguage(language) # Loop structure is build to prefer finer language matching over project priority for currentLanguage in reversed(relevantLanguages): for project in self.__projects: for translation in project.getTranslations().values(): if translation.getLanguage() == currentLanguage: Console.debug("Adding %s entries from %s @ %s...", len(translation.getTable()), currentLanguage, project.getName()) combined += translation Console.info("Combined number of translations: %s", len(combined.getTable())) Console.outdent() self.__translationBundles[language] = combined return combined
def __addDir(self, directory, distname): Console.debug("Scanning directory: %s" % directory) Console.indent() path = os.path.join(self.__path, directory) if not os.path.exists(path): return for dirPath, dirNames, fileNames in os.walk(path): for dirName in dirNames: # Filter dotted directories like .git, .bzr, .hg, .svn, etc. if dirName.startswith("."): dirNames.remove(dirName) # Filter sub projects if os.path.exists( os.path.join(dirPath, dirName, "jasyproject.json")): dirNames.remove(dirName) relDirPath = os.path.relpath(dirPath, path) for fileName in fileNames: if fileName[0] == ".": continue relPath = os.path.normpath(os.path.join(relDirPath, fileName)).replace( os.sep, "/") fullPath = os.path.join(dirPath, fileName) self.addFile(relPath, fullPath, distname) Console.outdent()
def loadLibrary(self, objectName, fileName, encoding="utf-8", doc=None): """ Creates a new object inside the user API (jasyscript.py) with the given name containing all @share'd functions and fields loaded from the given file. """ if objectName in self.__scriptEnvironment: raise UserError("Could not import library %s as the object name %s is already used." % (fileName, objectName)) # Create internal class object for storing shared methods class Shared(object): pass exportedModule = Shared() exportedModule.__doc__ = doc or "Imported from %s" % os.path.relpath(fileName, os.getcwd()) counter = 0 # Method for being used as a decorator to share methods to the outside def share(func): nonlocal counter setattr(exportedModule, func.__name__, func) counter += 1 return func # Execute given file. Using clean new global environment # but add additional decorator for allowing to define shared methods # and the session object (self). code = open(fileName, "r", encoding=encoding).read() exec(compile(code, os.path.abspath(fileName), "exec"), {"share" : share, "session" : self}) # Export destination name as global Console.debug("Importing %s shared methods under %s...", counter, objectName) self.__scriptEnvironment[objectName] = exportedModule return counter
def __extendContent(node, call, targetBlock, stopCombineAt): """ Builds up a list of selector/mediaqueries to insert after the extend to produce the @content sections on the intended selectors. """ for child in reversed(node): if child: __extendContent(child, call, targetBlock, stopCombineAt) if node.type == "content" and hasattr(call, "rules"): # Extends support @content as well. In this case we produce a new selector # which matches the position of the content section and append it after # the original extended mixin on return Console.debug("Inserting content section into new virtual selector") selector, media = Util.combineSelector(node, stop=stopCombineAt) selectorNode = Node.Node(type="selector") selectorNode.name = selector selectorNode.append(copy.deepcopy(call.rules), "rules") # Support media queries, too if media: mediaNode = Node.Node(type="media") mediaNode.name = media mediaNode.append(selectorNode) targetBlock.append(mediaNode) else: targetBlock.append(selectorNode)
def __addDir(self, directory, distname): Console.debug("Scanning directory: %s" % directory) Console.indent() path = os.path.join(self.__path, directory) if not os.path.exists(path): return for dirPath, dirNames, fileNames in os.walk(path): for dirName in dirNames: # Filter dotted directories like .git, .bzr, .hg, .svn, etc. if dirName.startswith("."): dirNames.remove(dirName) # Filter sub projects if os.path.exists(os.path.join(dirPath, dirName, "jasyproject.json")): dirNames.remove(dirName) relDirPath = os.path.relpath(dirPath, path) for fileName in fileNames: if fileName[0] == ".": continue relPath = os.path.normpath(os.path.join(relDirPath, fileName)).replace(os.sep, "/") fullPath = os.path.join(dirPath, fileName) self.addFile(relPath, fullPath, distname) Console.outdent()
def executeCommand(args, failMessage=None, path=None, wrapOutput=True): """ Executes the given process and outputs failMessage when errors happen. :param args: :type args: str or list :param failMessage: Message for exception when command fails :type failMessage: str :param path: Directory path where the command should be executed :type path: str :raise Exception: Raises an exception whenever the shell command fails in execution :type wrapOutput: bool :param wrapOutput: Whether shell output should be wrapped and returned (and passed through to Console.debug()) """ if isinstance(args, str): args = shlex.split(args) prevpath = os.getcwd() # Execute in custom directory if path: path = os.path.abspath(os.path.expanduser(path)) os.chdir(path) Console.debug("Executing command: %s", " ".join(args)) Console.indent() # Using shell on Windows to resolve binaries like "git" if not wrapOutput: returnValue = subprocess.call(args, shell=sys.platform == "win32") result = returnValue else: output = tempfile.TemporaryFile(mode="w+t") returnValue = subprocess.call(args, stdout=output, stderr=output, shell=sys.platform == "win32") output.seek(0) result = output.read().strip("\n\r") output.close() # Change back to previous path os.chdir(prevpath) if returnValue != 0 and failMessage: raise Exception("Error during executing shell command: %s (%s)" % (failMessage, result)) if wrapOutput: for line in result.splitlines(): Console.debug(line) Console.outdent() return result
def exportToJson(self, items=None): """ Exports asset data for usage at the client side. Utilizes JavaScript class jasy.Asset to inject data into the client at runtime. """ # Processing assets assets = self.__assets # Destination folder for assets assetPath = os.path.join(self.__profile.getDestinationPath(), self.__profile.getAssetOutputFolder()) result = {} filterExpr = self.__compileFilterExpr(items) if items else None for fileId in assets: if filterExpr and not filterExpr.match(fileId): continue entry = {} # t = file type # u = full file url # h = file hash (based on content) # d = asset data (image size, etc.) assetItem = assets[fileId] self.__copylist.add(assetItem) if self.__profile.getUseSource(): # Compute relative folder from asset location to even external # locations (e.g. auto cloned remote projects) entry["u"] = os.path.relpath(assetItem.getPath(), assetPath) elif self.__profile.getHashAssets(): # Export checksum (SHA1 encoded as URL-safe Base62) entry["h"] = assetItem.getChecksum() # Store file type as analyzed by asset item entry["t"] = assetItem.getType(short=True) # Store additional data figured out by asset item e.g. # image dimensions, video format, play duration, ... assetData = assetItem.exportData() if assetData: entry["d"] = assetData result[fileId] = entry # Ignore empty result if not result: return None Console.debug("Exported %s assets", len(result)) return json.dumps({"assets": self.__structurize(result)}, indent=2, sort_keys=True)
def __process(node, scanMixins=False, active=None): """ Recursively processes the given node. - scanMixins: Whether mixins or selectors should be processed (phase1 vs. phase2) - active: Whether replacements should happen """ modified = 0 if active is None: active = not scanMixins for child in reversed(list(node)): if child is not None: if child.type == "mixin": if scanMixins: modified += __process(child, scanMixins=scanMixins, active=True) else: # Only process non mixin childs modified += __process(child, scanMixins=scanMixins, active=active) if active and isMixinCall(node) and not isExtendCall(node): name = node.name mixin = __findMixin(node.parent, name) if not mixin: raise Exception( "Unknown mixin \"%s\" to include! Do you miss an include for another style sheet?" % (name)) replacements = __resolveMixin(mixin, getattr(node, "params", None)) Console.debug("Replacing call %s at line %s with mixin from line %s" % (name, node.line, replacements.line)) __injectContent(replacements, node) # Reverse inject all children of that block # at the same position as the original call parent = node.parent pos = parent.index(node) for child in reversed(replacements): parent.insert(pos, child) # Finally remove original node parent.remove(node) modified += 1 return modified
def exportToJson(self, items=None): """ Exports asset data for usage at the client side. Utilizes JavaScript class jasy.Asset to inject data into the client at runtime. """ # Processing assets assets = self.__assets # Destination folder for assets assetPath = os.path.join(self.__profile.getDestinationPath(), self.__profile.getAssetOutputFolder()) result = {} filterExpr = self.__compileFilterExpr(items) if items else None for fileId in assets: if filterExpr and not filterExpr.match(fileId): continue entry = {} # t = file type # u = full file url # h = file hash (based on content) # d = asset data (image size, etc.) assetItem = assets[fileId] self.__copylist.add(assetItem) if self.__profile.getUseSource(): # Compute relative folder from asset location to even external # locations (e.g. auto cloned remote projects) entry["u"] = os.path.relpath(assetItem.getPath(), assetPath) elif self.__profile.getHashAssets(): # Export checksum (SHA1 encoded as URL-safe Base62) entry["h"] = assetItem.getChecksum() # Store file type as analyzed by asset item entry["t"] = assetItem.getType(short=True) # Store additional data figured out by asset item e.g. # image dimensions, video format, play duration, ... assetData = assetItem.exportData() if assetData: entry["d"] = assetData result[fileId] = entry # Ignore empty result if not result: return None Console.debug("Exported %s assets", len(result)) return json.dumps({ "assets" : self.__structurize(result) }, indent=2, sort_keys=True)
def addTask(task): """Registers the given task with its name.""" if task.name in __taskRegistry: Console.debug("Overriding task: %s" % task.name) else: Console.debug("Registering task: %s" % task.name) __taskRegistry[task.name] = task
def setCurrentPrefix(self, path): """Interface for Task class to configure the current prefix to use""" if path is None: self.__currentPrefix = None Console.debug("Resetting prefix to working directory") else: self.__currentPrefix = os.path.normpath(os.path.abspath(os.path.expanduser(path))) Console.debug("Setting prefix to: %s" % self.__currentPrefix)
def processSelectors(tree): """Processes all mixin includes inside selectors.""" Console.info("Merging mixins into selectors") Console.indent() modified = __process(tree, scanMixins=False) Console.debug("Merged %s mixins", modified) Console.outdent() return modified
def processMixins(tree): """Processes all mixin includes inside mixins.""" Console.info("Merging mixins with each other...") Console.indent() modified = __process(tree, scanMixins=True) Console.debug("Merged %s mixins", modified) Console.outdent() return modified
def processExtends(tree): """Processes all requests for mixin extends.""" Console.info("Processing extend requests...") Console.indent() modified = __extend(tree) Console.debug("Processed %s selectors", modified) Console.outdent() return modified
def cleanup(node): """Reprocesses JavaScript to remove dead paths.""" Console.debug("Removing dead code branches...") Console.indent() result = __cleanup(node) Console.outdent() return result
def setCurrentPrefix(self, path): """Interface for Task class to configure the current prefix to use""" if path is None: self.__currentPrefix = None Console.debug("Resetting prefix to working directory") else: self.__currentPrefix = os.path.normpath( os.path.abspath(os.path.expanduser(path))) Console.debug("Setting prefix to: %s" % self.__currentPrefix)
def getFields(self): """Returns the fields which are used by this stylesheet.""" field = "style:fields[%s]" % self.id fields = self.project.getCache().read(field, self.mtime) if fields is None: Console.debug("Collecting fields %s...", Console.colorize(self.id, "bold")) fields = collectFields(self.__getTree()) self.project.getCache().store(field, fields, self.mtime) return fields
def clear(self): """Clears the cache file(s)""" if self.__shelve is not None: Console.debug("Closing cache file %s..." % self.__file) self.__shelve.close() self.__shelve = None for fileName in glob.glob("%s*" % self.__file): Console.debug("Clearing cache file %s..." % fileName) os.remove(fileName)
def addClassName(self, className): """ Adds a class to the initial dependencies """ if not className in self.__classes: raise Exception("Unknown Class: %s" % className) Console.debug("Adding class: %s", className) self.__required.append(self.__classes[className]) del self.__included[:] return self
def close(self): """Closes the session and stores cache to the harddrive.""" Console.debug("Closing session...") Console.indent() for project in self.__projects: project.close() self.__projects = None Console.outdent()
def __extendContent(node, call, targetBlock, stopCombineAt): """ Builds up a list of selector/@media/@support to insert after the extend to produce the @content sections on the intended selectors. """ for child in reversed(list(node)): if child: __extendContent(child, call, targetBlock, stopCombineAt) if node.type == "content" and hasattr(call, "rules"): # Extends support @content as well. In this case we produce a new selector # which matches the position of the content section and append it after # the original extended mixin on return Console.debug("Inserting content section into new virtual selector") selector, media, supports = Util.combineSelector(node, stop=stopCombineAt) selectorNode = Node.Node(type="selector") selectorNode.name = selector selectorNode.append(copy.deepcopy(call.rules), "rules") # Support @supports if supports: supportsNode = Node.Node(type="supports") supportsNode.name = supports supportsBlock = Node.Node(type="block") supportsBlock.append(selectorNode) supportsNode.append(supportsBlock, "rules") # Update reference selectorNode = supportsNode # Support @media if media: mediaNode = Node.Node(type="media") mediaNode.name = media mediaBlock = Node.Node(type="block") mediaBlock.append(selectorNode) mediaNode.append(mediaBlock, "rules") # Update reference selectorNode = mediaNode # Insert selectorNode (or media node or supports node when updated) # If all kinds are used we should have the following structure: # @media->@supports->selector targetBlock.append(selectorNode)
def mergeMixin(className, mixinName, classApi, mixinApi): Console.debug("Merging %s into %s", mixinName, className) sectionLink = ["member", "property", "event"] for pos, section in enumerate(("members", "properties", "events")): mixinItems = getattr(mixinApi, section, None) if mixinItems: ScriptItems = getattr(classApi, section, None) if not ScriptItems: ScriptItems = {} setattr(classApi, section, ScriptItems) for name in mixinItems: # Overridden Check if name in ScriptItems: # If it was included, just store another origin if "origin" in ScriptItems[name]: ScriptItems[name]["origin"].append({ "name": mixinName, "link": "%s:%s~%s" % (sectionLink[pos], mixinName, name) }) # Otherwise add it to the overridden list else: if "overridden" not in ScriptItems[name]: ScriptItems[name]["overridden"] = [] ScriptItems[name]["overridden"].append({ "name": mixinName, "link": "%s:%s~%s" % (sectionLink[pos], mixinName, name) }) # Remember where classes are included from else: ScriptItems[name] = {} ScriptItems[name].update(mixinItems[name]) if "origin" not in ScriptItems[name]: ScriptItems[name]["origin"] = [] ScriptItems[name]["origin"].append({ "name": mixinName, "link": "%s:%s~%s" % (sectionLink[pos], mixinName, name) })
def __compileFilterExpr(self, classes): """Returns the regular expression object to use for filtering""" # Merge asset hints from all classes and remove duplicates hints = set() for classObj in classes: hints.update(classObj.getMetaData(self.__session.getCurrentPermutation()).assets) # Compile filter expressions matcher = "^%s$" % "|".join(["(%s)" % fnmatch.translate(hint) for hint in hints]) Console.debug("Compiled asset matcher: %s" % matcher) return re.compile(matcher)
def getMetaData(self, permutation): """Returns the meta data of this stylesheet.""" permutation = self.filterPermutation(permutation) field = "style:meta[%s]-%s" % (self.id, permutation) meta = self.project.getCache().read(field, self.mtime) if meta is None: Console.debug("Collecting meta data %s...", Console.colorize(self.id, "bold")) meta = MetaData.MetaData(self.__getPermutatedTree(permutation)) self.project.getCache().store(field, meta, self.mtime) return meta
def executeCommand(args, failmsg=None, path=None): """ Executes the given process and outputs failmsg when errors happen. Returns the combined shell output (stdout and strerr combined). :param args: :type args: str or list :param failmsg: Message for exception when command fails :type failmsg: str :param path: Directory path where the command should be executed :type path: str :raise Exception: Raises an exception whenever the shell command fails in execution """ if type(args) == str: args = shlex.split(args) prevpath = os.getcwd() # Execute in custom directory if path: path = os.path.abspath(os.path.expanduser(path)) os.chdir(path) Console.debug("Executing command: %s", " ".join(args)) Console.indent() # Using shell on Windows to resolve binaries like "git" output = tempfile.TemporaryFile(mode="w+t") returnValue = subprocess.call(args, stdout=output, stderr=output, shell=sys.platform == "win32") output.seek(0) result = output.read().strip("\n\r") output.close() # Change back to previous path os.chdir(prevpath) if returnValue != 0: raise Exception("Error during executing shell command: %s (%s)" % (failmsg, result)) for line in result.splitlines(): Console.debug(line) Console.outdent() return result
def __processOperator(node, values): Console.debug("Process operator: %s", node.type) # Resolve first child of operation first = node[0] if first.type == "variable": first = values[first.name] # Resolve second child of operation second = node[1] if second.type == "variable": second = values[second.name] return __computeOperation(first, second, node, node.type, values)
def clear(self): """Removes all generated sprite files found in the base directory.""" Console.info("Cleaning sprite files...") Console.indent() for dirPath, dirNames, fileNames in os.walk(self.base): for fileName in fileNames: if fileName.startswith("jasysprite"): filePath = os.path.join(dirPath, fileName) Console.debug("Removing file: %s", filePath) os.remove(filePath) Console.outdent()
def getIncludes(self, permutation): """Returns the includes which are referenced by this stylesheet.""" field = "style:includes[%s]" % self.id includes = self.project.getCache().read(field, self.mtime) if includes is None: Console.debug("Collecting includes %s...", Console.colorize(self.id, "bold")) includes = [] for includeName, includeNode in includeGenerator(self.__getPermutatedTree(permutation)): includes.append(includeName) self.project.getCache().store(field, includes, self.mtime) return includes
def __injectContent(node, call): """Inserts content section of call into prepared content area of mixin clone.""" for child in reversed(list(node)): if child: __injectContent(child, call) if node.type == "content": if hasattr(call, "rules"): Console.debug( "Inserting content section from call into mixin clone") node.parent.insertAllReplace(node, copy.deepcopy(call.rules)) else: Console.debug("Removing unused content section from mixin clone") node.parent.remove(node)
def __addRuntimeData(self, runtime): assets = self.__assets data = self.__data for fileId in runtime: if not fileId in assets: Console.debug("Unknown asset: %s" % fileId) continue if not fileId in data: data[fileId] = {} data[fileId].update(runtime[fileId]) return self
def close(self): """Closes the session and stores cache to the harddrive.""" if not self.__projects: return Console.debug("Closing session...") Console.indent() for project in self.__projects: project.close() self.__projects = None Console.outdent()
def __addContent(self, content): Console.debug("Adding manual content") Console.indent() for fileId in content: fileContent = content[fileId] if len(fileContent) == 0: raise UserError("Empty content!") # If the user defines a file extension for JS public idenfiers # (which is not required) we filter them out if fileId.endswith(".js"): raise UserError( "JavaScript files should define the exported name, not a file name: %s" % fileId) fileExtension = os.path.splitext(fileContent[0])[1] # Support for joining text content if len(fileContent) == 1: filePath = os.path.join(self.__path, fileContent[0]) else: filePath = [ os.path.join(self.__path, filePart) for filePart in fileContent ] # Structure files if fileExtension in classExtensions: construct = jasy.item.Class.ClassItem dist = self.classes elif fileExtension in translationExtensions: construct = jasy.item.Translation.TranslationItem dist = self.translations else: construct = jasy.item.Asset.AssetItem dist = self.assets # Check for duplication if fileId in dist: raise UserError("Item ID was registered before: %s" % fileId) # Create instance item = construct(self, fileId).attach(filePath) Console.debug("Registering %s %s" % (item.kind, fileId)) dist[fileId] = item Console.outdent()