# Copyright 2016 Lars Wirzenius # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . # # =*= License: GPL-3+ =*= import locale import markdown import os import time import uuid import cliapp import distixlib class HtmlPlugin(cliapp.Plugin): def enable(self): self.app.add_subcommand( 'html', self.html, arg_synopsis='OUTPUT-DIR') def html(self, args): '''Generate static HTML web pages of the repository and its tickets.''' dirname = self.parse_args(args) self.create_output(dirname) repo = distixlib.Repository('.') site = StaticSite() site.set_output_dir(dirname) site.set_repository(repo) site.render() def parse_args(self, args): if len(args) != 1: raise UsageError() return args[0] def create_output(self, dirname): if os.path.isdir(dirname): if os.listdir(dirname): raise OutputNotEmptyError(dirname=dirname) elif os.path.exists(dirname): raise OutputExistsError(dirname=dirname) else: os.mkdir(dirname) class StaticSite(object): def __init__(self): self._dirname = None self._repo = None self._ticket_store = None self._renderer = distixlib.HtmlRenderer() self._timestamp = None def set_repository(self, repo): self._repo = repo self._ticket_store = repo.open_ticket_store(distixlib.tickets_dir_name) def set_output_dir(self, dirname): self._dirname = dirname def render(self): self._timestamp = self._format_timestamp() self._render_ticket_list( 'index.html.j2', 'index.html', self._ticket_is_open) self._render_ticket_list( 'closed.html.j2', 'closed.html', self._ticket_is_closed) for ticket_id in self._ticket_store.get_ticket_ids(): ticket = self._ticket_store.get_ticket(ticket_id) self._render_ticket(ticket) self._ticket_store.clear_ticket_cache() self._copy_static_files() def _format_timestamp(self): return time.strftime('%Y-%d-%m %H:%M:%S') def _render_ticket_list(self, template, filename, condition): variables = { 'description_html': self._make_html(self._repo.get_description()), 'tickets': self._sort_tickets(self._get_tickets(condition)), 'timestamp': self._timestamp, } html = self._renderer.render(template, variables) self._write_file(filename, html) def _sort_tickets(self, tickets): return sorted(tickets, key=lambda t: t.get_newest_message_timestamp()) def _render_ticket(self, ticket): thread = distixlib.MessageThread() for msg in ticket.get_messages(): thread.add_message(msg) msg_renderer = distixlib.MessageRenderer() metadata = ticket.get_ticket_metadata() variables = { 'ticket': ticket, 'metadata': metadata, 'msgs': [ msg_renderer.summary(msg) for msg in thread.get_messages_in_date_order() ], 'timestamp': self._timestamp, } html = self._renderer.render('ticket.html.j2', variables) self._write_file('{}.html'.format(ticket.get_ticket_id()), html) def _make_html(self, text): return markdown.markdown(text) def _get_tickets(self, condition): for ticket_id in self._ticket_store.get_ticket_ids(): ticket = self._ticket_store.get_ticket(ticket_id) self._ticket_store.clear_ticket_cache() if condition(ticket): lt = TicketLight() lt.set_ticket_id(ticket_id) lt.set_title(ticket.get_title()) lt.set_newest_message_timestamp( ticket.get_newest_message_timestamp()) yield lt def _ticket_is_open(self, ticket): return not self._ticket_is_closed(ticket) def _ticket_is_closed(self, ticket): return self._ticket_has_key_value(ticket, 'status', 'closed') def _ticket_has_key_value(self, ticket, key, value): metadata = ticket.get_ticket_metadata() return key in metadata and value in metadata.get_all_values(key) def _copy_static_files(self): filenames = ['distix.css'] for filename in filenames: data = self._read_file(filename) self._write_file(filename, data) def _read_file(self, filename): pathname = os.path.join( os.path.dirname(distixlib.__file__), filename) with open(pathname) as f: return f.read() def _write_file(self, filename, text): with open(os.path.join(self._dirname, filename), 'w') as f: f.write(text.encode('UTF8')) class TicketLight(object): def __init__(self): self._ticket_id = None self._title = None self._timestamp = None def set_ticket_id(self, ticket_id): self._ticket_id = ticket_id def set_title(self, title): self._title = title def set_newest_message_timestamp(self, timestamp): self._timestamp = timestamp def get_ticket_id(self): return self._ticket_id def get_title(self): return self._title def get_newest_message_timestamp(self): return self._timestamp class UsageError(distixlib.StructuredError): msg = '"html" command must get exactly one argument: the output directory' class OutputNotEmptyError(distixlib.StructuredError): msg = 'Output directory {dirname} is not empty' class OutputExistsError(distixlib.StructuredError): msg = 'Output directory {dirname} exists, but is not an empty directory'