Esempio n. 1
0
    def addProject(self, project):
        """
        Adds the given project to the list of known projects. Projects should be added in order of their priority. This
        adds the field configuration of each project to the session fields. Fields must not conflict between different
        projects (same name).

        :param project: Instance of Project to append to the list
        :type project: object

        """

        result = Project.getProjectDependencies(project, "external", self.__updateRepositories)
        for project in result:

            Console.info("Adding %s...", Console.colorize(project.getName(), "bold"))
            Console.indent()

            # Append to session list
            self.__projects.append(project)

            # Import library methods
            libraryPath = os.path.join(project.getPath(), "jasylibrary.py")
            if os.path.exists(libraryPath):
                self.loadLibrary(project.getName(), libraryPath, doc="Library of project %s" % project.getName())

            # Import command methods
            commandPath = os.path.join(project.getPath(), "jasycommand.py")
            if os.path.exists(commandPath):
                self.loadCommands(project.getName(), commandPath)

            # Import project defined fields which might be configured using "activateField()"
            fields = project.getFields()
            for name in fields:
                entry = fields[name]

                if name in self.__fields:
                    raise UserError("Field '%s' was already defined!" % (name))

                if "check" in entry:
                    check = entry["check"]
                    if check in ["Boolean", "String", "Number"] or isinstance(check, list):
                        pass
                    else:
                        raise UserError("Unsupported check: '%s' for field '%s'" % (check, name))

                self.__fields[name] = entry


            Console.outdent()
Esempio n. 2
0
    def __addContent(self, content):
        Console.debug("Adding manual content")

        Console.indent()
        for fileId in content:
            fileContent = content[fileId]
            if len(fileContent) == 0:
                raise UserError("Empty content!")

            # If the user defines a file extension for JS public idenfiers
            # (which is not required) we filter them out
            if fileId.endswith(".js"):
                raise UserError(
                    "JavaScript files should define the exported name, not a file name: %s"
                    % fileId)

            fileExtension = os.path.splitext(fileContent[0])[1]

            # Support for joining text content
            if len(fileContent) == 1:
                filePath = os.path.join(self.__path, fileContent[0])
            else:
                filePath = [
                    os.path.join(self.__path, filePart)
                    for filePart in fileContent
                ]

            # Structure files
            if fileExtension in classExtensions:
                construct = jasy.item.Class.ClassItem
                dist = self.classes
            elif fileExtension in translationExtensions:
                construct = jasy.item.Translation.TranslationItem
                dist = self.translations
            else:
                construct = jasy.item.Asset.AssetItem
                dist = self.assets

            # Check for duplication
            if fileId in dist:
                raise UserError("Item ID was registered before: %s" % fileId)

            # Create instance
            item = construct(self, fileId).attach(filePath)
            Console.debug("Registering %s %s" % (item.kind, fileId))
            dist[fileId] = item

        Console.outdent()
Esempio n. 3
0
    def addProfile(self, name, root=None, config=None, items=None):
        """
        Adds a new profile to the manager. This is basically the plain
        version of addSourceProfile/addBuildProfile which gives complete
        manual control of where to load the assets from. This is useful
        for e.g. supporting a complete custom loading scheme aka complex
        CDN based setup.
        """

        profiles = self.__profiles
        for entry in profiles:
            if entry["name"] == name:
                raise UserError("Asset profile %s was already defined!" % name)

        profile = {"name": name}

        if root:
            if not root.endswith("/"):
                root += "/"

            profile["root"] = root

        if config is not None:
            profile.update(config)

        unique = len(profiles)
        profiles.append(profile)

        if items:
            for fileId in items:
                items[fileId]["p"] = unique

            self.__addRuntimeData(items)

        return unique
Esempio n. 4
0
    def __resolveConstructor(self, itemType):
        construct = self.__session.getItemType(itemType)

        if not construct:
            raise UserError("Could not resolve item type %s" % itemType)

        return construct
Esempio n. 5
0
def task(*args, **kwargs):
    """Specifies that this function is a task."""

    if len(args) == 1:

        func = args[0]

        if isinstance(func, Task):
            return func

        elif isinstance(func, types.FunctionType):
            return Task(func)

        # Compat to old Jasy 0.7.x task declaration
        elif isinstance(func, str):
            return task(**kwargs)

        else:
            raise UserError("Invalid task")

    else:

        def wrapper(func):
            return Task(func, **kwargs)

        return wrapper
