def __rebuildAsAssignment(node, firstVarStatement): """Rebuilds the items of a var statement into a assignment list and moves declarations to the given var statement.""" assignment = Node.Node(node.tokenizer, "semicolon") assignmentList = Node.Node(node.tokenizer, "comma") assignment.append(assignmentList, "expression") # Casting to list() creates a copy during the process (keeps loop stable) for child in list(node): if hasattr(child, "name"): # Cleanup initializer and move to assignment if hasattr(child, "initializer"): assign = __createSimpleAssignment(child.name, child.initializer) assignmentList.append(assign) firstVarStatement.append(child) else: # JS 1.7 Destructing Expression for identifier in child.names: firstVarStatement.append(__createDeclaration(identifier.value)) if hasattr(child, "initializer"): assign = __createMultiAssignment(child.names, child.initializer) assignmentList.append(assign) node.remove(child) # Patch parent node to contain assignment instead of declaration if len(assignmentList) > 0: node.parent.replace(node, assignment) # Special process for "for-in" loops # It is OK to be second because of assignments are not allowed at # all in for-in loops and so the first if basically does nothing # for these kind of statements. elif getattr(node, "rel", None) == "iterator": if hasattr(child, "name"): node.parent.replace(node, __createIdentifier(child.name)) else: # JS 1.7 Destructing Expressions node.parent.replace(node, child.names) # Edge case. Not yet found if this happen realistically else: if hasattr(node, "rel"): Console.warn("Remove related node (%s) from parent: %s" % (node.rel, node)) node.parent.remove(node) # Minor post-cleanup. Remove useless comma statement when only one expression is the result if len(assignmentList) == 1: assignment.replace(assignmentList, assignmentList[0])
def Charset(tokenizer, staticContext): tokenType = tokenizer.get() Console.warn("CSS @charset %s ", tokenType) if tokenType != "string": raise ParseError("Invalid @charset declaration. Requires the encoding being a string!", tokenizer) encoding = tokenizer.token.value if encoding.lower() != "utf-8": raise ParseError("Jasy is not able to process non UTF-8 stylesheets!", tokenizer) Console.warn("Found unnecessary @charset definition for encoding %s", encoding) return Node.Node(tokenizer, "block")
def default(self, *args, **query): """ This method returns the content of existing files on the file system. Query string might be used for cache busting and are otherwise ignored. """ # Append special header to all responses cherrypy.response.headers["X-Jasy-Version"] = jasyVersion # Enable cross domain access to this server enableCrossDomain() # When it's a file name in the local folder... load it if args: path = os.path.join(*args) else: path = "index.html" path = os.path.join(self.root, path) # Check for existance first if os.path.isfile(path): if self.enableDebug: Console.info("Serving file: %s", path) # Default content type to autodetection by Python mimetype API contentType = None # Support overriding by extensions extension = os.path.splitext(path)[1] if extension: extension = extension.lower()[1:] if extension in self.mimeTypes: contentType = self.mimeTypes[extension] + "; charset=" + locale.getpreferredencoding() return cherrypy.lib.static.serve_file(os.path.abspath(path), content_type=contentType) # Otherwise return a classic 404 else: if self.enableDebug: Console.warn("File at location %s not found at %s!", path, os.path.abspath(path)) raise cherrypy.NotFound(path)
def extractSummary(text): try: text = stripMarkup.sub("", newlineMatcher.sub(" ", text)) matched = paragraphExtract.match(text) except TypeError: matched = None if matched: summary = matched.group(1) if summary is not None: if not summary.endswith((".", "!", "?")): summary = summary.strip() + "." return summary else: Console.warn("Unable to extract summary for: %s", text) return None
def open(self): """Opens a cache file in the given path.""" try: self.__shelve = shelve.open(self.__file, flag="c") storedVersion = jasy.core.Util.getKey(self.__shelve, "jasy-version") storedHost = jasy.core.Util.getKey(self.__shelve, "jasy-host") if storedVersion == jasy.__version__ and storedHost == hostId: return if storedVersion is not None or storedHost is not None: Console.debug("Jasy version or host has been changed. Recreating cache...") self.clear() self.__shelve = shelve.open(self.__file, flag="n") self.__shelve["jasy-version"] = jasy.__version__ self.__shelve["jasy-host"] = hostId except dbm.error as dbmerror: errno = None try: errno = dbmerror.errno except: pass if errno is 35: raise IOError("Cache file is locked by another process!") elif "type could not be determined" in str(dbmerror): Console.error("Could not detect cache file format: %s" % self.__file) Console.warn("Recreating cache database...") self.clear() elif "module is not available" in str(dbmerror): Console.error("Unsupported cache file format: %s" % self.__file) Console.warn("Recreating cache database...") self.clear() else: raise dbmerror
def getDependencies(self, permutation=None, items=None, fields=None, warnings=True): """Returns a set of dependencies seen through the given list of known items (ignoring all unknown items in original set).""" meta = self.getMetaData(permutation) result = set() # Manually defined names/classes for entry in meta.requires: if entry == self.id: pass elif entry in items and items[entry].kind == "jasy.Style": result.add(items[entry]) elif "*" in entry: reobj = re.compile(fnmatch.translate(entry)) for itemId in items: if itemId != self.id: if reobj.match(itemId): result.add(items[itemId]) elif warnings: Console.warn("Missing item for require command: %s in %s", entry, self.id) return result
def getBreaks(self, permutation=None, items=None, warnings=True): """ Returns all down-priorized dependencies. These are dependencies which are required to make the item work, but are not required being available before the current item. """ meta = self.getMetaData(permutation) result = set() for entry in meta.breaks: if entry == self.id: pass elif entry in items and items[entry].kind == "jasy.Style": result.add(items[entry]) elif "*" in entry: reobj = re.compile(fnmatch.translate(entry)) for itemId in items: if itemId != self.id: if reobj.match(itemId): result.add(items[itemId]) elif warnings: Console.warn("Missing item for break command: %s in %s", entry, self.id) return result
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 __extend(node): """ Finds extend requests for mixins aka. - mixins calls without params - simple variables in a block For all found extend requests it detects the flattened selector and appends the selector section of the extendable mixin accordingly. After that it removes the original mixin request. """ modified = 0 for child in reversed(list(node)): # Ignore all mixin declarations. Can't operate inside them. # For these things to work we have to wait for the include mechanics to resolve them first # (which actually just remove these mixin declarations though) if child is not None: modified += __extend(child) if isExtendCall(node): name = node.name Console.debug("Extend request to mixin %s at: %s", name, node.line) Console.indent() mixin = __findMixin(node.parent, name) if not mixin: raise Exception("Could not find mixin %s as required by extend request at line %s" % (node.name, node.line)) Console.debug("Found matching mixin declaration at line: %s", mixin.line) selector, media, supports = Util.combineSelector(node.parent, stop=mixin.parent) # There is no possibility to handle this in a series of CSS selectors. This is why # we have to use an include like approach instead of extend to correctly deal # with the situation. This should work well, but is not as efficient regarding # output file size. if media or supports: Console.warn("Extending inside a @media/@support structure behaves like including (larger result size): %s %s + %s", media, supports, ", ".join(selector)) replacements = __resolveMixin(mixin, None) Console.debug("Replacing call %s at line %s with mixin from line %s" % (name, node.line, replacements.line)) # Reverse inject all children of that block # at the same position as the original call parent = node.parent pos = parent.index(node) parent.insertAll(pos, replacements) elif selector: Console.debug("Extending selector of mixin by: %s", ", ".join(selector)) if hasattr(mixin, "selector"): # We iterate from in inverse mode, so add new selectors to the front mixin.selector[0:0] = selector else: mixin.selector = selector virtualBlock = Node.Node(type="block") __extendContent(mixin.rules, node, virtualBlock, mixin) if len(virtualBlock) > 0: callSelector, callMedia, callSupports = Util.combineSelector(node) if callSelector: virtualSelector = Node.Node(type="selector") virtualSelector.name = callSelector if callMedia: virtualMedia = Node.Node(type="media") virtualMedia.name = callMedia if callSupports: virtualSupports = Node.Node(type="supports") virtualSupports.name = callSupports if callSelector: virtualSelector.append(virtualBlock, "rules") elif callMedia: virtualMedia.append(virtualBlock, "rules") elif callSupports: virtualSupports.append(virtualBlock, "rules") if callSupports: virtualTop = virtualSupports elif callMedia: virtualTop = virtualMedia elif callSelector: virtualTop = virtualSelector pos = mixin.parent.index(mixin) mixin.parent.insert(pos + 1, virtualTop) node.parent.remove(node) Console.outdent() modified += 1 return modified
def __cleanup(node): """ Reprocesses JavaScript to remove dead paths """ optimized = False # Process from inside to outside for child in reversed(node): # None children are allowed sometimes e.g. during array_init like [1,2,,,7,8] if child != None: if __cleanup(child): optimized = True # Optimize if cases if node.type == "if": check = __checkCondition(node.condition) if check is not None: optimized = True Console.debug("Optimizing if/else at line %s", node.line) if check is True: node.parent.replace(node, node.thenPart) elif check is False: if hasattr(node, "elsePart"): node.parent.replace(node, node.elsePart) else: node.parent.remove(node) # Optimize hook statement if node.type == "hook": check = __checkCondition(node[0]) if check is not None: Console.debug("Optimizing hook at line %s", node.line) optimized = True if check is True: node.parent.replace(node, node[1]) elif check is False: node.parent.replace(node, node[2]) # Optimize switch statement if node.type == "switch" and node.discriminant.type in ("string", "number"): discriminant = node.discriminant.value fallback = None matcher = None allowed = ["default", "case"] for child in node: # Require that every case block ends with a break (no fall-throughs) if child.type == "case": block = child[len(child)-1] if len(block) == 0 or block[len(block)-1].type != "break": Console.warn("Could not optimize switch statement (at line %s) because of fallthrough break statement.", node.line) return False if child.type == "default": fallback = child.statements elif child.type == "case" and child.label.value == discriminant: matcher = child.statements # Remove break statement matcher.pop() if matcher or fallback: if not matcher: matcher = fallback node.parent.replace(node, matcher) Console.debug("Optimizing switch at line %s", node.line) optimized = True return optimized
def __process(self, apiData, classFilter=None, internals=False, privates=False, printErrors=True, highlightCode=True): knownClasses = set(list(apiData)) # # Attaching Links to Source Code (Lines) # Building Documentation Summaries # Console.info("Adding Source Links...") for className in apiData: classApi = apiData[className] constructData = getattr(classApi, "construct", None) if constructData is not None: if "line" in constructData: constructData["sourceLink"] = "source:%s~%s" % (className, constructData["line"]) for section in ("properties", "events", "statics", "members"): sectionData = getattr(classApi, section, None) if sectionData is not None: for name in sectionData: if "line" in sectionData[name]: sectionData[name]["sourceLink"] = "source:%s~%s" % (className, sectionData[name]["line"]) # # Including Mixins / IncludedBy # Console.info("Resolving Mixins...") Console.indent() # Just used temporary to keep track of which classes are merged mergedClasses = set() def getApi(className): classApi = apiData[className] if className in mergedClasses: return classApi classIncludes = getattr(classApi, "includes", None) if classIncludes: for mixinName in classIncludes: if not mixinName in apiData: Console.error("Invalid mixin %s in class %s", className, mixinName) continue mixinApi = apiData[mixinName] if not hasattr(mixinApi, "includedBy"): mixinApi.includedBy = set() mixinApi.includedBy.add(className) mergeMixin(className, mixinName, classApi, getApi(mixinName)) mergedClasses.add(className) return classApi for className in apiData: apiData[className] = getApi(className) Console.outdent() # # Checking links # Console.info("Checking Links...") additionalTypes = ("Call", "Identifier", "Map", "Integer", "Node", "Element") def checkInternalLink(link, className): match = internalLinkParse.match(link) if not match: return 'Invalid link "#%s"' % link if match.group(3) is not None: className = match.group(3) if not className in knownClasses and not className in apiData: return 'Invalid class in link "#%s"' % link # Accept all section/item values for named classes, # as it might be pretty complicated to verify this here. if not className in apiData: return True classApi = apiData[className] sectionName = match.group(2) itemName = match.group(5) if itemName is None: return True if sectionName is not None: if not sectionName in linkMap: return 'Invalid section in link "#%s"' % link section = getattr(classApi, linkMap[sectionName], None) if section is None: return 'Invalid section in link "#%s"' % link else: if itemName in section: return True return 'Invalid item in link "#%s"' % link for sectionName in ("statics", "members", "properties", "events"): section = getattr(classApi, sectionName, None) if section and itemName in section: return True return 'Invalid item link "#%s"' % link def checkLinksInItem(item): # Process types if "type" in item: if item["type"] == "Function": # Check param types if "params" in item: for paramName in item["params"]: paramEntry = item["params"][paramName] if "type" in paramEntry: for paramTypeEntry in paramEntry["type"]: if not paramTypeEntry["name"] in knownClasses and not paramTypeEntry["name"] in additionalTypes and not ("builtin" in paramTypeEntry or "pseudo" in paramTypeEntry): item["errornous"] = True Console.error('Invalid param type "%s" in %s' % (paramTypeEntry["name"], className)) if not "pseudo" in paramTypeEntry and paramTypeEntry["name"] in knownClasses: paramTypeEntry["linkable"] = True # Check return types if "returns" in item: for returnTypeEntry in item["returns"]: if not returnTypeEntry["name"] in knownClasses and not returnTypeEntry["name"] in additionalTypes and not ("builtin" in returnTypeEntry or "pseudo" in returnTypeEntry): item["errornous"] = True Console.error('Invalid return type "%s" in %s' % (returnTypeEntry["name"], className)) if not "pseudo" in returnTypeEntry and returnTypeEntry["name"] in knownClasses: returnTypeEntry["linkable"] = True elif not item["type"] in builtinTypes and not item["type"] in pseudoTypes and not item["type"] in additionalTypes: item["errornous"] = True Console.error('Invalid type "%s" in %s' % (item["type"], className)) # Process doc if "doc" in item: def processInternalLink(match): linkUrl = match.group(2) if linkUrl.startswith("#"): linkCheck = checkInternalLink(linkUrl[1:], className) if linkCheck is not True: item["errornous"] = True if sectionName: Console.error("%s in %s:%s~%s" % (linkCheck, sectionName, className, name)) else: Console.error("%s in %s" % (linkCheck, className)) linkExtract.sub(processInternalLink, item["doc"]) Console.indent() # Process APIs for className in apiData: classApi = apiData[className] sectionName = None constructData = getattr(classApi, "construct", None) if constructData is not None: checkLinksInItem(constructData) for sectionName in ("properties", "events", "statics", "members"): section = getattr(classApi, sectionName, None) if section is not None: for name in section: checkLinksInItem(section[name]) Console.outdent() # # Filter Internals/Privates # Console.info("Filtering Items...") def isVisible(entry): if "visibility" in entry: visibility = entry["visibility"] if visibility == "private" and not privates: return False if visibility == "internal" and not internals: return False return True def filterInternalsPrivates(classApi, field): data = getattr(classApi, field, None) if data: for name in list(data): if not isVisible(data[name]): del data[name] for className in apiData: filterInternalsPrivates(apiData[className], "statics") filterInternalsPrivates(apiData[className], "members") # # Connection Interfaces / ImplementedBy # Console.info("Connecting Interfaces...") Console.indent() for className in apiData: classApi = getApi(className) if not hasattr(classApi, "main"): continue classType = classApi.main["type"] if classType == "core.Class": classImplements = getattr(classApi, "implements", None) if classImplements: for interfaceName in classImplements: interfaceApi = apiData[interfaceName] implementedBy = getattr(interfaceApi, "implementedBy", None) if not implementedBy: implementedBy = interfaceApi.implementedBy = [] implementedBy.append(className) connectInterface(className, interfaceName, classApi, interfaceApi) Console.outdent() # # Merging Named Classes # Console.info("Merging Named Classes...") Console.indent() for className in list(apiData): classApi = apiData[className] destName = classApi.main["name"] if destName is not None and destName != className: Console.debug("Extending class %s with %s", destName, className) if destName in apiData: destApi = apiData[destName] destApi.main["from"].append(className) else: destApi = apiData[destName] = Data.ApiData(destName, highlight=highlightCode) destApi.main = { "type" : "Extend", "name" : destName, "from" : [className] } # If there is a "main" tag found in the class use its API description if "tags" in classApi.main and classApi.main["tags"] is not None and "main" in classApi.main["tags"]: if "doc" in classApi.main: destApi.main["doc"] = classApi.main["doc"] classApi.main["extension"] = True # Read existing data construct = getattr(classApi, "construct", None) statics = getattr(classApi, "statics", None) members = getattr(classApi, "members", None) if construct is not None: if hasattr(destApi, "construct"): Console.warn("Overriding constructor in extension %s by %s", destName, className) destApi.construct = copy.copy(construct) if statics is not None: if not hasattr(destApi, "statics"): destApi.statics = {} for staticName in statics: destApi.statics[staticName] = copy.copy(statics[staticName]) destApi.statics[staticName]["from"] = className destApi.statics[staticName]["fromLink"] = "static:%s~%s" % (className, staticName) if members is not None: if not hasattr(destApi, "members"): destApi.members = {} for memberName in members: destApi.members[memberName] = copy.copy(members[memberName]) destApi.members[memberName]["from"] = className destApi.members[memberName]["fromLink"] = "member:%s~%s" % (className, memberName) Console.outdent() # # Connecting Uses / UsedBy # Console.info("Collecting Use Patterns...") # This matches all uses with the known classes and only keeps them if matched allClasses = set(list(apiData)) for className in apiData: uses = apiData[className].uses # Rebuild use list cleanUses = set() for use in uses: if use != className and use in allClasses: cleanUses.add(use) useEntry = apiData[use] if not hasattr(useEntry, "usedBy"): useEntry.usedBy = set() useEntry.usedBy.add(className) apiData[className].uses = cleanUses # # Collecting errors # Console.info("Collecting Errors...") Console.indent() for className in sorted(apiData): classApi = apiData[className] errors = [] if isErrornous(classApi.main): errors.append({ "kind": "Main", "name": None, "line": 1 }) if hasattr(classApi, "construct"): if isErrornous(classApi.construct): errors.append({ "kind": "Constructor", "name": None, "line": classApi.construct["line"] }) for section in ("statics", "members", "properties", "events"): items = getattr(classApi, section, {}) for itemName in items: item = items[itemName] if isErrornous(item): errors.append({ "kind": itemMap[section], "name": itemName, "line": item["line"] }) if errors: if printErrors: Console.warn("Found errors in %s", className) errorsSorted = sorted(errors, key=lambda entry: entry["line"]) if printErrors: Console.indent() for entry in errorsSorted: if entry["name"]: Console.warn("%s: %s (line %s)", entry["kind"], entry["name"], entry["line"]) else: Console.warn("%s (line %s)", entry["kind"], entry["line"]) Console.outdent() classApi.errors = errorsSorted Console.outdent() # # Building Search Index # Console.info("Building Search Index...") search = {} def addSearch(classApi, field): data = getattr(classApi, field, None) if data: for name in data: if not name in search: search[name] = set() search[name].add(className) for className in apiData: classApi = apiData[className] addSearch(classApi, "statics") addSearch(classApi, "members") addSearch(classApi, "properties") addSearch(classApi, "events") # # Post Process (dict to sorted list) # Console.info("Post Processing Data...") for className in sorted(apiData): classApi = apiData[className] convertTags(classApi.main) construct = getattr(classApi, "construct", None) if construct: convertFunction(construct) convertTags(construct) for section in ("statics", "members", "properties", "events"): items = getattr(classApi, section, None) if items: sortedList = [] for itemName in sorted(items): item = items[itemName] item["name"] = itemName if "type" in item and item["type"] == "Function": convertFunction(item) convertTags(item) sortedList.append(item) setattr(classApi, section, sortedList) # # Collecting Package Docs # Console.info("Collecting Package Docs...") Console.indent() # Inject existing package docs into api data for project in self.__session.getProjects(): docs = project.getDocs() for packageName in docs: if self.__isIncluded(packageName, classFilter): Console.debug("Creating package documentation %s", packageName) apiData[packageName] = docs[packageName].getApi() # Fill missing package docs for className in sorted(apiData): splits = className.split(".") packageName = splits[0] for split in splits[1:]: if not packageName in apiData: Console.warn("Missing package documentation %s", packageName) apiData[packageName] = Data.ApiData(packageName, highlight=highlightCode) apiData[packageName].main = { "type" : "Package", "name" : packageName } packageName = "%s.%s" % (packageName, split) # Now register all classes in their parent namespace/package for className in sorted(apiData): splits = className.split(".") packageName = ".".join(splits[:-1]) if packageName: package = apiData[packageName] # debug("- Registering class %s in parent %s", className, packageName) entry = { "name" : splits[-1], "link" : className, } classMain = apiData[className].main if "doc" in classMain and classMain["doc"]: summary = Text.extractSummary(classMain["doc"]) if summary: entry["summary"] = summary if "type" in classMain and classMain["type"]: entry["type"] = classMain["type"] if not hasattr(package, "content"): package.content = [entry] else: package.content.append(entry) Console.outdent() # # Writing API Index # Console.debug("Building Index...") index = {} for className in sorted(apiData): classApi = apiData[className] mainInfo = classApi.main # Create structure for className current = index for split in className.split("."): if not split in current: current[split] = {} current = current[split] # Store current type current["$type"] = mainInfo["type"] # Keep information if if hasattr(classApi, "content"): current["$content"] = True # # Return # return apiData, index, search
def __cleanup(node): """Reprocesses JavaScript to remove dead paths.""" optimized = False # Process from inside to outside for child in reversed(node): # None children are allowed sometimes e.g. during array_init like [1,2,,,7,8] if child is not None: if __cleanup(child): optimized = True # Optimize if cases if node.type == "if": check = __checkCondition(node.condition) if check is not None: optimized = True Console.debug("Optimizing if/else at line %s", node.line) if check is True: node.parent.replace(node, node.thenPart) elif check is False: if hasattr(node, "elsePart"): node.parent.replace(node, node.elsePart) else: node.parent.remove(node) # Optimize hook statement if node.type == "hook": check = __checkCondition(node[0]) if check is not None: Console.debug("Optimizing hook at line %s", node.line) optimized = True if check is True: node.parent.replace(node, node[1]) elif check is False: node.parent.replace(node, node[2]) # Optimize switch statement if node.type == "switch" and node.discriminant.type in ("string", "number"): discriminant = node.discriminant.value fallback = None matcher = None allowed = ["default", "case"] for child in node: # Require that every case block ends with a break (no fall-throughs) if child.type == "case": block = child[len(child) - 1] if len(block) == 0 or block[len(block) - 1].type != "break": Console.warn( "Could not optimize switch statement (at line %s) because of fallthrough break statement.", node.line) return False if child.type == "default": fallback = child.statements elif child.type == "case" and child.label.value == discriminant: matcher = child.statements # Remove break statement matcher.pop() if matcher or fallback: if not matcher: matcher = fallback node.parent.replace(node, matcher) Console.debug("Optimizing switch at line %s", node.line) optimized = True return optimized
def warn(self, message, line): Console.warn("%s at line %s in %s" % (message, line, self.id))
def __extend(node, scanMixins=False): """ Finds extend requests for mixins aka - mixins calls without params - simple variables in a block For all found extend requests it detects the flattened selector and appends the selector section of the extendable mixin accordingly. After that it removes the original mixin request. """ modified = 0 for child in reversed(node): # Ignore all mixin declarations. Can't operate inside them. # For these things to work we have to wait for the include mechanics to resolve them first # (which actually just remove these mixin declarations though) if child is not None and (scanMixins or child.type != "mixin"): modified += __extend(child) if isExtendCall(node): name = node.name Console.debug("Extend request to mixin %s at: %s", name, node.line) Console.indent() mixin = __findMixin(node.parent, name) if not mixin: raise Exception("Could not find mixin %s as required by extend request at line %s" % (node.name, node.line)) Console.debug("Found matching mixin declaration at line: %s", mixin.line) # selector, media = Util.combineSelector(node.parent, stop=node.parent) selector, media = Util.combineSelector(node.parent) if media: Console.warn("Extending inside media query behaves like including (less efficient): %s + %s", media, ", ".join(selector)) replacements = __resolveMixin(mixin, None) Console.debug("Replacing call %s at line %s with mixin from line %s" % (name, node.line, replacements.line)) # 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) else: Console.debug("Extending selector of mixin by: %s", ", ".join(selector)) if hasattr(mixin, "selector"): # We iterate from in inverse mode, so add new selectors to the front mixin.selector[0:0] = selector else: mixin.selector = selector virtualBlock = Node.Node(type="block") __extendContent(mixin.rules, node, virtualBlock, mixin) if len(virtualBlock) > 0: callSelector, callMedia = Util.combineSelector(node) if callSelector: virtualSelector = Node.Node(type="selector") virtualSelector.name = callSelector if callMedia: virtualMedia = Node.Node(type="media") virtualMedia.name = callMedia if callSelector: virtualSelector.append(virtualBlock, "rules") elif callMedia: virtualMedia.append(virtualBlock, "rules") if callMedia: virtualTop = virtualMedia elif callSelector: virtualTop = virtualSelector pos = mixin.parent.index(mixin) mixin.parent.insert(pos+1, virtualTop) node.parent.remove(node) Console.outdent() modified += 1 return modified
def process(url, start=0, fetch=50): """ Main processing engine """ pos = start # End will be updated during each request with incoming data end = pos + fetch Console.header("Tumblr Import") Console.info("Importing data...") Console.indent() while pos < end: Console.info("Requesting %s-%s of %s" % (pos, pos+fetch-1, end)) response = requests.get(url % (pos, fetch)) if response.status_code != 200: raise Exception("Error during communication with Tumblr: %s" % r.status) tree = ElementTree.fromstring(response.content) # This element contains all posts allPosts = tree.find("posts") # Update end pointer end = int(allPosts.get("total")) # Iterate trough all posts for post in allPosts: postType = post.get("type") postTimeStamp = post.get("unix-timestamp") postExportDate = str(datetime.datetime.fromtimestamp(int(postTimeStamp))) postSlug = post.get("slug") postFormat = post.get("format") postDateOnly = postExportDate[0:postExportDate.find(" ")] postFileName = "%s-%s" % (postDateOnly, postSlug) if postType == "quote": quoteText = post.find("quote-text").text quoteComment = post.find("quote-source").text # Post-process quoteText = markdownify.markdownify("<blockquote>" + quoteText + "</blockquote>").rstrip("\n").lstrip("\n") quoteComment = markdownify.markdownify(quoteComment).rstrip("\n") fileContent = quoteTemplate % (postSlug, postExportDate, quoteText + "\n\n" + quoteComment) elif postType == "photo": photoText = post.find("photo-caption").text try: photoLinkUrl = post.find("photo-link-url").text except: photoLinkUrl = None photoUrl = post.find("photo-url").text # Post-process photoText = markdownify.markdownify(photoText).rstrip("\n") # Downloading image photoResponse = requests.get(photoUrl, allow_redirects=True) if photoResponse.status_code != 200: Console.error("Unable to load photo. Status: %s; URL: %s", photoResponse.status_code, photoUrl) continue # Build extension based on response headers (safer than using file extension) photoType = photoResponse.headers["content-type"] if "png" in photoType: photoExtension = ".png" elif "jpeg" in photoType or "jpg" in photoType: photoExtension = ".jpeg" elif "gif" in photoType: photoExtension = ".gif" else: Console.error("Unknown photo format: %s; Status: %s; URL: %s", photoType, photoResponse.status_code, photoUrl) continue # Generating checksum photoHash = hashlib.sha1(photoResponse.content).hexdigest() # Generate file name and path from existing data photoFileName = "%s-%s-%s%s" % (postDateOnly, postSlug, photoHash[0:10], photoExtension) photoPath = os.path.join(photoFolder, photoFileName) # Do not repeatly write identical files if not os.path.exists(photoPath): photoFile = open(photoPath, "wb") photoFile.write(photoResponse.content) photoFile.close() # Generate basic image tag photoAsset = '<img src="{{@asset.url %s/%s/%s}}" alt=""/>' % (projectName, photoAssetFolder, photoFileName) # Wrap with a link when it should be link to an external site if photoLinkUrl: photoAsset = '<a href="%s">%s</a>' % (photoLinkUrl, photoAsset) fileContent = photoTemplate % (postSlug, postExportDate, photoAsset + "\n\n" + photoText) elif postType == "link": linkUrl = post.find("link-url").text try: linkText = post.find("link-text").text except: linkText = linkUrl # Post-process if linkText != linkUrl: linkText = markdownify.markdownify(linkText).rstrip("\n") fileContent = linkTemplate % (postSlug, postExportDate, "[%s](%s)" % (linkText, linkUrl)) elif postType == "video": videoCode = post.find("video-source").text videoText = post.find("video-caption").text # Post-process videoText = markdownify.markdownify(videoText).rstrip("\n") fileContent = videoTemplate % (postSlug, postExportDate, videoCode + "\n\n" + videoText) elif postType == "regular": postText = post.find("regular-body").text try: postTitle = post.find("regular-title").text except: # Ignore posts without title Console.warn("Ignoring post without title!") continue postText = markdownify.markdownify(postText).rstrip("\n") fileContent = regularTemplate % (postSlug, postExportDate, postTitle, postText) else: Console.warn("Unknown POST-TYPE: %s" % postType) print(ElementTree.dump(post)) continue # Write post file fileHandle = open(os.path.join(postFolder, postDateOnly + "-" + postType + "-" + postSlug + ".markdown"), "w") fileHandle.write(fileContent) fileHandle.close() # Update for next requests pos = pos + fetch Console.outdent() Console.info("Successfully imported")
def __outdent(self, text, indent, startLineNo): """ Outdent multi line comment text and filtering empty lines """ lines = [] # First, split up the comments lines and remove the leading indentation for lineNo, line in enumerate((indent + text).split("\n")): if line.startswith(indent): lines.append(line[len(indent):].rstrip()) elif line.strip() == "": lines.append("") else: # Only warn for doc comments, otherwise it might just be code commented # out which is sometimes formatted pretty crazy when commented out if self.variant == "doc": Console.warn( "Could not outdent doc comment at line %s in %s", startLineNo + lineNo, self.fileId) return text # Find first line with real content, then grab the one after it to get the # characters which need outdentString = "" for lineNo, line in enumerate(lines): if line != "" and line.strip() != "": matchedDocIndent = docIndentReg.match(line) if not matchedDocIndent: # As soon as we find a non doc indent like line we stop break elif matchedDocIndent.group(2) != "": # otherwise we look for content behind the indent to get the # correct real indent (with spaces) outdentString = matchedDocIndent.group(1) break lineNo += 1 # Process outdenting to all lines (remove the outdentString from the start # of the lines) if outdentString != "": lineNo = 0 outdentStringLen = len(outdentString) for lineNo, line in enumerate(lines): if len(line) <= outdentStringLen: lines[lineNo] = "" else: if not line.startswith(outdentString): # Only warn for doc comments, otherwise it might just be code # commented out which is sometimes formatted pretty crazy when # commented out if self.variant == "doc": Console.warn( "Invalid indentation in doc comment at line %s in %s", startLineNo + lineNo, self.fileId) else: lines[lineNo] = line[outdentStringLen:] # Merge final lines and remove leading and trailing new lines return "\n".join(lines).strip("\n")
def __recurser(node, table): counter = 0 # Process children for child in list(node): if child is not None: counter += __recurser(child, table) # Process all method calls if node.type == "call": funcName = None funcNameNode = None # Uses global translation method (not typical) if node[0].type == "identifier": funcNameNode = node[0] # Uses namespaced translation method. # Typically core.locale.Translation.tr() or Translation.tr() elif node[0].type == "dot" and node[0][1].type == "identifier": funcNameNode = node[0][1] # Gettext methods only at the moment funcName = funcNameNode and funcNameNode.value if funcName in translationFunctions: Console.debug("Found translation method %s in %s", funcName, node.line) Console.indent() params = node[1] # Remove marktr() calls if funcName == "marktr": node.parent.remove(node) # Verify param types elif params[0].type is not "string": # maybe something marktr() relevant being used, in this case we need to keep the call and inline the data pass # Error handling elif (funcName == "trn" or funcName == "trc") and params[1].type != "string": Console.warn("Expecting translation string to be type string: %s at line %s" % (params[1].type, params[1].line)) # Signature tr(msg, arg1, ...) elif funcName == "tr": key = params[0].value if key in table: params[0].value = table[key] counter += 1 if len(params) == 1: node.parent.replace(node, params[0]) else: replacement = __splitTemplate(params[0].value, params[1:]) if replacement: node.parent.replace(node, replacement) # Signature trc(context, msg, arg1, ...) elif funcName == "trc": key = "%s[C:%s]" % (params[1].value, params[0].value) if key in table: params[1].value = table[key] counter += 1 if len(params) == 2: node.parent.replace(node, params[1]) else: replacement = __splitTemplate(params[1].value, params[2:]) if replacement: node.parent.replace(node, replacement) # Signature trn(msgSingular, msgPlural, int, arg1, ...) elif funcName == "trn": key = "%s[N:%s]" % (params[0].value, params[1].value) if not key in table: Console.outdent() return counter counter += 1 # Use optimized trnc() method instead of trn() funcNameNode.value = "trnc" # Remove first two string parameters params.remove(params[0]) params.remove(params[0]) # Inject new object into params container = Node.Node(None, "object_init") params.insert(0, container) # Create new construction with all properties generated from the translation table for plural in table[key]: pluralEntry = Node.Node(None, "property_init") pluralEntryIdentifier = Node.Node(None, "identifier") pluralEntryIdentifier.value = plural pluralEntryValue = Node.Node(None, "string") pluralEntryValue.value = table[key][plural] pluralEntry.append(pluralEntryIdentifier) pluralEntry.append(pluralEntryValue) container.append(pluralEntry) # Replace strings with plus operations to omit complex client side string operation if len(params) > 2: for pluralEntry in container: replacement = __splitTemplate(pluralEntry[1].value, params[2:]) if replacement: pluralEntry.replace(pluralEntry[1], replacement) # When all variables have been patched in all string with placeholder # we are able to remove the whole list of placeholder values afterwards while len(params) > 2: params.pop() Console.outdent() return counter
def getDependencies(self, permutation=None, items=None, fields=None, warnings=True): """ Returns a set of dependencies seen through the given list of known classes (ignoring all unknown items in original set) and configured fields with their individual detection classes. This method also makes use of the meta data and the variable data. """ permutation = self.filterPermutation(permutation) meta = self.getMetaData(permutation) scope = self.getScopeData(permutation) result = set() # Match fields with current permutation and give detection classes # Add detection classes of fields which are accessed but not permutated # to the list of dependencies for this class. if fields: accessedFields = self.getFields() if accessedFields: for fieldName in accessedFields: if permutation is None or not permutation.has(fieldName): if fieldName in fields: result.add(fields[fieldName]) # Manually defined names/classes for name in meta.requires: if name != self.id and name in items and items[name].kind == "jasy.Script": result.add(items[name]) elif "*" in name: reobj = re.compile(fnmatch.translate(name)) for className in items: if className != self.id: if reobj.match(className): result.add(items[className]) elif warnings: Console.warn("Missing class (required): %s in %s", name, self.id) # Globally modified names (mostly relevant when working without namespaces) for name in scope.shared: if name != self.id and name in items and items[name].kind == "jasy.Script": result.add(items[name]) # Add classes from detected package access for package in scope.packages: if package in aliases: className = aliases[package] if className in items: result.add(items[className]) continue orig = package while True: if package == self.id: break elif package in items and items[package].kind == "jasy.Script": aliases[orig] = package result.add(items[package]) break else: pos = package.rfind(".") if pos == -1: break package = package[0:pos] # Manually excluded names/classes for name in meta.optionals: if name != self.id and name in items and items[name].kind == "jasy.Script": result.remove(items[name]) elif warnings: Console.warn("Missing class (optional): %s in %s", name, self.id) return result
def getDependencies(self, permutation=None, classes=None, warnings=True): """ Returns a set of dependencies seen through the given list of known classes (ignoring all unknown items in original set). This method makes use of the meta data (see core/MetaData.py) and the variable data (see parse/ScopeData.py). """ permutation = self.filterPermutation(permutation) meta = self.getMetaData(permutation) scope = self.getScopeData(permutation) result = set() # Manually defined names/classes for name in meta.requires: if name != self.id and name in classes and classes[name].kind == "class": result.add(classes[name]) elif "*" in name: reobj = re.compile(fnmatch.translate(name)) for className in classes: if className != self.id: if reobj.match(className): result.add(classes[className]) elif warnings: Console.warn("- Missing class (required): %s in %s", name, self.id) # Globally modified names (mostly relevant when working without namespaces) for name in scope.shared: if name != self.id and name in classes and classes[name].kind == "class": result.add(classes[name]) # Add classes from detected package access for package in scope.packages: if package in aliases: className = aliases[package] if className in classes: result.add(classes[className]) continue orig = package while True: if package == self.id: break elif package in classes and classes[package].kind == "class": aliases[orig] = package result.add(classes[package]) break else: pos = package.rfind(".") if pos == -1: break package = package[0:pos] # Manually excluded names/classes for name in meta.optionals: if name != self.id and name in classes and classes[name].kind == "class": result.remove(classes[name]) elif warnings: Console.warn("- Missing class (optional): %s in %s", name, self.id) return result
def __extend(node): """ Finds extend requests for mixins aka. - mixins calls without params - simple variables in a block For all found extend requests it detects the flattened selector and appends the selector section of the extendable mixin accordingly. After that it removes the original mixin request. """ modified = 0 for child in reversed(list(node)): # Ignore all mixin declarations. Can't operate inside them. # For these things to work we have to wait for the include mechanics to resolve them first # (which actually just remove these mixin declarations though) if child is not None: modified += __extend(child) if isExtendCall(node): name = node.name Console.debug("Extend request to mixin %s at: %s", name, node.line) Console.indent() mixin = __findMixin(node.parent, name) if not mixin: raise Exception( "Could not find mixin %s as required by extend request at line %s" % (node.name, node.line)) Console.debug("Found matching mixin declaration at line: %s", mixin.line) selector, media, supports = Util.combineSelector(node.parent, stop=mixin.parent) # There is no possibility to handle this in a series of CSS selectors. This is why # we have to use an include like approach instead of extend to correctly deal # with the situation. This should work well, but is not as efficient regarding # output file size. if media or supports: Console.warn( "Extending inside a @media/@support structure behaves like including (larger result size): %s %s + %s", media, supports, ", ".join(selector)) replacements = __resolveMixin(mixin, None) Console.debug( "Replacing call %s at line %s with mixin from line %s" % (name, node.line, replacements.line)) # Reverse inject all children of that block # at the same position as the original call parent = node.parent pos = parent.index(node) parent.insertAll(pos, replacements) elif selector: Console.debug("Extending selector of mixin by: %s", ", ".join(selector)) if hasattr(mixin, "selector"): # We iterate from in inverse mode, so add new selectors to the front mixin.selector[0:0] = selector else: mixin.selector = selector virtualBlock = Node.Node(type="block") __extendContent(mixin.rules, node, virtualBlock, mixin) if len(virtualBlock) > 0: callSelector, callMedia, callSupports = Util.combineSelector( node) if callSelector: virtualSelector = Node.Node(type="selector") virtualSelector.name = callSelector if callMedia: virtualMedia = Node.Node(type="media") virtualMedia.name = callMedia if callSupports: virtualSupports = Node.Node(type="supports") virtualSupports.name = callSupports if callSelector: virtualSelector.append(virtualBlock, "rules") elif callMedia: virtualMedia.append(virtualBlock, "rules") elif callSupports: virtualSupports.append(virtualBlock, "rules") if callSupports: virtualTop = virtualSupports elif callMedia: virtualTop = virtualMedia elif callSelector: virtualTop = virtualSelector pos = mixin.parent.index(mixin) mixin.parent.insert(pos + 1, virtualTop) node.parent.remove(node) Console.outdent() modified += 1 return modified
def __outdent(self, text, indent, startLineNo): """ Outdent multi line comment text and filtering empty lines """ lines = [] # First, split up the comments lines and remove the leading indentation for lineNo, line in enumerate((indent+text).split("\n")): if line.startswith(indent): lines.append(line[len(indent):].rstrip()) elif line.strip() == "": lines.append("") else: # Only warn for doc comments, otherwise it might just be code commented # out which is sometimes formatted pretty crazy when commented out if self.variant == "doc": Console.warn("Could not outdent doc comment at line %s in %s", startLineNo+lineNo, self.fileId) return text # Find first line with real content, then grab the one after it to get the # characters which need outdentString = "" for lineNo, line in enumerate(lines): if line != "" and line.strip() != "": matchedDocIndent = docIndentReg.match(line) if not matchedDocIndent: # As soon as we find a non doc indent like line we stop break elif matchedDocIndent.group(2) != "": # otherwise we look for content behind the indent to get the # correct real indent (with spaces) outdentString = matchedDocIndent.group(1) break lineNo += 1 # Process outdenting to all lines (remove the outdentString from the start # of the lines) if outdentString != "": lineNo = 0 outdentStringLen = len(outdentString) for lineNo, line in enumerate(lines): if len(line) <= outdentStringLen: lines[lineNo] = "" else: if not line.startswith(outdentString): # Only warn for doc comments, otherwise it might just be code # commented out which is sometimes formatted pretty crazy when # commented out if self.variant == "doc": Console.warn( "Invalid indentation in doc comment at line %s in %s", startLineNo+lineNo, self.fileId) else: lines[lineNo] = line[outdentStringLen:] # Merge final lines and remove leading and trailing new lines return "\n".join(lines).strip("\n")
def connectInterface(className, interfaceName, classApi, interfaceApi): Console.debug("- Connecting %s with %s", className, interfaceName) # # Properties # interfaceProperties = getattr(interfaceApi, "properties", None) if interfaceProperties: classProperties = getattr(classApi, "properties", {}) for name in interfaceProperties: if not name in classProperties: Console.warn("Class %s is missing implementation for property %s of interface %s!", className, name, interfaceName) else: # Add reference to interface if not "interface" in classProperties[name]: classProperties[name]["defined"] = [] classProperties[name]["defined"].append({ "name": interfaceName, "link": "property:%s~%s" % (interfaceName, name) }) # Copy over documentation if not "doc" in classProperties[name] and "doc" in interfaceProperties[name]: classProperties[name]["doc"] = interfaceProperties[name]["doc"] if not "summary" in classProperties[name] and "summary" in interfaceProperties[name]: classProperties[name]["summary"] = interfaceProperties[name]["summary"] if "errornous" in classProperties[name] and not "errornous" in interfaceProperties[name]: del classProperties[name]["errornous"] # Update tags with data from interface if "tags" in interfaceProperties[name]: if not "tags" in classProperties[name]: classProperties[name]["tags"] = {} safeUpdate(classProperties[name]["tags"], interfaceProperties[name]["tags"]) # # Events # interfaceEvents = getattr(interfaceApi, "events", None) if interfaceEvents: classEvents = getattr(classApi, "events", {}) for name in interfaceEvents: if not name in classEvents: Console.warn("Class %s is missing implementation for event %s of interface %s!", className, name, interfaceName) else: # Add reference to interface if not "interface" in classEvents[name]: classEvents[name]["defined"] = [] classEvents[name]["defined"].append({ "name": interfaceName, "link": "event:%s~%s" % (interfaceName, name) }) # Copy user event type and documentation from interface if not "doc" in classEvents[name] and "doc" in interfaceEvents[name]: classEvents[name]["doc"] = interfaceEvents[name]["doc"] if not "summary" in classEvents[name] and "summary" in interfaceEvents[name]: classEvents[name]["summary"] = interfaceEvents[name]["summary"] if not "type" in classEvents[name] and "type" in interfaceEvents[name]: classEvents[name]["type"] = interfaceEvents[name]["type"] if "errornous" in classEvents[name] and not "errornous" in interfaceEvents[name]: del classEvents[name]["errornous"] # Update tags with data from interface if "tags" in interfaceEvents[name]: if not "tags" in classEntry: classEvents[name]["tags"] = {} safeUpdate(classEvents[name]["tags"], interfaceEvents[name]["tags"]) # # Members # interfaceMembers = getattr(interfaceApi, "members", None) if interfaceMembers: classMembers = getattr(classApi, "members", {}) for name in interfaceMembers: if not name in classMembers: Console.warn("Class %s is missing implementation for member %s of interface %s!", className, name, interfaceName) else: interfaceEntry = interfaceMembers[name] classEntry = classMembers[name] # Add reference to interface if not "interface" in classEntry: classEntry["defined"] = [] classEntry["defined"].append({ "name": interfaceName, "link": "member:%s~%s" % (interfaceName, name) }) # Copy over doc from interface if not "doc" in classEntry and "doc" in interfaceEntry: classEntry["doc"] = interfaceEntry["doc"] if not "summary" in classEntry and "summary" in interfaceEntry: classEntry["summary"] = interfaceEntry["summary"] if "errornous" in classEntry and not "errornous" in interfaceEntry: del classEntry["errornous"] # Priorize return value from interface (it's part of the interface feature set to enforce this) if "returns" in interfaceEntry: classEntry["returns"] = interfaceEntry["returns"] # Update tags with data from interface if "tags" in interfaceEntry: if not "tags" in classEntry: classEntry["tags"] = {} safeUpdate(classEntry["tags"], interfaceEntry["tags"]) # Copy over params from interface if "params" in interfaceEntry: # Fix completely missing parameters if not "params" in classEntry: classEntry["params"] = {} for paramName in interfaceEntry["params"]: # Prefer data from interface if not paramName in classEntry["params"]: classEntry["params"][paramName] = {} classEntry["params"][paramName].update(interfaceEntry["params"][paramName]) # Clear errournous documentation flags if "errornous" in classEntry["params"][paramName] and not "errornous" in interfaceEntry["params"][paramName]: del classEntry["params"][paramName]["errornous"]