PageRenderTime 24ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/qooxdoo/tool/bin/create-application.py

https://github.com/Wkasel/qooxdoo
Python | 380 lines | 295 code | 50 blank | 35 comment | 55 complexity | 6903f01fd13d02b10a72b073bc580405 MD5 | raw file
  1. #!/usr/bin/env python
  2. # -*- coding: utf-8 -*-
  3. ################################################################################
  4. #
  5. # qooxdoo - the new era of web development
  6. #
  7. # http://qooxdoo.org
  8. #
  9. # Copyright:
  10. # 2008 - 2010 1&1 Internet AG, Germany, http://www.1und1.de
  11. #
  12. # License:
  13. # LGPL: http://www.gnu.org/licenses/lgpl.html
  14. # EPL: http://www.eclipse.org/org/documents/epl-v10.php
  15. # See the LICENSE file in the project's top-level directory for details.
  16. #
  17. # Authors:
  18. # * Fabian Jakobs (fjakobs)
  19. # * Andreas Ecker (ecker)
  20. #
  21. ################################################################################
  22. import re, os, sys, optparse, shutil, errno, stat, codecs, glob
  23. from string import Template
  24. import qxenviron
  25. from ecmascript.frontend import lang
  26. from generator.runtime.Log import Log
  27. from misc import Path
  28. SCRIPT_DIR = qxenviron.scriptDir
  29. FRAMEWORK_DIR = os.path.normpath(os.path.join(SCRIPT_DIR, os.pardir, os.pardir))
  30. SKELETON_DIR = unicode(os.path.normpath(os.path.join(FRAMEWORK_DIR, "component", "skeleton")))
  31. APP_DIRS = [x for x in os.listdir(SKELETON_DIR) if not re.match(r'^\.',x)]
  32. R_ILLEGAL_NS_CHAR = re.compile(r'(?u)[^\.\w]') # allow unicode, but disallow $
  33. R_SHORT_DESC = re.compile(r'(?m)^short:: (.*)$') # to search "short:: ..." in skeleton's 'readme.txt'
  34. QOOXDOO_VERSION = '' # will be filled later
  35. def getAppInfos():
  36. appInfos = {}
  37. for dir in APP_DIRS:
  38. readme = os.path.join(SKELETON_DIR, dir, "readme.txt")
  39. appinfo = ""
  40. if os.path.isfile(readme):
  41. cont = open(readme, "r").read()
  42. mo = R_SHORT_DESC.search(cont)
  43. if mo:
  44. appinfo = mo.group(1)
  45. appInfos[dir] = appinfo
  46. return appInfos
  47. APP_INFOS = getAppInfos()
  48. def getQxVersion():
  49. global QOOXDOO_VERSION
  50. versionFile = os.path.join(FRAMEWORK_DIR, "version.txt")
  51. version = codecs.open(versionFile,"r", "utf-8").read()
  52. version = version.strip()
  53. QOOXDOO_VERSION = version
  54. return
  55. def createApplication(options):
  56. out = options.out
  57. if sys.platform == 'win32' and re.match( r'^[a-zA-Z]:$', out):
  58. out = out + '\\'
  59. else:
  60. out = os.path.expanduser(out)
  61. if not os.path.isdir(out):
  62. if os.path.isdir(normalizePath(out)):
  63. out = normalizePath(out)
  64. else:
  65. console.error("Output directory '%s' does not exist" % out)
  66. sys.exit(1)
  67. outDir = os.path.join(out, options.name)
  68. copySkeleton(options.skeleton_path, options.type, outDir, options.namespace)
  69. if options.type == "contribution":
  70. patchSkeleton(os.path.join(outDir, "trunk"), FRAMEWORK_DIR, options)
  71. else:
  72. patchSkeleton(outDir, FRAMEWORK_DIR, options)
  73. return
  74. def copySkeleton(skeleton_path, app_type, dir, namespace):
  75. console.log("Copy skeleton into the output directory: %s" % dir)
  76. def rename_folders(root_dir):
  77. # rename name space parts of paths
  78. # rename in class path
  79. source_dir = os.path.join(root_dir, "source", "class", "custom")
  80. out_dir = os.path.join(root_dir, "source", "class")
  81. expand_dir(source_dir, out_dir, namespace)
  82. # rename in resource path
  83. resource_dir = os.path.join(root_dir, "source", "resource", "custom")
  84. out_dir = os.path.join(root_dir, "source", "resource")
  85. expand_dir(resource_dir, out_dir, namespace)
  86. # rename in script path
  87. script_dir = os.path.join(root_dir, "source", "script")
  88. script_files = glob.glob(os.path.join(script_dir, "custom.*js"))
  89. if script_files:
  90. for script_file in script_files:
  91. os.rename(script_file, script_file.replace("custom", namespace))
  92. template = os.path.join(skeleton_path, app_type)
  93. if not os.path.isdir(template):
  94. console.error("Unknown application type '%s'." % app_type)
  95. sys.exit(1)
  96. try:
  97. shutil.copytree(template, dir)
  98. except OSError:
  99. console.error("Failed to copy skeleton, maybe the directory already exists")
  100. sys.exit(1)
  101. if app_type == "contribution":
  102. app_dir = os.path.join(dir, "trunk")
  103. else:
  104. app_dir = dir
  105. rename_folders(app_dir)
  106. if app_type == "contribution":
  107. rename_folders(os.path.join(app_dir, "demo", "default"))
  108. #clean svn directories
  109. for root, dirs, files in os.walk(dir, topdown=False):
  110. if ".svn" in dirs:
  111. filename = os.path.join(root, ".svn")
  112. shutil.rmtree(filename, ignore_errors=False, onerror=handleRemoveReadonly)
  113. def expand_dir(indir, outroot, namespace):
  114. "appends namespace parts to outroot, and renames indir to the last part"
  115. if not (os.path.isdir(indir) and os.path.isdir(outroot)):
  116. return
  117. ns_parts = namespace.split('.')
  118. target = outroot
  119. for part in ns_parts:
  120. target = os.path.join(target, part)
  121. if part == ns_parts[-1]: # it's the last part
  122. os.rename(indir, target)
  123. else:
  124. os.mkdir(target)
  125. def patchSkeleton(dir, framework_dir, options):
  126. absPath = normalizePath(framework_dir)
  127. if absPath[-1] == "/":
  128. absPath = absPath[:-1]
  129. if sys.platform == 'cygwin':
  130. if re.match( r'^\.{1,2}\/', dir ):
  131. relPath = Path.rel_from_to(normalizePath(dir), framework_dir)
  132. elif re.match( r'^/cygdrive\b', dir):
  133. relPath = Path.rel_from_to(dir, framework_dir)
  134. else:
  135. relPath = Path.rel_from_to(normalizePath(dir), normalizePath(framework_dir))
  136. else:
  137. relPath = Path.rel_from_to(normalizePath(dir), normalizePath(framework_dir))
  138. relPath = re.sub(r'\\', "/", relPath)
  139. if relPath[-1] == "/":
  140. relPath = relPath[:-1]
  141. if not os.path.isdir(os.path.join(dir, relPath)):
  142. console.error("Relative path to qooxdoo directory is not correct: '%s'" % relPath)
  143. sys.exit(1)
  144. if options.type == "contribution":
  145. relPath = os.path.join(os.pardir, os.pardir, "qooxdoo", QOOXDOO_VERSION)
  146. relPath = re.sub(r'\\', "/", relPath)
  147. for root, dirs, files in os.walk(dir):
  148. for file in files:
  149. split = file.split(".")
  150. if len(split) >= 3 and split[-2] == "tmpl":
  151. outFile = os.path.join(root, ".".join(split[:-2] + split[-1:]))
  152. inFile = os.path.join(root, file)
  153. console.log("Patching file '%s'" % outFile)
  154. #config = MyTemplate(open(inFile).read())
  155. config = Template(open(inFile).read())
  156. out = open(outFile, "w")
  157. out.write(
  158. config.substitute({
  159. "Name": options.name,
  160. "Namespace": options.namespace,
  161. "NamespacePath" : (options.namespace).replace('.', '/'),
  162. "REL_QOOXDOO_PATH": relPath,
  163. "ABS_QOOXDOO_PATH": absPath,
  164. "QOOXDOO_VERSION": QOOXDOO_VERSION,
  165. "Cache" : options.cache,
  166. }).encode('utf-8')
  167. )
  168. out.close()
  169. os.remove(inFile)
  170. for root, dirs, files in os.walk(dir):
  171. for file in [file for file in files if file.endswith(".py")]:
  172. os.chmod(os.path.join(root, file), (stat.S_IRWXU
  173. |stat.S_IRGRP |stat.S_IXGRP
  174. |stat.S_IROTH |stat.S_IXOTH)) # 0755
  175. def handleRemoveReadonly(func, path, exc):
  176. # For Windows the 'readonly' must not be set for resources to be removed
  177. excvalue = exc[1]
  178. if func in (os.rmdir, os.remove) and excvalue.errno == errno.EACCES:
  179. os.chmod(path, stat.S_IRWXU| stat.S_IRWXG| stat.S_IRWXO) # 0777
  180. func(path)
  181. else:
  182. raise
  183. def normalizePath(path):
  184. # Fix Windows annoyance to randomly return drive letters uppercase or lowercase.
  185. # Under Cygwin the user could also supply a lowercase drive letter. For those
  186. # two systems, the drive letter is always converted to uppercase, the remaining
  187. # path to lowercase
  188. if not sys.platform == 'win32' and not sys.platform == 'cygwin':
  189. return path
  190. path = re.sub(r'\\+', "/", path)
  191. if sys.platform == 'cygwin':
  192. search = re.match( r'^/cygdrive/([a-zA-Z])(/.*)$', path)
  193. if search:
  194. return search.group(1).upper() + ":" + search.group(2).lower()
  195. search = re.match( r'^([a-zA-Z])(:.*)$', path )
  196. if search:
  197. return search.group(1).upper() + search.group(2).lower()
  198. return path
  199. def checkNamespace(options):
  200. # check availability and spelling
  201. if not options.namespace:
  202. if R_ILLEGAL_NS_CHAR.search(options.name):
  203. convertedName = R_ILLEGAL_NS_CHAR.sub("_", options.name)
  204. console.log("WARNING: Converted illegal characters in name (from %s to %s)" % (options.name, convertedName))
  205. options.name = convertedName
  206. options.namespace = convertedName.lower()
  207. else:
  208. options.namespace = options.name.lower()
  209. else:
  210. options.namespace = options.namespace.decode('utf-8')
  211. if R_ILLEGAL_NS_CHAR.search(options.namespace):
  212. convertedNamespace = R_ILLEGAL_NS_CHAR.sub("_", options.namespace)
  213. console.log("WARNING: Converted illegal characters in namespace (from %s to %s)" % (options.namespace, convertedNamespace))
  214. options.namespace = convertedNamespace
  215. for namepart in options.namespace.split("."):
  216. # check well-formed identifier
  217. if not re.match(lang.IDENTIFIER_REGEXP, namepart):
  218. console.error("Name space part must be a legal JS identifier, but is not: '%s'" % namepart)
  219. sys.exit(1)
  220. # check reserved words
  221. if namepart in (lang.GLOBALS + lang.RESERVED.keys()):
  222. console.error("JS reserved word '%s' is not allowed as name space part" % namepart)
  223. sys.exit(1)
  224. def skeletonsHelpString():
  225. helpString = "Type of the application to create, one of: "
  226. helpString += str(map(str, sorted(APP_INFOS.keys()))) + "."
  227. helpString += str("; ".join(["'%s' -- %s" % (x, y) for x,y in sorted(APP_INFOS.items())]))
  228. helpString += ". (Default: %default)"
  229. return helpString
  230. def main():
  231. parser = optparse.OptionParser()
  232. parser.set_usage('''\
  233. %prog --name APPLICATIONNAME [--out DIRECTORY]
  234. [--namespace NAMESPACE] [--type TYPE]
  235. [-logfile LOGFILE] [--skeleton-path PATH]
  236. Script to create a new qooxdoo application.
  237. Example: For creating a regular GUI application \'myapp\' you could execute:
  238. %prog --name myapp''')
  239. parser.add_option(
  240. "-n", "--name", dest="name", metavar="APPLICATIONNAME",
  241. help="Name of the application. An application folder with identical name will be created. (Required)"
  242. )
  243. parser.add_option(
  244. "-o", "--out", dest="out", metavar="DIRECTORY", default=".",
  245. help="Output directory for the application folder. (Default: %default)"
  246. )
  247. parser.add_option(
  248. "-s", "--namespace", dest="namespace", metavar="NAMESPACE", default=None,
  249. help="Applications's top-level namespace. (Default: APPLICATIONNAME)"
  250. )
  251. parser.add_option(
  252. "-t", "--type", dest="type", metavar="TYPE", default="gui",
  253. help=skeletonsHelpString()
  254. )
  255. parser.add_option(
  256. "-l", "--logfile", dest="logfile", metavar="LOGFILE",
  257. default=None, type="string", help="Log file"
  258. )
  259. parser.add_option(
  260. "-p", "--skeleton-path", dest="skeleton_path", metavar="PATH", default=SKELETON_DIR,
  261. help="(Advanced) Path where the script looks for skeletons. " +
  262. "The directory must contain sub directories named by " +
  263. "the application types. (Default: %default)"
  264. )
  265. parser.add_option(
  266. "--cache", dest="cache", metavar="PATH", default="${TMPDIR}/qx${QOOXDOO_VERSION}/cache",
  267. help="Path to the cache directory; will be entered into config.json's CACHE macro (Default: %default)"
  268. )
  269. (options, args) = parser.parse_args(sys.argv[1:])
  270. if not options.name:
  271. parser.print_help()
  272. sys.exit(1)
  273. else:
  274. options.name = options.name.decode('utf-8')
  275. # Initialize console
  276. global console
  277. console = Log(options.logfile, "info")
  278. checkNamespace(options)
  279. getQxVersion()
  280. createApplication(options)
  281. console.log("DONE")
  282. pattern = r"""
  283. %(delim)s(?:
  284. (?P<escaped>%(delim)s) | # Escape sequence of two delimiters
  285. (?P<named>%(id)s) | # delimiter and a Python identifier
  286. {(?P<braced>%(id)s)} | # delimiter and a braced identifier
  287. (?P<invalid>) # Other ill-formed delimiter exprs
  288. )
  289. """
  290. class MyTemplate(Template):
  291. #delimiter = "%"
  292. pattern = r"""
  293. \$(?:
  294. (?P<escaped>\$) | # Escape sequence of two delimiters
  295. {(?P<braced>[_a-z][_a-z0-9]*)} | # delimiter and a braced identifier
  296. (?P<invalid>) # Other ill-formed delimiter exprs
  297. )
  298. """
  299. if __name__ == '__main__':
  300. try:
  301. main()
  302. except KeyboardInterrupt:
  303. print
  304. print "Keyboard interrupt!"
  305. sys.exit(1)