root/traclegosscript/anyrelease/traclegos/legos.py

Revision 4845, 13.7 kB (checked in by k0s, 2 months ago)

dont use a default database of SQLite ... if left as none, do nothing and assume the user knows what theyre doing

  • Property svn:executable set to
Line 
1 #!/usr/bin/env python
2 """
3 driver to create trac projects --
4 the head of the octopus
5 """
6
7 import inspect
8 import os
9 import pkg_resources
10 import sys
11
12 from optparse import OptionParser
13 from paste.script.templates import var
14 from paste.script.templates import Template
15 from trac.admin.console import TracAdmin
16 from trac.env import Environment
17 from traclegos.admin import TracLegosAdmin
18 from traclegos.config import ConfigMunger
19 from traclegos.db import available_databases
20 from traclegos.db import SQLite
21 from traclegos.pastescript.command import create_distro_command
22 from traclegos.pastescript.string import PasteScriptStringTemplate
23 from traclegos.pastescript.var import vars2dict, dict2vars
24 from traclegos.project import project_dict
25 from traclegos.project import TracProject
26 from traclegos.repository import available_repositories
27 from traclegos.templates import ProjectTemplates
28 from StringIO import StringIO
29
30 # TODO: warn about duplicate variables or options (optionally)
31
32 class TracLegos(object):
33     """tool for assembling trac projects from building blocks"""
34
35     # global options -- these should apply to any trac instance
36     # XXX maybe these should move to TracProject
37     # (like so many other things)
38     options = [ var('project', 'name of project'),
39                 var('directory', 'directory for trac projects'),
40                 var('description', 'description of the trac project'),
41                 var('url', 'alternate url for project'),
42                 var('favicon', 'favicon for the project')
43                 ]
44
45     interactive = True
46
47     def __init__(self, directory, master=None, inherit=None,
48                  site_templates=None, vars=None, options=()):
49         """
50         * directory: directory for project creation
51         * master: path to master trac instance
52         * inherit: inherited configuration to update
53         * site_templates: global site configuration used for all projects
54         * vars: values of variables for interpolation
55         * options: descriptions and annotations on variables
56         """
57
58         self.directory = directory
59         assert self.directory # must have a directory to work in!
60         self.site_templates = site_templates or []
61         self.vars = { 'url': '', 'favicon': ''}
62         self.vars.update(vars or {})
63         self.master = master
64         self.inherit = inherit or master or None
65         self.options.extend(options)
66
67         if not os.path.exists(self.directory):
68             os.mkdir(self.directory)
69
70         self.vars['directory'] = self.directory
71
72     ### utility functions
73
74     @classmethod
75     def arguments(cls):
76         """
77         returns a dictionary arguments to the constructor, __init__,
78         and their defaults or None for required arguments
79         """
80         argspec = inspect.getargspec(cls.__init__)
81         args = dict([(i, None) for i in argspec[0][1:]])
82         args.update(dict(zip(argspec[0][len(argspec[0])- len(argspec[-1]):],
83                              argspec[-1])))
84         return args
85
86     def project_templates(self, templates):
87         # apply site configuration last (override)
88         return ProjectTemplates(*(templates + self.site_templates))
89    
90     def repository_fields(self, project):
91         """
92         arguments to RepositorySetup.setup that may be interpolated
93         from variables
94         """
95         return { 'SVNSync': {'repository_dir': os.path.join(self.directory, project, 'mirror') },
96                  'NewSVN': {'repository_dir': os.path.join(self.directory, project, 'svn') }
97                  }
98
99     def create_project(self, project, templates, vars=None,
100                        database=None, repository=None):
101         """
102         * project: path name of project
103         * templates: templates to be applied on project creation
104         * vars: variables for interpolation
105         * database:  type of database to use
106         * repository: type of repository to use
107         """
108        
109         ### set variables
110
111         dirname = os.path.join(self.directory, project)
112         _vars = vars or {}
113         vars = self.vars.copy()
114         vars.update(_vars)
115         vars['project'] = project       
116
117         ### munge configuration
118
119         # get templates
120         if not isinstance(templates, ProjectTemplates):
121             if database:
122                 templates.append(database.config())
123             if repository:
124                 templates.append(repository.config())
125             templates = self.project_templates(templates)
126
127         # determine the vars/options
128         optdict = templates.vars(self.options)
129         repo_fields = {}
130         if database:
131             vars2dict(optdict, *database.options)
132         if repository:
133             vars2dict(optdict, *repository.options)
134             repo_fields = self.repository_fields(project).get(repository.name, {})
135
136         ### interpolate configuration
137
138         command = create_distro_command(interactive=self.interactive)
139        
140         # check for missing variables
141         missing = templates.missing(vars)
142         missing.update(set(optdict.keys()).difference(vars.keys()))
143         if missing:
144
145             # use default repo fields if they are missing
146             for field in repo_fields:
147                 if field in missing:
148                     vars[field] = repo_fields[field]
149                     missing.remove(field)
150
151             # add missing variables to the optdict
152             for missed in missing:
153                 if missed not in optdict:
154                     optdict[missed] = var(missed, '')
155
156             if missing:
157                 paste_template = Template(project)
158                 paste_template._read_vars = dict2vars(optdict) # XXX bad touch
159                 paste_template.check_vars(vars, command)
160        
161
162         ### create the database
163         if database:
164             database.setup(**vars)
165        
166         ### create the trac environment
167         options = templates.options_tuples(vars)
168         options.append(('project', 'name', project)) # XXX needed?
169         if self.inherit:
170             options.append(('inherit', 'file', self.inherit))
171         env = Environment(dirname, create=True, options=options)
172
173         ### repository setup
174         if repository:
175             repository.setup(**vars)
176             repos = env.get_repository()
177             repos.sync()
178
179         ### read the generated configuration
180         _conf_file = os.path.join(dirname, 'conf', 'trac.ini')
181         fp = file(_conf_file)
182         _conf = fp.read()
183         fp.close()
184
185         ### run pastescript templates
186         command.interactive = False
187         for paste_template in templates.pastescript_templates:
188             paste_template.run(command, dirname, vars)
189
190         # write back munged configuration
191         munger = ConfigMunger(_conf, options)
192         fp = file(_conf_file, 'w')
193         munger.write(fp)
194         fp.close()
195
196         # TODO: update the inherited file:
197         # * intertrac
198
199         ### trac-admin operations
200         admin = TracLegosAdmin(dirname)
201
202         # remove the default items
203         admin.delete_all()
204
205         # trac-admin upgrade the project
206         env = Environment(dirname)
207         if env.needs_upgrade():
208             env.upgrade()
209
210         # get the default wiki pages
211         # TODO: add options for importing existing wiki pages
212         cnx = env.get_db_cnx()
213         cursor = cnx.cursor()
214         pages_dir = pkg_resources.resource_filename('trac.wiki',
215                                                     'default-pages')
216         admin._do_wiki_load(pages_dir, cursor) # should probably make this silent
217         cnx.commit()
218
219         # TODO:  addition of groups, milestones, versions, etc
220         # via trac-admin
221
222
223 ### site configuration
224
225 sections = set(('site-configuration', 'variables', ))
226
227 def site_configuration(*ini_files):
228     """returns a dictionary of configuration from .ini files"""
229     conf = ConfigMunger(*ini_files).dict()
230     for section in sections:
231         if section not in conf:
232             conf[section] = {}
233     return conf
234
235 def traclegos_argspec(*dictionaries):
236     """
237     returns an argspec from a list of dictionaries appropriate to
238     constructing a TracLegos instance;
239     later dictionaries take precedence over earlier dictionaries
240     """
241     argspec = TracLegos.arguments()
242     for key in argspec:
243         args = [ dictionary.get(key) for dictionary in dictionaries ]
244         argspec[key] = reduce(lambda x, y: y or x, args, argspec[key])
245     return argspec
246
247 def traclegos_factory(ini_files, configuration, variables):
248     """
249     returns configuration needed to drive a TracLegos constructor
250     * ini_files: site configuration .ini files   
251     * configuration: dictionary of overrides to TracLegos arguments
252     * variables: used for template substitution
253     """
254     # XXX could instead return a constructed TracLegos instance
255     conf = site_configuration(*ini_files)
256     argspec = traclegos_argspec(conf['site-configuration'],
257                                 configuration)
258     argspec['vars'] = argspec['vars'] or {}
259     argspec['vars'].update(conf['variables'])
260     argspec['vars'].update(variables)
261     return argspec
262
263 ### functions for the command line front-end to TracLegos
264
265 def get_parser():
266     """return an OptionParser object for TracLegos"""
267
268     parser = OptionParser()
269
270     # command line parser options
271     parser.add_option("-c", "--conf",
272                       dest="conf", action="append", default=[],
273                       help="site configuration files")
274     parser.add_option("-d", "--directory",
275                       dest="directory", default=".",
276                       help="trac projects directory")
277     parser.add_option("-i", "--inherit", dest="inherit", default=None,
278                       help=".ini file to inherit from")
279     parser.add_option("-r", "--requires", # XXX not used yet
280                       dest="requirements", action="append", default=[],
281                       help="requirements files for plugins") #
282     parser.add_option("-s", "--repository",  dest="repository",
283                       help="repository type to use")
284     parser.add_option("-t", dest="templates", action="append", default=[],
285                       help="trac.ini templates to be applied in order")
286     parser.add_option("--db", "--database",
287                       dest="database", default="SQLite",
288                       help="database type to use")
289    
290     parser.add_option("--list-templates", dest="listprojects",
291                       action="store_true", default=False,
292                       help="list available TracProject PasteScript templates")
293     parser.add_option("--list-repositories", dest="listrepositories",
294                       action="store_true", default=False,
295                       help="list repository types available for setup by TracLegos")
296     parser.add_option("--list-databases", dest="listdatabases",
297                       action="store_true", default=False,
298                       help="list available database types available for setup by TracLegos")
299
300 #    parser.add_option("--list-variables", dest="listvariables",
301 #                      help="list variables for a [someday: list of] templates") # TODO
302
303
304     parser.set_usage("%prog [options] project <project2> <...> var1=1 var2=2 ...")
305     parser.set_description("assemble a trac project from components")
306     return parser
307
308 def parse(parser, args=None):
309     """return (legos, projects) or None"""
310
311     options, args = parser.parse_args(args)
312
313     # parse command line variables and determine list of projects
314     projects = [] # projects to create
315     vars = {} # defined variables
316     for arg in args:
317         if '=' in arg:
318             variable, value = arg.split('=', 1)
319             vars[variable] = value
320         else:
321             projects.append(arg)
322
323     # list the packaged pastescript TracProject templates
324     if options.listprojects:
325         print 'Available projects:'
326         for name, template in project_dict().items():
327             print '%s: %s' % (name, template.summary)
328         return
329
330     # get the repository
331     repository = None
332     if options.repository:
333         repository = available_repositories().get(options.repository)
334         if not repository:
335             print 'Error: repository type "%s" not available\n' % options.repository
336             options.listrepositories = True
337    
338     # list the available SCM repository setup agents
339     if options.listrepositories:
340         print 'Available repositories:'
341         for name, repository in available_repositories().items():
342             if name is not 'NoRepository': # no need to print this one
343                 print '%s: %s' % (name, repository.description)
344         return
345
346     # get the database
347     database = available_databases().get(options.database)
348     if not database:
349         print 'Error: database type "%s" not available\n' % options.database
350         options.listdatabases = True
351
352     # list the available database setup agents
353     if options.listdatabases:
354         print 'Available databases:'
355         for name, database in available_databases().items():
356             print '%s: %s' % (name, database.description)
357         return
358
359     if not projects: # print help if no projects given
360         parser.print_help()
361         return
362
363     # parse and apply site-configuration (including variables)
364     argspec = traclegos_factory(options.conf, options.__dict__, vars)
365     # project creator
366     legos = TracLegos(**argspec)
367    
368     return legos, projects, { 'templates': options.templates,
369                               'repository': repository,
370                               'database': database }
371
372 def main(args=None):
373     """main command line entry point"""
374
375     # command line parser
376     parser = get_parser()   
377    
378     # get some legos
379     parsed = parse(parser)
380     if parsed == None:
381         return # exit condition
382     legos, projects, arguments = parsed
383
384     # create the projects
385     for project in projects:
386         legos.create_project(project, **arguments)
387
388 if __name__ == '__main__':
389     main(sys.argv[1:])
Note: See TracBrowser for help on using the browser.