Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow format issue output text within individual sections and in general #103

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,26 @@

package io.spring.githubchangeloggenerator;

import static io.spring.githubchangeloggenerator.ApplicationProperties.FormatPlaceholder.*;
import static io.spring.githubchangeloggenerator.ApplicationProperties.FormatPlaceholder.TITLE;

import io.spring.githubchangeloggenerator.github.service.Repository;
Comment on lines +19 to +22
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't re-order the imports. It adds noise to the diff, making the changes harder to review. If you run ./gradlew build, you will see failures from both Checkstyle and Spring Java Format that should be corrected please.

import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.bind.DefaultValue;
import org.springframework.util.Assert;

import io.spring.githubchangeloggenerator.github.service.Repository;

/**
* Configuration properties for the GitHub repo.
*
* @author Madhura Bhave
* @author Phillip Webb
* @author Mahendra Bishnoi
* @author Gary Russell
* @author Dinar Shagaliev
*/
@ConfigurationProperties(prefix = "changelog")
public class ApplicationProperties {
Expand Down Expand Up @@ -75,12 +77,13 @@ public class ApplicationProperties {

public ApplicationProperties(Repository repository, @DefaultValue("title") MilestoneReference milestoneReference,
List<Section> sections, Issues issues, Contributors contributors, List<ExternalLink> externalLinks,
@DefaultValue("false") boolean addSections) {
@DefaultValue("false") boolean addSections) {

Comment on lines +80 to +81
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks like a whitespace-only change. Please revert.

Assert.notNull(repository, "Repository must not be null");
this.repository = repository;
this.milestoneReference = milestoneReference;
this.sections = (sections != null) ? sections : Collections.emptyList();
this.issues = (issues != null) ? issues : new Issues(null, null, null, true);
this.issues = (issues != null) ? issues : new Issues(null, null, null, true, null);
this.contributors = (contributors != null) ? contributors : new Contributors(null, null);
this.externalLinks = (externalLinks != null) ? externalLinks : Collections.emptyList();
this.addSections = addSections;
Expand Down Expand Up @@ -140,11 +143,25 @@ public static class Section {
*/
private final Set<String> labels;

public Section(String title, @DefaultValue("default") String group, IssueSort sort, Set<String> labels) {

/**
* Whether to generate a link to each issue in the changelog.
*/
private final boolean generateLinks;
Comment on lines +147 to +150
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need a second property for this? The Issues inner-class already has one.


/**
* Format used to customize an output of issues in the section.
*/
private final String format;

public Section(String title, @DefaultValue("default") String group, IssueSort sort,
boolean generateLinks, String format, Set<String> labels) {
this.title = title;
this.group = (group != null) ? group : "default";
this.group = group;
this.sort = sort;
this.labels = labels;
this.generateLinks = generateLinks;
this.format = format;
this.labels = labels;
}

public String getTitle() {
Expand All @@ -159,6 +176,14 @@ public IssueSort getSort() {
return this.sort;
}

public String getFormat() {
return format;
}

public boolean isGenerateLinks() {
return generateLinks;
}

public Set<String> getLabels() {
return this.labels;
}
Expand Down Expand Up @@ -190,12 +215,20 @@ public static class Issues {
*/
private final boolean generateLinks;

/**
* Default format used to customize an output of issues in the section.
*/
private final String defaultFormat;

public Issues(IssueSort sort, IssuesExclude exclude, Set<PortedIssue> ports,
@DefaultValue("true") boolean generateLinks) {
@DefaultValue("true") Boolean generateLinks, String defaultFormat) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why has generatedLinks been changed from boolean to Boolean?

this.sort = sort;
this.exclude = (exclude != null) ? exclude : new IssuesExclude(null);
this.ports = (ports != null) ? ports : Collections.emptySet();
this.generateLinks = generateLinks;
this.generateLinks = (defaultFormat != null) || generateLinks;
this.defaultFormat = (defaultFormat != null) ? defaultFormat
: String.format("%s [#%s](%s)", TITLE.placeholder, NUMBER.placeholder,
URL.placeholder);
}

public IssueSort getSort() {
Expand All @@ -214,7 +247,11 @@ public boolean isGenerateLinks() {
return this.generateLinks;
}

}
public String getDefaultFormat() {
return this.defaultFormat;
}

}

/**
* Issues exclude.
Expand Down Expand Up @@ -361,4 +398,18 @@ public enum IssueSort {

}

public enum FormatPlaceholder {

TITLE("${title}"),
URL("${url}"),
NUMBER("${number}");

public final String placeholder;

FormatPlaceholder(String placeholder) {
this.placeholder = placeholder;
}

}

}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@

package io.spring.githubchangeloggenerator;

import static io.spring.githubchangeloggenerator.ApplicationProperties.FormatPlaceholder.NUMBER;
import static io.spring.githubchangeloggenerator.ApplicationProperties.FormatPlaceholder.TITLE;
import static io.spring.githubchangeloggenerator.ApplicationProperties.FormatPlaceholder.URL;

import io.spring.githubchangeloggenerator.ApplicationProperties.ExternalLink;
import io.spring.githubchangeloggenerator.ApplicationProperties.IssueSort;
import io.spring.githubchangeloggenerator.ApplicationProperties.PortedIssue;
import io.spring.githubchangeloggenerator.github.payload.Issue;
import io.spring.githubchangeloggenerator.github.payload.Label;
import io.spring.githubchangeloggenerator.github.payload.User;
import io.spring.githubchangeloggenerator.github.service.GitHubService;
import io.spring.githubchangeloggenerator.github.service.Repository;
Comment on lines +19 to +30
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please correct the imports as per the problems reported by Checkstyle when you run ./gradlew build.

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
Expand All @@ -29,26 +41,17 @@
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import org.springframework.stereotype.Component;
import org.springframework.util.FileCopyUtils;

import io.spring.githubchangeloggenerator.ApplicationProperties.ExternalLink;
import io.spring.githubchangeloggenerator.ApplicationProperties.IssueSort;
import io.spring.githubchangeloggenerator.ApplicationProperties.PortedIssue;
import io.spring.githubchangeloggenerator.github.payload.Issue;
import io.spring.githubchangeloggenerator.github.payload.Label;
import io.spring.githubchangeloggenerator.github.payload.User;
import io.spring.githubchangeloggenerator.github.service.GitHubService;
import io.spring.githubchangeloggenerator.github.service.Repository;

/**
* Generates a changelog markdown file which includes bug fixes, enhancements and
* contributors for a given milestone.
*
* @author Madhura Bhave
* @author Phillip Webb
* @author Mahendra Bishnoi
* @author Dinar Shagaliev
*/
@Component
public class ChangelogGenerator {
Expand Down Expand Up @@ -80,6 +83,8 @@ public class ChangelogGenerator {

private final boolean generateLinks;

private final String defaultFormat;

public ChangelogGenerator(GitHubService service, ApplicationProperties properties) {
this.service = service;
this.repository = properties.getRepository();
Expand All @@ -92,6 +97,7 @@ public ChangelogGenerator(GitHubService service, ApplicationProperties propertie
this.portedIssues = properties.getIssues().getPorts();
this.externalLinks = properties.getExternalLinks();
this.generateLinks = properties.getIssues().isGenerateLinks();
this.defaultFormat = properties.getIssues().getDefaultFormat();
}

/**
Expand Down Expand Up @@ -142,14 +148,17 @@ private String generateContent(List<Issue> issues) {
return content.toString();
}

private void addSectionContent(StringBuilder content, Map<ChangelogSection, List<Issue>> sectionIssues) {
sectionIssues.forEach((section, issues) -> {
sort(section.getSort(), issues);
content.append((content.length() != 0) ? String.format("%n") : "");
content.append("## ").append(section).append(String.format("%n%n"));
issues.stream().map(this::getFormattedIssue).forEach(content::append);
});
}
private void addSectionContent(StringBuilder content,
Map<ChangelogSection, List<Issue>> sectionIssues) {
sectionIssues.forEach((section, issues) -> {
sort(section.getSort(), issues);
content.append((!content.isEmpty()) ? String.format("%n") : "");
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Switching to isEmpty() is unrelated to allowing more control over the formatting. Please revert.

content.append("## ").append(section).append(String.format("%n%n"));

issues.stream().map(issue -> this.getFormattedIssue(issue, section))
.forEach(content::append);
});
}

private void sort(IssueSort sort, List<Issue> issues) {
sort = (sort != null) ? sort : this.sort;
Expand All @@ -158,18 +167,36 @@ private void sort(IssueSort sort, List<Issue> issues) {
}
}

private String getFormattedIssue(Issue issue) {
String title = issue.getTitle();
for (Escape escape : escapes) {
title = escape.apply(title);
private String getFormattedIssue(Issue issue, ChangelogSection section) {
String format = getFormat(section);

String formattedIssueStr = "- " + format + "\n";
String title = escape(issue.getTitle());

formattedIssueStr = formattedIssueStr.replace(TITLE.placeholder, title);
formattedIssueStr = formattedIssueStr.replace(NUMBER.placeholder, issue.getNumber());
formattedIssueStr = formattedIssueStr.replace(URL.placeholder, issue.getUrl());

return formattedIssueStr;
}

private String getFormat(ChangelogSection section) {
if (section.getFormat() != null) {
return section.getFormat();
} else if (!generateLinks) {
return "${title}";
}
return (this.generateLinks) ? String.format("- %s %s%n", title, getLinkToIssue(issue))
: String.format("- %s%n", title);
return defaultFormat;
}

private String getLinkToIssue(Issue issue) {
return "[#" + issue.getNumber() + "]" + "(" + issue.getUrl() + ")";
}
private String escape(String text) {
String escaped = text;
for (Escape escape : escapes) {
escaped = escape.apply(escaped);
}

return escaped;
}

private Set<User> getContributors(List<Issue> issues) {
if (this.excludeContributors.contains("*")) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
* A single section of a changelog report.
*
* @author Phillip Webb
* @author Dinar Shagaliev
*/
class ChangelogSection {

Expand All @@ -42,17 +43,20 @@ class ChangelogSection {

private final Set<String> labels;

ChangelogSection(String title, String group, IssueSort sort, String... labels) {
this(title, group, sort, new LinkedHashSet<>(Arrays.asList(labels)));
private final String format;

ChangelogSection(String title, String group, IssueSort sort, String format, String... labels) {
this(title, group, sort, format, new LinkedHashSet<>(Arrays.asList(labels)));
}

ChangelogSection(String title, String group, IssueSort sort, Set<String> labels) {
ChangelogSection(String title, String group, IssueSort sort, String format, Set<String> labels) {
Assert.hasText(title, "Title must not be empty");
Assert.isTrue(!CollectionUtils.isEmpty(labels), "Labels must not be empty");
this.title = title;
this.group = group;
this.sort = sort;
this.labels = labels;
this.format = format;
}

String getGroup() {
Expand All @@ -79,4 +83,8 @@ public String toString() {
return this.title;
}

public String getFormat() {
return this.format;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
*
* @author Phillip Webb
* @author Gary Russell
* @author Dinar Shagaliev
*/
class ChangelogSections {

Expand All @@ -50,7 +51,7 @@ class ChangelogSections {
}

private static void add(List<ChangelogSection> sections, String title, String... labels) {
sections.add(new ChangelogSection(title, null, null, labels));
sections.add(new ChangelogSection(title, null, null, null, labels));
}

private final List<ChangelogSection> sections;
Expand All @@ -74,8 +75,8 @@ private List<ChangelogSection> adapt(ApplicationProperties properties) {
}

private ChangelogSection adapt(ApplicationProperties.Section propertySection) {
return new ChangelogSection(propertySection.getTitle(), propertySection.getGroup(), propertySection.getSort(),
propertySection.getLabels());
return new ChangelogSection(propertySection.getTitle(), propertySection.getGroup(),
propertySection.getSort(), propertySection.getFormat(), propertySection.getLabels());
}

Map<ChangelogSection, List<Issue>> collate(List<Issue> issues) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@
* Tests for {@link ApplicationProperties}.
*
* @author Phillip Webb
* @author Dinar Shagaliev
*/
class ApplicationPropertiesTests {

Expand All @@ -49,7 +50,7 @@ void loadYaml() throws Exception {
assertThat(repository.getOwner()).isEqualTo("testorg");
assertThat(repository.getName()).isEqualTo("testrepo");
List<Section> sections = properties.getSections();
assertThat(sections).hasSize(2);
assertThat(sections).hasSize(3);
assertThat(sections.get(0).getTitle()).isEqualTo(":star: New Features");
assertThat(sections.get(0).getLabels()).containsExactly("enhancement");
assertThat(sections.get(0).getGroup()).isEqualTo("default");
Expand All @@ -58,8 +59,13 @@ void loadYaml() throws Exception {
assertThat(sections.get(1).getLabels()).containsExactly("bug");
assertThat(sections.get(1).getGroup()).isEqualTo("test");
assertThat(sections.get(1).getSort()).isNull();
assertThat(sections.get(2).getTitle()).isEqualTo("Experimental");
assertThat(sections.get(2).getLabels()).containsExactly("experimental");
assertThat(sections.get(2).getFormat()).isEqualTo("Experimental feature: ${number}: ${title}");
assertThat(properties.getIssues().getExcludes().getLabels()).containsExactly("hide");
assertThat(properties.getIssues().getSort()).isEqualTo(IssueSort.TITLE);
assertThat(properties.getIssues().getDefaultFormat()).isEqualTo("${number}: ${title}");
assertThat(properties.getIssues().isGenerateLinks()).isTrue();
assertThat(properties.getContributors().getTitle()).isEqualTo("Nice one!");
assertThat(properties.getContributors().getExclude().getNames()).containsExactly("philwebb");
assertThat(properties.getExternalLinks().get(0).getName()).isEqualTo("Release Notes 1");
Expand Down
Loading