Esempio n. 6
0
    def executeScript(self,
                      fileName,
                      autoDelete=True,
                      optional=False,
                      encoding="utf-8"):
        """
        Executes the given script for configuration proposes and deletes the file afterwards (by default).

        Returns True when the file was found and processed.

        """

        if not os.path.exists(fileName):
            if optional:
                return False
            else:
                raise UserError("Could not find configuration script: %s" %
                                fileName)

        env = {"config": self, "file": File}

        code = open(fileName, "r", encoding=encoding).read()
        exec(compile(code, os.path.abspath(fileName), "exec"), globals(), env)

        if autoDelete:
            File.rm(fileName)

        return True
Esempio n. 7
0
File: Sheet.py Progetto: zynga/jasy
    def write(self, filename, debug=False):

        if Image is None:
            raise UserError("Missing Python PIL which is required to create sprite sheets!")

        img = Image.new('RGBA', (self.width, self.height))
        draw = ImageDraw.Draw(img)

        #draw.rectangle((0, 0, self.width, self.height), fill=(255, 255, 0, 255))

        # Load images and pack them in
        for block in self.blocks:
            res = Image.open(block.image.src)

            x, y = block.fit.x, block.fit.y
            if block.rotated:
                Console.debug('%s is rotated' % block.image.src)
                res = res.rotate(90)

            img.paste(res, (x, y))
            del res

            if debug:
                x, y, w, h = block.fit.x, block.fit.y, block.w, block.h
                draw.rectangle((x, y , x + w , y + h), outline=(0, 0, 255, 255) if block.rotated else (255, 0, 0, 255))

        if debug:
            for i, block in enumerate(self.packer.getUnused()):
                x, y, w, h = block.x, block.y, block.w, block.h
                draw.rectangle((x, y , x + w , y + h), fill=(255, 255, 0, 255))

        img.save(filename)
Esempio n. 8
0
def writeConfig(data, fileName, indent=2, encoding="utf-8"):
    """
    Writes the given data structure to the given file name.

    Based on the given extension a different file format is choosen. Currently use either .yaml or .json.

    """

    fileHandle = open(fileName, mode="w", encoding=encoding)

    fileExt = os.path.splitext(fileName)[1]

    if fileExt == ".yaml":
        yaml.dump(data,
                  fileHandle,
                  default_flow_style=False,
                  indent=indent,
                  allow_unicode=True)
        fileHandle.close()

    elif fileExt == ".json":
        json.dump(data, fileHandle, indent=indent, ensure_ascii=False)
        fileHandle.close()

    else:
        fileHandle.close()
        raise UserError("Unsupported config type: %s" % fileExt)
Esempio n. 9
0
File: Task.py Progetto: zynga/jasy
def runTask(project, task, **kwargs):
    """
    Executes the given task of the given projects. 
    
    This happens inside a new sandboxed session during which the 
    current session is paused/resumed automatically.
    """

    remote = session.getProjectByName(project)
    if remote is not None:
        remotePath = remote.getPath()
        remoteName = remote.getName()
    elif os.path.isdir(project):
        remotePath = project
        remoteName = os.path.basename(project)
    else:
        raise UserError("Unknown project or invalid path: %s" % project)

    Console.info("Running %s of project %s...", Console.colorize(task, "bold"),
                 Console.colorize(remoteName, "bold"))

    # Pauses this session to allow sub process fully accessing the same projects
    session.pause()

    # Build parameter list from optional arguments
    params = ["--%s=%s" % (key, kwargs[key]) for key in kwargs]
    if not "prefix" in kwargs:
        params.append("--prefix=%s" % session.getCurrentPrefix())

    # Full list of args to pass to subprocess
    args = [__command, task] + params

    # Change into sub folder and execute jasy task
    oldPath = os.getcwd()
    os.chdir(remotePath)
    returnValue = subprocess.call(args, shell=sys.platform == "win32")
    os.chdir(oldPath)

    # Resumes this session after sub process was finished
    session.resume()

    # Error handling
    if returnValue != 0:
        raise UserError("Executing of sub task %s from project %s failed" %
                        (task, project))
Esempio n. 10
0
    def setDefaultLocale(self, locale):
        """
        Sets the default locale
        """

        if not "locale" in self.__fields:
            raise UserError("Define locales first!")

        self.__fields["locale"]["default"] = locale
