def init(self, autoInitialize=True, updateRepositories=True, scriptEnvironment=None): """ Initialize the actual session with projects :param autoInitialize: Whether the projects should be automatically added when the current folder contains a valid Jasy project. :param updateRepositories: Whether to update repositories of all project dependencies. :param scriptEnvironment: API object as being used for loadLibrary to add Python features offered by projects. """ self.__scriptEnvironment = scriptEnvironment self.__updateRepositories = updateRepositories if autoInitialize and jasy.core.Config.findConfig("jasyproject"): try: self.addProject(jasy.core.Project.getProjectFromPath(".")) except UserError as err: Console.outdent(True) Console.error(err) raise UserError("Critical: Could not initialize session!") Console.info("Active projects (%s):", len(self.__projects)) Console.indent() for project in self.__projects: if project.version: Console.info("%s @ %s", Console.colorize(project.getName(), "bold"), Console.colorize(project.version, "magenta")) else: Console.info(Console.colorize(project.getName(), "bold")) 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 __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 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 ask(self, question, name, accept=None, required=True, default=None, force=False, parse=True): """ Asks the user for value for the given configuration field: :param question: Question to ask the user :type question: string :param name: Name of field to store value in :type name: string :param accept: Any of the supported types to validate for (see matchesType) :type accept: string :param required: Whether the field is required :type required: boolean :param default: Default value whenever user has given no value """ while True: msg = "- %s?" % question if accept is not None: msg += Console.colorize(" [%s]" % accept, "grey") if default is None: msg += Console.colorize(" (%s)" % name, "magenta") else: msg += Console.colorize(" (%s=%s)" % (name, default), "magenta") msg += ": " sys.stdout.write(msg) # Do not ask user for solved items if not force and self.has(name): print( "%s %s" % (self.get(name), Console.colorize("(pre-filled)", "cyan"))) return # Read user input, but ignore any leading/trailing white space value = input().strip() # Fallback to default if no value is given and field is not required if not required and value == "": value = default # Don't accept empty values if value == "": continue # Try setting the current value if self.set(name, value, accept=accept, parse=parse): break
def generateApi(api): """Returns a stringified output for the given API set.""" import jasy.env.Task as Task result = [] for key in sorted(api): if key.startswith("__"): continue value = api[key] if isinstance(value, Task.Task): continue msg = Console.colorize(key, "bold") if inspect.isfunction(value): msg += Console.colorize(highlightArgs(value), "bold") elif inspect.isclass(value): msg += Console.colorize(highlightArgs(value.__init__, True), "bold") humanType = extractType(value) if humanType: msg += Console.colorize(" [%s]" % extractType(value), "magenta") msg += extractDoc(value) or "" result.append(msg) if inspect.isclass(value) or inspect.ismodule(value) or isinstance( value, object): if inspect.isclass(value): sprefix = "" elif inspect.ismodule(value) or isinstance(value, object): sprefix = "%s." % key smembers = dict(inspect.getmembers(value)) for skey in sorted(smembers): if not "__" in skey: svalue = smembers[skey] if inspect.ismethod(svalue) or inspect.isfunction(svalue): msg = " - %s%s" % (sprefix, Console.colorize(skey, "bold")) msg += highlightArgs(svalue, humanType in ("Class", "Object")) msg += extractDoc(svalue, indent=6) or "" result.append(msg) result.append("") return "\n".join(result)
def info(): """ Prints information about Jasy to the console. """ import jasy.core.Console as Console print("Jasy %s is a powerful web tooling framework" % __version__) print("Copyright (c) 2010-2012 Zynga Inc. %s" % Console.colorize("http://zynga.com/", "underline")) print("Visit %s for details." % Console.colorize("https://github.com/zynga/jasy", "underline")) print()
def generateApi(api): """Returns a stringified output for the given API set.""" import jasy.env.Task as Task result = [] for key in sorted(api): if key.startswith("__"): continue value = api[key] if isinstance(value, Task.Task): continue msg = Console.colorize(key, "bold") if inspect.isfunction(value): msg += Console.colorize(highlightArgs(value), "bold") elif inspect.isclass(value): msg += Console.colorize(highlightArgs(value.__init__, True), "bold") humanType = extractType(value) if humanType: msg += Console.colorize(" [%s]" % extractType(value), "magenta") msg += extractDoc(value) or "" result.append(msg) if inspect.isclass(value) or inspect.ismodule(value) or isinstance(value, object): if inspect.isclass(value): sprefix = "" elif inspect.ismodule(value) or isinstance(value, object): sprefix = "%s." % key smembers = dict(inspect.getmembers(value)) for skey in sorted(smembers): if not "__" in skey: svalue = smembers[skey] if inspect.ismethod(svalue) or inspect.isfunction(svalue): msg = " - %s%s" % (sprefix, Console.colorize(skey, "bold")) msg += highlightArgs(svalue, humanType in ("Class", "Object")) msg += extractDoc(svalue, indent=6) or "" result.append(msg) result.append("") return "\n".join(result)
def ask(self, question, name, accept=None, required=True, default=None, force=False, parse=True): """ Asks the user for value for the given configuration field: :param question: Question to ask the user :type question: string :param name: Name of field to store value in :type name: string :param accept: Any of the supported types to validate for (see matchesType) :type accept: string :param required: Whether the field is required :type required: boolean :param default: Default value whenever user has given no value """ while True: msg = "- %s?" % question if accept is not None: msg += Console.colorize(" [%s]" % accept, "grey") if default is None: msg += Console.colorize(" (%s)" % name, "magenta") else: msg += Console.colorize(" (%s=%s)" % (name, default), "magenta") msg += ": " sys.stdout.write(msg) # Do not ask user for solved items if not force and self.has(name): print("%s %s" % (self.get(name), Console.colorize("(pre-filled)", "cyan"))) return # Read user input, but ignore any leading/trailing white space value = input().strip() # Fallback to default if no value is given and field is not required if not required and value == "": value = default # Don't accept empty values if value == "": continue # Try setting the current value if self.set(name, value, accept=accept, parse=parse): break
def __resolve(project): name = project.getName() # List of required projects Console.info("Getting requirements of %s...", Console.colorize(name, "bold")) Console.indent() requires = project.getRequires(checkoutDirectory, updateRepositories) Console.outdent() if not requires: return Console.debug("Processing %s requirements...", len(requires)) Console.indent() # Adding all project in reverse order. # Adding all local ones first before going down to their requirements for requiredProject in reversed(requires): requiredName = requiredProject.getName() if not requiredName in names: Console.debug("Adding: %s %s (via %s)", requiredName, requiredProject.version, project.getName()) names[requiredName] = True result.append(requiredProject) else: Console.debug("Blocking: %s %s (via %s)", requiredName, requiredProject.version, project.getName()) # Process all requirements of added projects for requiredProject in requires: if requiredProject.hasRequires(): __resolve(requiredProject) Console.outdent()
def getCompressed(self, profile): """Returns the compressed CSS code of this item.""" field = "style:compressed[%s]-%s" % (self.id, profile.getId()) mtime = self.getModificationTime(profile) compressed = self.project.getCache().read(field, mtime) if compressed is None: Console.info("Compressing tree %s...", Console.colorize(self.id, "bold")) # Start with the merged tree (includes resolved) tree = self.getMergedTree(profile) # Reduce tree Engine.reduceTree(tree, profile) # Compress tree compressed = Engine.compressTree(tree, profile.getCompressionLevel(), profile.getFormattingLevel()) # Store in cache self.project.getCache().store(field, compressed, mtime) return compressed
def loadCommands(self, objectName, fileName, encoding="utf-8"): """Loads new commands into the session wide command registry.""" counter = 0 commands = self.__commands # Method for being used as a decorator to share methods to the outside def share(func): name = "%s.%s" % (objectName, func.__name__) if name in commands: raise Exception("Command %s already exists!" % name) commands[name] = func nonlocal counter 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.info("Imported %s.", Console.colorize("%s commands" % counter, "magenta")) return counter
def processSprites(self): """Processes jasysprite files to merge sprite data into asset registry.""" assets = self.__assets configs = [fileId for fileId in assets if assets[fileId].isImageSpriteConfig()] if configs: Console.info("Processing %s...", Console.colorize("%s sprites", "magenta") % len(configs)) sprites = [] Console.indent() for fileId in configs: Console.debug("Processing %s...", fileId) asset = assets[fileId] spriteBase = os.path.dirname(fileId) try: spriteConfig = asset.getParsedObject() except ValueError as err: raise UserError("Could not parse jasysprite at %s: %s" % (fileId, err)) Console.indent() for spriteImage in spriteConfig: spriteImageId = "%s/%s" % (spriteBase, spriteImage) singleRelPaths = spriteConfig[spriteImage] Console.debug("Image %s combines %s images", spriteImageId, len(singleRelPaths)) for singleRelPath in singleRelPaths: singleId = "%s/%s" % (spriteBase, singleRelPath) singleData = singleRelPaths[singleRelPath] singleItem = assets[singleId] # Verify that sprite sheet is up-to-date fileChecksum = singleItem.getChecksum() storedChecksum = singleData["checksum"] Console.debug("Checksum Compare: %s <=> %s", fileChecksum, storedChecksum) if storedChecksum != fileChecksum: raise UserError("Sprite Sheet is not up-to-date. Checksum of %s differs." % singleId) if spriteImageId not in sprites: spriteImageIndex = len(sprites) sprites.append(spriteImageId) else: spriteImageIndex = sprites.index(spriteImageId) # Add relevant data to find image on sprite sheet singleItem.addImageSpriteData(spriteImageIndex, singleData["left"], singleData["top"]) Console.outdent() # The config file does not make any sense on the client side Console.debug("Deleting sprite config from assets: %s", fileId) del assets[fileId] Console.outdent() self.__sprites = sprites
def __getTree(self, context=None): field = "tree[%s]" % self.id tree = self.project.getCache().read(field, self.mtime) if not tree: Console.info("Processing class %s %s...", Console.colorize(self.id, "bold"), Console.colorize("[%s]" % context, "cyan")) Console.indent() tree = Parser.parse(self.getText(), self.id) ScopeScanner.scan(tree) Console.outdent() self.project.getCache().store(field, tree, self.mtime, True) return tree
def info(): """Prints information about Jasy to the console.""" import jasy.core.Console as Console print("Konstrukteur %s is a static site generator" % __version__) print("Visit %s for details." % Console.colorize("https://github.com/fastner/konstrukteur", "underline")) print()
def info(): """Prints information about Jasy to the console.""" import jasy.core.Console as Console print("Jasy %s is a powerful web tooling framework" % __version__) print("Visit %s for details." % Console.colorize("https://github.com/sebastian-software/jasy", "underline")) print()
def runTask(project, task, **kwargs): """ Executes the given task of the given projects. This happens inside a new sandboxed session during which the current session is paused/resumed automatically. """ remote = session.getProjectByName(project) if remote is not None: remotePath = remote.getPath() remoteName = remote.getName() elif os.path.isdir(project): remotePath = project remoteName = os.path.basename(project) else: raise UserError("Unknown project or invalid path: %s" % project) Console.info("Running %s of project %s...", Console.colorize(task, "bold"), Console.colorize(remoteName, "bold")) # Pauses this session to allow sub process fully accessing the same projects session.pause() # Build parameter list from optional arguments params = ["--%s=%s" % (key, kwargs[key]) for key in kwargs] if not "prefix" in kwargs: params.append("--prefix=%s" % session.getCurrentPrefix()) # Full list of args to pass to subprocess args = [__command, task] + params # Change into sub folder and execute jasy task oldPath = os.getcwd() os.chdir(remotePath) returnValue = subprocess.call(args, shell=sys.platform == "win32") os.chdir(oldPath) # Resumes this session after sub process was finished session.resume() # Error handling if returnValue != 0: raise UserError("Executing of sub task %s from project %s failed" % (task, project))
def __getTree(self, context=None): field = "tree[%s]" % self.id tree = self.project.getCache().read(field, self.mtime) if not tree: Console.info( "Processing class %s %s...", Console.colorize(self.id, "bold"), Console.colorize("[%s]" % context, "cyan"), ) Console.indent() tree = Parser.parse(self.getText(), self.id) ScopeScanner.scan(tree) Console.outdent() self.project.getCache().store(field, tree, self.mtime, True) return tree
def doCompleteDoctor(): """Checks for uninstalled or too old versions of requirements and gives a complete output""" Console.header("Doctor") dists = [dist for dist in pip.get_installed_distributions()] keys = [dist.key for dist in pip.get_installed_distributions()] versions = {} for dist in dists: versions[dist.key] = dist.version def checkSingleInstallation(keys, versions, packageName, minVersion, installPath, updatePath): Console.info('%s:' % packageName) Console.indent() if packageName.lower() in keys: Console.info(Console.colorize('Found installation', "green")) if LooseVersion(minVersion) > LooseVersion("0.0"): if LooseVersion(versions[packageName.lower()]) >= LooseVersion(minVersion): Console.info(Console.colorize('Version is OK (needed: %s installed: %s)' % (minVersion, versions[packageName.lower()]), "green")) else: Console.info(Console.colorize(Console.colorize('- Version is NOT OK (needed: %s installed: %s)' % (minVersion, versions[packageName.lower()]) , "red"), "bold")) Console.info('Update to the newest version of %s using %s' % (packageName, updatePath)) else: Console.info(Console.colorize(Console.colorize('Did NOT find installation', "red"), "bold")) Console.info('Install the newest version of %s using %s' % (packageName, installPath)) Console.outdent() # Required packages Console.info(Console.colorize("Required Packages:", "bold")) Console.indent() for entry in needs: checkSingleInstallation(keys, versions, entry["packageName"], entry["minVersion"], entry["installPath"], entry["updatePath"]) Console.outdent() # Optional packages Console.info("") Console.info(Console.colorize("Optional Packages:", "bold")) Console.indent() for entry in optionals: checkSingleInstallation(keys, versions, entry["packageName"], entry["minVersion"], entry["installPath"], entry["updatePath"]) Console.outdent()
def runTask(project, task, **kwargs): """ Executes the given task of the given projects. This happens inside a new sandboxed session during which the current session is paused/resumed automatically. """ remote = session.getProjectByName(project) if remote is not None: remotePath = remote.getPath() remoteName = remote.getName() elif os.path.isdir(project): remotePath = project remoteName = os.path.basename(project) else: raise UserError("Unknown project or invalid path: %s" % project) Console.info("Running %s of project %s...", Console.colorize(task, "bold"), Console.colorize(remoteName, "bold")) # Pauses this session to allow sub process fully accessing the same projects session.pause() # Build parameter list from optional arguments params = ["--%s=%s" % (key, kwargs[key]) for key in kwargs] if not "prefix" in kwargs: params.append("--prefix=%s" % session.getCurrentPrefix()) # Full list of args to pass to subprocess args = [__command, task] + params # Change into sub folder and execute jasy task oldPath = os.getcwd() os.chdir(remotePath) returnValue = subprocess.call(args, shell=sys.platform == "win32") os.chdir(oldPath) # Resumes this session after sub process was finished session.resume() # Error handling if returnValue != 0: raise UserError("Executing of sub task %s from project %s failed" % (task, project))
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 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 printOptions(self, indent=16): for name in sorted(self.__defaults): col = len(name) msg = " --%s" % name for shortcut in self.__shortcuts: if self.__shortcuts[shortcut] == name: col += len(" [-%s]" % shortcut) msg += Console.colorize(" [-%s]" % shortcut, "grey") if name in self.__help: msg += ": " diff = indent - col if diff > 0: msg += " " * diff msg += Console.colorize(self.__help[name], "magenta") print(msg)
def __getTree(self, context=None): """ Returns the parsed tree """ field = "tree[%s]" % self.id tree = self.project.getCache().read(field, self.mtime) if not tree: Console.info( "Processing stylesheet %s %s...", Console.colorize(self.id, "bold"), Console.colorize("[%s]" % context, "cyan"), ) Console.indent() tree = Parser.parse(self.getText(), self.id) Console.outdent() self.project.getCache().store(field, tree, self.mtime, True) return tree
def init(self, autoInitialize=True, updateRepositories=True, scriptEnvironment=None): """ Initialize the actual session with projects. :param autoInitialize: Whether the projects should be automatically added when the current folder contains a valid Jasy project. :param updateRepositories: Whether to update repositories of all project dependencies. :param scriptEnvironment: API object as being used for loadLibrary to add Python features offered by projects. :param commandEnvironment: API object as being used for loadCommands to add Python features for any item nodes. """ self.__scriptEnvironment = scriptEnvironment self.__updateRepositories = updateRepositories if autoInitialize and Config.findConfig("jasyproject"): Console.info("Initializing session...") Console.indent() try: self.addProject(Project.getProjectFromPath(".", self)) except UserError as err: Console.outdent(True) Console.error(err) raise UserError("Critical: Could not initialize session!") self.getVirtualProject() Console.debug("Active projects (%s):", len(self.__projects)) Console.indent() for project in self.__projects: if project.version: Console.debug("%s @ %s", Console.colorize(project.getName(), "bold"), Console.colorize(project.version, "magenta")) else: Console.debug(Console.colorize(project.getName(), "bold")) Console.outdent() Console.outdent()
def checkSingleInstallation(keys, versions, packageName, minVersion, installPath, updatePath): Console.info('%s:' % packageName) Console.indent() if packageName.lower() in keys: Console.info(Console.colorize('Found installation', "green")) if LooseVersion(minVersion) > LooseVersion("0.0"): if LooseVersion(versions[packageName.lower()]) >= LooseVersion( minVersion): Console.info( Console.colorize( 'Version is OK (needed: %s installed: %s)' % (minVersion, versions[packageName.lower()]), "green")) else: Console.info( Console.colorize( Console.colorize( '- Version is NOT OK (needed: %s installed: %s)' % (minVersion, versions[packageName.lower()]), "red"), "bold")) Console.info( 'Update to the newest version of %s using %s' % (packageName, updatePath)) else: Console.info( Console.colorize( Console.colorize('Did NOT find installation', "red"), "bold")) Console.info('Install the newest version of %s using %s' % (packageName, installPath)) Console.outdent()
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 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 addProject(self, project): """ Adds the given project to the list of known projects. Projects should be added in order of their priority. This adds the field configuration of each project to the session fields. Fields must not conflict between different projects (same name). :param project: Instance of Project to append to the list :type project: object """ result = Project.getProjectDependencies(project, "external", self.__updateRepositories) for project in result: Console.info("Adding %s...", Console.colorize(project.getName(), "bold")) Console.indent() # Append to session list self.__projects.append(project) # Import library methods libraryPath = os.path.join(project.getPath(), "jasylibrary.py") if os.path.exists(libraryPath): self.loadLibrary(project.getName(), libraryPath, doc="Library of project %s" % project.getName()) # Import command methods commandPath = os.path.join(project.getPath(), "jasycommand.py") if os.path.exists(commandPath): self.loadCommands(project.getName(), commandPath) # Import project defined fields which might be configured using "activateField()" fields = project.getFields() for name in fields: entry = fields[name] if name in self.__fields: raise UserError("Field '%s' was already defined!" % (name)) if "check" in entry: check = entry["check"] if check in ["Boolean", "String", "Number"] or isinstance(check, list): pass else: raise UserError("Unsupported check: '%s' for field '%s'" % (check, name)) self.__fields[name] = entry 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 def itemtype(type, name): def wrap(cls): id = "%s.%s" % (objectName, type[0].upper() + type[1:]) self.addItemType(id, name, cls) return cls return wrap def postscan(): def wrap(f): self.__postscans.append(f) return f return wrap # 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, "itemtype": itemtype, "postscan": postscan, "session" : self}) # Export destination name as global self.__scriptEnvironment[objectName] = exportedModule Console.info("Imported %s.", Console.colorize("%s methods" % counter, "magenta")) return counter
def __getTree(self): """Returns the abstract syntax tree of the stylesheet.""" field = "style:tree[%s]" % self.id tree = self.project.getCache().read(field, self.mtime) if not tree: Console.info("Parsing stylesheet %s...", Console.colorize(self.id, "bold")) Console.indent() tree = Engine.getTree(self.getText(), self.id) Console.outdent() self.project.getCache().store(field, tree, self.mtime, True) return tree
def __getTree(self): """Returns the abstract syntax tree.""" field = "script:tree[%s]" % self.id tree = self.project.getCache().read(field, self.mtime) if not tree: Console.info("Processing class %s...", Console.colorize(self.id, "bold")) Console.indent() tree = Parser.parse(self.getText(), self.id) ScopeScanner.scan(tree) Console.outdent() self.project.getCache().store(field, tree, self.mtime, True) return tree
def __getTree(self): """Returns the abstract syntax tree.""" field = "script:tree[%s]" % self.id tree = self.project.getCache().read(field, self.mtime) if not tree: Console.info("Processing class %s...", Console.colorize(self.id, "bold")) Console.indent() tree = Parser.parse(self.getText(), self.id) ScopeScanner.scan(tree) Console.outdent() self.project.getCache().store(field, tree, self.mtime, True) return tree
def checkSingleInstallation(keys, versions, packageName, minVersion, installPath, updatePath): if packageName.lower() in keys: if LooseVersion(minVersion) > LooseVersion("0.0"): if LooseVersion(versions[packageName.lower()]) < LooseVersion(minVersion): Console.info(Console.colorize(Console.colorize('Jasy requirement error: "%s"' % packageName, "red"), "bold")) Console.indent() Console.info(Console.colorize(Console.colorize('Version is NOT OK (needed: %s installed: %s)' % (minVersion, versions[packageName.lower()]) , "red"), "bold")) Console.info('Update to the newest version of %s using %s' % (packageName, updatePath)) Console.outdent() return False else: Console.info(Console.colorize(Console.colorize('Jasy requirement error: "%s"' % packageName, "red"), "bold")) Console.indent() Console.info(Console.colorize(Console.colorize('Did NOT find installation', "red"), "bold")) Console.info('Install the newest version of %s using %s' % (packageName, installPath)) Console.outdent() return False return True
def highlightArgs(value, inClassOrObject=False): argsspec = inspect.getfullargspec(value) if inClassOrObject and argsspec.args and argsspec.args[0] == "self": del argsspec.args[0] argmsg = "(%s" % ", ".join(argsspec.args) if argsspec.varkw is not None: if argsspec.args: argmsg += ", " argmsg += "..." argmsg += ")" return Console.colorize(argmsg, "cyan")
def highlightArgs(value, inClassOrObject=False): argsspec = inspect.getfullargspec(value) if inClassOrObject and argsspec.args and argsspec.args[0] == "self": del argsspec.args[0] argmsg = "(%s" % ", ".join(argsspec.args) if argsspec.varkw is not None: if argsspec.args: argmsg += ", " argmsg += "..." argmsg += ")" return Console.colorize(argmsg, "cyan")
def set(self, name, value, accept=None, parse=False): """ Saves the given value under the given field """ # Don't accept None value if value is None: return False # Parse value for easy type checks if parse: try: parsedValue = eval(value) except: pass else: value = parsedValue # Convert tuples/sets into JSON compatible array if type(value) in (tuple, set): value = list(value) # Check for given type if accept is not None and not matchesType(value, accept): print(Console.colorize(" - Invalid value: %s" % str(value), "red")) return False if "." in name: splits = name.split(".") current = self.__data for split in splits[:-1]: if not split in current: current[split] = {} current = current[split] current[splits[-1]] = value else: self.__data[name] = value return True
def checkSingleInstallation(keys, versions, packageName, minVersion, installPath, updatePath): if packageName.lower() in keys: if LooseVersion(minVersion) > LooseVersion("0.0"): if LooseVersion(versions[packageName.lower()]) < LooseVersion( minVersion): Console.info( Console.colorize( Console.colorize( 'Jasy requirement error: "%s"' % packageName, "red"), "bold")) Console.indent() Console.info( Console.colorize( Console.colorize( 'Version is NOT OK (needed: %s installed: %s)' % (minVersion, versions[packageName.lower()]), "red"), "bold")) Console.info( 'Update to the newest version of %s using %s' % (packageName, updatePath)) Console.outdent() return False else: Console.info( Console.colorize( Console.colorize( 'Jasy requirement error: "%s"' % packageName, "red"), "bold")) Console.indent() Console.info( Console.colorize( Console.colorize('Did NOT find installation', "red"), "bold")) Console.info('Install the newest version of %s using %s' % (packageName, installPath)) Console.outdent() return False return True
def set(self, name, value, accept=None, parse=False): """ Saves the given value under the given field """ # Don't accept None value if value is None: return False # Parse value for easy type checks if parse: try: parsedValue = eval(value) except: pass else: value = parsedValue # Convert tuples/sets into JSON compatible array if type(value) in (tuple, set): value = list(value) # Check for given type if accept is not None and not matchesType(value, accept): print(Console.colorize(" - Invalid value: %s" % str(value), "red")) return False if "." in name: splits = name.split(".") current = self.__data for split in splits[:-1]: if not split in current: current[split] = {} current = current[split] current[splits[-1]] = value else: self.__data[name] = value return True
def checkSingleInstallation(keys, versions, packageName, minVersion, installPath, updatePath): Console.info('%s:' % packageName) Console.indent() if packageName.lower() in keys: Console.info(Console.colorize('Found installation', "green")) if LooseVersion(minVersion) > LooseVersion("0.0"): if LooseVersion(versions[packageName.lower()]) >= LooseVersion(minVersion): Console.info(Console.colorize('Version is OK (needed: %s installed: %s)' % (minVersion, versions[packageName.lower()]), "green")) else: Console.info(Console.colorize(Console.colorize('- Version is NOT OK (needed: %s installed: %s)' % (minVersion, versions[packageName.lower()]) , "red"), "bold")) Console.info('Update to the newest version of %s using %s' % (packageName, updatePath)) else: Console.info(Console.colorize(Console.colorize('Did NOT find installation', "red"), "bold")) Console.info('Install the newest version of %s using %s' % (packageName, installPath)) Console.outdent()
def __resolve(project): name = project.getName() # List of required projects Console.info("Getting requirements of %s...", Console.colorize(name, "bold")) Console.indent() requires = project.getRequires(checkoutDirectory, updateRepositories) Console.outdent() if not requires: return Console.debug("Processing %s requirements...", len(requires)) Console.indent() # Adding all project in reverse order. # Adding all local ones first before going down to their requirements childProjects = [] for requiredProject in reversed(requires): requiredName = requiredProject.getName() if requiredName not in names: Console.debug("Adding: %s %s (via %s)", requiredName, requiredProject.version, project.getName()) names[requiredName] = True result.append(requiredProject) childProjects.append(requiredProject) elif not requiredProject in result: Console.debug("Blocking: %s %s (via %s)", requiredName, requiredProject.version, project.getName()) requiredProject.pause() # Process all requirements of added projects for requiredProject in reversed(childProjects): if requiredProject.hasRequires(): __resolve(requiredProject) Console.outdent()
def __getPermutatedTree(self, permutation=None): """ Returns a permutated tree: a copy of the original tree where conditions based on the given permutation are resolved. """ if permutation is None: return self.__getTree() permutation = self.filterPermutation(permutation) field = "style:permutated[%s]-%s" % (self.id, permutation) tree = self.project.getCache().read(field, self.mtime) if not tree: tree = copy.deepcopy(self.__getTree()) Console.info("Permutating stylesheet %s...", Console.colorize(self.id, "bold")) Console.indent() Engine.permutateTree(tree, permutation) Console.outdent() self.project.getCache().store(field, tree, self.mtime, True) return tree
def help(): """Shows this help screen.""" import jasy jasy.info() print(Console.colorize(Console.colorize("Usage", "underline"), "bold")) print(" $ jasy [<options...>] <task1> [<args...>] [<task2> [<args...>]]") print() print(Console.colorize(Console.colorize("Global Options", "underline"), "bold")) Task.getOptions().printOptions() print() print(Console.colorize(Console.colorize("Available Tasks", "underline"), "bold")) Task.printTasks() print()
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 massFilePatcher(path, data): # Convert method with access to local data def convertPlaceholder(mo): field = mo.group(1) value = data.get(field) # Verify that None means missing if value is None and not data.has(field): raise ValueError('No value for placeholder "%s"' % field) # Requires value being a string return str(value) # Patching files recursively Console.info("Patching files...") Console.indent() for dirPath, dirNames, fileNames in os.walk(path): relpath = os.path.relpath(dirPath, path) # Filter dotted directories like .git, .bzr, .hg, .svn, etc. for dirname in dirNames: if dirname.startswith("."): dirNames.remove(dirname) for fileName in fileNames: filePath = os.path.join(dirPath, fileName) fileRel = os.path.normpath(os.path.join(relpath, fileName)) Console.debug("Processing: %s..." % fileRel) fileHandle = open(filePath, "r", encoding="utf-8", errors="surrogateescape") fileContent = [] # Parse file line by line to detect binary files early and omit # fully loading them into memory try: isBinary = False for line in fileHandle: if '\0' in line: isBinary = True break else: fileContent.append(line) if isBinary: Console.debug("Ignoring binary file: %s", fileRel) continue except UnicodeDecodeError as ex: Console.warn("Can't process file: %s: %s", fileRel, ex) continue fileContent = "".join(fileContent) # Update content with available data try: resultContent = fieldPattern.sub(convertPlaceholder, fileContent) except ValueError as ex: Console.warn("Unable to process file %s: %s!", fileRel, ex) continue # Only write file if there where any changes applied if resultContent != fileContent: Console.info("Updating: %s...", Console.colorize(fileRel, "bold")) fileHandle = open(filePath, "w", encoding="utf-8", errors="surrogateescape") fileHandle.write(resultContent) fileHandle.close() Console.outdent()
def create(name="myproject", origin=None, originVersion=None, skeleton=None, destination=None, session=None, **argv): """ Creates a new project from a defined skeleton or an existing project's root directory (only if there is a jasycreate config file). :param name: The name of the new created project :type name: string :param origin: Path or git url to the base project :type origin: string :param originVersion: Version of the base project from wich will be created. :type originVersion: string :param skeleton: Name of a defined skeleton. None for creating from root :type skeleton: string :param destination: Destination path for the new created project :type destination: string :param session: An optional session to use as origin project :type session: object """ if not validProjectName.match(name): raise UserError( "Invalid project name: %s (Use lowercase characters and numbers only for broadest compabibility)" % name) # # Initial Checks # # Figuring out destination folder if destination is None: destination = name destinationPath = os.path.abspath(os.path.expanduser(destination)) if os.path.exists(destinationPath): raise UserError( "Cannot create project %s in %s. File or folder exists!" % (name, destinationPath)) # Origin can be either: # 1) None, which means a skeleton from the current main project # 2) An repository URL # 3) A project name known inside the current session # 4) Relative or absolute folder path originPath = None originName = None if origin is None: originProject = session and session.getMain() if originProject is None: raise UserError( "Auto discovery failed! No Jasy projects registered!") originPath = originProject.getPath() originName = originProject.getName() originRevision = None elif Repository.isUrl(origin): Console.info("Using remote skeleton") tempDirectory = tempfile.TemporaryDirectory() originPath = os.path.join(tempDirectory.name, "clone") originUrl = origin Console.indent() originRevision = Repository.update(originUrl, originVersion, originPath) Console.outdent() if originRevision is None: raise UserError("Could not clone origin repository!") Console.debug("Cloned revision: %s" % originRevision) if findConfig(os.path.join( originPath, "jasycreate")) or os.path.isfile( os.path.join(originPath, "jasycreate.py")): originProject = None else: originProject = getProjectFromPath(originPath, session) originName = originProject.getName() else: originProject = session and session.getProjectByName(origin) originVersion = None originRevision = None if originProject is not None: originPath = originProject.getPath() originName = origin elif os.path.isdir(origin): originPath = origin if findConfig(os.path.join( originPath, "jasycreate")) or os.path.isfile( os.path.join(originPath, "jasycreate.py")): originProject = None else: originProject = getProjectFromPath(originPath, session) originName = originProject.getName() else: raise UserError("Invalid value for origin: %s" % origin) # Figure out the skeleton root folder if originProject is not None: skeletonDir = os.path.join( originPath, originProject.getConfigValue("skeletonDir", "skeleton")) else: skeletonDir = originPath if not os.path.isdir(skeletonDir): raise UserError('The project %s offers no skeletons!' % originName) # For convenience: Use first skeleton in skeleton folder if no other selection was applied if skeleton is None: if originProject is not None: skeleton = getFirstSubFolder(skeletonDir) else: skeleton = skeletonDir # Finally we have the skeleton path (the root folder to copy for our app) skeletonPath = os.path.join(skeletonDir, skeleton) if not os.path.isdir(skeletonPath): raise UserError('Skeleton %s does not exist in project "%s"' % (skeleton, originName)) # # Actual Work # # Prechecks done if originName: Console.info('Creating %s from %s %s...', Console.colorize(name, "bold"), Console.colorize(skeleton + " @", "bold"), Console.colorize(originName, "magenta")) else: Console.info('Creating %s from %s...', Console.colorize(name, "bold"), Console.colorize(skeleton, "bold")) Console.debug('Skeleton: %s', Console.colorize(skeletonPath, "grey")) Console.debug('Destination: %s', Console.colorize(destinationPath, "grey")) # Copying files to destination Console.info("Copying files...") shutil.copytree(skeletonPath, destinationPath) Console.debug("Files were copied successfully.") # Close origin project if originProject: originProject.close() # Change to directory before continuing os.chdir(destinationPath) # Create configuration file from question configs and custom scripts Console.info("Starting configuration...") config = Config() config.set("name", name) config.set("jasy.version", jasy.__version__) if originName: config.set("origin.name", originName) config.set("origin.version", originVersion) config.set("origin.revision", originRevision) config.set("origin.skeleton", os.path.basename(skeletonPath)) config.injectValues(**argv) if originProject is not None: config.readQuestions("jasycreate", optional=True) config.executeScript("jasycreate.py", optional=True) # Do actual replacement of placeholders massFilePatcher(destinationPath, config) Console.debug("Files were patched successfully.") # Done Console.info('Your application %s was created successfully!', Console.colorize(name, "bold"))
def doCompleteDoctor(): """Checks for uninstalled or too old versions of requirements and gives a complete output""" Console.header("Doctor") dists = [dist for dist in pip.get_installed_distributions()] keys = [dist.key for dist in pip.get_installed_distributions()] versions = {} for dist in dists: versions[dist.key] = dist.version def checkSingleInstallation(keys, versions, packageName, minVersion, installPath, updatePath): Console.info('%s:' % packageName) Console.indent() if packageName.lower() in keys: Console.info(Console.colorize('Found installation', "green")) if LooseVersion(minVersion) > LooseVersion("0.0"): if LooseVersion(versions[packageName.lower()]) >= LooseVersion( minVersion): Console.info( Console.colorize( 'Version is OK (needed: %s installed: %s)' % (minVersion, versions[packageName.lower()]), "green")) else: Console.info( Console.colorize( Console.colorize( '- Version is NOT OK (needed: %s installed: %s)' % (minVersion, versions[packageName.lower()]), "red"), "bold")) Console.info( 'Update to the newest version of %s using %s' % (packageName, updatePath)) else: Console.info( Console.colorize( Console.colorize('Did NOT find installation', "red"), "bold")) Console.info('Install the newest version of %s using %s' % (packageName, installPath)) Console.outdent() # Required packages Console.info(Console.colorize("Required Packages:", "bold")) Console.indent() for entry in needs: checkSingleInstallation(keys, versions, entry["packageName"], entry["minVersion"], entry["installPath"], entry["updatePath"]) Console.outdent() # Optional packages Console.info("") Console.info(Console.colorize("Optional Packages:", "bold")) Console.indent() for entry in optionals: checkSingleInstallation(keys, versions, entry["packageName"], entry["minVersion"], entry["installPath"], entry["updatePath"]) Console.outdent()