odoo/openerp/cli/scaffold.py

188 lines
7.0 KiB
Python
Raw Normal View History

2014-05-28 09:31:00 +00:00
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
import ast
2014-05-28 09:31:00 +00:00
import functools
import keyword
import os
import re
import sys
import jinja2
from . import Command
from openerp.modules.module import get_module_root
2014-05-28 09:31:00 +00:00
MANIFEST = '__openerp__'
2014-05-28 09:31:00 +00:00
class Scaffold(Command):
"Generate an Odoo module skeleton."
def run(self, args):
# TODO: bash completion file
parser = argparse.ArgumentParser(
prog="%s scaffold" % sys.argv[0].split(os.path.sep)[-1],
description=self.__doc__
)
parser.add_argument('--init', type=identifier, help='Initialize a new Odoo module')
2014-05-28 09:31:00 +00:00
parser.add_argument('--dest', default=".",
help='Directory where the module should be created/updated (default to current directory)')
parser.add_argument('--model', type=identifier, help="Name of the model to add")
parser.add_argument('--controller', type=identifier, help="Name of the controller to add")
parser.add_argument('--web', action='store_true', default=False,
help="Generate structure for a webclient module")
parser.add_argument('--theme', action='store_true', default=False,
help="Generate structure for a Website theme")
if not args:
sys.exit(parser.print_help())
args = parser.parse_args(args=args)
dest = directory(args.dest)
if args.init:
dest = os.path.join(dest, args.init)
if os.path.exists(dest):
die("Can't initialize module in `%s`: Directory already exists." % dest)
if get_module_root(dest):
die("Can't init a new module in another Odoo module, you probably want to run this "
"command from your project's root")
else:
mroot = get_module_root(dest)
if not mroot:
die("The path `%s` provided does not point to an existing Odoo module. "
"Forgot to `--init` ?" % dest)
dest = mroot
scaffold = ScaffoldModule(dest)
if args.model:
scaffold.add_model(args.model)
if args.controller:
scaffold.add_controller(args.controller)
if args.web:
scaffold.add_webclient_structure()
class ScaffoldModule(object):
"""
Object for scaffolding existing or new Odoo modules
@param path: Path of an existing module or path of module to create
"""
def __init__(self, path):
env = jinja2.Environment(loader=jinja2.PackageLoader(
'openerp.cli', 'scaffold'))
env.filters['snake'] = snake
self.env = env
self.path = functools.partial(os.path.join, directory(path))
self.created = not os.path.exists(self.path())
directory(path, create=True)
if self.created:
self.module_name = self.path().split(os.path.sep)[-1]
self.render_file('%s.jinja2' % MANIFEST, self.path('%s.py' % MANIFEST))
else:
# TODO: get this information from manifest
self.module_name = self.path().split(os.path.sep)[-1]
def add_model(self, model):
model_module = snake(model)
model_file = self.path('models', '%s.py' % model_module)
if os.path.exists(model_file):
die("Model `%s` already exists !" % model_file)
self.add_init_import(self.path('__init__.py'), 'models')
self.add_init_import(self.path('models', '__init__.py'), model_module)
self.render_file('models.jinja2', model_file, model=model)
self.render_file('ir.model.access.jinja2', self.path('security', 'ir.model.access.csv'),
if_exists='append', model=model)
def add_controller(self, controller):
controller_module = snake(controller)
controller_file = self.path('controllers', '%s.py' % controller_module)
if os.path.exists(controller_file):
die("Controller `%s` already exists !" % controller_file)
self.add_init_import(self.path('__init__.py'), 'controllers')
# Check if the controller name correspond to a model and expose result to templates
has_model = self.has_import(self.path('models', '__init__.py'), controller_module)
self.add_init_import(self.path('controllers', '__init__.py'), controller_module)
self.render_file('controllers.jinja2', controller_file, controller=controller,
has_model=has_model)
def add_webclient_structure(self):
prefix = '%s.%%s' % self.module_name
for ext in ('js', 'css', 'xml'):
self.render_file('webclient_%s.jinja2' % ext,
self.path('static', 'src', ext, prefix % ext))
def has_import(self, initfile, module):
with open(initfile, 'r') as f:
for imp in ast.parse(f.read()).body:
if isinstance(imp, ast.Import):
if module in [mod.name for mod in imp.names]:
return True
return False
def ensure_dependency_to(self, module):
# TODO: update dependencies according to --web and --theme
# if args.web:
# args.dependency = 'web'
# elif args.theme:
# args.dependency = 'website'
pass
def add_init_import(self, initfile, module):
if not(os.path.exists(initfile) and self.has_import(initfile, module)):
self.render_file('__init__.jinja2', initfile, if_exists='append', modules=[module])
def render_file(self, template, dest, if_exists='skip', **kwargs):
mode = 'a'
if os.path.exists(dest):
if if_exists == 'replace':
mode = 'w'
elif if_exists != 'append':
print "File `%s` already exists. Skipping it..." % dest
return
else:
kwargs['file_created'] = True
2014-05-28 09:31:00 +00:00
outdir = os.path.dirname(dest)
if not os.path.exists(outdir):
os.makedirs(outdir)
content = self.env.get_template(template).render(module_name=self.module_name, **kwargs)
with open(dest, mode) as f:
f.write(content)
2014-05-28 09:31:00 +00:00
def snake(s):
""" snake cases ``s``
:param str s:
:return: str
"""
# insert a space before each uppercase character preceded by a
# non-uppercase letter
s = re.sub(r'(?<=[^A-Z])\B([A-Z])', r' \1', s)
# lowercase everything, split on whitespace and join
return '_'.join(s.lower().split())
def identifier(s):
if keyword.iskeyword(s):
die("%s is a Python keyword and can not be used as a name" % s)
if not re.match('[A-Za-z_][A-Za-z0-9_]*', s):
die("%s is not a valid Python identifier" % s)
return s
def directory(p, create=False):
2014-05-28 09:31:00 +00:00
expanded = os.path.abspath(
os.path.expanduser(
os.path.expandvars(p)))
if create and not os.path.exists(expanded):
2014-05-28 09:31:00 +00:00
os.makedirs(expanded)
if create and not os.path.isdir(expanded):
2014-05-28 09:31:00 +00:00
die("%s exists but is not a directory" % p)
return expanded
def die(message, code=1):
print >>sys.stderr, message
sys.exit(code)