diff --git a/README.rst b/README.rst index 398a3187..3098bd27 100644 --- a/README.rst +++ b/README.rst @@ -86,12 +86,13 @@ The default output is plain text of maximum width 79 characters. This can be adjusted using the ``--width`` parameter. To disable shortening altogether use ``--width=0``. The default width value can be saved in the config file as well. Use ``--format=wiki`` to -enable simple MoinMoin wiki syntax. For stats which support them, -``--brief`` and ``--verbose`` can be used to specify a different -level of detail to be shown. +enable simple MoinMoin wiki syntax or ``--format=markdown`` to +enable markdown syntax. For stats which support them, ``--brief`` +and ``--verbose`` can be used to specify a different level of +detail to be shown. ---format=FMT - Output style, possible values: text (default) or wiki +--format {text,markdown,wiki} + Output style, default: text --width=WIDTH Maximum width of the report output (default: 79) diff --git a/did/cli.py b/did/cli.py index 1d585693..8c51acc5 100644 --- a/did/cli.py +++ b/did/cli.py @@ -77,8 +77,8 @@ def __init__(self, arguments=None): # Formating options group = self.parser.add_argument_group("Format") group.add_argument( - "--format", default="text", - help="Output style, possible values: text (default) or wiki") + "--format", default="text", choices=["text", "markdown", "wiki"], + help="Output style, default: text") group.add_argument( "--width", default=width, type=int, help="Maximum width of the report output (default: %(default)s)") @@ -149,8 +149,17 @@ def parse(self): raise RuntimeError( "Invalid date range ({0} to {1})".format( opt.since, opt.until.date - delta(days=1))) - header = "Status report for {0} ({1} to {2}).".format( + + header = "Status report for {0} ({1} to {2})".format( period, opt.since, opt.until.date - delta(days=1)) + if opt.format == "markdown": + # In markdown the first line must be a header + # using alternate syntax allowing to use did's + # output in commit messages as well + header = f"{header}\n{'=' * len(header)}" + else: + # In markdown no trailing punctuation is allowed in headings + header = header + "." # Finito log.debug("Gathered options:") diff --git a/did/plugins/bugzilla.py b/did/plugins/bugzilla.py index eda7aba0..8e6fefdb 100644 --- a/did/plugins/bugzilla.py +++ b/did/plugins/bugzilla.py @@ -140,6 +140,10 @@ def __str__(self): """ Consistent identifier and summary for displaying """ if self.options.format == "wiki": return "<> - {1}".format(self.id, self.summary) + elif self.options.format == "markdown": + link = self.parent.url.replace("xmlrpc.cgi", "show_bug.cgi?id=") + return "[{0}#{1}]({2}{1}) - {3}".format( + self.prefix, str(self.id), link, self.summary) else: return "{0}#{1} - {2}".format( self.prefix, str(self.id).rjust(7, "0"), self.summary) diff --git a/did/plugins/github.py b/did/plugins/github.py index bd684e6e..3972d899 100644 --- a/did/plugins/github.py +++ b/did/plugins/github.py @@ -110,7 +110,7 @@ def search(self, query): class Issue(object): """ GitHub Issue """ - def __init__(self, data): + def __init__(self, data, parent): self.data = data self.title = data["title"] matched = re.search( @@ -118,12 +118,18 @@ def __init__(self, data): self.owner = matched.groups()[0] self.project = matched.groups()[1] self.id = matched.groups()[2] + self.options = parent.options def __str__(self): """ String representation """ - return "{0}/{1}#{2} - {3}".format( - self.owner, self.project, - str(self.id).zfill(PADDING), self.data["title"]) + if self.options.format == "markdown": + return "[{0}/{1}#{2}]({3}) - {4}".format( + self.owner, self.project, + str(self.id), self.data["html_url"], self.data["title"].strip()) + else: + return "{0}/{1}#{2} - {3}".format( + self.owner, self.project, + str(self.id).zfill(PADDING), self.data["title"]) # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ @@ -139,7 +145,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until) query += "+type:issue" self.stats = [ - Issue(issue) for issue in self.parent.github.search(query)] + Issue(issue, self.parent) for issue in self.parent.github.search(query)] class IssuesClosed(Stats): @@ -151,7 +157,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until) query += "+type:issue" self.stats = [ - Issue(issue) for issue in self.parent.github.search(query)] + Issue(issue, self.parent) for issue in self.parent.github.search(query)] class IssueCommented(Stats): @@ -163,7 +169,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until) query += "+type:issue" self.stats = [ - Issue(issue) for issue in self.parent.github.search(query)] + Issue(issue, self.parent) for issue in self.parent.github.search(query)] class PullRequestsCreated(Stats): @@ -176,7 +182,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until) query += "+type:pr" self.stats = [ - Issue(issue) for issue in self.parent.github.search(query)] + Issue(issue, self.parent) for issue in self.parent.github.search(query)] class PullRequestsCommented(Stats): @@ -189,7 +195,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until) query += "+type:pr" self.stats = [ - Issue(issue) for issue in self.parent.github.search(query)] + Issue(issue, self.parent) for issue in self.parent.github.search(query)] class PullRequestsClosed(Stats): @@ -202,7 +208,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until) query += "+type:pr" self.stats = [ - Issue(issue) for issue in self.parent.github.search(query)] + Issue(issue, self.parent) for issue in self.parent.github.search(query)] class PullRequestsReviewed(Stats): @@ -215,7 +221,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until) query += "+type:pr" self.stats = [ - Issue(issue) for issue in self.parent.github.search(query)] + Issue(issue, self.parent) for issue in self.parent.github.search(query)] # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/did/plugins/gitlab.py b/did/plugins/gitlab.py index b9727d26..cfd01448 100644 --- a/did/plugins/gitlab.py +++ b/did/plugins/gitlab.py @@ -163,9 +163,10 @@ def search(self, user, since, until, target_type, action_name): class Issue(object): """ GitLab Issue """ - def __init__(self, data, gitlabapi): + def __init__(self, data, parent): + self.parent = parent self.data = data - self.gitlabapi = gitlabapi + self.gitlabapi = parent.gitlab self.project = self.gitlabapi.get_project(data['project_id']) self.id = self.iid() self.title = data['target_title'] @@ -176,9 +177,22 @@ def iid(self): def __str__(self): """ String representation """ - return "{0}#{1} - {2}".format( - self.project['path_with_namespace'], - str(self.id).zfill(PADDING), self.title) + if self.parent.options.format == "markdown": + endpoint = "merge_requests" + if self.data['target_type'] == 'Issue' or ( + self.data['target_type'] == 'Note' + and self.data['note']['noteable_type'] == 'Issue' + ): + endpoint = "issues" + return "[{1}#{3}]({0}/{1}/-/{2}/{3}) - {4}".format( + self.gitlabapi.url, + self.project['path_with_namespace'], + endpoint, + str(self.id), self.title) + else: + return "{0}#{1} - {2}".format( + self.project['path_with_namespace'], + str(self.id).zfill(PADDING), self.title) class MergeRequest(Issue): @@ -223,7 +237,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until, 'Issue', 'opened') self.stats = [ - Issue(issue, self.parent.gitlab) + Issue(issue, self.parent) for issue in results] @@ -237,7 +251,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until, 'Note', 'commented on') self.stats = [ - Note(issue, self.parent.gitlab) + Note(issue, self.parent) for issue in results if issue['note']['noteable_type'] == 'Issue'] @@ -252,7 +266,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until, 'Issue', 'closed') self.stats = [ - Issue(issue, self.parent.gitlab) + Issue(issue, self.parent) for issue in results] @@ -266,7 +280,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until, 'MergeRequest', 'opened') self.stats = [ - MergeRequest(mr, self.parent.gitlab) + MergeRequest(mr, self.parent) for mr in results] @@ -280,7 +294,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until, 'Note', 'commented on') self.stats = [ - Note(issue, self.parent.gitlab) + Note(issue, self.parent) for issue in results if issue['note']['noteable_type'] == 'MergeRequest'] @@ -295,7 +309,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until, 'MergeRequest', 'accepted') self.stats = [ - MergeRequest(mr, self.parent.gitlab) + MergeRequest(mr, self.parent) for mr in results] @@ -309,7 +323,7 @@ def fetch(self): self.user.login, self.options.since, self.options.until, 'MergeRequest', 'approved') self.stats = [ - MergeRequest(mr, self.parent.gitlab) + MergeRequest(mr, self.parent) for mr in results] diff --git a/did/plugins/jira.py b/did/plugins/jira.py index 6e9b6bf8..a3bfea3d 100644 --- a/did/plugins/jira.py +++ b/did/plugins/jira.py @@ -112,23 +112,32 @@ class Issue(object): """ Jira issue investigator """ - def __init__(self, issue=None, prefix=None): + def __init__(self, issue=None, parent=None): """ Initialize issue """ if issue is None: return + self.parent = parent + self.options = parent.options self.issue = issue self.key = issue["key"] self.summary = issue["fields"]["summary"] self.comments = issue["fields"]["comment"]["comments"] matched = re.match(r"(\w+)-(\d+)", self.key) self.identifier = matched.groups()[1] - if prefix is not None: - self.prefix = prefix + if parent.prefix is not None: + self.prefix = parent.prefix else: self.prefix = matched.groups()[0] def __str__(self): """ Jira key and summary for displaying """ + if self.options.format == "markdown": + return "[{0}-{1}]({2}) - {3}".format( + self.prefix, + self.identifier, + f"{self.parent.url}/browse/{self.issue['key']}", + self.summary + ) return "{0}-{1} - {2}".format( self.prefix, self.identifier, self.summary) @@ -168,7 +177,10 @@ def search(query, stats): if len(issues) >= data["total"]: break # Return the list of issue objects - return [Issue(issue, prefix=stats.parent.prefix) for issue in issues] + return [ + Issue(issue, parent=stats.parent) + for issue in issues + ] def updated(self, user, options): """ True if the issue was commented by given user """ diff --git a/did/utils.py b/did/utils.py index d166795f..3852b4f0 100644 --- a/did/utils.py +++ b/did/utils.py @@ -186,6 +186,8 @@ def item(text, level=0, options=None): return # Four space for each level, additional space for wiki format indent = level * 4 + if options.format == "markdown": + indent = level * 2 if options.format == "wiki" and level == 0: indent = 1 # Shorten the text if necessary to match the desired maximum width diff --git a/docs/conf.py b/docs/conf.py index 65dd9b09..36923276 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -175,7 +175,7 @@ # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. -# html_use_smartypants = True +smartquotes = False # Custom sidebar templates, maps document names to template names. # html_sidebars = {} diff --git a/docs/examples.rst b/docs/examples.rst index 20513c3f..34f0fee9 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -94,7 +94,9 @@ corresponding option groups for each of them:: --footer All above Format: - --format FORMAT Output style, possible values: text (default) or wiki + Format: + --format {text,markdown,wiki} + Output style, default: text --width WIDTH Maximum width of the report output (default: 79) --brief Show brief summary only, do not list individual items --verbose Include more details (like modified git directories)