diff --git a/README.md b/README.md index 4fed7d8..312e375 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,13 @@ natives (130 MB → ~24 MB for macOS arm64) and a jlink'ed runtime (~64 MB) — To re-measure: `unzip -v target/pholio.jar | sort -k3 -n -r | head -20` (third column: compressed size). +### Credits + +`src/main/resources/userguide/credits.md` is the single list of credits — shipped libraries with their +licenses, recognition models, map and place data. The application shows it from the `?` in Settings +(`CreditsView`), and each release copies it to `distrib/CREDITS.md`. Add a library or data source there when +you add one to the application. + ### Useful scripts - `python3 tools/icons/make-macos-icon.py` — regenerates the macOS icon, `icon.png` and `pholio.icns` diff --git a/src/main/java/org/icroco/pholio/ui/common/MarkdownPage.java b/src/main/java/org/icroco/pholio/ui/common/MarkdownPage.java new file mode 100644 index 0000000..9650c44 --- /dev/null +++ b/src/main/java/org/icroco/pholio/ui/common/MarkdownPage.java @@ -0,0 +1,59 @@ +package org.icroco.pholio.ui.common; + +import atlantafx.base.util.BBCodeParser; +import javafx.scene.control.Hyperlink; +import javafx.scene.layout.VBox; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; +import java.util.Locale; +import java.util.function.Consumer; + +/** + * A bundled Markdown page (the user guide and credits under {@code src/main/resources/userguide}) shown + * natively: loaded in the current language when a translation exists, converted by {@link MarkdownToBBCode} + * and laid out by AtlantaFX's {@link BBCodeParser}. + */ +public final class MarkdownPage { + + private static final Logger log = LoggerFactory.getLogger(MarkdownPage.class); + + private MarkdownPage() { + } + + /** + * {@code basePath_.md} when it exists, otherwise {@code basePath.md}; empty if neither does. + * + * @param basePath classpath location without extension, e.g. {@code /userguide/credits} + */ + public static String load(String basePath, Locale locale) { + for (String resource : new String[]{basePath + "_" + locale.getLanguage() + ".md", basePath + ".md"}) { + try (InputStream in = MarkdownPage.class.getResourceAsStream(resource)) { + if (in != null) { + return new String(in.readAllBytes(), StandardCharsets.UTF_8); + } + } + catch (IOException e) { + log.warn("Could not read {}", resource, e); + } + } + return ""; + } + + /** + * Lays {@code markdown} out; every link hands its URL to {@code onLink} when clicked (BBCodeParser + * builds links as {@link Hyperlink}s carrying their URL as user data, with no action of their own). + */ + public static VBox render(String markdown, Consumer onLink) { + VBox page = BBCodeParser.createLayout(MarkdownToBBCode.convert(markdown)); + page.lookupAll(".hyperlink").forEach(node -> { + if (node instanceof Hyperlink link && link.getUserData() instanceof String url) { + link.setOnAction(event -> onLink.accept(url)); + } + }); + return page; + } +} diff --git a/src/main/java/org/icroco/pholio/ui/common/MarkdownToBBCode.java b/src/main/java/org/icroco/pholio/ui/common/MarkdownToBBCode.java index a3ff5b4..ab0efbd 100644 --- a/src/main/java/org/icroco/pholio/ui/common/MarkdownToBBCode.java +++ b/src/main/java/org/icroco/pholio/ui/common/MarkdownToBBCode.java @@ -11,7 +11,8 @@ import java.util.regex.Pattern; * maintained once, as plain Markdown readable anywhere, and still shown natively in the application. * *

