Point instance badge sources at the PNG !41

merged merged by cmc on 2026-08-31 23:28 UTC · krz/gitbay-ios:badge-png-source into main

4 files changed, +66 −4

Layout: unified · split

gitbay.xcodeproj/project.pbxproj +1 −1
@@ -591,7 +591,7 @@
591 repositoryURL = "ssh://git@gitbay.org/krz/org-swift.git"; 591 repositoryURL = "ssh://git@gitbay.org/krz/org-swift.git";
592 requirement = { 592 requirement = {
593 kind = upToNextMinorVersion; 593 kind = upToNextMinorVersion;
594 minimumVersion = 0.3.1; 594 minimumVersion = 0.4.0;
595 }; 595 };
596 }; 596 };
597 8B2898B0303FF3E5001BF44A /* XCRemoteSwiftPackageReference "Highlightr" */ = { 597 8B2898B0303FF3E5001BF44A /* XCRemoteSwiftPackageReference "Highlightr" */ = {
gitbay.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +2 −2
@@ -15,8 +15,8 @@
15 "kind" : "remoteSourceControl", 15 "kind" : "remoteSourceControl",
16 "location" : "ssh://git@gitbay.org/krz/org-swift.git", 16 "location" : "ssh://git@gitbay.org/krz/org-swift.git",
17 "state" : { 17 "state" : {
18 "revision" : "6e2ef80a16a9eb61bff7f9928c230f4c5dff8240", 18 "revision" : "a5dc89631cf65a183edae1bfa22ef147bd8f1686",
19 "version" : "0.3.1" 19 "version" : "0.4.0"
20 } 20 }
21 } 21 }
22 ], 22 ],
gitbay/Views/Repos/ReadmeView.swift +20 −1
@@ -22,7 +22,7 @@ struct ReadmeView: View {
22 if isOrg { 22 if isOrg {
23 // Qualified: `OrgView` here is org-mode, not the organization view. 23 // Qualified: `OrgView` here is org-mode, not the organization view.
24 OrgSwiftUI.OrgView( 24 OrgSwiftUI.OrgView(
25 content, 25 Self.preferringPNGBadges(content, host: host),
26 options: orgOptions, 26 options: orgOptions,
27 styler: OrgSyntaxStyler(colorScheme: colorScheme), 27 styler: OrgSyntaxStyler(colorScheme: colorScheme),
28 // org's TODO/DONE are the same open/done distinction the forge already 28 // org's TODO/DONE are the same open/done distinction the forge already
@@ -34,6 +34,25 @@ struct ReadmeView: View {
34 } 34 }
35 } 35 }
36 36
37 /// Point badge sources on this instance at the PNG the forge also serves.
38 ///
39 /// No Apple image decoder reads SVG outside an asset catalog, so the SVG badge a README
40 /// names cannot render at all. Only badges on the instance being browsed are rewritten —
41 /// a badge from anywhere else has no PNG to offer, and is left as authored.
42 static func preferringPNGBadges(_ content: String, host: String) -> String {
43 guard !host.isEmpty,
44 let pattern = try? NSRegularExpression(
45 pattern: "(https://\(NSRegularExpression.escapedPattern(for: host))"
46 + "/[^/\\s\\]]+/[^/\\s\\]]+/badge/[\\w.-]+)\\.svg"
47 )
48 else { return content }
49 return pattern.stringByReplacingMatches(
50 in: content,
51 range: NSRange(content.startIndex..., in: content),
52 withTemplate: "$1.png"
53 )
54 }
55
37 /// gitbay serves raw bytes at `/{owner}/{repo}/raw/{ref}/…` and the file page at 56 /// gitbay serves raw bytes at `/{owner}/{repo}/raw/{ref}/…` and the file page at
38 /// `/blob/…`, so relative images resolve against `raw` and links against `blob`. 57 /// `/blob/…`, so relative images resolve against `raw` and links against `blob`.
39 private var orgOptions: OrgRenderOptions { 58 private var orgOptions: OrgRenderOptions {
gitbayTests/ReadmeBadgeTests.swift added +43
@@ -0,0 +1,43 @@
1import Testing
2@testable import gitbay
3
4@MainActor
5struct ReadmeBadgeSourceTests {
6
7 private func rewrite(_ content: String, host: String = "gitbay.org") -> String {
8 ReadmeView.preferringPNGBadges(content, host: host)
9 }
10
11 @Test func aBadgeOnThisInstancePointsAtThePNG() {
12 #expect(rewrite("[[https://gitbay.org/krz/gitbay/builds][file:https://gitbay.org/krz/gitbay/badge/build.svg]]")
13 == "[[https://gitbay.org/krz/gitbay/builds][file:https://gitbay.org/krz/gitbay/badge/build.png]]")
14 }
15
16 @Test func aBadgeForAnotherRepoOnTheSameInstanceIsRewritten() {
17 #expect(rewrite("file:https://gitbay.org/krz/orgo/badge/build.svg")
18 == "file:https://gitbay.org/krz/orgo/badge/build.png")
19 }
20
21 /// Somewhere else's badge has no PNG to offer, so it is left as authored.
22 @Test func aBadgeOnAnotherHostIsLeftAlone() {
23 let shields = "file:https://img.shields.io/badge/build-passing.svg"
24 #expect(rewrite(shields) == shields)
25 }
26
27 /// Only the badge path is rewritten; an SVG committed to the repo is not a badge.
28 @Test func anSVGThatIsNotABadgeIsLeftAlone() {
29 let diagram = "file:https://gitbay.org/krz/gitbay/raw/HEAD/docs/diagram.svg"
30 #expect(rewrite(diagram) == diagram)
31 }
32
33 @Test func anEmptyHostRewritesNothing() {
34 let badge = "file:https://gitbay.org/krz/gitbay/badge/build.svg"
35 #expect(rewrite(badge, host: "") == badge)
36 }
37
38 /// The host is matched literally: its dots are not wildcards.
39 @Test func theHostIsNotTreatedAsAPattern() {
40 let impostor = "file:https://gitbayxorg/krz/gitbay/badge/build.svg"
41 #expect(rewrite(impostor) == impostor)
42 }
43}