/couchapp/generator.py

http://github.com/couchapp/couchapp · Python · 368 lines · 188 code · 55 blank · 125 comment · 33 complexity · 8fefa555f81a4bd0c2951d7669912f4c MD5 · raw file

  1. # -*- coding: utf-8 -*-
  2. #
  3. # This file is part of couchapp released under the Apache 2 license.
  4. # See the NOTICE for more information.
  5. from __future__ import with_statement
  6. import logging
  7. import os
  8. import sys
  9. from shutil import Error, copy2, copytree
  10. from couchapp import localdoc
  11. from couchapp.errors import AppError
  12. from couchapp.util import is_py2exe, is_windows, relpath, setup_dir, user_path
  13. from couchapp.util import setup_dirs
  14. __all__ = ["init_basic", "init_template", "generate_function", "generate"]
  15. logger = logging.getLogger(__name__)
  16. DEFAULT_APP_TREE = (
  17. '_attachments',
  18. 'filters',
  19. 'lists',
  20. 'shows',
  21. 'updates',
  22. 'views',
  23. )
  24. TEMPLATE_TYPES = (
  25. 'app',
  26. 'functions',
  27. 'vendor',
  28. )
  29. def init_basic(path):
  30. '''
  31. Generate a basic CouchApp which contain following files::
  32. /path/
  33. .couchapprc
  34. .couchappignore
  35. _attachments/
  36. lists/
  37. shows/
  38. updates/
  39. views/
  40. .. versionadded:: 1.1
  41. '''
  42. setup_dir(path, require_empty=True)
  43. setup_dirs(os.path.join(path, n) for n in DEFAULT_APP_TREE)
  44. save_id(path, '_design/{0}'.format(os.path.split(path)[-1]))
  45. localdoc.document(path, create=True)
  46. def init_template(path, template='default'):
  47. '''
  48. Generates a CouchApp via template
  49. :param str path: the app dir
  50. :param str template: the templates set name. In following example, it is
  51. ``mytmpl``.
  52. We expect template dir has following structure::
  53. templates/
  54. app/
  55. functions/
  56. vendor/
  57. mytmpl/
  58. app/
  59. functions/
  60. vendor/
  61. vuejs/
  62. myvue/
  63. app/
  64. functions/
  65. vendor/
  66. vueform/
  67. app/
  68. functions/
  69. vendor/
  70. The ``templates/app`` will be used as default app template.
  71. ``templates/functions`` and ``templates/vender`` are default, also.
  72. And we can create a dir ``mytmpl`` as custom template set.
  73. The template set name can be nested, e.g. ``vuejs/myvue``.
  74. ..versionadded:: 1.1
  75. '''
  76. if template in TEMPLATE_TYPES:
  77. raise AppError('template name connot be {0}.'.format(TEMPLATE_TYPES))
  78. tmpl_name = os.path.normpath(template) if template else ''
  79. # copy ``<template set>/app``
  80. src_dir = find_template_dir(tmpl_name, 'app', raise_error=True)
  81. copy_helper(src_dir, path)
  82. # construct basic dirs
  83. setup_dirs((os.path.join(path, n) for n in DEFAULT_APP_TREE),
  84. require_empty=False)
  85. # add vendor
  86. src_dir = find_template_dir(tmpl_name, tmpl_type='vendor')
  87. if src_dir is None:
  88. logger.debug('vendor not found in template set "{0}". '
  89. 'fallback to default vendor.'.format(tmpl_name))
  90. src_dir = find_template_dir(tmpl_type='vendor')
  91. vendor_dir = os.path.join(path, 'vendor')
  92. copy_helper(src_dir, vendor_dir)
  93. save_id(path, '_design/{0}'.format(os.path.split(path)[-1]))
  94. localdoc.document(path, create=True)
  95. def generate_function(path, func_type, name, template='default'):
  96. '''
  97. Generate function from template
  98. :param path: the app dir
  99. :param func_type: function type. e.g. ``view``, ``show``.
  100. :param name: the function name
  101. :param template: the template set
  102. The big picture of template dir is discribed
  103. in :py:func:`~couchapp.generate.init_template`.
  104. Here we show the detail structure of ``functions`` dir::
  105. functions/
  106. filter.js
  107. list.js
  108. map.js
  109. reduce.js
  110. show.js
  111. spatial.js
  112. update.js
  113. validate_doc_update.js
  114. ...
  115. myfunc.js
  116. '''
  117. tmpl_name = os.path.join(*template.split('/'))
  118. tmpl_dir = find_template_dir(tmpl_name, tmpl_type='functions',
  119. raise_error=True)
  120. file_list = [] # [(src, dest), ...]
  121. empty_dir = False
  122. if func_type == 'view':
  123. dir_ = os.path.join(path, 'views', name)
  124. empty_dir = True
  125. file_list.append(('map.js', 'map.js'))
  126. file_list.append(('reduce.js', 'reduce.js'))
  127. elif func_type in ('filter', 'list', 'show', 'update'):
  128. dir_ = os.path.join(path, '{0}s'.format(func_type))
  129. file_list.append(('{0}.js'.format(func_type),
  130. '{0}.js'.format(name)))
  131. elif func_type == 'function': # user defined function
  132. dir_ = path
  133. file_list.append(('{0}.js'.format(name),
  134. '{0}.js'.format(name)))
  135. elif func_type == 'spatial':
  136. dir_ = os.path.join(path, 'spatial')
  137. file_list.append(('spatial.js', '{0}.js'.format(name)))
  138. elif func_type == 'validate_doc_update':
  139. dir_ = path
  140. file_list.append(('validate_doc_update.js', 'validate_doc_update.js'))
  141. else:
  142. raise AppError('unrecognized function type "{0}"'.format(func_type))
  143. setup_dir(dir_, require_empty=empty_dir)
  144. for src, dest in file_list:
  145. full_src = os.path.join(tmpl_dir, src)
  146. full_dest = os.path.join(dir_, dest)
  147. try:
  148. copy2(full_src, full_dest)
  149. except Error:
  150. logger.warning('function "%s" not found in "%s"', src, tmpl_dir)
  151. else:
  152. logger.debug('function "%s" generated successfully', dest)
  153. logger.info('enjoy the %s function, "%s"!', func_type, name)
  154. def generate_vendor(path, name, template='default'):
  155. '''
  156. Generate vendor from template set
  157. :param path: the app dir
  158. :param name: the vendor name
  159. :param template: the template set
  160. '''
  161. tmpl_name = os.path.join(*template.split('/'))
  162. tmpl_dir = find_template_dir(tmpl_name, tmpl_type='vendor',
  163. raise_error=True)
  164. vendor_dir = os.path.join(path, 'vendor')
  165. setup_dir(vendor_dir)
  166. copy_helper(tmpl_dir, vendor_dir)
  167. logger.debug('vendor dir "%s"', tmpl_dir)
  168. logger.info('vendor "%s" generated successfully', name)
  169. def copy_helper(src, dest):
  170. '''
  171. copy helper similar to ``shutil.copytree``
  172. But we do not require ``dest`` non-exist
  173. :param str src: source dir
  174. :param str dest: destination dir
  175. e.g::
  176. foo/
  177. bar.txt
  178. baz/
  179. *empty dir*
  180. ``copy_helper('foo', 'bar')`` will copy ``bar.txt`` as ``baz/bar.txt``.
  181. ..versionchanged: 1.1
  182. '''
  183. if not os.path.isdir(src):
  184. raise OSError('source "{0}" is not a directory'.format(src))
  185. setup_dir(dest, require_empty=False)
  186. for p in os.listdir(src):
  187. _src = os.path.join(src, p)
  188. _dest = os.path.join(dest, p)
  189. if os.path.isdir(_src):
  190. copytree(_src, _dest)
  191. else:
  192. copy2(_src, _dest)
  193. def find_template_dir(tmpl_name='default', tmpl_type='', raise_error=False):
  194. '''
  195. Find template dir for different platform
  196. :param tmpl_name: The template name under ``templates``.
  197. It can be empty string.
  198. If it is set to ``default``, we will use consider
  199. the tmpl_name as empty.
  200. e.g. ``mytmpl`` mentioned in the docstring of
  201. :py:func:`~couchapp.generate.init_template`
  202. :param tmpl_type: the type of template.
  203. e.g. 'app', 'functions', 'vendor'
  204. :param bool raise_error: raise ``AppError`` if not found
  205. :return: the absolute path or ``None`` if not found
  206. We will check the ``<search path>/templates/<tmpl_name>/<tmpl_type>`` is
  207. dir or not. The first matched win.
  208. For posix platform, the search locations are following:
  209. - ~/.couchapp/
  210. - <module dir path>/
  211. - <module dir path>/../
  212. - /usr/share/couchapp/
  213. - /usr/local/share/couchapp/
  214. - /opt/couchapp/
  215. For darwin (OSX) platform, we have some extra search locations:
  216. - ${HOME}/Library/Application Support/Couchapp/
  217. For windows with standlone binary (py2exe):
  218. - <executable dir path>/
  219. - <executable dir path>/../
  220. For windows with python interpreter:
  221. - ${USERPROFILE}/.couchapp/
  222. - <module dir path>/
  223. - <module dir path>/../
  224. - <python prefix>/Lib/site-packages/couchapp/
  225. ..versionchanged:: 1.1
  226. '''
  227. if tmpl_type and tmpl_type not in TEMPLATE_TYPES:
  228. raise AppError('invalid template type "{0}"'.format(tmpl_type))
  229. if tmpl_name == 'default':
  230. tmpl_name = ''
  231. modpath = os.path.dirname(__file__)
  232. search_paths = user_path() + [
  233. modpath,
  234. os.path.join(modpath, '..'),
  235. ]
  236. if os.name == 'posix':
  237. search_paths.extend([
  238. '/usr/share/couchapp',
  239. '/usr/local/share/couchapp',
  240. '/opt/couchapp',
  241. ])
  242. elif is_py2exe():
  243. search_paths.append(os.path.dirname(sys.executable))
  244. elif is_windows():
  245. search_paths.append(
  246. os.path.join(sys.prefix, 'Lib', 'site-packages', 'couchapp')
  247. )
  248. # extra path for darwin
  249. if sys.platform.startswith('darwin'):
  250. search_paths.append(
  251. os.path.expanduser('~/Library/Application Support/Couchapp')
  252. )
  253. # the first win!
  254. for path in search_paths:
  255. path = os.path.normpath(path)
  256. path = os.path.join(path, 'templates', tmpl_name, tmpl_type)
  257. if os.path.isdir(path):
  258. logger.debug('template path match: "{0}"'.format(path))
  259. return path
  260. logger.debug('template search path: "{0}" not found'.format(path))
  261. if raise_error:
  262. logger.info('please use "-d" to checkout search paths.')
  263. raise AppError('template "{0}/{1}" not found.'.format(
  264. tmpl_name, tmpl_type))
  265. return None
  266. def generate(path, kind, name, **opts):
  267. kinds = ('view', 'list', 'show', 'filter', 'function', 'vendor', 'update',
  268. 'spatial')
  269. if kind not in kinds:
  270. raise AppError("Can't generate '{0}' in your couchapp. "
  271. 'generator is unknown.'.format(kind))
  272. if kind == 'vendor':
  273. return generate_vendor(path, name, opts.get('template', 'default'))
  274. generate_function(path, kind, name, opts.get('template', 'default'))
  275. def save_id(app_path, name):
  276. '''
  277. Save ``name`` into ``app_path/_id`` file.
  278. if file exists, we will overwride it.
  279. :param str app_dir:
  280. :param str name:
  281. '''
  282. with open(os.path.join(app_path, '_id'), 'wb') as f:
  283. f.write(name)