def processAnimations(self): """Processes jasyanimation files to merge animation data into asset registry.""" assets = self.__assets configs = [fileId for fileId in assets if assets[fileId].isImageAnimationConfig()] if configs: Console.info("Processing %s...", Console.colorize("%s animations", "magenta") % len(configs)) Console.indent() for fileId in configs: Console.debug("Processing %s...", fileId) asset = assets[fileId] base = os.path.dirname(fileId) try: config = asset.getParsedObject() except ValueError as err: raise UserError("Could not parse jasyanimation at %s: %s" % (fileId, err)) for relPath in config: imageId = "%s/%s" % (base, relPath) data = config[relPath] if imageId not in assets: raise UserError("Unknown asset %s in %s" % (imageId, fileId)) animationAsset = assets[imageId] if "rows" in data or "columns" in data: rows = Util.getKey(data, "rows", 1) columns = Util.getKey(data, "columns", 1) frames = Util.getKey(data, "frames") animationAsset.addImageAnimationData(columns, rows, frames) if frames is None: frames = rows * columns elif "layout" in data: layout = data["layout"] animationAsset.addImageAnimationData(None, None, layout=layout) frames = len(layout) else: raise UserError("Invalid image frame data for: %s" % imageId) Console.debug(" - Animation %s has %s frames", imageId, frames) Console.debug(" - Deleting animation config from assets: %s", fileId) del assets[fileId] Console.outdent()
def expandFileName(self, fileName): """ Replaces placeholders inside the given filename and returns the result. The placeholders are based on the current state of the session. These are the currently supported placeholders: - {{locale}}: Name of current locale e.g. de_DE - {{permutation}}: SHA1 checksum of current permutation - {{id}}: SHA1 checksum based on permutation and repository branch/revision """ if "{{destination}}" in fileName: fileName = fileName.replace("{{destination}}", self.getDestinationPath()) if self.__permutation: if "{{permutation}}" in fileName: fileName = fileName.replace("{{permutation}}", self.__permutation.getChecksum()) if "{{id}}" in fileName: buildId = "%s@%s" % (self.__permutation.getKey(), self.__session.getMain().getRevision()) buildHash = Util.generateChecksum(buildId) fileName = fileName.replace("{{id}}", buildHash) if "{{locale}}" in fileName: locale = self.__permutation.get("locale") fileName = fileName.replace("{{locale}}", locale) elif "{{id}}" in fileName: fileName = fileName.replace("{{id}}", "none@%s" % (self.__session.getMain().getRevision())) return fileName
def printTasks(indent=16): """Prints out a list of all avaible tasks and their descriptions.""" for name in sorted(__taskRegistry): obj = __taskRegistry[name] formattedName = name if obj.__doc__: space = (indent - len(name)) * " " print(" %s: %s%s" % (formattedName, space, Console.colorize(obj.__doc__, "magenta"))) else: print(" %s" % formattedName) if obj.availableArgs or obj.hasFlexArgs: text = "" if obj.availableArgs: text += Util.hyphenate("--%s <var>" % " <var> --".join(obj.availableArgs)) if obj.hasFlexArgs: if text: text += " ..." else: text += "--<name> <var>" print(" %s" % (Console.colorize(text, "grey")))
def setId(self, id): super().setId(id) self.extension = os.path.splitext(self.id.lower())[1] self.type = Util.getKey(AssetExtensions, self.extension, "other") self.shortType = self.type[0]
def __getFilteredPages(self, currentItem): """Return sorted list of only pages of same language and not hidden.""" pages = self.__pages currentLang = currentItem["lang"] pageList = [pageItem for pageItem in pages if pageItem["lang"] == currentLang and not pageItem["status"] == "hidden"] return sorted(pageList, key=lambda pageItem: JasyUtil.getKey(pageItem, "pos", 1000000))
def __init__(self): atexit.register(self.close) # Behaves like Date.now() in JavaScript: UTC date in milliseconds self.__timeStamp = int(round(time.time() * 1000)) self.__timeHash = Util.generateChecksum(str(self.__timeStamp)) self.__projects = [] self.__fields = {} self.__translationBundles = {}
def executeTask(taskname, **kwargs): """Executes the given task by name with any optional named arguments.""" if taskname in __taskRegistry: try: camelCaseArgs = {Util.camelize(key) : kwargs[key] for key in kwargs} return __taskRegistry[taskname](**camelCaseArgs) except UserError as err: raise except: Console.error("Unexpected error! Could not finish task %s successfully!" % taskname) raise else: raise UserError("No such task: %s" % taskname)
def expandFileName(self, fileName): """ Replaces placeholders inside the given filename and returns the result. The placeholders are based on the current state of the session. These are the currently supported placeholders: - {{locale}}: Name of current locale e.g. de_DE - {{permutation}}: SHA1 checksum of current permutation - {{id}}: SHA1 checksum based on permutation and repository branch/revision """ if "{{destination}}" in fileName: fileName = fileName.replace("{{destination}}", self.getDestinationPath()) if self.__permutation: if "{{permutation}}" in fileName: fileName = fileName.replace("{{permutation}}", self.__permutation.getChecksum()) if "{{id}}" in fileName: buildId = "%s@%s" % (self.__permutation.getKey(), self.__session.getMain().getRevision()) buildHash = Util.generateChecksum(buildId) fileName = fileName.replace("{{id}}", buildHash) if "{{locale}}" in fileName: locale = self.__permutation.get("locale") fileName = fileName.replace("{{locale}}", locale) elif "{{id}}" in fileName: fileName = fileName.replace( "{{id}}", "none@%s" % (self.__session.getMain().getRevision())) return fileName
def getTargetFolder(url, version=None): """ Returns the target folder name based on the URL and version using SHA1 checksums. :param url: URL to the repository :type url: string :param version: Version to use :type url: string """ if Git.isUrl(url): version = Git.expandVersion(version) folder = url[url.rindex("/") + 1:] if folder.endswith(".git"): folder = folder[:-4] identifier = "%s@%s" % (url, version) version = version[version.rindex("/") + 1:] hash = Util.generateChecksum(identifier) return "%s-%s-%s" % (folder, version, hash)
def __init__(self, combination): self.__combination = combination self.__key = self.__buildKey(combination) self.__checksum = Util.generateChecksum(self.__key)
def processAnimations(self): """Processes jasyanimation files to merge animation data into asset registry.""" assets = self.__assets configs = [ fileId for fileId in assets if assets[fileId].isImageAnimationConfig() ] if configs: Console.info( "Processing %s...", Console.colorize("%s animations", "magenta") % len(configs)) Console.indent() for fileId in configs: Console.debug("Processing %s...", fileId) asset = assets[fileId] base = os.path.dirname(fileId) try: config = asset.getParsedObject() except ValueError as err: raise UserError("Could not parse jasyanimation at %s: %s" % (fileId, err)) for relPath in config: imageId = "%s/%s" % (base, relPath) data = config[relPath] if imageId not in assets: raise UserError("Unknown asset %s in %s" % (imageId, fileId)) animationAsset = assets[imageId] if "rows" in data or "columns" in data: rows = Util.getKey(data, "rows", 1) columns = Util.getKey(data, "columns", 1) frames = Util.getKey(data, "frames") animationAsset.addImageAnimationData(columns, rows, frames) if frames is None: frames = rows * columns elif "layout" in data: layout = data["layout"] animationAsset.addImageAnimationData(None, None, layout=layout) frames = len(layout) else: raise UserError("Invalid image frame data for: %s" % imageId) Console.debug(" - Animation %s has %s frames", imageId, frames) Console.debug(" - Deleting animation config from assets: %s", fileId) del assets[fileId] Console.outdent()
def __init__(self, session): Console.info("Initializing profile...") Console.indent() # Reference to global session object self.__session = session # Initialize data instance self.__data = {} # Set default values (which require serialization) self.setJsOutputFolder("js") self.setCssOutputFolder("css") self.setAssetOutputFolder("asset") self.setTemplateOutputFolder("tmpl") self.setCompressionLevel(0) self.setFormattingLevel(100) # Initialize file manager fileManager = self.__fileManager = FileManager.FileManager(self) # Enforce scan of projects session.scan() # Part registry holds information about all parts of the application to build self.__parts = {} # Copy fields and commands from session # This happens to have local access to all of them + being able to add and tweak data locally self.__fields = copy.copy(session.getFields()) self.__commands = copy.copy(session.getCommands()) # Behaves like Date.now() in JavaScript: UTC date in milliseconds self.__timeStamp = int(round(time.time() * 1000)) self.__timeHash = Util.generateChecksum(str(self.__timeStamp)) # No further steps when session has no projects e.g. during "distclean" if not session.getProjects(): return # Initialize asset manager Console.info("Initializing asset manager...") Console.indent() assetManager = self.__assetManager = AssetManager.AssetManager(self) # Registering assets for project in self.getProjects(): assetManager.addProject(project) # Enable sprite sheets and image animations assetManager.processSprites() assetManager.processAnimations() Console.outdent() # Registering commands Console.info("Registering commands...") self.addCommand("asset.url", lambda fileId: assetManager.getAssetUrl(fileId), "url") self.addCommand("asset.width", lambda fileId: assetManager.getAssetWidth(fileId), "px") self.addCommand("asset.height", lambda fileId: assetManager.getAssetHeight(fileId), "px") self.addCommand("sprite.url", lambda fileId: assetManager.getSpriteUrl(fileId), "url") self.addCommand("sprite.left", lambda fileId: assetManager.getSpriteLeft(fileId), "px") self.addCommand("sprite.top", lambda fileId: assetManager.getSpriteTop(fileId), "px") self.addCommand("sprite.width", lambda fileId: assetManager.getSpriteWidth(fileId), "px") self.addCommand("sprite.height", lambda fileId: assetManager.getSpriteHeight(fileId), "px") self.addCommand( "animation.columns", lambda fileId: assetManager.getAnimationColumns(fileId), "number") self.addCommand("animation.rows", lambda fileId: assetManager.getAnimationRows(fileId), "number") self.addCommand("animation.frames", lambda fileId: assetManager.getAnimationFrames(fileId), "number") Console.outdent()
def scan(self): if self.scanned: return updatemsg = "[updated]" if self.__modified else "[cached]" if self.version: Console.info("Scanning %s @ %s %s...", Console.colorize(self.getName(), "bold"), Console.colorize(self.version, "magenta"), Console.colorize(updatemsg, "grey")) else: Console.info("Scanning %s %s...", Console.colorize(self.getName(), "bold"), Console.colorize(updatemsg, "grey")) Console.indent() # Support for pre-initialize projects... setup = self.__setup if setup and self.__modified: Console.info("Running setup...") Console.indent() for cmd in setup: Console.info("Executing %s...", cmd) result = None try: result = None result = Util.executeCommand(cmd, "Failed to execute setup command %s" % cmd, path=self.__path) except Exception as ex: if result: Console.error(result) raise UserError("Could not scan project %s: %s" % (self.__name, ex)) Console.outdent() # Processing custom content section. Only supports classes and assets. if self.__config.has("content"): self.kind = "manual" self.__addContent(self.__config.get("content")) else: # Read scan path from config if not self.__config.has("scan"): if self.__hasDir("source"): self.kind = "application" scan = self.__resolveScanConfig(structures[self.kind]) elif self.__hasDir("src"): self.kind = "resource" scan = self.__resolveScanConfig(structures[self.kind]) else: self.kind = "flat" scan = self.__resolveScanConfig(structures[self.kind]) else: scan = self.__resolveScanConfig(self.__config.get("scan")) for config in scan: if isinstance(config["paths"], str): self.__addDir(config["paths"], config["regex"], config["type"], config["package"]) else: for path in config["paths"]: self.__addDir(path, config["regex"], config["type"], config["package"]) # Generate summary summary = [] for section in self.items.keys(): content = self.items[section] name, constructor = self.__resolveConstructor(section) if content: summary.append(Console.colorize("%s %s" % (len(content), name), "magenta")) # Print out if summary: Console.info("Content: %s" % (", ".join(summary))) self.scanned = True Console.outdent()
def scan(self): if self.scanned: return updatemsg = "[updated]" if self.__modified else "[cached]" if self.version: Console.info("Scanning %s @ %s %s...", Console.colorize(self.getName(), "bold"), Console.colorize(self.version, "magenta"), Console.colorize(updatemsg, "grey")) else: Console.info("Scanning %s %s...", Console.colorize(self.getName(), "bold"), Console.colorize(updatemsg, "grey")) Console.indent() # Support for pre-initialize projects... setup = self.__setup if setup and self.__modified: Console.info("Running setup...") Console.indent() for cmd in setup: Console.info("Executing %s...", cmd) result = None try: result = None result = Util.executeCommand( cmd, "Failed to execute setup command %s" % cmd, path=self.__path) except Exception as ex: if result: Console.error(result) raise UserError("Could not scan project %s: %s" % (self.__name, ex)) Console.outdent() # Processing custom content section. Only supports classes and assets. if self.__config.has("content"): self.kind = "manual" self.__addContent(self.__config.get("content")) else: # Read scan path from config if not self.__config.has("scan"): if self.__hasDir("source"): self.kind = "application" scan = self.__resolveScanConfig(structures[self.kind]) elif self.__hasDir("src"): self.kind = "resource" scan = self.__resolveScanConfig(structures[self.kind]) else: self.kind = "flat" scan = self.__resolveScanConfig(structures[self.kind]) else: scan = self.__resolveScanConfig(self.__config.get("scan")) for config in scan: if isinstance(config["paths"], str): self.__addDir(config["paths"], config["regex"], config["type"], config["package"]) else: for path in config["paths"]: self.__addDir(path, config["regex"], config["type"], config["package"]) # Generate summary summary = [] for section in self.items.keys(): content = self.items[section] name, constructor = self.__resolveConstructor(section) if content: summary.append( Console.colorize("%s %s" % (len(content), name), "magenta")) # Print out if summary: Console.info("Content: %s" % (", ".join(summary))) self.scanned = True Console.outdent()
def __init__(self, session): Console.info("Initializing profile...") Console.indent() # Reference to global session object self.__session = session # Initialize data instance self.__data = {} # Set default values (which require serialization) self.setJsOutputFolder("js") self.setCssOutputFolder("css") self.setAssetOutputFolder("asset") self.setTemplateOutputFolder("tmpl") self.setCompressionLevel(0) self.setFormattingLevel(100) # Initialize file manager fileManager = self.__fileManager = FileManager.FileManager(self) # Enforce scan of projects session.scan() # Part registry holds information about all parts of the application to build self.__parts = {} # Copy fields and commands from session # This happens to have local access to all of them + being able to add and tweak data locally self.__fields = copy.copy(session.getFields()) self.__commands = copy.copy(session.getCommands()) # Behaves like Date.now() in JavaScript: UTC date in milliseconds self.__timeStamp = int(round(time.time() * 1000)) self.__timeHash = Util.generateChecksum(str(self.__timeStamp)) # No further steps when session has no projects e.g. during "distclean" if not session.getProjects(): return # Initialize asset manager Console.info("Initializing asset manager...") Console.indent() assetManager = self.__assetManager = AssetManager.AssetManager(self) # Registering assets for project in self.getProjects(): assetManager.addProject(project) # Enable sprite sheets and image animations assetManager.processSprites() assetManager.processAnimations() Console.outdent() # Registering commands Console.info("Registering commands...") self.addCommand("asset.url", lambda fileId: assetManager.getAssetUrl(fileId), "url") self.addCommand("asset.width", lambda fileId: assetManager.getAssetWidth(fileId), "px") self.addCommand("asset.height", lambda fileId: assetManager.getAssetHeight(fileId), "px") self.addCommand("sprite.url", lambda fileId: assetManager.getSpriteUrl(fileId), "url") self.addCommand("sprite.left", lambda fileId: assetManager.getSpriteLeft(fileId), "px") self.addCommand("sprite.top", lambda fileId: assetManager.getSpriteTop(fileId), "px") self.addCommand("sprite.width", lambda fileId: assetManager.getSpriteWidth(fileId), "px") self.addCommand("sprite.height", lambda fileId: assetManager.getSpriteHeight(fileId), "px") self.addCommand("animation.columns", lambda fileId: assetManager.getAnimationColumns(fileId), "number") self.addCommand("animation.rows", lambda fileId: assetManager.getAnimationRows(fileId), "number") self.addCommand("animation.frames", lambda fileId: assetManager.getAnimationFrames(fileId), "number") Console.outdent()
def getRequires(self, checkoutDirectory="external", updateRepositories=True): """ Return the project requirements as project instances """ global projects result = [] for entry in self.__requires: if type(entry) is dict: source = entry["source"] config = Util.getKey(entry, "config") version = Util.getKey(entry, "version") kind = Util.getKey(entry, "kind") else: source = entry config = None version = None kind = None # Versions are expected being string type if version is not None: version = str(version) revision = None if Repository.isUrl(source): kind = kind or Repository.getType(source) path = os.path.abspath( os.path.join(checkoutDirectory, Repository.getTargetFolder(source, version))) # Only clone and update when the folder is unique in this session # This reduces git/hg/svn calls which are typically quite expensive if not path in projects: revision = Repository.update(source, version, path, updateRepositories) if revision is None: raise UserError("Could not update repository %s" % source) else: kind = "local" if not source.startswith(("/", "~")): path = os.path.join(self.__path, source) else: path = os.path.abspath(os.path.expanduser(source)) if path in projects: project = projects[path] else: fullversion = [] # Produce user readable version when non is defined if version is None and revision is not None: version = "master" if version is not None: if "/" in version: fullversion.append(version[version.rindex("/") + 1:]) else: fullversion.append(version) if revision is not None: # Shorten typical long revisions as used by e.g. Git if type(revision) is str and len(revision) > 20: fullversion.append(revision[:10]) else: fullversion.append(revision) if fullversion: fullversion = "-".join(fullversion) else: fullversion = None project = Project(path, config, fullversion) projects[path] = project result.append(project) return result
def scan(self): if self.scanned: return updatemsg = "[updated]" if self.__modified else "[cached]" Console.info("Scanning project %s %s...", self.__name, Console.colorize(updatemsg, "grey")) Console.indent() # Support for pre-initialize projects... setup = self.__setup if setup and self.__modified: Console.info("Running setup...") Console.indent() for cmd in setup: Console.info("Executing %s...", cmd) result = None try: result = None result = Util.executeCommand( cmd, "Failed to execute setup command %s" % cmd, path=self.__path) except Exception as ex: if result: Console.error(result) raise UserError("Could not scan project %s: %s" % (self.__name, ex)) Console.outdent() # Processing custom content section. Only supports classes and assets. if self.__config.has("content"): self.kind = "manual" self.__addContent(self.__config.get("content")) # Application projects elif self.__hasDir("source"): self.kind = "application" if self.__hasDir("source/class"): self.__addDir("source/class", "classes") if self.__hasDir("source/asset"): self.__addDir("source/asset", "assets") if self.__hasDir("source/translation"): self.__addDir("source/translation", "translations") # Compat - please change to class/style/asset instead elif self.__hasDir("src"): self.kind = "resource" self.__addDir("src", "classes") # Resource projects else: self.kind = "resource" if self.__hasDir("class"): self.__addDir("class", "classes") if self.__hasDir("asset"): self.__addDir("asset", "assets") if self.__hasDir("translation"): self.__addDir("translation", "translations") # Generate summary summary = [] for section in ["classes", "assets", "translations"]: content = getattr(self, section, None) if content: summary.append("%s %s" % (len(content), section)) # Print out if summary: Console.info("Done %s: %s" % (Console.colorize("[%s]" % self.kind, "grey"), Console.colorize(", ".join(summary), "green"))) else: Console.error("Project is empty!") self.scanned = True Console.outdent()
def scan(self): if self.scanned: return updatemsg = "[updated]" if self.__modified else "[cached]" Console.info("Scanning project %s %s...", self.__name, Console.colorize(updatemsg, "grey")) Console.indent() # Support for pre-initialize projects... setup = self.__setup if setup and self.__modified: Console.info("Running setup...") Console.indent() for cmd in setup: Console.info("Executing %s...", cmd) result = None try: result = None result = Util.executeCommand(cmd, "Failed to execute setup command %s" % cmd, path=self.__path) except Exception as ex: if result: Console.error(result) raise UserError("Could not scan project %s: %s" % (self.__name, ex)) Console.outdent() # Processing custom content section. Only supports classes and assets. if self.__config.has("content"): self.kind = "manual" self.__addContent(self.__config.get("content")) # Application projects elif self.__hasDir("source"): self.kind = "application" if self.__hasDir("source/class"): self.__addDir("source/class", "classes") if self.__hasDir("source/style"): self.__addDir("source/style", "styles") if self.__hasDir("source/asset"): self.__addDir("source/asset", "assets") if self.__hasDir("source/translation"): self.__addDir("source/translation", "translations") # Compat - please change to class/style/asset instead elif self.__hasDir("src"): self.kind = "resource" self.__addDir("src", "classes") # Resource projects else: self.kind = "resource" if self.__hasDir("class"): self.__addDir("class", "classes") if self.__hasDir("style"): self.__addDir("style", "styles") if self.__hasDir("asset"): self.__addDir("asset", "assets") if self.__hasDir("translation"): self.__addDir("translation", "translations") # Generate summary summary = [] for section in ["classes", "styles", "translations", "assets"]: content = getattr(self, section, None) if content: summary.append("%s %s" % (len(content), section)) # Print out if summary: Console.info("Content: %s" % (Console.colorize(", ".join(summary), "green"))) else: Console.error("Project is empty!") self.scanned = True Console.outdent()
def getRequires(self, checkoutDirectory="external", updateRepositories=True): """ Return the project requirements as project instances """ global projects result = [] for entry in self.__requires: if type(entry) is dict: source = entry["source"] config = Util.getKey(entry, "config") version = Util.getKey(entry, "version") kind = Util.getKey(entry, "kind") else: source = entry config = None version = None kind = None # Versions are expected being string type if version is not None: version = str(version) revision = None if Repository.isUrl(source): kind = kind or Repository.getType(source) path = os.path.abspath(os.path.join(checkoutDirectory, Repository.getTargetFolder(source, version))) # Only clone and update when the folder is unique in this session # This reduces git/hg/svn calls which are typically quite expensive if not path in projects: revision = Repository.update(source, version, path, updateRepositories) if revision is None: raise UserError("Could not update repository %s" % source) else: kind = "local" if not source.startswith(("/", "~")): path = os.path.join(self.__path, source) else: path = os.path.abspath(os.path.expanduser(source)) path = os.path.normpath(path) if path in projects: project = projects[path] else: fullversion = [] # Produce user readable version when non is defined if version is None and revision is not None: version = "master" if version is not None: if "/" in version: fullversion.append(version[version.rindex("/")+1:]) else: fullversion.append(version) if revision is not None: # Shorten typical long revisions as used by e.g. Git if type(revision) is str and len(revision) > 20: fullversion.append(revision[:10]) else: fullversion.append(revision) if fullversion: fullversion = "-".join(fullversion) else: fullversion = None project = Project(path, config, fullversion) projects[path] = project result.append(project) return result