def __init__(self, id, config): self.id = id self.config = config self.host = getKey(config, "host") self.auth = getKey(config, "auth") self.enableDebug = getKey(config, "debug", False) self.enableMirror = getKey(config, "mirror", False) self.enableOffline = getKey(config, "offline", False) if self.enableMirror: self.mirror = Cache(os.getcwd(), "jasymirror-%s" % self.id, hashkeys=True) info('Proxy "%s" => "%s" [debug:%s|mirror:%s|offline:%s]', self.id, self.host, self.enableDebug, self.enableMirror, self.enableOffline)
def __init__(self, path, config=None, version=None): """ Constructor call of the project. - First param is the path of the project relative to the current working directory. - Config can be read from jasyproject.json or using constructor parameter @config - Parent is used for structural debug messages (dependency trees) """ if not os.path.isdir(path): raise JasyError("Invalid project path: %s" % path) # Only store and work with full path self.__path = os.path.abspath(os.path.expanduser(path)) # Store given params self.version = version # Intialize item registries self.classes = {} self.assets = {} self.docs = {} self.translations = {} # Load project configuration self.__config = Config(config) self.__config.loadValues(os.path.join(self.__path, "jasyproject"), optional=True) # Initialize cache try: self.__cache = Cache(self.__path) except IOError as err: raise JasyError("Could not initialize project. Cache file in %s could not be initialized! %s" % (self.__path, err)) # Read name from manifest or use the basename of the project's path self.__name = self.__config.get("name", getProjectNameFromPath(self.__path)) # Read requires self.__requires = self.__config.get("requires", {}) # Defined whenever no package is defined and classes/assets are not stored in the toplevel structure. self.__package = self.__config.get("package", self.__name if self.__config.has("name") else None) # Read fields (for injecting data into the project and build permutations) self.__fields = self.__config.get("fields", {})
def __init__(self, path): """ Constructor call of the project. First param is the path of the project relative to the current working directory. """ path = os.path.normpath(path) if not os.path.isdir(path): raise JasyError("Invalid project path: %s (Absolute: %s)" % (path, os.path.abspath(path))) # Only store and work with full path self.__path = os.path.abspath(path) # Initialize cache try: self.__cache = Cache(self.__path) except IOError as err: raise JasyError("Could not initialize project. Cache file could not be initialized! %s" % err) # Load project configuration projectConfigPath = os.path.join(self.__path, "jasyproject.json") if not os.path.exists(projectConfigPath): raise JasyError("Missing jasyproject.json at: %s" % projectConfigPath) # Parse project configuration try: projectData = json.load(open(projectConfigPath)) except ValueError as err: raise JasyError("Could not parse jasyproject.json at %s: %s" % (projectConfigPath, err)) # Read name from manifest or use the basename of the project's path if "name" in projectData: self.__name = projectData["name"] else: self.__name = os.path.basename(self.__path) # Defined whenever no package is defined and classes/assets are not stored in the toplevel structure. if "package" in projectData: self.__package = projectData["package"] else: self.__package = self.__name # Whether we need to parse files for get their correct name (using @name attributes) if "fuzzy" in projectData: self.__fuzzy = projectData["fuzzy"] else: self.__fuzzy = False # Read fields (for injecting data into the project and build permuations) if "fields" in projectData: self.__fields = projectData["fields"] else: self.__fields = {} # Try to figure out folder structure automatically if os.path.isdir(os.path.join(self.__path, "source", "class")): self.__classPath = os.path.join("source", "class") self.__assetPath = os.path.join("source", "asset") self.__translationPath = os.path.join("source", "translation") elif os.path.isdir(os.path.join(self.__path, "class")): self.__classPath = "class" self.__assetPath = "asset" self.__translationPath = "translation" elif os.path.isdir(os.path.join(self.__path, "src")): self.__classPath = "src" self.__assetPath = "src" self.__translationPath = "src" else: self.__classPath = "" self.__assetPath = "" self.__translationPath = "" logging.info("Initialized project %s" % self.__name)
class Project(): def __init__(self, path): """ Constructor call of the project. First param is the path of the project relative to the current working directory. """ path = os.path.normpath(path) if not os.path.isdir(path): raise JasyError("Invalid project path: %s (Absolute: %s)" % (path, os.path.abspath(path))) # Only store and work with full path self.__path = os.path.abspath(path) # Initialize cache try: self.__cache = Cache(self.__path) except IOError as err: raise JasyError("Could not initialize project. Cache file could not be initialized! %s" % err) # Load project configuration projectConfigPath = os.path.join(self.__path, "jasyproject.json") if not os.path.exists(projectConfigPath): raise JasyError("Missing jasyproject.json at: %s" % projectConfigPath) # Parse project configuration try: projectData = json.load(open(projectConfigPath)) except ValueError as err: raise JasyError("Could not parse jasyproject.json at %s: %s" % (projectConfigPath, err)) # Read name from manifest or use the basename of the project's path if "name" in projectData: self.__name = projectData["name"] else: self.__name = os.path.basename(self.__path) # Defined whenever no package is defined and classes/assets are not stored in the toplevel structure. if "package" in projectData: self.__package = projectData["package"] else: self.__package = self.__name # Whether we need to parse files for get their correct name (using @name attributes) if "fuzzy" in projectData: self.__fuzzy = projectData["fuzzy"] else: self.__fuzzy = False # Read fields (for injecting data into the project and build permuations) if "fields" in projectData: self.__fields = projectData["fields"] else: self.__fields = {} # Try to figure out folder structure automatically if os.path.isdir(os.path.join(self.__path, "source", "class")): self.__classPath = os.path.join("source", "class") self.__assetPath = os.path.join("source", "asset") self.__translationPath = os.path.join("source", "translation") elif os.path.isdir(os.path.join(self.__path, "class")): self.__classPath = "class" self.__assetPath = "asset" self.__translationPath = "translation" elif os.path.isdir(os.path.join(self.__path, "src")): self.__classPath = "src" self.__assetPath = "src" self.__translationPath = "src" else: self.__classPath = "" self.__assetPath = "" self.__translationPath = "" logging.info("Initialized project %s" % self.__name) __dirFilter = [".svn", ".git", ".hg", ".bzr"] __internalFiles = ("jasyproject.json", "jasyscript.py", "jasycache", "jasycache.db"), def __str__(self): return self.__path def getName(self): return self.__name def getPath(self): return self.__path def getPackage(self): return self.__package def isFuzzy(self): return self.__fuzzy def getCache(self): return self.__cache def clearCache(self): self.__cache.clear() def close(self): self.__cache.close() def getFields(self): """ Return the project defined fields which may be configured by the build script """ return self.__fields def getClassByName(self, className): """ Finds a class by its name. """ try: return self.getClasses()[className] except KeyError: return None def getClassPath(self, relative=False): """ Returns the full path to the JavaScript classes """ if self.__classPath is None: return None return self.__classPath if relative else os.path.join(self.__path, self.__classPath) def getAssetPath(self, relative=False): """ Returns the full path to the assets """ if self.__assetPath is None: return None return self.__assetPath if relative else os.path.join(self.__path, self.__assetPath) def getTranslationPath(self, relative=False): """ Returns the full path to the translation files """ if self.__translationPath is None: return None return self.__translationPath if relative else os.path.join(self.__path, self.__translationPath) def getClasses(self): """ Returns all project JavaScript classes. Requires all files to have a "js" extension. """ if self.__classPath is None: return None try: return self.classes except AttributeError: classPath = os.path.join(self.__path, self.__classPath) classes = {} if classPath and os.path.exists(classPath): for dirPath, dirNames, fileNames in os.walk(classPath): for dirName in dirNames: if dirName in self.__dirFilter: dirNames.remove(dirName) for fileName in fileNames: if fileName[0] == ".": continue if fileName.endswith(".js"): classObj = Class(os.path.join(dirPath, fileName), self) className = classObj.getName() if className in classes: raise Exception("Class duplication detected: %s and %s" % (classObj.getPath(), classes[className].getPath())) classes[className] = classObj logging.debug("Project %s contains %s classes", self.__name, len(classes)) self.classes = classes return classes def getAssets(self): """ Returns all project asssets (images, stylesheets, static data, etc.). Does not filter for specific extensions but ignores files starting with a dot or files used internally by Jasy like cache files or project configuration. """ if self.__assetPath is None: return None try: return self.assets except AttributeError: assetPath = os.path.join(self.__path, self.__assetPath) assets = {} package = self.__package if assetPath and os.path.exists(assetPath): assetPathLen = len(assetPath) + 1 for dirPath, dirNames, fileNames in os.walk(assetPath): for dirName in dirNames: if dirName in self.__dirFilter: dirNames.remove(dirName) for fileName in fileNames: if fileName[0] == "." or (fileName.endswith(".js") and self.__assetPath == self.__classPath): continue # Exclude internally managed files if fileName in self.__internalFiles: continue filePath = os.path.join(dirPath, fileName) relPath = filePath[assetPathLen:] # Support for pre-fixed package which is not used in filesystem, but in assets if package: name = "%s%s%s" % (package, os.sep, relPath) else: name = relPath # always using unix paths for the asset ID assets[name.replace(os.sep, "/")] = filePath logging.debug("Project %s contains %s assets", self.__name, len(assets)) self.assets = assets return assets def getTranslations(self): """ Returns all translation files. Currently supports only gettext style PO files with a "po" extension. """ if self.__translationPath is None: return None try: return self.translations except AttributeError: translationPath = os.path.join(self.__path, self.__translationPath) translations = {} if translationPath and os.path.exists(translationPath): for dirPath, dirNames, fileNames in os.walk(translationPath): for dirName in dirNames: if dirName in self.__dirFilter: dirNames.remove(dirName) for fileName in fileNames: if fileName[0] == "." or not fileName.endswith(".po"): continue translations[os.path.splitext(fileName)[0]] = os.path.join(dirPath, fileName) logging.debug("Project %s contains %s translations", self.__name, len(translations)) self.translations = translations return translations
def __init__(self, path, config=None, version=None, repo=None, revision=None): """ Constructor call of the project. - First param is the path of the project relative to the current working directory. - Config can be read from jasyproject.json or using constructor parameter @config - Parent is used for structural debug messages (dependency trees) """ if not os.path.isdir(path): raise JasyError("Invalid project path: %s" % path) # Only store and work with full path self.__path = os.path.abspath(os.path.expanduser(path)) # Store given params self.__version = version self.__repo = repo self.__revision = revision # Intialize item registries self.classes = {} self.assets = {} self.docs = {} self.translations = {} # Load project configuration configFilePath = os.path.join(self.__path, "jasyproject.json") isJasyProject = os.path.exists(configFilePath) if isJasyProject: try: storedConfig = json.load(open(configFilePath)) except ValueError as err: raise JasyError("Could not parse jasyproject.json at %s: %s" % (configFilePath, err)) if config: for key in storedConfig: if not key in config: config[key] = storedConfig[key] else: config = storedConfig if config is None: raise JasyError("Could not initialize project configuration in %s!" % self.__path) # Initialize cache try: self.__cache = Cache(self.__path) except IOError as err: raise JasyError("Could not initialize project. Cache file could not be initialized! %s" % err) # Read name from manifest or use the basename of the project's path self.__name = getKey(config, "name", getProjectNameFromPath(self.__path)) # Read requires self.__requires = getKey(config, "requires", {}) # Defined whenever no package is defined and classes/assets are not stored in the toplevel structure. self.__package = getKey(config, "package", self.__name if isJasyProject else None) # Read fields (for injecting data into the project and build permuations) self.__fields = getKey(config, "fields", {}) # Store config self.__config = config # This section is a must for non jasy projects if not "content" in config and not isJasyProject: raise JasyError("Missing 'content' section for compat project!")
class Project(): kind = "none" def __init__(self, path, config=None, version=None, repo=None, revision=None): """ Constructor call of the project. - First param is the path of the project relative to the current working directory. - Config can be read from jasyproject.json or using constructor parameter @config - Parent is used for structural debug messages (dependency trees) """ if not os.path.isdir(path): raise JasyError("Invalid project path: %s" % path) # Only store and work with full path self.__path = os.path.abspath(os.path.expanduser(path)) # Store given params self.__version = version self.__repo = repo self.__revision = revision # Intialize item registries self.classes = {} self.assets = {} self.docs = {} self.translations = {} # Load project configuration configFilePath = os.path.join(self.__path, "jasyproject.json") isJasyProject = os.path.exists(configFilePath) if isJasyProject: try: storedConfig = json.load(open(configFilePath)) except ValueError as err: raise JasyError("Could not parse jasyproject.json at %s: %s" % (configFilePath, err)) if config: for key in storedConfig: if not key in config: config[key] = storedConfig[key] else: config = storedConfig if config is None: raise JasyError("Could not initialize project configuration in %s!" % self.__path) # Initialize cache try: self.__cache = Cache(self.__path) except IOError as err: raise JasyError("Could not initialize project. Cache file could not be initialized! %s" % err) # Read name from manifest or use the basename of the project's path self.__name = getKey(config, "name", getProjectNameFromPath(self.__path)) # Read requires self.__requires = getKey(config, "requires", {}) # Defined whenever no package is defined and classes/assets are not stored in the toplevel structure. self.__package = getKey(config, "package", self.__name if isJasyProject else None) # Read fields (for injecting data into the project and build permuations) self.__fields = getKey(config, "fields", {}) # Store config self.__config = config # This section is a must for non jasy projects if not "content" in config and not isJasyProject: raise JasyError("Missing 'content' section for compat project!") # # Project Scan/Init # def init(self): config = self.__config # Processing custom content section. Only supports classes and assets. if "content" in config: self.__kind = "manual" self.__addContent(config["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)) if summary: msg = "%s " % colorize(self.getName(), "bold") if self.__version: msg += "@ %s" % colorize(self.__version, "magenta") rev = self.__revision if rev is not None: if type(rev) is str and len(rev) > 10: rev = rev[:6] msg += colorize("-%s " % rev, "grey") else: msg += " " msg += "[%s]: %s" % (colorize(self.__kind, "cyan"), colorize(", ".join(summary), "grey")) info(msg) else: error("Project %s is empty!", self.getName()) # # FILE SYSTEM INDEXER # def __hasDir(self, directory): full = os.path.join(self.__path, directory) if os.path.exists(full): if not os.path.isdir(full): raise JasyError("Expecting %s to be a directory: %s" % full) return True return False def __addContent(self, content): debug("Adding manual content") indent() for fileId in content: fileContent = content[fileId] if len(fileContent) == 0: raise JasyError("Empty content!") # If the user defines a file extension for JS public idenfiers # (which is not required) we filter them out if fileId.endswith(".js"): raise JasyError("JavaScript files should define the exported name, not a file name: %s" % fileId) fileExtension = os.path.splitext(fileContent[0])[1] # Support for joining text content if len(fileContent) == 1: filePath = os.path.join(self.__path, fileContent[0]) else: filePath = [os.path.join(self.__path, filePart) for filePart in fileContent] # Structure files if fileExtension in classExtensions: construct = Class dist = self.classes elif fileExtension in translationExtensions: construct = Translation dist = self.translations else: construct = Asset dist = self.assets # Check for duplication if fileId in dist: raise JasyError("Item ID was registered before: %s" % fileId) # Create instance item = construct(self, fileId).attach(filePath) debug("Registering %s %s" % (item.kind, fileId)) dist[fileId] = item outdent() def __addDir(self, directory, distname): debug("Scanning directory: %s" % directory) indent() path = os.path.join(self.__path, directory) if not os.path.exists(path): return for dirPath, dirNames, fileNames in os.walk(path): for dirName in dirNames: # Filter dotted directories like .git, .bzr, .hg, .svn, etc. if dirName.startswith("."): dirNames.remove(dirName) # Filter sub projects if os.path.exists(os.path.join(dirPath, dirName, "jasyproject.json")): dirNames.remove(dirName) relDirPath = os.path.relpath(dirPath, path) for fileName in fileNames: if fileName[0] == ".": continue relPath = os.path.normpath(os.path.join(relDirPath, fileName)).replace(os.sep, "/") fullPath = os.path.join(dirPath, fileName) self.addFile(relPath, fullPath, distname) outdent() def addFile(self, relPath, fullPath, distname, override=False): fileName = os.path.basename(relPath) fileExtension = os.path.splitext(fileName)[1] # Prepand package if self.__package: fileId = "%s/" % self.__package else: fileId = "" # Structure files if fileExtension in classExtensions and distname == "classes": fileId += os.path.splitext(relPath)[0] construct = Class dist = self.classes elif fileExtension in translationExtensions and distname == "translations": fileId += os.path.splitext(relPath)[0] construct = Translation dist = self.translations elif fileName in docFiles: fileId += os.path.dirname(relPath) fileId = fileId.strip("/") # edge case when top level directory construct = Doc dist = self.docs else: fileId += relPath construct = Asset dist = self.assets # Only assets keep unix style paths identifiers if construct != Asset: fileId = fileId.replace("/", ".") # Check for duplication if fileId in dist and not override: raise JasyError("Item ID was registered before: %s" % fileId) # Create instance item = construct(self, fileId).attach(fullPath) debug("Registering %s %s" % (item.kind, fileId)) dist[fileId] = item # # ESSENTIALS # def getRequires(self, prefix="external"): """ Return the project requirements as project instances """ result = [] for entry in self.__requires: repo = None revision = None if type(entry) is dict: source = entry["source"] config = getKey(entry, "config") version = getKey(entry, "version") else: source = entry config = None version = None if version: info("Processing: %s @ %s", source, version) else: info("Processing: %s", source) indent() if isGitRepositoryUrl(source): if not version: version = "master" # Auto cloning always happens relative to main project root folder (not to project requiring it) retval = cloneGit(source, version, prefix=prefix) if not retval: raise JasyError("Could not clone GIT repository %s" % source) path, revision = retval path = os.path.abspath(path) repo = "git" else: if not source.startswith(("/", "~")): path = os.path.join(self.__path, source) else: path = source # Other references to requires projects are always relative to the project requiring it path = os.path.normpath(os.path.expanduser(path)) repo = "local" project = getProjectFromPath(path, config, version, repo, revision) result.append(project) outdent() return result def getFields(self): """ Return the project defined fields which may be configured by the build script """ return self.__fields def getClassByName(self, className): """ Finds a class by its name.""" try: return self.getClasses()[className] except KeyError: return None def getName(self): return self.__name def getPath(self): return self.__path def getPackage(self): return self.__package def toRelativeUrl(self, path, prefix="", subpath="source"): root = os.path.join(self.__path, subpath) relpath = os.path.relpath(path, root) if prefix: if not prefix[-1] == os.sep: prefix += os.sep relpath = os.path.normpath(prefix + relpath) return relpath.replace(os.sep, "/") # # CACHE API # def getCache(self): """Returns the cache instance""" return self.__cache def clean(self): """Clears the cache of the project""" info("Clearing cache of %s..." % self.__name) self.__cache.clear() def close(self): """Closes the project which deletes the internal caches""" self.__cache.close() self.__cache = None self.classes = None self.assets = None self.docs = None self.translations = None def pause(self): """Pauses the project so that other processes could modify/access it""" self.__cache.close() def resume(self): """Resumes the paused project""" self.__cache.open() # # LIST ACCESSORS # def getDocs(self): """Returns all package docs""" return self.docs def getClasses(self): """ Returns all project JavaScript classes. Requires all files to have a "js" extension. """ return self.classes def getAssets(self): """ Returns all project asssets (images, stylesheets, static data, etc.). """ return self.assets def getTranslations(self): """ Returns all translation files. Supports gettext style PO files with .po extension. """ return self.translations
class Proxy(object): def __init__(self, id, config): self.id = id self.config = config self.host = getKey(config, "host") self.auth = getKey(config, "auth") self.enableDebug = getKey(config, "debug", False) self.enableMirror = getKey(config, "mirror", False) self.enableOffline = getKey(config, "offline", False) if self.enableMirror: self.mirror = Cache(os.getcwd(), "jasymirror-%s" % self.id, hashkeys=True) info('Proxy "%s" => "%s" [debug:%s|mirror:%s|offline:%s]', self.id, self.host, self.enableDebug, self.enableMirror, self.enableOffline) # These headers will be blocked between header copies __blockHeaders = CaseInsensitiveDict.fromkeys([ "content-encoding", "content-length", "connection", "keep-alive", "proxy-authenticate", "proxy-authorization", "transfer-encoding", "remote-addr", "host" ]) @cherrypy.expose 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. """ url = self.config["host"] + "/".join(args) result = None # Try using offline mirror if feasible if self.enableMirror and cherrypy.request.method == "GET": mirrorId = "%s[%s]" % (url, json.dumps(query, separators=(',',':'), sort_keys=True)) result = self.mirror.read(mirrorId) if result is not None and self.enableDebug: info("Mirrored: %s" % url) # Check if we're in forced offline mode if self.enableOffline and result is None: info("Offline: %s" % url) raise cherrypy.NotFound(url) # Load URL from remote server if result is None: # Prepare headers headers = CaseInsensitiveDict() for name in cherrypy.request.headers: if not name in self.__blockHeaders: headers[name] = cherrypy.request.headers[name] # Load URL from remote host try: if self.enableDebug: info("Requesting: %s", url) # Apply headers for basic HTTP authentification if "X-Proxy-Authorization" in headers: headers["Authorization"] = headers["X-Proxy-Authorization"] del headers["X-Proxy-Authorization"] # Add headers for different authentification approaches if self.auth: # Basic Auth if self.auth["method"] == "basic": headers["Authorization"] = b"Basic " + base64.b64encode(("%s:%s" % (self.auth["user"], self.auth["password"])).encode("ascii")) # We disable verifícation of SSL certificates to be more tolerant on test servers result = requests.get(url, params=query, headers=headers, verify=False) except Exception as err: if self.enableDebug: info("Request failed: %s", err) raise cherrypy.HTTPError(403) # Storing result into mirror if self.enableMirror and cherrypy.request.method == "GET": # Wrap result into mirrorable entry resultCopy = Result(result.headers, result.content) self.mirror.store(mirrorId, resultCopy) # Copy response headers to our reponse for name in result.headers: if not name.lower() in self.__blockHeaders: cherrypy.response.headers[name] = result.headers[name] # Append special header to all responses cherrypy.response.headers["X-Jasy-Version"] = jasy.__version__ # Enable cross domain access to this server enableCrossDomain() return result.content
def __init__(self, path): path = os.path.normpath(path) if not os.path.isdir(path): raise ProjectException("Invalid project path: %s (Absolute: %s)" % (path, os.path.abspath(path))) # Only store and work with full path path = os.path.abspath(path) self.__path = path self.__dirFilter = [".svn",".git",".hg",".bzr"] try: self.__cache = Cache(self.__path) except IOError as err: raise ProjectException("Could not initialize project. Cache file could not be initialized! %s" % err) manifestPath = os.path.join(path, "manifest.json") if not os.path.exists(manifestPath): raise ProjectException("Missing manifest.json at: %s" % manifestPath) try: manifestData = json.load(open(manifestPath)) except ValueError as err: raise ProjectException("Could not parse manifest.json at %s: %s" % (manifestPath, err)) # Read name from manifest or use the basename of the project's path if "name" in manifestData: self.__name = manifestData["name"] else: self.__name = os.path.basename(path) # Detect kind automatically if "kind" in manifestData: self.__kind = manifestData["kind"] elif os.path.isdir(os.path.join(self.__path, "source", "class")): self.__kind = "full" elif os.path.isdir(os.path.join(self.__path, "class")): self.__kind = "basic" elif os.path.isdir(os.path.join(self.__path, "src")): self.__kind = "classic" else: self.__kind = "flat" # Defined whenever no package is defined and classes/assets are not stored in the toplevel structure. if "package" in manifestData: self.__package = manifestData["package"] else: self.__package = self.__name # Whether we need to parse files for get their correct name (using @name attributes) if "fuzzy" in manifestData: self.__fuzzy = manifestData["fuzzy"] else: self.__fuzzy = False # Read fields (for injecting data into the project and build permuations) if "fields" in manifestData: self.__fields = manifestData["fields"] else: self.__fields = {} logging.info("Initialized project %s (%s)" % (self.__name, self.__kind)) # Do kind specific intialization if self.__kind == "full": self.__classPath = os.path.join("source", "class") self.__assetPath = os.path.join("source", "asset") self.__translationPath = os.path.join("source", "translation") elif self.__kind == "basic": self.__classPath = "class" self.__assetPath = "asset" self.__translationPath = "translation" elif self.__kind == "classic": self.__classPath = "src" self.__assetPath = "src" self.__translationPath = None elif self.__kind == "flat": self.__classPath = "" self.__assetPath = "" self.__translationPath = None else: raise ProjectException("Unsupported kind of project: %s" % self.__kind)
class Project(): def __init__(self, path): path = os.path.normpath(path) if not os.path.isdir(path): raise ProjectException("Invalid project path: %s (Absolute: %s)" % (path, os.path.abspath(path))) # Only store and work with full path path = os.path.abspath(path) self.__path = path self.__dirFilter = [".svn",".git",".hg",".bzr"] try: self.__cache = Cache(self.__path) except IOError as err: raise ProjectException("Could not initialize project. Cache file could not be initialized! %s" % err) manifestPath = os.path.join(path, "manifest.json") if not os.path.exists(manifestPath): raise ProjectException("Missing manifest.json at: %s" % manifestPath) try: manifestData = json.load(open(manifestPath)) except ValueError as err: raise ProjectException("Could not parse manifest.json at %s: %s" % (manifestPath, err)) # Read name from manifest or use the basename of the project's path if "name" in manifestData: self.__name = manifestData["name"] else: self.__name = os.path.basename(path) # Detect kind automatically if "kind" in manifestData: self.__kind = manifestData["kind"] elif os.path.isdir(os.path.join(self.__path, "source", "class")): self.__kind = "full" elif os.path.isdir(os.path.join(self.__path, "class")): self.__kind = "basic" elif os.path.isdir(os.path.join(self.__path, "src")): self.__kind = "classic" else: self.__kind = "flat" # Defined whenever no package is defined and classes/assets are not stored in the toplevel structure. if "package" in manifestData: self.__package = manifestData["package"] else: self.__package = self.__name # Whether we need to parse files for get their correct name (using @name attributes) if "fuzzy" in manifestData: self.__fuzzy = manifestData["fuzzy"] else: self.__fuzzy = False # Read fields (for injecting data into the project and build permuations) if "fields" in manifestData: self.__fields = manifestData["fields"] else: self.__fields = {} logging.info("Initialized project %s (%s)" % (self.__name, self.__kind)) # Do kind specific intialization if self.__kind == "full": self.__classPath = os.path.join("source", "class") self.__assetPath = os.path.join("source", "asset") self.__translationPath = os.path.join("source", "translation") elif self.__kind == "basic": self.__classPath = "class" self.__assetPath = "asset" self.__translationPath = "translation" elif self.__kind == "classic": self.__classPath = "src" self.__assetPath = "src" self.__translationPath = None elif self.__kind == "flat": self.__classPath = "" self.__assetPath = "" self.__translationPath = None else: raise ProjectException("Unsupported kind of project: %s" % self.__kind) def __str__(self): return self.__path def getName(self): return self.__name def getPath(self): return self.__path def getPackage(self): return self.__package def isFuzzy(self): return self.__fuzzy def getCache(self): return self.__cache def clearCache(self): self.__cache.clear() def close(self): self.__cache.close() def getFields(self): """ Return the project defined fields which may be configured by the build script """ return self.__fields def getClassByName(self, className): try: return self.getClasses()[className] except KeyError: return None def getClassPath(self, relative=False): """ Returns the full path to the JavaScript classes """ if self.__classPath is None: return None return self.__classPath if relative else os.path.join(self.__path, self.__classPath) def getAssetPath(self, relative=False): """ Returns the full path to the assets (images, stylesheets, etc.) """ if self.__assetPath is None: return None return self.__assetPath if relative else os.path.join(self.__path, self.__assetPath) def getTranslationPath(self, relative=False): """ Returns the full path to the translation files (gettext *.po files) """ if self.__translationPath is None: return None return self.__translationPath if relative else os.path.join(self.__path, self.__translationPath) def getClasses(self): """ Returns all project JavaScript classes """ if self.__classPath is None: return None try: return self.classes except AttributeError: classPath = os.path.join(self.__path, self.__classPath) classes = {} if classPath and os.path.exists(classPath): for dirPath, dirNames, fileNames in os.walk(classPath): for dirName in dirNames: if dirName in self.__dirFilter: dirNames.remove(dirName) for fileName in fileNames: if fileName.endswith(".js") and fileName[0] != ".": classObj = Class(os.path.join(dirPath, fileName), self) className = classObj.getName() if className in classes: raise Exception("Class duplication detected: %s and %s" % (classObj.getPath(), classes[className].getPath())) classes[className] = classObj logging.debug("Project %s contains %s classes", self.__name, len(classes)) self.classes = classes return classes def getAssets(self): """ Returns all project asssets (images, stylesheets, etc.) """ if self.__assetPath is None: return None try: return self.assets except AttributeError: assetPath = os.path.join(self.__path, self.__assetPath) assets = {} package = self.__package if assetPath and os.path.exists(assetPath): assetPathLen = len(assetPath) + 1 for dirPath, dirNames, fileNames in os.walk(assetPath): for dirName in dirNames: if dirName in self.__dirFilter: dirNames.remove(dirName) for fileName in fileNames: if fileName in ("manifest.json", "generate.py", "cache"): continue if fileName[0] == "." or fileName.endswith((".js", ".txt", ".md")): continue filePath = os.path.join(dirPath, fileName) relPath = filePath[assetPathLen:] # Support for pre-fixed package which is not used in filesystem, but in assets if package: name = "%s%s%s" % (package, os.sep, relPath) else: name = relPath # always using unix paths for the asset ID assets[name.replace(os.sep, "/")] = filePath logging.debug("Project %s contains %s assets", self.__name, len(assets)) self.assets = assets return assets def getTranslations(self): """ Returns all translation files (gettext *.po files)""" if self.__translationPath is None: return None try: return self.translations except AttributeError: translationPath = os.path.join(self.__path, self.__translationPath) translations = {} if translationPath and os.path.exists(translationPath): for dirPath, dirNames, fileNames in os.walk(translationPath): for dirName in dirNames: if dirName in self.__dirFilter: dirNames.remove(dirName) for fileName in fileNames: if fileName[0] == "." or not fileName.endswith(".po"): continue translations[os.path.splitext(fileName)[0]] = os.path.join(dirPath, fileName) logging.debug("Project %s contains %s translations", self.__name, len(translations)) self.translations = translations return translations
class Project(): kind = "none" def __init__(self, path, config=None, version=None): """ Constructor call of the project. - First param is the path of the project relative to the current working directory. - Config can be read from jasyproject.json or using constructor parameter @config - Parent is used for structural debug messages (dependency trees) """ if not os.path.isdir(path): raise JasyError("Invalid project path: %s" % path) # Only store and work with full path self.__path = os.path.abspath(os.path.expanduser(path)) # Store given params self.version = version # Intialize item registries self.classes = {} self.assets = {} self.docs = {} self.translations = {} # Load project configuration self.__config = Config(config) self.__config.loadValues(os.path.join(self.__path, "jasyproject"), optional=True) # Initialize cache try: self.__cache = Cache(self.__path) except IOError as err: raise JasyError("Could not initialize project. Cache file in %s could not be initialized! %s" % (self.__path, err)) # Read name from manifest or use the basename of the project's path self.__name = self.__config.get("name", getProjectNameFromPath(self.__path)) # Read requires self.__requires = self.__config.get("requires", {}) # Defined whenever no package is defined and classes/assets are not stored in the toplevel structure. self.__package = self.__config.get("package", self.__name if self.__config.has("name") else None) # Read fields (for injecting data into the project and build permutations) self.__fields = self.__config.get("fields", {}) # # Project Scan/Init # def scan(self): # 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)) # Import library methods libraryPath = os.path.join(self.__path, "jasylibrary.py") if os.path.exists(libraryPath): methodNumber = loadLibrary(self.__name, libraryPath) summary.append("%s methods" % methodNumber) # Print out if summary: info("Scanned %s %s: %s" % (colorize(self.__name, "bold"), colorize("[%s]" % self.kind, "grey"), colorize(", ".join(summary), "green"))) else: error("Project %s is empty!", self.__name) # # FILE SYSTEM INDEXER # def __hasDir(self, directory): full = os.path.join(self.__path, directory) if os.path.exists(full): if not os.path.isdir(full): raise JasyError("Expecting %s to be a directory: %s" % full) return True return False def __addContent(self, content): debug("Adding manual content") indent() for fileId in content: fileContent = content[fileId] if len(fileContent) == 0: raise JasyError("Empty content!") # If the user defines a file extension for JS public idenfiers # (which is not required) we filter them out if fileId.endswith(".js"): raise JasyError("JavaScript files should define the exported name, not a file name: %s" % fileId) fileExtension = os.path.splitext(fileContent[0])[1] # Support for joining text content if len(fileContent) == 1: filePath = os.path.join(self.__path, fileContent[0]) else: filePath = [os.path.join(self.__path, filePart) for filePart in fileContent] # Structure files if fileExtension in classExtensions: construct = Class dist = self.classes elif fileExtension in translationExtensions: construct = Translation dist = self.translations else: construct = Asset dist = self.assets # Check for duplication if fileId in dist: raise JasyError("Item ID was registered before: %s" % fileId) # Create instance item = construct(self, fileId).attach(filePath) debug("Registering %s %s" % (item.kind, fileId)) dist[fileId] = item outdent() def __addDir(self, directory, distname): debug("Scanning directory: %s" % directory) indent() path = os.path.join(self.__path, directory) if not os.path.exists(path): return for dirPath, dirNames, fileNames in os.walk(path): for dirName in dirNames: # Filter dotted directories like .git, .bzr, .hg, .svn, etc. if dirName.startswith("."): dirNames.remove(dirName) # Filter sub projects if os.path.exists(os.path.join(dirPath, dirName, "jasyproject.json")): dirNames.remove(dirName) relDirPath = os.path.relpath(dirPath, path) for fileName in fileNames: if fileName[0] == ".": continue relPath = os.path.normpath(os.path.join(relDirPath, fileName)).replace(os.sep, "/") fullPath = os.path.join(dirPath, fileName) self.addFile(relPath, fullPath, distname) outdent() def addFile(self, relPath, fullPath, distname, override=False): fileName = os.path.basename(relPath) fileExtension = os.path.splitext(fileName)[1] # Prepand package if self.__package: fileId = "%s/" % self.__package else: fileId = "" # Structure files if fileExtension in classExtensions and distname == "classes": fileId += os.path.splitext(relPath)[0] construct = Class dist = self.classes elif fileExtension in translationExtensions and distname == "translations": fileId += os.path.splitext(relPath)[0] construct = Translation dist = self.translations elif fileName in docFiles: fileId += os.path.dirname(relPath) fileId = fileId.strip("/") # edge case when top level directory construct = Doc dist = self.docs else: fileId += relPath construct = Asset dist = self.assets # Only assets keep unix style paths identifiers if construct != Asset: fileId = fileId.replace("/", ".") # Check for duplication if fileId in dist and not override: raise JasyError("Item ID was registered before: %s" % fileId) # Create instance item = construct(self, fileId).attach(fullPath) debug("Registering %s %s" % (item.kind, fileId)) dist[fileId] = item # # ESSENTIALS # def getRequires(self, prefix="external"): """ Return the project requirements as project instances """ global projects result = [] for entry in self.__requires: if type(entry) is dict: source = entry["source"] config = getKey(entry, "config") version = getKey(entry, "version") kind = getKey(entry, "kind") else: source = entry config = None version = None kind = None revision = None if isRepository(source): kind = kind or getRepositoryType(source) path = os.path.abspath(os.path.join(prefix, getRepositoryFolder(source, version, kind))) # Only clone and update when the folder is unique in this session # This reduces git/hg/svn calls which are typically quite expensive if not path in projects: revision = updateRepository(source, version, path) if revision is None: raise JasyError("Could not update repository %s" % source) else: kind = "local" if not source.startswith(("/", "~")): path = os.path.join(self.__path, source) else: path = os.path.abspath(os.path.expanduser(source)) if path in projects: project = projects[path] else: fullversion = [] # Produce user readable version when non is defined if version is None and revision is not None: version = "master" if version is not None: if "/" in version: fullversion.append(version[version.rindex("/")+1:]) else: fullversion.append(version) if revision is not None: # Shorten typical long revisions as used by e.g. Git if type(revision) is str and len(revision) > 20: fullversion.append(revision[:10]) else: fullversion.append(revision) if fullversion: fullversion = "-".join(fullversion) else: fullversion = None project = Project(path, config, fullversion) projects[path] = project result.append(project) return result def getFields(self): """ Return the project defined fields which may be configured by the build script """ return self.__fields def getClassByName(self, className): """ Finds a class by its name.""" try: return self.getClasses()[className] except KeyError: return None def getName(self): return self.__name def getPath(self): return self.__path def getPackage(self): return self.__package def getConfigValue(self, key, default=None): return self.__config.get(key, default) def toRelativeUrl(self, path, prefix="", subpath="source"): root = os.path.join(self.__path, subpath) relpath = os.path.relpath(path, root) if prefix: if not prefix[-1] == os.sep: prefix += os.sep relpath = os.path.normpath(prefix + relpath) return relpath.replace(os.sep, "/") # # CACHE API # def getCache(self): """Returns the cache instance""" return self.__cache def clean(self): """Clears the cache of the project""" info("Clearing cache of %s..." % self.__name) self.__cache.clear() def close(self): """Closes the project which deletes the internal caches""" self.__cache.close() self.__cache = None self.classes = None self.assets = None self.docs = None self.translations = None def pause(self): """Pauses the project so that other processes could modify/access it""" self.__cache.close() def resume(self): """Resumes the paused project""" self.__cache.open() # # LIST ACCESSORS # def getDocs(self): """Returns all package docs""" return self.docs def getClasses(self): """ Returns all project JavaScript classes. Requires all files to have a "js" extension. """ return self.classes def getAssets(self): """ Returns all project asssets (images, stylesheets, static data, etc.). """ return self.assets def getTranslations(self): """ Returns all translation files. Supports gettext style PO files with .po extension. """ return self.translations