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 @@ |
| |
1 | import Testing |
| |
2 | @testable import gitbay |
| |
3 | |
| |
4 | @MainActor |
| |
5 | struct 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 | } |