Supported: {@code #}/{@code ##}/{@code ###} headings, paragraphs (consecutive lines joined, blank - * line between two), {@code -}/{@code *} bullet lists, {@code **bold**} and {@code `code`}. Anything + * line between two), {@code -}/{@code *} bullet lists, {@code **bold**}, {@code `code`} and + * {@code [links](https://…)} — rendered as {@code Hyperlink}s whose URL is their user data. Anything * else is passed through as plain text — enough for a guide page, not a general-purpose Markdown engine. */ public final class MarkdownToBBCode { @@ -19,9 +20,10 @@ public final class MarkdownToBBCode { private static final Pattern HEADING = Pattern.compile("^(#{1,3})\\s+(.*)$"); private static final Pattern BULLET = Pattern.compile("^[-*]\\s+(.*)$"); /** - * Code first in the alternation, so {@code **} inside a code span stays literal instead of turning bold. + * Code first in the alternation, so {@code **} or a link inside a code span stays literal. Bold content + * is converted again, so a link can sit inside bold text ({@code **[Pholio](…)**}). */ - private static final Pattern INLINE = Pattern.compile("`([^`]+)`|\\*\\*(.+?)\\*\\*"); + private static final Pattern INLINE = Pattern.compile("`([^`]+)`|\\[([^\\]]+)\\]\\(([^)\\s]+)\\)|\\*\\*(.+?)\\*\\*"); private MarkdownToBBCode() { } @@ -98,6 +100,10 @@ public final class MarkdownToBBCode { private static String inline(String text) { return INLINE.matcher(text).replaceAll(match -> Matcher.quoteReplacement( - match.group(1) != null ? "[code]" + match.group(1) + "[/code]" : "[b]" + match.group(2) + "[/b]")); + match.group(1) != null ? "[code]" + match.group(1) + "[/code]" + // Quoted: BBCodeParser reads a tag ending in "/]" as self-closing, so an unquoted URL with a + // trailing slash would swallow the link. The parser strips the quotes from the value. + : match.group(2) != null ? "[url=\"" + match.group(3) + "\"]" + match.group(2) + "[/url]" + : "[b]" + inline(match.group(4)) + "[/b]")); } } diff --git a/src/main/java/org/icroco/pholio/ui/shell/CreditsView.java b/src/main/java/org/icroco/pholio/ui/shell/CreditsView.java index ed7905d..2d1d4fe 100644 --- a/src/main/java/org/icroco/pholio/ui/shell/CreditsView.java +++ b/src/main/java/org/icroco/pholio/ui/shell/CreditsView.java @@ -1,51 +1,42 @@ package org.icroco.pholio.ui.shell; import atlantafx.base.theme.Styles; +import javafx.application.HostServices; import javafx.geometry.Insets; import javafx.geometry.Pos; import javafx.scene.control.Button; import javafx.scene.control.Label; import javafx.scene.control.ScrollPane; -import javafx.scene.control.Separator; import javafx.scene.layout.HBox; import javafx.scene.layout.StackPane; import javafx.scene.layout.VBox; import org.icroco.pholio.infra.i18n.I18nService; import org.icroco.pholio.ui.common.Disposable; import org.icroco.pholio.ui.common.FxUtils; +import org.icroco.pholio.ui.common.MarkdownPage; import org.icroco.pholio.ui.common.UiView; +import org.jspecify.annotations.Nullable; import org.kordamp.ikonli.feather.Feather; import org.kordamp.ikonli.javafx.FontIcon; -import java.util.List; - /** - * "About" — the open source libraries this application is built on, plus the map data attribution - * {@code GeocodingProviderEditView}'s own OpenStreetMap-backed lookups require. Opened from the help icon - * in {@link SettingsView#header()}; a plain informational panel, dismissed by its own close button (or the - * usual click-outside/Escape any non-persistent {@link ModalService} dialog already allows). + * "About" — the credits page, opened from the help icon in {@link SettingsView#header()}: the open source + * libraries Pholio ships, the recognition models and the map/place data it relies on, with their licenses. + * + *

The content is {@code src/main/resources/userguide/credits.md}, the single source of those credits: + * rendered here through {@link MarkdownPage}, and copied as {@code CREDITS.md} into the public distrib + * repository by the release's {@code distrib} step. Update that file, not this view. Its links open in the + * user's browser through {@link HostServices} — absent in tests, where they simply do nothing. */ @UiView public class CreditsView extends StackPane implements Disposable { - /** - * Proper nouns, not translated. Runtime dependencies only (see {@code pom.xml}) — build/test-only - * tooling (JUnit, ArchUnit, JMH, the Maven plugins…) credits no part of the running application. - */ - private static final List OPEN_SOURCE_LIBRARIES = List.of( - "Spring Boot", "JavaFX", "AtlantaFX", "GemFx", "Ikonli", - "H2 Database", "Flyway", "Jackson", "MapStruct", "Jilt", "Lombok", - "picocli", "Caffeine", "metadata-extractor", "Apache Commons Imaging", - "XMPCore", "ONNX Runtime", "jSystemThemeDetector", "threeten-extra", "gatherers4j" - ); + private static final String PAGE = "/userguide/credits"; - private final Label title = new Label(); - private final Label openSourceHeader = new Label(); - private final Label mapDataHeader = new Label(); - private final Label mapDataCredit = new Label(); - private final Button closeButton = new Button(); + private final Label title = new Label(); + private final Button closeButton = new Button(); - public CreditsView(I18nService i18n, ModalService modalService) { + public CreditsView(I18nService i18n, ModalService modalService, @Nullable HostServices hostServices) { getStyleClass().addAll("modal-card", Styles.BG_DEFAULT, Styles.ELEVATED_2); setMaxSize(USE_PREF_SIZE, USE_PREF_SIZE); @@ -55,20 +46,15 @@ public class CreditsView extends StackPane implements Disposable { HBox header = new HBox(10, infoIcon, title); header.setAlignment(Pos.CENTER_LEFT); - openSourceHeader.getStyleClass().add(Styles.TEXT_BOLD); - openSourceHeader.textProperty().bind(i18n.binding("credits.openSource.header")); - VBox libraries = new VBox(2, OPEN_SOURCE_LIBRARIES.stream().map(CreditsView::libraryLabel).toArray(Label[]::new)); - - mapDataHeader.getStyleClass().add(Styles.TEXT_BOLD); - mapDataHeader.textProperty().bind(i18n.binding("credits.mapData.header")); - mapDataCredit.setWrapText(true); - mapDataCredit.getStyleClass().add(Styles.TEXT_MUTED); - mapDataCredit.textProperty().bind(i18n.binding("credits.mapData.openStreetMap")); - - VBox body = new VBox(6, openSourceHeader, libraries, new Separator(), mapDataHeader, mapDataCredit); - ScrollPane scroll = new ScrollPane(body); + VBox page = MarkdownPage.render(MarkdownPage.load(PAGE, i18n.locale()), url -> { + if (hostServices != null) { + hostServices.showDocument(url); + } + }); + page.getStyleClass().add("credits-page"); + ScrollPane scroll = new ScrollPane(page); scroll.setFitToWidth(true); - scroll.setPrefHeight(280); + scroll.setPrefHeight(420); closeButton.textProperty().bind(i18n.binding("action.close")); closeButton.getStyleClass().add(Styles.ACCENT); @@ -78,23 +64,14 @@ public class CreditsView extends StackPane implements Disposable { VBox card = new VBox(16, header, scroll, footer); card.setPadding(new Insets(24)); - card.setPrefWidth(420); + card.setPrefWidth(560); getChildren().add(card); } - private static Label libraryLabel(String name) { - Label label = new Label("• " + name); - label.getStyleClass().add(Styles.TEXT_MUTED); - return label; - } - @Override public void dispose() { title.textProperty().unbind(); - openSourceHeader.textProperty().unbind(); - mapDataHeader.textProperty().unbind(); - mapDataCredit.textProperty().unbind(); closeButton.textProperty().unbind(); closeButton.setOnAction(null); } diff --git a/src/main/java/org/icroco/pholio/ui/shell/SearchHelpPopup.java b/src/main/java/org/icroco/pholio/ui/shell/SearchHelpPopup.java index b2773bd..c2c9ccb 100644 --- a/src/main/java/org/icroco/pholio/ui/shell/SearchHelpPopup.java +++ b/src/main/java/org/icroco/pholio/ui/shell/SearchHelpPopup.java @@ -1,6 +1,5 @@ package org.icroco.pholio.ui.shell; -import atlantafx.base.util.BBCodeParser; import javafx.geometry.Bounds; import javafx.geometry.Insets; import javafx.scene.control.ScrollPane; @@ -9,17 +8,11 @@ import javafx.scene.layout.VBox; import javafx.stage.Popup; import javafx.stage.Window; import org.icroco.pholio.infra.i18n.I18nService; -import org.icroco.pholio.ui.common.MarkdownToBBCode; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.io.IOException; -import java.io.InputStream; -import java.nio.charset.StandardCharsets; +import org.icroco.pholio.ui.common.MarkdownPage; /** * The search bar's syntax guide: {@code userguide/search-syntax.md} (or its {@code _} variant), - * rendered through {@link MarkdownToBBCode} and AtlantaFX's {@link BBCodeParser}, in a popup that drops + * rendered through {@link MarkdownPage}, in a popup that drops * down from {@link AppHeaderBar}'s search field — same left edge and width, flush against its bottom edge — * so it reads as the field unfolding rather than as a separate window. * @@ -28,8 +21,6 @@ import java.nio.charset.StandardCharsets; */ final class SearchHelpPopup extends Popup { - private static final Logger log = LoggerFactory.getLogger(SearchHelpPopup.class); - private static final String PAGE = "/userguide/search-syntax"; /** @@ -63,7 +54,7 @@ final class SearchHelpPopup extends Popup { if (anchor.getScene() == null) { return; } - VBox page = BBCodeParser.createLayout(MarkdownToBBCode.convert(loadPage())); + VBox page = MarkdownPage.render(MarkdownPage.load(PAGE, i18n.locale()), url -> {}); page.getStyleClass().add("search-help-page"); page.setPadding(new Insets(PADDING)); scroll.setContent(page); @@ -76,18 +67,4 @@ final class SearchHelpPopup extends Popup { scroll.setPrefHeight(Math.min(page.prefHeight(width), available)); show(anchor, anchorBounds.getMinX(), anchorBounds.getMaxY()); } - - private String loadPage() { - for (String resource : new String[]{PAGE + "_" + i18n.locale().getLanguage() + ".md", PAGE + ".md"}) { - try (InputStream in = SearchHelpPopup.class.getResourceAsStream(resource)) { - if (in != null) { - return new String(in.readAllBytes(), StandardCharsets.UTF_8); - } - } - catch (IOException e) { - log.warn("Could not read search guide {}", resource, e); - } - } - return ""; - } } diff --git a/src/main/resources/images/spashscreen.png b/src/main/resources/images/spashscreen.png new file mode 100644 index 0000000..54eb7a9 Binary files /dev/null and b/src/main/resources/images/spashscreen.png differ diff --git a/src/main/resources/messages.properties b/src/main/resources/messages.properties index 29947d0..9c7662c 100644 --- a/src/main/resources/messages.properties +++ b/src/main/resources/messages.properties @@ -228,9 +228,6 @@ settings.appearance.language.en=English settings.ai.provider.none=None # --- Credits (About dialog, opened from the preferences header's help icon) --- credits.title=Credits -credits.openSource.header=Open source libraries -credits.mapData.header=Map data -credits.mapData.openStreetMap=Address search and map data © OpenStreetMap contributors, available under the Open Database License (ODbL). # --- Onboarding (first-run coach marks) --- onboarding.nav.title=Your library lives here onboarding.nav.text=Browse your photos, albums, tags and more from this rail. diff --git a/src/main/resources/messages_fr.properties b/src/main/resources/messages_fr.properties index c6ff0e2..561abce 100644 --- a/src/main/resources/messages_fr.properties +++ b/src/main/resources/messages_fr.properties @@ -231,9 +231,6 @@ settings.appearance.language.en=English settings.ai.provider.none=Aucun # --- Crédits (boîte de dialogue "À propos", ouverte depuis l'icône d'aide des préférences) --- credits.title=Crédits -credits.openSource.header=Bibliothèques open source -credits.mapData.header=Données cartographiques -credits.mapData.openStreetMap=Recherche d'adresse et données cartographiques © contributeurs d'OpenStreetMap, disponibles sous licence Open Database License (ODbL). # --- Présentation initiale (coach marks) --- onboarding.nav.title=Votre photothèque est ici onboarding.nav.text=Parcourez vos photos, albums, mots-clés et plus depuis ce panneau. diff --git a/src/main/resources/userguide/credits.md b/src/main/resources/userguide/credits.md new file mode 100644 index 0000000..1db74d6 --- /dev/null +++ b/src/main/resources/userguide/credits.md @@ -0,0 +1,51 @@ +# Credits + +Pholio stands on the work of many open source projects and open data providers. Thank you to all of them. + +## Open source libraries + +Libraries shipped inside Pholio, with their license. + +- **[Spring Boot](https://spring.io/projects/spring-boot)**, **[Spring Framework](https://spring.io/projects/spring-framework)** and **[Spring Data JDBC](https://spring.io/projects/spring-data-jdbc)** — Apache License 2.0 +- **[OpenJFX](https://openjfx.io)** — GPL v2 with Classpath Exception +- **[AtlantaFX](https://github.com/mkpaz/atlantafx)** — MIT +- **[AtlantaFX Themes](https://github.com/dlsc-software-consulting-gmbh/themes)**, **[GemsFX](https://github.com/dlsc-software-consulting-gmbh/GemsFX)** and **[PickerFX](https://github.com/dlsc-software-consulting-gmbh/PickerFX)** by DLSC — Apache License 2.0 +- **[ControlsFX](https://controlsfx.github.io)** — BSD 3-Clause +- **[ValidatorFX](https://github.com/effad/ValidatorFX)** — BSD 3-Clause +- **[Ikonli](https://github.com/kordamp/ikonli)**, with the Material Design and Feather icon packs — Apache License 2.0 +- **[DevToolsFX](https://github.com/mkpaz/devtoolsfx)** — MIT +- **[H2 Database](https://h2database.com)** — MPL 2.0 / EPL 1.0 +- **[Flyway](https://github.com/flyway/flyway)** — Apache License 2.0 +- **[Jackson](https://github.com/FasterXML/jackson)** — Apache License 2.0 +- **[MapStruct](https://mapstruct.org)** — Apache License 2.0 +- **[picocli](https://picocli.info)** — Apache License 2.0 +- **[Caffeine](https://github.com/ben-manes/caffeine)** — Apache License 2.0 +- **[metadata-extractor](https://github.com/drewnoakes/metadata-extractor)** — Apache License 2.0 +- **[Apache Commons Imaging](https://commons.apache.org/proper/commons-imaging/)** and **[Apache Commons Codec](https://commons.apache.org/proper/commons-codec/)** — Apache License 2.0 +- **[Adobe XMPCore](https://github.com/adobe/XMP-Toolkit-SDK)** — BSD 3-Clause +- **[TwelveMonkeys ImageIO](https://github.com/haraldk/TwelveMonkeys)** — BSD 3-Clause +- **[ONNX Runtime](https://onnxruntime.ai)** — MIT +- **[Byte Buddy](https://bytebuddy.net)** — Apache License 2.0 +- **[Java Native Access](https://github.com/java-native-access/jna)** — Apache License 2.0 / LGPL 2.1 +- **[OSHI](https://github.com/oshi/oshi)** — MIT +- **[jSystemThemeDetector](https://github.com/Dansoftowner/jSystemThemeDetector)** and **[JFA](https://github.com/0x4a616e/jfa)** — Apache License 2.0 +- **[ThreeTen-Extra](https://www.threeten.org/threeten-extra/)** — BSD 3-Clause +- **[Gatherers4j](https://github.com/tginsberg/gatherers4j)** — Apache License 2.0 +- **[JSpecify](https://jspecify.dev)** — Apache License 2.0 +- **[SLF4J](https://www.slf4j.org)** — MIT, and **[Logback](https://logback.qos.ch)** — EPL 2.0 / LGPL 2.1 + +Built with, but not shipped: **[Lombok](https://projectlombok.org)** (MIT) and **[Jilt](https://github.com/skinny85/jilt)** (Apache License 2.0). + +## Recognition models + +Face and animal recognition run entirely on your computer, with these models, used exactly as published. + +- **[YuNet](https://github.com/opencv/opencv_zoo/tree/main/models/face_detection_yunet)** face detection, from OpenCV Zoo — MIT +- **[SFace](https://github.com/opencv/opencv_zoo/tree/main/models/face_recognition_sface)** face recognition, from OpenCV Zoo — Apache License 2.0 +- **[YOLOX-Nano](https://github.com/Megvii-BaseDetection/YOLOX)** object detection, by Megvii — Apache License 2.0 + +## Map and place data + +- Offline place names come from **[GeoNames](https://www.geonames.org)**, licensed under [Creative Commons Attribution 4.0](https://creativecommons.org/licenses/by/4.0/). +- Map tiles © **[OpenStreetMap](https://www.openstreetmap.org/copyright)** contributors, available under the Open Database License (ODbL). +- When configured, online place search is provided by **[LocationIQ](https://locationiq.com)**. diff --git a/src/test/java/org/icroco/pholio/ui/common/MarkdownPageTest.java b/src/test/java/org/icroco/pholio/ui/common/MarkdownPageTest.java new file mode 100644 index 0000000..41e960a --- /dev/null +++ b/src/test/java/org/icroco/pholio/ui/common/MarkdownPageTest.java @@ -0,0 +1,52 @@ +package org.icroco.pholio.ui.common; + +import javafx.scene.control.Hyperlink; +import javafx.scene.layout.VBox; +import org.assertj.core.api.SoftAssertions; +import org.icroco.pholio.ui.FxTestToolkit; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +import static org.icroco.pholio.ui.FxTestToolkit.onFxThread; +import static org.icroco.pholio.ui.FxTestToolkit.runOnFxThread; + +class MarkdownPageTest { + + @BeforeEach + void setUp() { + FxTestToolkit.requireToolkit(); + } + + @Test + void loadsTheTranslationWhenThereIsOneAndFallsBackOtherwise() { + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(MarkdownPage.load("/userguide/search-syntax", Locale.FRENCH)).startsWith("# Rechercher"); + softly.assertThat(MarkdownPage.load("/userguide/search-syntax", Locale.GERMAN)).startsWith("# Searching"); + softly.assertThat(MarkdownPage.load("/userguide/credits", Locale.FRENCH)).startsWith("# Credits"); + softly.assertThat(MarkdownPage.load("/userguide/does-not-exist", Locale.ENGLISH)).isEmpty(); + softly.assertAll(); + } + + @Test + void creditsLinksAreHyperlinksThatReportTheirUrlWhenClicked() { + List opened = new ArrayList<>(); + VBox page = onFxThread(() -> MarkdownPage.render(MarkdownPage.load("/userguide/credits", Locale.ENGLISH), opened::add)); + List links = onFxThread(() -> page.lookupAll(".hyperlink").stream() + .filter(Hyperlink.class::isInstance) + .map(Hyperlink.class::cast) + .toList()); + Hyperlink onnx = links.stream().filter(link -> "ONNX Runtime".equals(link.getText())).findFirst().orElseThrow(); + + runOnFxThread(onnx::fire); + + SoftAssertions softly = new SoftAssertions(); + softly.assertThat(links).hasSizeGreaterThan(30); + softly.assertThat(onnx.getUserData()).isEqualTo("https://onnxruntime.ai"); + softly.assertThat(opened).containsExactly("https://onnxruntime.ai"); + softly.assertAll(); + } +} diff --git a/src/test/java/org/icroco/pholio/ui/common/MarkdownToBBCodeTest.java b/src/test/java/org/icroco/pholio/ui/common/MarkdownToBBCodeTest.java index 7a1a2ab..10a84b2 100644 --- a/src/test/java/org/icroco/pholio/ui/common/MarkdownToBBCodeTest.java +++ b/src/test/java/org/icroco/pholio/ui/common/MarkdownToBBCodeTest.java @@ -31,6 +31,37 @@ class MarkdownToBBCodeTest { + "After the list.")); } + @Test + void convertsLinksIncludingInsideBold() { + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(MarkdownToBBCode.convert("See [H2](https://h2database.com).")) + .isEqualTo("See [url=\"https://h2database.com\"]H2[/url]."); + softly.assertThat(MarkdownToBBCode.convert("- **[Spring Boot](https://spring.io)** — Apache")) + .isEqualTo("[ul][li][b][url=\"https://spring.io\"]Spring Boot[/url][/b] — Apache[/li][/ul]"); + softly.assertThat(MarkdownToBBCode.convert("`[not](a link)`")).isEqualTo("[code][not](a link)[/code]"); + }); + } + + @Test + void bundledCreditsConvert() throws IOException { + try (InputStream in = MarkdownToBBCodeTest.class.getResourceAsStream("/userguide/credits.md")) { + SoftAssertions.assertSoftly(softly -> { + softly.assertThat(in).isNotNull(); + if (in != null) { + try { + String bbcode = MarkdownToBBCode.convert(new String(in.readAllBytes(), StandardCharsets.UTF_8)); + softly.assertThat(bbcode).startsWith("[heading=3]Credits[/heading]") + .contains("[url=\"https://onnxruntime.ai\"]ONNX Runtime[/url]") + .doesNotContain("](http"); + } + catch (IOException e) { + softly.fail("could not read credits.md", e); + } + } + }); + } + } + @Test void boldMarkersInsideCodeStayLiteral() { SoftAssertions.assertSoftly(softly -> softly.assertThat(MarkdownToBBCode.convert("`**x**`")) diff --git a/tools/release/Release.java b/tools/release/Release.java index 7519e36..a2de09d 100644 --- a/tools/release/Release.java +++ b/tools/release/Release.java @@ -38,8 +38,10 @@ import java.util.regex.Pattern; * {@code repo}, described by that release note, with the jar (renamed {@code pholio-.jar}), * its SHA-256 and the note itself as assets, through the {@code gh} CLI; *

  • {@code tag } — commits the POM alone and tags that commit {@code v}, locally only; - *
  • {@code distrib } — commits that release note alone in the {@code distrib/} - * worktree and pushes it (to {@code Imag-In/Pholio}'s {@code main}, per that worktree's upstream); + *
  • {@code distrib } — commits that release note in the {@code distrib/} worktree, + * together with {@code CREDITS.md} refreshed from {@code src/main/resources/userguide/credits.md} (the + * one source of the credits, also shown in the application), and pushes (to {@code Imag-In/Pholio}'s + * {@code main}, per that worktree's upstream); *
  • {@code next } — prints the version that would follow {@code current}; the pure * rule, for tests; *
  • {@code render-notes } — prints the note {@link #renderNotes} @@ -274,10 +276,14 @@ public final class Release { if (!Files.exists(distribDir.resolve(".git"))) { throw new IllegalStateException(distribDir + " is not the distrib worktree (git worktree add --orphan -b distrib distrib)"); } - String note = distribDir.relativize(notePath(distribDir.resolve("release_note"), version)).toString(); - run(List.of("git", "add", "--", note), distribDir, false); - // Like tag: that one file only, whatever else is pending in the worktree. - run(List.of("git", "commit", "-m", "docs(release): notes for " + version, "--", note), distribDir, false); + String note = distribDir.relativize(notePath(distribDir.resolve("release_note"), version)).toString(); + String credits = "CREDITS.md"; + Files.copy(pom.toAbsolutePath().getParent().resolve("src/main/resources/userguide/credits.md"), + distribDir.resolve(credits), StandardCopyOption.REPLACE_EXISTING); + run(List.of("git", "add", "--", note, credits), distribDir, false); + // Like tag: those files only, whatever else is pending in the worktree (an unchanged CREDITS.md + // simply adds nothing to the commit). + run(List.of("git", "commit", "-m", "docs(release): notes for " + version, "--", note, credits), distribDir, false); run(List.of("git", "push"), distribDir, false); System.out.println("[release] committed and pushed " + note + " from " + distribDir); }