Esempio n. 11
0
File: File.py Progetto: zynga/jasy
def mkdir(name):
    """Creates directory (works recursively)"""

    if os.path.isdir(name):
        return
    elif os.path.exists(name):
        raise UserError("Error creating directory %s - File exists!" % name)

    return os.makedirs(name)
Esempio n. 12
0
    def __hasDir(self, directory):
        full = os.path.join(self.__path, directory)
        if os.path.exists(full):
            if not os.path.isdir(full):
                raise UserError("Expecting %s to be a directory: %s" % full)

            return True

        return False
Esempio n. 13
0
    def __addContent(self, content):
        Console.info("Adding manual content")

        Console.indent()
        for fileId in content:
            entry = content[fileId]
            if not isinstance(entry, dict):
                raise UserError(
                    "Invalid manual content section for file %s. Requires a dict with type and source definition!"
                    % fileId)

            itemType = entry["type"]
            fileContent = entry["source"]

            if len(fileContent) == 0:
                raise UserError("Empty content!")

            # Support for joining text content
            if len(fileContent) == 1:
                filePath = os.path.join(self.__path, fileContent[0])
            else:
                filePath = [
                    os.path.join(self.__path, filePart)
                    for filePart in fileContent
                ]

            name, construct = self.__resolveConstructor(itemType)
            item = construct(self, fileId).attach(filePath)
            Console.debug("Registering %s %s" % (item.kind, fileId))

            if not itemType in self.items:
                self.items[itemType] = {}

            # Check for duplication
            if fileId in self.items[itemType]:
                raise UserError("Item ID was registered before: %s" % fileId)

            self.items[itemType][fileId] = item

        Console.outdent()
Esempio n. 14
0
    def getHtml(self, highlight=True):
        """
        Returns the comment text converted to HTML.

        :param highlight: Whether to highlight the code
        :type highlight: bool

        """

        if not Text.supportsMarkdown:
            raise UserError(
                "Markdown is not supported by the system. Documentation comments could converted to HTML."
            )

        if highlight:

            if self.__highlightedText is None:

                highlightedText = ""

                for block in self.__blocks:

                    if block["type"] == "comment":
                        highlightedText += Text.highlightCodeBlocks(
                            Text.markdownToHtml(block["processed"]))
                    else:
                        highlightedText += "\n%s" % Text.highlightCodeBlocks(
                            Text.markdownToHtml(block["text"]))

                self.__highlightedText = highlightedText

            return self.__highlightedText

        else:

            if self.__processedText is None:

                processedText = ""

                for block in self.__blocks:

                    if block["type"] == "comment":
                        processedText += Text.markdownToHtml(
                            block["processed"])
                    else:
                        processedText += "\n%s\n\n" % block["text"]

                self.__processedText = processedText.strip()

            return self.__processedText
Esempio n. 15
0
def executeTask(taskname, **kwargs):
    """Executes the given task by name with any optional named arguments."""

    if taskname in __taskRegistry:
        try:
            camelCaseArgs = {Util.camelize(key) : kwargs[key] for key in kwargs}
            return __taskRegistry[taskname](**camelCaseArgs)
        except UserError as err:
            raise
        except:
            Console.error("Unexpected error! Could not finish task %s successfully!" % taskname)
            raise
    else:
        raise UserError("No such task: %s" % taskname)
Esempio n. 16
0
def __splitTemplate(value, valueParams):
    """ 
    Split string into plus-expression(s) 

    - patchParam: string node containing the placeholders
    - valueParams: list of params to inject
    """

    # Convert list with nodes into Python dict
    # [a, b, c] => {0:a, 1:b, 2:c}
    mapper = {pos: value for pos, value in enumerate(valueParams)}

    result = []
    splits = __replacer.split(value)
    if len(splits) == 1:
        return None

    pair = Node.Node(None, "plus")

    for entry in splits:
        if entry == "":
            continue

        if len(pair) == 2:
            newPair = Node.Node(None, "plus")
            newPair.append(pair)
            pair = newPair

        if __replacer.match(entry):
            pos = int(entry[1]) - 1

            # Items might be added multiple times. Copy to protect original.
            try:
                repl = mapper[pos]
            except KeyError:
                raise UserError("Invalid positional value: %s in %s" %
                                (entry, value))

            copied = copy.deepcopy(mapper[pos])
            if copied.type not in ("identifier", "call"):
                copied.parenthesized = True
            pair.append(copied)

        else:
            child = Node.Node(None, "string")
            child.value = entry
            pair.append(child)

    return pair
