Skip to content

Commit 0c84d57

Browse files
authored
Add a utility tool for previewing event symbol rendering (#984)
This adds a development-only utility executable target to the package which allows previewing the event symbols rendered using various console output styles. <img width="815" alt="Screenshot 2025-02-28 at 2 14 47 PM" src="https://github.com/user-attachments/assets/6a1a58d3-10e0-4c51-8c1c-c3581fa0f310" /> ### Motivation: This is a tool I made and found useful while working on #983. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent bc10850 commit 0c84d57

File tree

3 files changed

+128
-1
lines changed

3 files changed

+128
-1
lines changed

Package.swift

+10
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,16 @@ let package = Package(
134134
.enableLibraryEvolution(applePlatformsOnly: true),
135135
]
136136
),
137+
138+
// Utility targets: These are utilities intended for use when developing
139+
// this package, not for distribution.
140+
.executableTarget(
141+
name: "SymbolShowcase",
142+
dependencies: [
143+
"Testing",
144+
],
145+
swiftSettings: .packageSettings
146+
),
137147
],
138148

139149
cxxLanguageStandard: .cxx20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2025 Apple Inc. and the Swift project authors
5+
// Licensed under Apache License v2.0 with Runtime Library Exception
6+
//
7+
// See https://swift.org/LICENSE.txt for license information
8+
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
//
10+
11+
@_spi(Experimental) @_spi(ForToolsIntegrationOnly) import Testing
12+
import Foundation
13+
14+
/// A type which acts as the main entry point for this executable target.
15+
@main enum SymbolShowcaseMain {
16+
static func main() {
17+
let nameColumnWidth = symbols.reduce(into: 0) { $0 = max($0, $1.0.count) } + 4
18+
let styleColumnWidth = styles.reduce(into: 0) { $0 = max($0, $1.label.count) } + 2
19+
let totalHeaderWidth = nameColumnWidth + (styleColumnWidth * styles.count)
20+
21+
// Print the table header.
22+
print("Name".padding(toLength: nameColumnWidth), terminator: "")
23+
for style in styles {
24+
print(style.label.padding(toLength: styleColumnWidth), terminator: "")
25+
}
26+
print()
27+
print(String(repeating: "=", count: totalHeaderWidth))
28+
29+
// Print a row for each symbol, with a preview of each style.
30+
for (label, symbol) in symbols {
31+
print(label.padding(toLength: nameColumnWidth), terminator: "")
32+
for style in styles {
33+
print(style.string(for: symbol), terminator: "")
34+
print("".padding(toLength: styleColumnWidth - 1), terminator: "")
35+
}
36+
print()
37+
}
38+
}
39+
40+
/// The symbols to preview.
41+
fileprivate static var symbols: KeyValuePairs<String, Event.Symbol> {
42+
[
43+
"Default": .default,
44+
"Pass": .pass(knownIssueCount: 0),
45+
"Pass w/known issues": .pass(knownIssueCount: 1),
46+
"Pass with warnings": .passWithWarnings,
47+
"Skip": .skip,
48+
"Fail": .fail,
49+
"Difference": .difference,
50+
"Warning": .warning,
51+
"Details": .details,
52+
"Attachment": .attachment,
53+
]
54+
}
55+
56+
/// The styles to preview.
57+
fileprivate static var styles: [Style] {
58+
var styles: [Style] = [
59+
Style(label: "Unicode", usesColor: false),
60+
Style(label: "w/color", usesColor: true),
61+
]
62+
63+
#if os(macOS) || (os(iOS) && targetEnvironment(macCatalyst))
64+
styles.append(contentsOf: [
65+
Style(label: "SF Symbols", usesColor: false, usesSFSymbols: true),
66+
Style(label: "w/color", usesColor: true, usesSFSymbols: true),
67+
])
68+
#endif
69+
70+
return styles
71+
}
72+
}
73+
74+
/// A type representing a style of symbol to preview.
75+
fileprivate struct Style {
76+
/// The label for this style, displayed in its column header.
77+
var label: String
78+
79+
/// Whether this style should render symbols using ANSI color.
80+
var usesColor: Bool
81+
82+
#if os(macOS) || (os(iOS) && targetEnvironment(macCatalyst))
83+
/// Whether this style should use SF Symbols.
84+
var usesSFSymbols: Bool = false
85+
#endif
86+
87+
/// Return a string for the specified symbol based on this style's options.
88+
///
89+
/// - Parameters:
90+
/// - symbol: The symbol to format into a string.
91+
///
92+
/// - Returns: A formatted string representing the specified symbol.
93+
func string(for symbol: Event.Symbol) -> String {
94+
var options = Event.ConsoleOutputRecorder.Options()
95+
options.useANSIEscapeCodes = usesColor
96+
options.ansiColorBitDepth = usesColor ? 8 : 1
97+
#if os(macOS) || (os(iOS) && targetEnvironment(macCatalyst))
98+
options.useSFSymbols = usesSFSymbols
99+
#endif
100+
101+
return symbol.stringValue(options: options)
102+
}
103+
}
104+
105+
extension String {
106+
/// Returns a new string formed from this String by either removing characters
107+
/// from the end, or by appending as many occurrences as necessary of a given
108+
/// pad string.
109+
///
110+
/// - Parameters:
111+
/// - newLength: The length to pad to.
112+
///
113+
/// - Returns: A padded string.
114+
fileprivate func padding(toLength newLength: Int) -> Self {
115+
padding(toLength: newLength, withPad: " ", startingAt: 0)
116+
}
117+
}

Sources/Testing/Events/Recorder/Event.ConsoleOutputRecorder.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ extension Event.Symbol {
136136
///
137137
/// - Returns: A string representation of `self` appropriate for writing to
138138
/// a stream.
139-
fileprivate func stringValue(options: Event.ConsoleOutputRecorder.Options) -> String {
139+
package func stringValue(options: Event.ConsoleOutputRecorder.Options) -> String {
140140
let useColorANSIEscapeCodes = options.useANSIEscapeCodes && options.ansiColorBitDepth >= 4
141141

142142
var symbolCharacter = String(unicodeCharacter)

0 commit comments

Comments
 (0)