Source code for jasy.core.Create

#
# Jasy - Web Tooling Framework
# Copyright 2010-2012 Zynga Inc.
# Copyright 2013-2014 Sebastian Werner
#

import re
import os
import os.path
import shutil
import tempfile

import jasy

from jasy.core.Project import getProjectFromPath
from jasy.core.Util import getKey
from jasy.core.Config import Config, findConfig
from jasy import UserError

import jasy.core.Console as Console
import jasy.vcs.Repository as Repository


[docs]def getFirstSubFolder(start): for root, dirs, files in os.walk(start): for directory in dirs: if not directory.startswith("."): return directory return None
fieldPattern = re.compile(r"\$\${([_a-z][_a-z0-9\.]*)}", re.IGNORECASE | re.VERBOSE)
[docs]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()
validProjectName = re.compile(r"^[a-z][a-z0-9]*$")
[docs]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"))