Esempio n. 17
0
    def loadLibrary(self, objectName, fileName, encoding="utf-8", doc=None):
        """
        Creates a new object inside the user API (jasyscript.py) with the given name
        containing all @share'd functions and fields loaded from the given file.
        """

        if objectName in self.__scriptEnvironment:
            raise UserError("Could not import library %s as the object name %s is already used." % (fileName, objectName))

        # Create internal class object for storing shared methods
        class Shared(object):
            pass
        exportedModule = Shared()
        exportedModule.__doc__ = doc or "Imported from %s" % os.path.relpath(fileName, os.getcwd())
        counter = 0

        # Method for being used as a decorator to share methods to the outside
        def share(func):
            nonlocal counter
            setattr(exportedModule, func.__name__, func)
            counter += 1

            return func

        def itemtype(type, name):
            def wrap(cls):
                id = "%s.%s" % (objectName, type[0].upper() + type[1:])
                self.addItemType(id, name, cls)
                return cls
            return wrap

        def postscan():
            def wrap(f):
                self.__postscans.append(f)
                return f
            return wrap

        # Execute given file. Using clean new global environment
        # but add additional decorator for allowing to define shared methods
        # and the session object (self).
        code = open(fileName, "r", encoding=encoding).read()
        exec(compile(code, os.path.abspath(fileName), "exec"), {"share" : share, "itemtype": itemtype, "postscan": postscan, "session" : self})

        # Export destination name as global
        self.__scriptEnvironment[objectName] = exportedModule

        Console.info("Imported %s.", Console.colorize("%s methods" % counter, "magenta"))

        return counter
Esempio n. 18
0
def loadConfig(fileName, encoding="utf-8"):
    """Loads the given configuration file (filename without extension) and returns the parsed object structure."""

    configName = findConfig(fileName)
    if configName is None:
        raise UserError("Unsupported config file: %s" % fileName)

    fileHandle = open(configName, mode="r", encoding=encoding)

    fileExt = os.path.splitext(configName)[1]

    if fileExt == ".yaml":
        result = yaml.load(fileHandle)
    elif fileExt == ".json":
        result = json.load(fileHandle)

    fileHandle.close()
    return result
Esempio n. 19
0
    def addFile(self, relPath, fullPath, itemType, package, override=False):

        fileName = os.path.basename(relPath)
        fileExtension = os.path.splitext(fileName)[1]

        name, construct = self.__resolveConstructor(itemType)
        item = construct.fromPath(self, relPath, package).attach(fullPath)
        fileId = item.getId()
        Console.debug("Registering %s %s" % (item.kind, fileId))

        if not itemType in self.items:
            self.items[itemType] = {}

        # Check for duplication
        if fileId in self.items[itemType] and not override:
            raise UserError("Item ID was registered before: %s" % fileId)

        self.items[itemType][fileId] = item
Esempio n. 20
0
File: Class.py Progetto: zynga/jasy
    def getHighlightedCode(self):
        field = "highlighted[%s]" % self.id
        source = self.project.getCache().read(field, self.mtime)
        if source is None:
            if highlight is None:
                raise UserError(
                    "Could not highlight JavaScript code! Please install Pygments."
                )

            lexer = JavascriptLexer(tabsize=2)
            formatter = HtmlFormatter(full=True,
                                      style="autumn",
                                      linenos="table",
                                      lineanchors="line")
            source = highlight(self.getText(), lexer, formatter)

            self.project.getCache().store(field, source, self.mtime)

        return source
