diff --git a/openerp/cli/__init__.py b/openerp/cli/__init__.py index 2f9caad3ba6..44141625121 100644 --- a/openerp/cli/__init__.py +++ b/openerp/cli/__init__.py @@ -36,6 +36,7 @@ class Help(Command): import server import deploy +import scaffold def main(): args = sys.argv[1:] diff --git a/openerp/cli/scaffold.py b/openerp/cli/scaffold.py new file mode 100644 index 00000000000..4d9304c6e9e --- /dev/null +++ b/openerp/cli/scaffold.py @@ -0,0 +1,106 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +import argparse +import functools +import keyword +import os +import re +import sys + +import jinja2 + +from . import Command + +class Scaffold(Command): + "Generate an Odoo module skeleton." + + def __init__(self): + super(Scaffold, self).__init__() + env = jinja2.Environment(loader=jinja2.PackageLoader( + 'openerp.cli', 'scaffold')) + env.filters['snake'] = snake + self.env = env + + def scaffold(self, args): + # TODO: make this function callable even if the module already + # exists. (update mode for scaffolding) + from pudb import set_trace;set_trace() ############################## Breakpoint ############################## + args.dependency = 'base' + if args.web: + args.dependency = 'web' + elif args.theme: + args.dependency = 'website' + dest = os.path.abspath(os.path.expanduser(args.dest)) + + module_name = snake(args.module) + module = functools.partial(os.path.join, dest, module_name) + + if os.path.exists(module()): + message = "The path `%s` already exists." % module() + die(message) + + self.dump('__openerp__.jinja2', module('__openerp__.py'), config=args) + self.dump('__init__.jinja2', module('__init__.py'), modules=[ + args.controller and 'controllers', + args.model and 'models' + ]) + self.dump('ir.model.access.jinja2', module('security', 'ir.model.access.csv'), config=args) + + + def dump(self, template, dest, **kwargs): + outdir = os.path.dirname(dest) + if not os.path.exists(outdir): + os.makedirs(outdir) + self.env.get_template(template).stream(**kwargs).dump(dest) + # add trailing newline which jinja removes + with open(dest, 'a') as f: + f.write('\n') + + def run(self, args): + parser = argparse.ArgumentParser( + prog="%s scaffold" % sys.argv[0].split(os.path.sep)[-1], + description=self.__doc__ + ) + parser.add_argument('module', help="Name of the module to generate") + parser.add_argument('dest', nargs='?', help='Directory where the module should be created (default to current directory)', default=".") + + + if not args: + sys.exit(parser.print_help()) + + args = parser.parse_args(args=args) + + self.scaffold(args) + +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): + expanded = os.path.abspath( + os.path.expanduser( + os.path.expandvars(p))) + if not os.path.exists(expanded): + os.makedirs(expanded) + if not os.path.isdir(expanded): + die("%s exists but is not a directory" % p) + return expanded + +def die(message, code=1): + print >>sys.stderr, message + sys.exit(code) diff --git a/openerp/cli/scaffold/__init__.jinja2 b/openerp/cli/scaffold/__init__.jinja2 new file mode 100644 index 00000000000..0124cdd2d93 --- /dev/null +++ b/openerp/cli/scaffold/__init__.jinja2 @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +{% for module in modules if module -%} +import {{ module }} +{% endfor %} diff --git a/openerp/cli/scaffold/__openerp__.jinja2 b/openerp/cli/scaffold/__openerp__.jinja2 new file mode 100644 index 00000000000..0dbd6f83fbc --- /dev/null +++ b/openerp/cli/scaffold/__openerp__.jinja2 @@ -0,0 +1,26 @@ +# -*- coding: utf-8 -*- +{ + 'name': "{{ config.module }}", + # short description, used as subtitles on modules listings + 'summary': """ + Short (1 phrase/line) summary of the module's purpose, used as + subtitle on modules listing or apps.openerp.com""", + # long description of module purpose + 'description': """ + """, + # Who you are + 'author': "Acme Corp.", + 'website': "http://www.example.com", + + # categories can be used to filter modules in modules listing + 'category': 'Uncategorized', + 'version': '0.1', + + # any module necessary for this one to work correctly + 'depends': ['{{ config.dependency }}'], + 'data': [ + {{- "'security/ir.model.access.csv'" if config.model -}} + ], + 'tests': [ + ], +} diff --git a/openerp/cli/scaffold/controllers.jinja2 b/openerp/cli/scaffold/controllers.jinja2 new file mode 100644 index 00000000000..3cc47365d40 --- /dev/null +++ b/openerp/cli/scaffold/controllers.jinja2 @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- + +from openerp import http +from openerp.addons.web.controllers import main + +class {{ config.controller }}(main.Home): + @http.route('/', auth='none') + def index(self): + return "Hello, world!" diff --git a/openerp/cli/scaffold/ir.model.access.jinja2 b/openerp/cli/scaffold/ir.model.access.jinja2 new file mode 100644 index 00000000000..e5e2fb35fe7 --- /dev/null +++ b/openerp/cli/scaffold/ir.model.access.jinja2 @@ -0,0 +1,6 @@ +id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink +{% if config.model -%} +access_{{ config.module|snake }}_{{ config.model|snake }},{{- '' -}} +access_{{ config.module|snake }}_{{ config.model|snake }},{{- '' -}} +model_{{ config.module|snake }}_{{ config.model|snake }},,1,0,0,0 +{%- endif %} diff --git a/openerp/cli/scaffold/models.jinja2 b/openerp/cli/scaffold/models.jinja2 new file mode 100644 index 00000000000..878bf9be4f6 --- /dev/null +++ b/openerp/cli/scaffold/models.jinja2 @@ -0,0 +1,9 @@ +# -*- coding: utf-8 -*- +from openerp.osv import orm, fields + +class {{ config.model }}(orm.Model): + _name = "{{ config.module|snake }}.{{ config.model|snake }}" + + _columns = { + 'name': fields.char(), + }