[FIX] website_mail_group: restore missing snippet icon
[odoo/odoo.git] / openerpcommand / scaffold.py
1 """
2 Generate an OpenERP module skeleton.
3 """
4
5 import functools
6 import keyword
7 import os
8 import re
9 import sys
10
11 import jinja2
12
13 # FIXME: add logging
14 def run(args):
15     env = jinja2.Environment(loader=jinja2.PackageLoader(
16         'openerpcommand', 'templates'))
17     env.filters['snake'] = snake
18     args.dependency = 'web' if args.controller else 'base'
19
20     module_name = snake(args.module)
21     module = functools.partial(
22         os.path.join, args.modules_dir, module_name)
23
24     if args.controller is True:
25         args.controller = module_name
26
27     if args.model is True:
28         args.model = module_name
29
30     if os.path.exists(module()):
31         message = "The path `%s` already exists." % module()
32         die(message)
33
34     dump(env, '__openerp__.jinja2', module('__openerp__.py'), config=args)
35     dump(env, '__init__.jinja2', module('__init__.py'), modules=[
36         args.controller and 'controllers',
37         args.model and 'models'
38     ])
39     dump(env, 'ir.model.access.jinja2', module('security', 'ir.model.access.csv'), config=args)
40
41     if args.controller:
42         controller_module = snake(args.controller)
43         dump(env, '__init__.jinja2', module('controllers', '__init__.py'), modules=[controller_module])
44         dump(env, 'controllers.jinja2',
45              module('controllers', '%s.py' % controller_module),
46              config=args)
47
48     if args.model:
49         model_module = snake(args.model)
50         dump(env, '__init__.jinja2', module('models', '__init__.py'), modules=[model_module])
51         dump(env, 'models.jinja2', module('models', '%s.py' % model_module), config=args)
52
53 def add_parser(subparsers):
54     parser = subparsers.add_parser('scaffold',
55         description='Generate an OpenERP module skeleton.')
56     parser.add_argument('module', metavar='MODULE',
57         help='the name of the generated module')
58     parser.add_argument('modules_dir', metavar='DIRECTORY', type=directory,
59         help="Modules directory in which the new module should be generated")
60
61     controller = parser.add_mutually_exclusive_group()
62     controller.add_argument('--controller', type=identifier,
63         help="The name of the controller to generate")
64     controller.add_argument('--no-controller', dest='controller',
65         action='store_const', const=None, help="Do not generate a controller")
66
67     model = parser.add_mutually_exclusive_group()
68     model.add_argument('--model', type=identifier,
69        help="The name of the model to generate")
70     model.add_argument('--no-model', dest='model',
71        action='store_const', const=None, help="Do not generate a model")
72
73     mod = parser.add_argument_group("Module information",
74         "these are added to the module metadata and displayed on e.g. "
75         "apps.openerp.com. For company-backed modules, the company "
76         "information should be used")
77     mod.add_argument('--name', dest='author_name', default="",
78                      help="Name of the module author")
79     mod.add_argument('--website', dest='author_website', default="",
80                      help="Website of the module author")
81     mod.add_argument('--category', default="Uncategorized",
82         help="Broad categories to which the module belongs, used for "
83              "filtering within OpenERP and on apps.openerp.com."
84              "Defaults to %(default)s")
85     mod.add_argument('--summary', default="",
86         help="Short (1 phrase/line) summary of the module's purpose, used as "
87              "subtitle on modules listing or apps.openerp.com")
88
89     parser.set_defaults(run=run, controller=True, model=True)
90
91 def snake(s):
92     """ snake cases ``s``
93
94     :param str s:
95     :return: str
96     """
97     # insert a space before each uppercase character preceded by a
98     # non-uppercase letter
99     s = re.sub(r'(?<=[^A-Z])\B([A-Z])', r' \1', s)
100     # lowercase everything, split on whitespace and join
101     return '_'.join(s.lower().split())
102
103 def dump(env, template, dest, **kwargs):
104     outdir = os.path.dirname(dest)
105     if not os.path.exists(outdir):
106         os.makedirs(outdir)
107     env.get_template(template).stream(**kwargs).dump(dest)
108     # add trailing newline which jinja removes
109     with open(dest, 'a') as f:
110         f.write('\n')
111
112 def identifier(s):
113     if keyword.iskeyword(s):
114         die("%s is a Python keyword and can not be used as a name" % s)
115     if not re.match('[A-Za-z_][A-Za-z0-9_]*', s):
116         die("%s is not a valid Python identifier" % s)
117     return s
118
119 def directory(p):
120     expanded = os.path.abspath(
121         os.path.expanduser(
122             os.path.expandvars(p)))
123     if not os.path.exists(expanded):
124         os.makedirs(expanded)
125     if not os.path.isdir(expanded):
126         die("%s exists but is not a directory" % p)
127     return expanded
128
129 def die(message, code=1):
130     print >>sys.stderr, message
131     sys.exit(code)