Esempio n. 21
0
    def loadLibrary(self, objectName, fileName, encoding="utf-8", doc=None):
        """
        Creates a new object inside the user API (jasyscript.py) with the given name 
        containing all @share'd functions and fields loaded from the given file.
        """

        if objectName in self.__scriptEnvironment:
            raise UserError(
                "Could not import library %s as the object name %s is already used."
                % (fileName, objectName))

        # Create internal class object for storing shared methods
        class Shared(object):
            pass

        exportedModule = Shared()
        exportedModule.__doc__ = doc or "Imported from %s" % os.path.relpath(
            fileName, os.getcwd())
        counter = 0

        # Method for being used as a decorator to share methods to the outside
        def share(func):
            nonlocal counter
            setattr(exportedModule, func.__name__, func)
            counter += 1

            return func

        # Execute given file. Using clean new global environment
        # but add additional decorator for allowing to define shared methods
        # and the session object (self).
        code = open(fileName, "r", encoding=encoding).read()
        exec(compile(code, os.path.abspath(fileName), "exec"), {
            "share": share,
            "session": self
        })

        # Export destination name as global
        Console.debug("Importing %s shared methods under %s...", counter,
                      objectName)
        self.__scriptEnvironment[objectName] = exportedModule

        return counter
Esempio n. 22
0
    def getApi(self):
        field = "api[%s]" % self.id
        apidata = self.project.getCache().read(field,
                                               self.getModificationTime())

        if not Text.supportsMarkdown:
            raise UserError(
                "Missing Markdown feature to convert package docs into HTML.")

        if apidata is None:
            apidata = Data.ApiData(self.id)
            apidata.main["type"] = "Package"
            apidata.main["doc"] = Text.highlightCodeBlocks(
                Text.markdownToHtml(self.getText()))

            self.project.getCache().store(field, apidata,
                                          self.getModificationTime())

        return apidata
Esempio n. 23
0
    def readQuestions(self,
                      fileName,
                      force=False,
                      autoDelete=True,
                      optional=False,
                      encoding="utf-8"):
        """
        Reads the given configuration file with questions and deletes the file afterwards (by default).

        Returns True when the file was found and processed.

        """

        configFile = findConfig(fileName)
        if configFile is None:
            if optional:
                return False
            else:
                raise UserError(
                    "Could not find configuration file (questions): %s" %
                    configFile)

        data = loadConfig(configFile, encoding=encoding)
        for entry in data:
            question = entry["question"]
            name = entry["name"]

            accept = getKey(entry, "accept", None)
            required = getKey(entry, "required", True)
            default = getKey(entry, "default", None)
            force = getKey(entry, "force", False)

            self.ask(question,
                     name,
                     accept=accept,
                     required=required,
                     default=default,
                     force=force)

        if autoDelete:
            File.rm(configFile)

        return True
Esempio n. 24
0
    def __resolveScanConfig(self, configs):
        scan = []

        for path, config in configs.items():
            if isinstance(config, str):
                config = {"type": config, "package": self.__package}

            else:
                config = copy.deepcopy(config)

            if not "type" in config:
                raise UserError(
                    "No type configured in jasyproject configuration (scan section)"
                )

            if not "package" in config:
                config["package"] = self.__package

            if config["package"] == "":
                config["package"] = None

            config["origpath"] = path
            config["regex"], config["paths"] = self.__createPathRe(path)

            scan.append(config)

        def specificitySort(item):
            """Sorts for specificy of given scan path."""
            origPath = item["origpath"]

            if not "*" in origPath:
                num = 10000
            elif not origPath.endswith("*"):
                num = 1000
            else:
                num = 0

            num += len(origPath)
            return -num

        scan.sort(key=specificitySort)

        return scan
Esempio n. 25
0
    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 = jasy.item.Class.ClassItem
            dist = self.classes
        elif fileExtension in translationExtensions and distname == "translations":
            fileId += os.path.splitext(relPath)[0]
            construct = jasy.item.Translation.TranslationItem
            dist = self.translations
        elif fileName in docFiles:
            fileId += os.path.dirname(relPath)
            fileId = fileId.strip("/")  # edge case when top level directory
            construct = jasy.item.Doc.DocItem
            dist = self.docs
        else:
            fileId += relPath
            construct = jasy.item.Asset.AssetItem
            dist = self.assets

        # Only assets keep unix style paths identifiers
        if construct != jasy.item.Asset.AssetItem:
            fileId = fileId.replace("/", ".")

        # Check for duplication
        if fileId in dist and not override:
            raise UserError("Item ID was registered before: %s" % fileId)

        # Create instance
        item = construct(self, fileId).attach(fullPath)
        Console.debug("Registering %s %s" % (item.kind, fileId))
        dist[fileId] = item
