Index: 0.11/tracjsgantt/tracjsgantt.py =================================================================== --- 0.11/tracjsgantt/tracjsgantt.py (revision 10568) +++ 0.11/tracjsgantt/tracjsgantt.py (working copy) @@ -16,9 +16,8 @@ from trac.web.chrome import ITemplateProvider, add_script, add_stylesheet from pkg_resources import resource_filename -from trac.wiki.api import parse_args +from tracmacroconfig import TracMacroConfig - class TracJSGanttSupport(Component): implements(IRequestFilter, ITemplateProvider) @@ -42,39 +41,61 @@ return template, data, content_type class TracJSGanttChart(WikiMacroBase): - def __init__(self): - # All the macro's options with default values. - # Anything else passed to the macro is a TracQuery field. - self.options = { - 'format': None, - 'formats': 'day|week|month|quarter', - 'sample': 0, - 'res': 1, - 'dur': 1, - 'comp': 1, - 'caption': 'Resource', - 'startDate': 1, - 'endDate': 1, - 'dateDisplay': 'mm/dd/yyyy', - 'openLevel': 999, - 'openClosedTickets': 1, - 'colorBy' : 'priority', - 'lwidth' : None, - 'root' : None, - 'goal' : None, - 'showdep' : 1, - 'userMap': 1, - 'omitMilestones': 0, - } - # self.options defaults are configurable from trac.ini - for opt in self.options.keys(): - v = self.config.get('trac-jsgantt','option.%s' % opt, default=self) - if v != self: - if isinstance(self.options[opt], (int, long)): - v = int(v) - self.options[opt] = v + # All the macro's options with default values. + # Anything else passed to the macro is a TracQuery field. + # Note that within trac.ini the options will be written + # with the prefix='option' prefix, e.g. 'option.format'. + # If the macro call has an option 'config=xxx', the options + # in trac.ini with that prefix will be used instead, e.g. 'xxx.format' + macroconfig = TracMacroConfig('trac-jsgantt', prefix='option') + macroconfig.InheritOption('inherit') + option_format = macroconfig.Option('format', + doc='Initial display format') + option_formats = macroconfig.ListOption('formats', + default=['day','week','month','quarter'], doc='Display formats') + option_sample = macroconfig.IntOption('sample', + default=0, doc='Show sample tasks') + option_res = macroconfig.IntOption('res', + default=1, doc='Show resource column') + option_dur = macroconfig.IntOption('dur', + default=1, doc='Show duration column') + option_comp = macroconfig.IntOption('comp', + default=1, doc='Show percent complete column') + option_caption = macroconfig.Option('caption', + default='Resource', doc='Caption to place to right of tasks:' + ' None, Caption, Resource, Duration, %Complete') + option_startDate = macroconfig.IntOption('startDate', + default=1, doc='Show start date column') + option_endDate = macroconfig.IntOption('endDate', + default=1, doc='Show end date column') + option_dateDisplay = macroconfig.Option('dateDisplay', + default='mm/dd/yyyy', doc="Date display format: 'mm/dd/yyyy'," + " 'dd/mm/yyyy', or 'yyyy-mm-dd'") + option_openLevel = macroconfig.IntOption('openLevel', + default=999, doc='Number of levels of tasks to show') + option_openClosedTickets = macroconfig.IntOption('openClosedTickets', + default=1, doc="Open (unfold) tickets in state 'closed'") + option_colorBy = macroconfig.Option('colorBy', + default='priority', doc='Field to use to choose task colors') + option_lwidth = macroconfig.IntOption('lwidth', + doc='The width, in pixels, of the table of task names') + option_root = macroconfig.ListOption('root', sep='|', + doc='Show these ticket ID or IDs, and their children') + option_goal = macroconfig.ListOption('goal', sep='|', + doc='Show these ticket ID or IDs, and their predecessors') + option_showdep = macroconfig.IntOption('showdep', + default=1, doc='Show dependencies (1) or not (0)') + option_userMap = macroconfig.IntOption('userMap', + default=1, doc='Map user IDs to full names (1)' + ' or display just the login names (0)') + option_omitMilestones = macroconfig.IntOption('omitMilestones', + default=0, doc='Only show milestones for displayed tickets (0)' + ' or only those specified by milestone= (1)') + def __init__(self): + self.macroconfig.setup(self.config, env=self.env) + # Configuration fields self.fields = {} self.fields['percent'] = \ @@ -133,48 +154,44 @@ self.parent_format = \ self.config.get('trac-jsgantt','parent_format', default='%s') - def _begin_gantt(self, options): - if options['format']: - format = options['format'] - else: - format = options['formats'].split('|')[0] - showdep = options['showdep'] + def _begin_gantt(self): + format = self.option_format or self.option_formats[0] text = '' text += '
\n' text += '\n' return chart - def _gantt_options(self, options): + def _gantt_options(self): opt = '' - opt += 'g.setShowRes(%s);\n' % options['res'] - opt += 'g.setShowDur(%s);\n' % options['dur'] - opt += 'g.setShowComp(%s);\n' % options['comp'] - w = options['lwidth'] + opt += 'g.setShowRes(%s);\n' % self.option_res + opt += 'g.setShowDur(%s);\n' % self.option_dur + opt += 'g.setShowComp(%s);\n' % self.option_comp + w = self.option_lwidth if w: opt += 'g.setLeftWidth(%s);\n' % w - opt += 'g.setCaptionType("%s");\n' % options['caption'] + opt += 'g.setCaptionType("%s");\n' % self.option_caption - opt += 'g.setShowStartDate(%s);\n' % options['startDate'] - opt += 'g.setShowEndDate(%s);\n' % options['endDate'] + opt += 'g.setShowStartDate(%s);\n' % self.option_startDate + opt += 'g.setShowEndDate(%s);\n' % self.option_endDate opt += 'g.setDateInputFormat("%s");\n' % self.jsDateFormat - opt += 'g.setDateDisplayFormat("%s");\n' % options['dateDisplay'] + opt += 'g.setDateDisplayFormat("%s");\n' % self.option_dateDisplay - opt += 'g.setFormatArr("%s");\n' % options['formats'].split('|') + opt += 'g.setFormatArr("%s");\n' % self.option_formats opt += 'g.setPopupFeatures("location=1,scrollbars=1");\n' return opt @@ -206,8 +223,7 @@ # Get the required columns for the tickets which match the # criteria in options. - def _query_tickets(self, options): - # Parents is a list of strings + def _query_tickets(self): def _expand(origins, field, format): if len(origins) == 0: return [] @@ -226,40 +242,29 @@ return origins + _expand(nodes, field, format) - - query_args = {} - for key in options.keys(): - if not key in self.options: - query_args[key] = options[key] - - if options['root']: + def _addin(option, field, format): if 'id' in query_args: query_args['id'] += '|' else: query_args['id'] = '' - if options['root'] == 'self': + nodes = option[:] + try: + selfindex = nodes.index('self') this_ticket = self._this_ticket() if this_ticket: - nodes = [ this_ticket ] - query_args['id'] += '|'.join(_expand(nodes, self.fields['parent'], self.parent_format)) - else: - nodes = options['root'].split('|') - query_args['id'] += '|'.join(_expand(nodes, self.fields['parent'], self.parent_format)) + nodes[selfindex] = this_ticket + else: + nodes.pop(selfindex) + except: + pass + query_args['id'] += '|'.join(_expand(nodes, field, format)) - if options['goal']: - if 'id' in query_args: - query_args['id'] += '|' - else: - query_args['id'] = '' - if options['goal'] == 'self': - this_ticket = self._this_ticket() - if this_ticket: - nodes = [ this_ticket ] - query_args['id'] += '|'.join(_expand(nodes, self.fields['succ'], '%s')) - else: - nodes = options['goal'].split('|') - query_args['id'] += '|'.join(_expand(nodes, self.fields['succ'], '%s')) + query_args = self.macroconfig.extras() + if self.option_root: + _addin(self.option_root, self.fields['parent'], self.parent_format) + if self.option_goal: + _addin(self.option_goal, self.fields['succ'], '%s') # Start with values that are always needed fields = [ @@ -277,8 +282,8 @@ fields.append(self.fields[field]) # Make sure the coloring field is included - if 'colorBy' in options and options['colorBy'] not in fields: - fields.append(options['colorBy']) + if self.option_colorBy not in fields: + fields.append(self.option_colorBy) # Make the query argument query_args['col'] = "|".join(fields) @@ -479,13 +484,13 @@ # Add tasks for milestones related to the tickets - def _add_milestones(self, options): - if options.get('milestone'): - milestones = options['milestone'].split('|') + def _add_milestones(self): + if self.macroconfig.option_is_extra('milestone'): + milestones = self.macroconfig.extra('milestone').split('|') else: milestones = [] - if not options['omitMilestones']: + if not self.option_omitMilestones: for t in self.tickets: if t['milestone'] != '' and t['milestone'] not in milestones: milestones.append(t['milestone']) @@ -553,7 +558,7 @@ self.tickets.append(milestoneTicket) - def _task_display(self, t, options): + def _task_display(self, t): def _buildMap(field): self.classMap = {} i = 0 @@ -573,12 +578,12 @@ self.classMap[name] = value display = None - colorBy = options['colorBy'] + colorBy = self.option_colorBy # Build the map the first time we need it if self.classMap == None: # Enums (TODO: what others should I list?) - if options['colorBy'] in ['priority', 'severity']: + if colorBy in ['priority', 'severity']: _buildEnumMap(colorBy) else: _buildMap(colorBy) @@ -609,14 +614,14 @@ # status - string displayed in tool tip ; FIXME - not displayed yet # summary - ticket summary # type - string displayed in tool tip FIXME - not displayed yet - def _format_ticket(self, ticket, options): + def _format_ticket(self, ticket): # Translate owner to full name def _owner(ticket): if ticket['type'] == self.milestoneType: owner_name = '' else: owner_name = ticket['owner'] - if options['userMap']: + if self.option_userMap: if self.user_map is None: self.user_map = {} for username, name, email in self.env.get_known_users(): @@ -683,7 +688,7 @@ task += '"%s",' % ticket['calc_finish'] # pDisplay - task += '"%s",' % self._task_display(ticket, options) + task += '"%s",' % self._task_display(ticket) # pLink task += '"%s",' % ticket['link'] @@ -712,19 +717,19 @@ task += '%s,' % 0 # If there's a parent field, but the ticket is in root, don't # link to parent - elif options['root'] and str(ticket['id']) in options['root'].split('|'): + elif self.option_root and str(ticket['id']) in self.option_root: task += '%s,' % 0 # If there's a parent field, root == self and this ticket is self, # don't link to parents - elif options['root'] and options['root'] == 'self' and str(ticket['id']) == self._this_ticket(): + elif self.option_root and 'self' in self.option_root and str(ticket['id']) == self._this_ticket(): task += '%s,' % 0 # If there's a parent, and the ticket is not a root, link to parent else: task += '%s,' % ticket[self.fields['parent']] # open - if ticket['level'] < options['openLevel'] and \ - ((options['openClosedTickets'] != 0) or \ + if ticket['level'] < self.option_openLevel and \ + (self.option_openClosedTickets or \ (ticket['status'] != 'closed')): open = 1 else: @@ -746,7 +751,7 @@ task += 'g.AddTaskItem(t);\n' return task - def _add_tasks(self, options): + def _add_tasks(self): def _sort_children(a,b): if self.ticketsByID[a]['tempEnd'] < self.ticketsByID[b]['tempEnd']: return -1 @@ -757,18 +762,18 @@ else: return -1 - if options.get('sample'): + if self.option_sample: tasks = self._add_sample_tasks() else: tasks = '' - self.tickets = self._query_tickets(options) + self.tickets = self._query_tickets() # Post process the query to add and compute fields so # displaying the tickets is easy self._process_tickets() # Add the milestone(s) with all their tickets depending on them. - self._add_milestones(options) + self._add_milestones() # Faster lookups for WBS and scheduling. self.ticketsByID = {} @@ -787,22 +792,10 @@ self.tickets.sort(key=itemgetter('wbs')) for ticket in self.tickets: - tasks += self._format_ticket(ticket, options) + tasks += self._format_ticket(ticket) return tasks - def _parse_options(self, content): - _, options = parse_args(content, strict=False) - - for opt in self.options.keys(): - if opt in options: - if isinstance(self.options[opt], (int, long)): - options[opt] = int(options[opt]) - else: - options[opt] = self.options[opt] - - return options - def _this_ticket(self): matches = re.match('/ticket/(\d+)', self.req.path_info) if not matches: @@ -815,15 +808,15 @@ # Each invocation needs to build its own map. self.classMap = None - options = self._parse_options(content) + self.macroconfig.options(content) chart = '' - tasks = self._add_tasks(options) + tasks = self._add_tasks() if len(tasks) == 0: chart += 'No tasks selected.' else: - chart += self._begin_gantt(options) - chart += self._gantt_options(options) + chart += self._begin_gantt() + chart += self._gantt_options() chart += tasks - chart += self._end_gantt(options) + chart += self._end_gantt() return chart Index: 0.11/setup.py =================================================================== --- 0.11/setup.py (revision 10568) +++ 0.11/setup.py (working copy) @@ -13,6 +13,10 @@ license='BSD', packages=['tracjsgantt'], package_data = { 'tracjsgantt': ['htdocs/*.js', 'htdocs/*.css'] }, + dependency_links = [ + 'http://trac-hacks.org/svn/tracmacroconfigplugin#egg=TracMacroConfigPlugin-0.2' + ], + install_requires = [ 'TracMacroConfigPlugin >= 0.2' ], entry_points = { 'trac.plugins': [ 'tracjsgantt = tracjsgantt'