9e24156b7928ed2341a003b6db06228965a35c8b
[odoo/odoo.git] / openerp / cli / scaffold.py
1 #!/usr/bin/env python
2 # -*- coding: utf-8 -*-
3 import argparse
4 import functools
5 import keyword
6 import os
7 import re
8 import sys
9
10 import jinja2
11
12 from . import Command
13
14 class Scaffold(Command):
15     "Generate an Odoo module skeleton."
16
17     def __init__(self):
18         super(Scaffold, self).__init__()
19         env = jinja2.Environment(loader=jinja2.PackageLoader(
20             'openerp.cli', 'scaffold'))
21         env.filters['snake'] = snake
22         self.env = env
23         self.manifest = '__openerp__'
24
25     def scaffold(self, args):
26         args.dependency = 'base'
27         # TODO: update dependencies according to --web and --theme
28         # if args.web:
29         #     args.dependency = 'web'
30         # elif args.theme:
31         #     args.dependency = 'website'
32
33         dest = directory(args.dest)
34         if args.init:
35             module_name = snake(args.init)
36             module = functools.partial(os.path.join, dest, module_name)
37             if os.path.exists(module()):
38                 die("Can't initialize module in `%s`: Directory already exists." % module())
39         else:
40             module_name = dest.split(os.path.sep)[-1]
41             # find the module's root directory
42             while not os.path.exists(os.path.join(dest, '%s.py' % self.manifest)):
43                 new_dest = os.path.abspath(os.path.join(dest, os.pardir))
44                 if dest == new_dest:
45                     die("Can't find module directory. Please `cd` to it's path or use --dest")
46                 module_name = dest.split(os.path.sep)[-1]
47                 dest = new_dest
48             module = functools.partial(os.path.join, dest)
49         args.module = module_name
50
51         if args.init:
52             self.dump('%s.jinja2' % self.manifest, module('%s.py' % self.manifest), config=args)
53
54         if args.model:
55             model_module = snake(args.model)
56             model_file = module('models', '%s.py' % model_module)
57             if os.path.exists(model_file):
58                 die("Model `%s` already exists !" % model_file)
59             self.add_init_import(module('__init__.py'), 'models')
60             self.add_init_import(module('models', '__init__.py'), model_module)
61             self.dump('models.jinja2', model_file, config=args)
62
63         if args.controller:
64             controller_module = snake(args.controller)
65             controller_file = module('controllers', '%s.py' % controller_module)
66             if os.path.exists(controller_file):
67                 die("Controller `%s` already exists !" % controller_file)
68             self.add_init_import(module('__init__.py'), 'controllers')
69             self.add_init_import(module('controllers', '__init__.py'), controller_module)
70             self.dump('controllers.jinja2', module('controllers', controller_file), config=args)
71
72         # self.dump('ir.model.access.jinja2', module('security', 'ir.model.access.csv'), config=args)
73         return
74
75     def add_init_import(self, path, module):
76         if not os.path.exists(path):
77             self.dump('__init__.jinja2', path, modules=[module])
78         else:
79             with open(path, "r") as f:
80                 lines = f.readlines()
81                 # TODO: regex
82                 if ('import %s' % module) in lines:
83                     return
84             with open(path, "a") as f:
85                 f.write('\nimport %s' % module)
86
87     def dump(self, template, dest, **kwargs):
88         outdir = os.path.dirname(dest)
89         if not os.path.exists(outdir):
90             os.makedirs(outdir)
91         self.env.get_template(template).stream(**kwargs).dump(dest)
92         # add trailing newline which jinja removes
93         with open(dest, 'a') as f:
94             f.write('\n')
95
96     def run(self, args):
97         parser = argparse.ArgumentParser(
98             prog="%s scaffold" % sys.argv[0].split(os.path.sep)[-1],
99             description=self.__doc__
100         )
101         parser.add_argument('--init', type=identifier, help='Initialize a new Odoo module')
102
103         parser.add_argument('--dest', default=".",
104             help='Directory where the module should be created/updated (default to current directory)')
105
106         parser.add_argument('--model', type=identifier, help="Name of the model to add")
107
108         parser.add_argument('--controller', type=identifier, help="Name of the controller to add")
109
110         parser.add_argument('--web', action='store_true', default=False,
111                          help="Generate structure for a webclient module")
112
113         parser.add_argument('--theme', action='store_true', default=False,
114                          help="Generate structure for a Website theme")
115
116         if not args:
117             sys.exit(parser.print_help())
118         args = parser.parse_args(args=args)
119         self.scaffold(args)
120
121 def snake(s):
122     """ snake cases ``s``
123
124     :param str s:
125     :return: str
126     """
127     # insert a space before each uppercase character preceded by a
128     # non-uppercase letter
129     s = re.sub(r'(?<=[^A-Z])\B([A-Z])', r' \1', s)
130     # lowercase everything, split on whitespace and join
131     return '_'.join(s.lower().split())
132
133 def identifier(s):
134     if keyword.iskeyword(s):
135         die("%s is a Python keyword and can not be used as a name" % s)
136     if not re.match('[A-Za-z_][A-Za-z0-9_]*', s):
137         die("%s is not a valid Python identifier" % s)
138     return s
139
140 def directory(p):
141     expanded = os.path.abspath(
142         os.path.expanduser(
143             os.path.expandvars(p)))
144     if not os.path.exists(expanded):
145         os.makedirs(expanded)
146     if not os.path.isdir(expanded):
147         die("%s exists but is not a directory" % p)
148     return expanded
149
150 def die(message, code=1):
151     print >>sys.stderr, message
152     sys.exit(code)