Esempio n. 26
0
    def init(self, autoInitialize=True, updateRepositories=True, scriptEnvironment=None):
        """
        Initialize the actual session with projects.

        :param autoInitialize: Whether the projects should be automatically added when the current folder contains a valid Jasy project.
        :param updateRepositories: Whether to update repositories of all project dependencies.
        :param scriptEnvironment: API object as being used for loadLibrary to add Python features offered by projects.
        :param commandEnvironment: API object as being used for loadCommands to add Python features for any item nodes.

        """

        self.__scriptEnvironment = scriptEnvironment
        self.__updateRepositories = updateRepositories

        if autoInitialize and Config.findConfig("jasyproject"):

            Console.info("Initializing session...")
            Console.indent()

            try:
                self.addProject(Project.getProjectFromPath(".", self))

            except UserError as err:
                Console.outdent(True)
                Console.error(err)
                raise UserError("Critical: Could not initialize session!")

            self.getVirtualProject()

            Console.debug("Active projects (%s):", len(self.__projects))
            Console.indent()

            for project in self.__projects:
                if project.version:
                    Console.debug("%s @ %s", Console.colorize(project.getName(), "bold"), Console.colorize(project.version, "magenta"))
                else:
                    Console.debug(Console.colorize(project.getName(), "bold"))

            Console.outdent()
            Console.outdent()
Esempio n. 27
0
    def __structurize(self, data):
        """
        This method structurizes the incoming data into a cascaded structure representing the file system location (aka
        file IDs) as a tree.

        It further extracts the extensions and merges files with the same name (but different extensions) into the same
        entry. This is especially useful for alternative formats like audio files, videos and fonts. It only respects
        the data of the first entry! So it is not a good idea to have different files with different content stored with
        the same name e.g. content.css and content.png.

        """

        root = {}

        # Easier to debug and understand when sorted
        for fileId in sorted(data):
            current = root
            splits = fileId.split("/")

            # Extract the last item aka the filename itself
            basename = splits.pop()

            # Find the current node to store info on
            for split in splits:
                if split not in current:
                    current[split] = {}
                elif not isinstance(current[split], dict):
                    raise UserError(
                        "Invalid asset structure. Folder names must not be identical to any filename without extension: \"%s\" in %s"
                        % (split, fileId))

                current = current[split]

            # Create entry
            Console.debug("Adding %s..." % fileId)
            current[basename] = data[fileId]

        return root
Esempio n. 28
0
    def attach(self, path):
        self.__path = path

        entry = None

        try:
            if isinstance(path, list):
                mtime = 0
                for entry in path:
                    entryTime = os.stat(entry).st_mtime
                    if entryTime > mtime:
                        mtime = entryTime

                self.mtime = mtime

            else:
                entry = path
                self.mtime = os.stat(entry).st_mtime

        except OSError as oserr:
            raise UserError("Invalid item path: %s" % entry)

        return self
Esempio n. 29
0
    def loadValues(self, fileName, optional=False, encoding="utf-8"):
        """
        Imports the values of the given config file Returns True when the file was found and processed.

        Note: Supports dotted names to store into sub trees
        Note: This method overrides keys when they are already defined!

        """

        configFile = findConfig(fileName)
        if configFile is None:
            if optional:
                return False
            else:
                raise UserError(
                    "Could not find configuration file (values): %s" %
                    configFile)

        data = loadConfig(configFile, encoding=encoding)
        for key in data:
            self.set(key, data[key])

        return True
