From 14f7d31a5f3d37f051b26d329eefd470ce010d5c Mon Sep 17 00:00:00 2001 From: Fabien Meghazi Date: Thu, 29 May 2014 16:25:30 +0200 Subject: [PATCH] Refactor scaffolding engine so it can be used from outside --- openerp/cli/scaffold.py | 204 +++++++++++--------- openerp/cli/scaffold/__init__.jinja2 | 2 +- openerp/cli/scaffold/__openerp__.jinja2 | 3 +- openerp/cli/scaffold/controllers.jinja2 | 12 +- openerp/cli/scaffold/ir.model.access.jinja2 | 10 +- openerp/cli/scaffold/models.jinja2 | 4 +- 6 files changed, 130 insertions(+), 105 deletions(-) diff --git a/openerp/cli/scaffold.py b/openerp/cli/scaffold.py index 4ea1fbd0200..930d454e831 100644 --- a/openerp/cli/scaffold.py +++ b/openerp/cli/scaffold.py @@ -12,94 +12,11 @@ import jinja2 from . import Command +MANIFEST = '__openerp__' + 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 - self.manifest = '__openerp__' - - def scaffold(self, args): - args.dependency = 'base' - # TODO: update dependencies according to --web and --theme - # if args.web: - # args.dependency = 'web' - # elif args.theme: - # args.dependency = 'website' - - dest = directory(args.dest) - if args.init: - args.module = snake(args.init) - module = functools.partial(os.path.join, dest, args.module) - if os.path.exists(module()): - die("Can't initialize module in `%s`: Directory already exists." % module()) - if self.get_module_root(dest): - die("Can't init a new module in another module, you probably want to run this " - "command from your project's root") - else: - args.module, dest = self.get_module_root(dest) - module = functools.partial(os.path.join, dest) - - if args.init: - self.dump('%s.jinja2' % self.manifest, module('%s.py' % self.manifest), config=args) - - if args.model: - model_module = snake(args.model) - model_file = module('models', '%s.py' % model_module) - if os.path.exists(model_file): - die("Model `%s` already exists !" % model_file) - self.add_init_import(module('__init__.py'), 'models') - self.add_init_import(module('models', '__init__.py'), model_module) - self.dump('models.jinja2', model_file, config=args) - self.dump('ir.model.access.jinja2', module('security', 'ir.model.access.csv'), config=args) - - if args.controller: - controller_module = snake(args.controller) - controller_file = module('controllers', '%s.py' % controller_module) - if os.path.exists(controller_file): - die("Controller `%s` already exists !" % controller_file) - self.add_init_import(module('__init__.py'), 'controllers') - # Check if the controller name correspond to a model and expose result to templates - args.has_model = self.has_import(module('models', '__init__.py'), controller_module) - self.add_init_import(module('controllers', '__init__.py'), controller_module) - self.dump('controllers.jinja2', module('controllers', controller_file), config=args) - - def get_module_root(self, path): - module_name = path.split(os.path.sep)[-1] - # find the module's root directory - while not os.path.exists(os.path.join(path, '%s.py' % self.manifest)): - new_path = os.path.abspath(os.path.join(path, os.pardir)) - if path == new_path: - return None - module_name = path.split(os.path.sep)[-1] - path = new_path - return (module_name, path) - - 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 add_init_import(self, initfile, module): - if not(os.path.exists(initfile) and self.has_import(initfile, module)): - self.dump('__init__.jinja2', initfile, modules=[module]) - - def dump(self, template, dest, **kwargs): - outdir = os.path.dirname(dest) - kwargs['create'] = not os.path.exists(dest) - if not os.path.exists(outdir): - os.makedirs(outdir) - content = self.env.get_template(template).render(**kwargs) - with open(dest, 'a') as f: - f.write(content) - def run(self, args): # TODO: bash completion file parser = argparse.ArgumentParser( @@ -124,7 +41,99 @@ class Scaffold(Command): if not args: sys.exit(parser.print_help()) args = parser.parse_args(args=args) - self.scaffold(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) + +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.dump('%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.dump('models.jinja2', model_file, model=model) + self.dump('ir.model.access.jinja2', self.path('security', 'ir.model.access.csv'), 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.dump('controllers.jinja2', self.path('controllers', controller_file), + controller=controller, has_model=has_model) + + 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.dump('__init__.jinja2', initfile, modules=[module]) + + def dump(self, template, dest, **kwargs): + outdir = os.path.dirname(dest) + kwargs['file_created'] = not os.path.exists(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, 'a') as f: + f.write(content) def snake(s): """ snake cases ``s`` @@ -145,16 +154,33 @@ def identifier(s): die("%s is not a valid Python identifier" % s) return s -def directory(p): +def directory(p, create=False): expanded = os.path.abspath( os.path.expanduser( os.path.expandvars(p))) - if not os.path.exists(expanded): + if create and not os.path.exists(expanded): os.makedirs(expanded) - if not os.path.isdir(expanded): + if create and not os.path.isdir(expanded): die("%s exists but is not a directory" % p) return expanded +def get_module_root(path): + """ + Get closest module's root begining from path + + @param path: Path from which the lookup should start + + @return: Module root path + """ + # find the module's root directory + while not os.path.exists(os.path.join(path, '%s.py' % MANIFEST)): + new_path = os.path.abspath(os.path.join(path, os.pardir)) + if path == new_path: + return None + path = new_path + return path + + 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 index b3cd02662e0..e8b1efb3fc7 100644 --- a/openerp/cli/scaffold/__init__.jinja2 +++ b/openerp/cli/scaffold/__init__.jinja2 @@ -1,3 +1,3 @@ -{% if create -%}# -*- coding: utf-8 -*-{%- endif %} +{% if file_created -%}# -*- coding: utf-8 -*-{%- endif %} {% for module in modules if module -%}import {{ module }} {%- endfor %} diff --git a/openerp/cli/scaffold/__openerp__.jinja2 b/openerp/cli/scaffold/__openerp__.jinja2 index c8bd331d063..38f60d5241b 100644 --- a/openerp/cli/scaffold/__openerp__.jinja2 +++ b/openerp/cli/scaffold/__openerp__.jinja2 @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- { - 'name': "{{ config.module }}", + 'name': "{{ module_name }}", 'summary': """ Short (1 phrase/line) summary of the module's purpose, used as @@ -21,7 +21,6 @@ # any module necessary for this one to work correctly 'depends': ['base'], 'data': [ - # {{- "'security/ir.model.access.csv'" if config.model -}} ], 'demo': [ diff --git a/openerp/cli/scaffold/controllers.jinja2 b/openerp/cli/scaffold/controllers.jinja2 index 78099b1f939..d8756e76524 100644 --- a/openerp/cli/scaffold/controllers.jinja2 +++ b/openerp/cli/scaffold/controllers.jinja2 @@ -2,12 +2,12 @@ from openerp import http from openerp.addons.web.controllers import main -class {{ config.controller }}(main.Home): - @http.route('/{{ config.module }}/{{ config.controller }}', auth='public') +class {{ controller }}(main.Home): + @http.route('/{{ module_name }}/{{ controller }}', auth='public') def index(self): return "Hello, world!" -{% if config.has_model %} - @http.route('/{{ config.module }}/{{ config.controller }}/'], type='http', auth='public') - def {{ config.controller }}(self, {{ config.controller }}, **kw): - return "Hello, %r!" % {{ config.controller }} +{% if has_model %} + @http.route('/{{ module_name }}/{{ controller }}/'], type='http', auth='public') + def {{ controller }}(self, {{ controller }}, **kw): + return "Hello, %r!" % {{ controller }} {% endif %} diff --git a/openerp/cli/scaffold/ir.model.access.jinja2 b/openerp/cli/scaffold/ir.model.access.jinja2 index e047031a15a..96bec7ba9f2 100644 --- a/openerp/cli/scaffold/ir.model.access.jinja2 +++ b/openerp/cli/scaffold/ir.model.access.jinja2 @@ -1,6 +1,6 @@ -{% if create -%}id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink{%- endif %} -{% 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 +{% if file_created -%}id,name,model_id:id,group_id:id,perm_read,perm_write,perm_create,perm_unlink{%- endif %} +{% if model -%} +access_{{ module_name|snake }}_{{ model|snake }},{{- '' -}} +access_{{ module_name|snake }}_{{ model|snake }},{{- '' -}} +model_{{ module_name|snake }}_{{ model|snake }},,1,0,0,0 {%- endif %} diff --git a/openerp/cli/scaffold/models.jinja2 b/openerp/cli/scaffold/models.jinja2 index 878bf9be4f6..6fe3aceadd3 100644 --- a/openerp/cli/scaffold/models.jinja2 +++ b/openerp/cli/scaffold/models.jinja2 @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from openerp.osv import orm, fields -class {{ config.model }}(orm.Model): - _name = "{{ config.module|snake }}.{{ config.model|snake }}" +class {{ model }}(orm.Model): + _name = "{{ module_name|snake }}.{{ model|snake }}" _columns = { 'name': fields.char(),