Esempio n. 30
0
def create(name="myproject",
           origin=None,
           originVersion=None,
           skeleton=None,
           destination=None,
           session=None,
           **argv):
    """
    Creates a new project from a defined skeleton or an existing project's root directory (only if there is a jasycreate
    config file).

    :param name: The name of the new created project
    :type name: string
    :param origin: Path or git url to the base project
    :type origin: string
    :param originVersion: Version of the base project from wich will be created.
    :type originVersion: string
    :param skeleton: Name of a defined skeleton. None for creating from root
    :type skeleton: string
    :param destination: Destination path for the new created project
    :type destination: string
    :param session: An optional session to use as origin project
    :type session: object

    """

    if not validProjectName.match(name):
        raise UserError(
            "Invalid project name: %s (Use lowercase characters and numbers only for broadest compabibility)"
            % name)

    #
    # Initial Checks
    #

    # Figuring out destination folder
    if destination is None:
        destination = name

    destinationPath = os.path.abspath(os.path.expanduser(destination))
    if os.path.exists(destinationPath):
        raise UserError(
            "Cannot create project %s in %s. File or folder exists!" %
            (name, destinationPath))

    # Origin can be either:
    # 1) None, which means a skeleton from the current main project
    # 2) An repository URL
    # 3) A project name known inside the current session
    # 4) Relative or absolute folder path

    originPath = None
    originName = None

    if origin is None:
        originProject = session and session.getMain()

        if originProject is None:
            raise UserError(
                "Auto discovery failed! No Jasy projects registered!")

        originPath = originProject.getPath()
        originName = originProject.getName()
        originRevision = None

    elif Repository.isUrl(origin):
        Console.info("Using remote skeleton")

        tempDirectory = tempfile.TemporaryDirectory()
        originPath = os.path.join(tempDirectory.name, "clone")
        originUrl = origin

        Console.indent()
        originRevision = Repository.update(originUrl, originVersion,
                                           originPath)
        Console.outdent()

        if originRevision is None:
            raise UserError("Could not clone origin repository!")

        Console.debug("Cloned revision: %s" % originRevision)
        if findConfig(os.path.join(
                originPath, "jasycreate")) or os.path.isfile(
                    os.path.join(originPath, "jasycreate.py")):
            originProject = None
        else:
            originProject = getProjectFromPath(originPath, session)
            originName = originProject.getName()

    else:
        originProject = session and session.getProjectByName(origin)
        originVersion = None
        originRevision = None

        if originProject is not None:
            originPath = originProject.getPath()
            originName = origin

        elif os.path.isdir(origin):
            originPath = origin
            if findConfig(os.path.join(
                    originPath, "jasycreate")) or os.path.isfile(
                        os.path.join(originPath, "jasycreate.py")):
                originProject = None
            else:
                originProject = getProjectFromPath(originPath, session)
                originName = originProject.getName()

        else:
            raise UserError("Invalid value for origin: %s" % origin)

    # Figure out the skeleton root folder
    if originProject is not None:
        skeletonDir = os.path.join(
            originPath, originProject.getConfigValue("skeletonDir",
                                                     "skeleton"))
    else:
        skeletonDir = originPath
    if not os.path.isdir(skeletonDir):
        raise UserError('The project %s offers no skeletons!' % originName)

    # For convenience: Use first skeleton in skeleton folder if no other selection was applied
    if skeleton is None:
        if originProject is not None:
            skeleton = getFirstSubFolder(skeletonDir)
        else:
            skeleton = skeletonDir

    # Finally we have the skeleton path (the root folder to copy for our app)
    skeletonPath = os.path.join(skeletonDir, skeleton)
    if not os.path.isdir(skeletonPath):
        raise UserError('Skeleton %s does not exist in project "%s"' %
                        (skeleton, originName))

    #
    # Actual Work
    #

    # Prechecks done
    if originName:
        Console.info('Creating %s from %s %s...',
                     Console.colorize(name, "bold"),
                     Console.colorize(skeleton + " @", "bold"),
                     Console.colorize(originName, "magenta"))
    else:
        Console.info('Creating %s from %s...', Console.colorize(name, "bold"),
                     Console.colorize(skeleton, "bold"))
    Console.debug('Skeleton: %s', Console.colorize(skeletonPath, "grey"))
    Console.debug('Destination: %s', Console.colorize(destinationPath, "grey"))

    # Copying files to destination
    Console.info("Copying files...")
    shutil.copytree(skeletonPath, destinationPath)
    Console.debug("Files were copied successfully.")

    # Close origin project
    if originProject:
        originProject.close()

    # Change to directory before continuing
    os.chdir(destinationPath)

    # Create configuration file from question configs and custom scripts
    Console.info("Starting configuration...")
    config = Config()

    config.set("name", name)
    config.set("jasy.version", jasy.__version__)
    if originName:
        config.set("origin.name", originName)
    config.set("origin.version", originVersion)
    config.set("origin.revision", originRevision)
    config.set("origin.skeleton", os.path.basename(skeletonPath))

    config.injectValues(**argv)
    if originProject is not None:
        config.readQuestions("jasycreate", optional=True)
        config.executeScript("jasycreate.py", optional=True)

    # Do actual replacement of placeholders
    massFilePatcher(destinationPath, config)
    Console.debug("Files were patched successfully.")

    # Done
    Console.info('Your application %s was created successfully!',
                 Console.colorize(name, "bold"))