Skip to content

Commit 4da0ac0

Browse files
authored
Merge pull request #2 from xcode-actions/feat/frizlab/new_emoji_sets
Add new emoji sets, remove environment-based emoji sets variants
2 parents 953aad3 + b9d823c commit 4da0ac0

File tree

7 files changed

+215
-109
lines changed

7 files changed

+215
-109
lines changed

Sources/CLTLogger.swift

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,11 @@ public extension CLTLogger {
272272
}()
273273

274274
static func defaultConstantsByLogLevelForEmoji(on fh: FileHandle) -> [Logger.Level: Constants] {
275+
return defaultConstantsByLogLevelForEmoji(on: fh, forcedEmojiSet: nil)
276+
}
277+
278+
/* The forced emoji set is for the tests. */
279+
internal static func defaultConstantsByLogLevelForEmoji(on fh: FileHandle, forcedEmojiSet: EmojiSet? = nil) -> [Logger.Level: Constants] {
275280
func addMeta(_ paddedEmoji: String) -> Constants {
276281
return .init(
277282
logPrefix: paddedEmoji + "",
@@ -284,11 +289,7 @@ public extension CLTLogger {
284289
}
285290
let envVars = ProcessInfo.processInfo.environment
286291
let outputEnvironment: OutputEnvironment = .detect(from: fh, envVars)
287-
let emojiSet = EmojiSet.default(for: outputEnvironment)
288-
/* To see all the emojis with the padding. If padding is correct, everything should be aligned. */
289-
//for emoji in Emoji.allCases {
290-
// print("\(emoji.rawValue)\(emoji.padding(for: outputEnvironment)) |")
291-
//}
292+
let emojiSet = forcedEmojiSet ?? EmojiSet.default(for: outputEnvironment)
292293
return [
293294
.trace: addMeta(emojiSet.paddedEmoji(for: .trace, in: outputEnvironment)),
294295
.debug: addMeta(emojiSet.paddedEmoji(for: .debug, in: outputEnvironment)),

Sources/Emoji.swift

Lines changed: 89 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -15,43 +15,100 @@ internal enum Emoji : String, CaseIterable {
1515
case redCross = ""
1616
case policeLight = "🚨"
1717
case worm = "🐛"
18-
case orangeDiamond = "🔶"
1918

20-
case ambulance = "🚑"
21-
case ladybug = "🐞"
22-
case monocle = "🧐"
23-
case greenCheck = ""
24-
case fearFace = "😱"
25-
26-
case redHeart = "❤️"
27-
case orangeHeart = "🧡"
28-
case yellowHeart = "💛"
29-
case greenHeart = "💚"
30-
case blueHeart = "💙"
31-
case purpleHeart = "💜"
32-
case blackHeart = "🖤"
33-
case greyHeart = "🩶"
34-
case brownHeart = "🤎"
35-
case whiteHeart = "🤍"
36-
case pinkHeart = "🩷"
37-
case lightBlueHeart = "🩵"
38-
19+
case ambulance = "🚑"
20+
case ladybug = "🐞"
21+
case monocle = "🧐"
22+
case greenCheck = ""
23+
case fearFace = "😱"
24+
25+
case greySmallSquare = "◽️"
26+
case blackSmallSquare = "◾️"
27+
case blueDiamond = "🔷"
28+
case orangeDiamond = "🔶"
29+
30+
case deepRedHeart = "♥️"
31+
case redHeart = "❤️"
32+
case orangeHeart = "🧡"
33+
case yellowHeart = "💛"
34+
case greenHeart = "💚"
35+
case blueHeart = "💙"
36+
case purpleHeart = "💜"
37+
case blackHeart = "🖤"
38+
case greyHeart = "🩶"
39+
case brownHeart = "🤎"
40+
case whiteHeart = "🤍"
41+
case pinkHeart = "🩷"
42+
case lightBlueHeart = "🩵"
43+
44+
case wrongWayCircle = "⛔️"
45+
case redCircle = "🔴"
46+
case orangeCircle = "🟠"
47+
case yellowCircle = "🟡"
48+
case greenCircle = "🟢"
49+
case blueCircle = "🔵"
50+
case purpleCircle = "🟣"
51+
case blackCircle = "⚫️"
52+
case brownCircle = "🟤"
53+
case whiteCircle = "⚪️"
54+
case redStrokeCircle = "⭕️"
55+
case selectedRadioCircle = "🔘" /* Ugly on Windows… */
56+
57+
case redSquare = "🟥"
58+
case orangeSquare = "🟧"
59+
case yellowSquare = "🟨"
60+
case greenSquare = "🟩"
61+
case blueSquare = "🟦"
62+
case purpleSquare = "🟪"
63+
case blackSquare = "⬛️"
64+
case brownSquare = "🟫"
65+
case whiteSquare = "⬜️"
66+
case blackStrokeSquare = "🔲"
67+
case whiteStrokeSquare = "🔳"
68+
69+
/* ⚠️ When this is modified, fallbacks in the EmojiSet enum should be verified. */
70+
func rendersAsText(in environment: OutputEnvironment) -> Bool {
71+
let textEmojis: Set<Emoji>
72+
switch environment {
73+
case .xcode, .macOSTerminal, .macOSiTerm2, .macOSUnknown, .unknown:
74+
/* All emojis are correct on these environments (or we don’t know and assume they are). */
75+
return false
76+
77+
case .windowsTerminal, .windowsConsole, .windowsUnknown:
78+
textEmojis = [.doubleExclamationPoint, .greySmallSquare, .blackSmallSquare, .deepRedHeart, .redStrokeCircle, .blackSquare, .whiteSquare]
79+
80+
case .macOSVSCode: textEmojis = [.cog, .warning, .doubleExclamationPoint, .redHeart, .deepRedHeart, .greySmallSquare, .blackSmallSquare]
81+
case .windowsVSCode: textEmojis = [.speaker, .doubleExclamationPoint, .deepRedHeart]
82+
case .unknownVSCode: return rendersAsText(in: .macOSVSCode) || rendersAsText(in: .windowsVSCode)
83+
}
84+
return textEmojis.contains(self)
85+
}
86+
3987
func padding(for environment: OutputEnvironment) -> String {
4088
guard environment != .xcode else {
4189
/* All emojis are correct on Xcode. */
4290
return ""
4391
}
4492

4593
switch self {
46-
case .poo, .notebook, .eyebrow, .redCross, .policeLight, .worm, .orangeDiamond,
94+
case .poo, .notebook, .eyebrow, .redCross, .policeLight, .worm,
4795
.orangeHeart, .yellowHeart, .greenHeart, .blueHeart, .purpleHeart,
4896
.blackHeart, .brownHeart, .whiteHeart:
4997
return ""
5098

51-
case .ambulance, .ladybug, .monocle, .greenCheck, .fearFace:
99+
case .redCircle, .orangeCircle, .yellowCircle, .greenCircle, .blueCircle,
100+
.purpleCircle, .brownCircle, .selectedRadioCircle:
101+
return ""
102+
103+
case .redSquare, .orangeSquare, .yellowSquare, .greenSquare, .blueSquare,
104+
.purpleSquare, .brownSquare, .blackStrokeSquare, .whiteStrokeSquare:
105+
return ""
106+
107+
case .ambulance, .ladybug, .monocle, .greenCheck, .fearFace,
108+
.blueDiamond, .orangeDiamond:
52109
return ""
53110

54-
case .cog, .warning, .doubleExclamationPoint, .redHeart:
111+
case .cog, .warning, .doubleExclamationPoint, .redHeart, .deepRedHeart:
55112
guard !environment.isVSCode, environment != .macOSTerminal
56113
else {return " "}
57114
return ""
@@ -61,11 +118,13 @@ internal enum Emoji : String, CaseIterable {
61118
else {return " "}
62119
return ""
63120

64-
case .exclamationPoint:
65-
/* Note: For the Windows Terminal and Console, we’re a negative 1 space…
66-
# We ignore this special case and return an empty string. */
121+
case .exclamationPoint, .greySmallSquare, .blackSmallSquare, .wrongWayCircle, .blackCircle, .whiteCircle, .redStrokeCircle, .blackSquare, .whiteSquare:
122+
/* Note: For the Windows Terminal and Console, we need a negative 1 space!
123+
* The output uses more space than most of the other emojis.
124+
* We could add one space to all other emojis but there is too much space if we do this,
125+
* so instead we ask the console to go back one char when outputting these emojis. */
67126
guard !environment.isWindowsShell
68-
else {return ""/*negative one space*/}
127+
else {return Self.negativeOneSpace}
69128
return ""
70129

71130
case .greyHeart, .pinkHeart, .lightBlueHeart:
@@ -79,4 +138,7 @@ internal enum Emoji : String, CaseIterable {
79138
rawValue + padding(for: environment)
80139
}
81140

141+
/* See <https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#cursor-controls>. */
142+
private static let negativeOneSpace: String = "\u{1B}[1D"
143+
82144
}

Sources/EmojiSet.swift

Lines changed: 56 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -4,110 +4,93 @@ import Logging
44

55

66

7-
internal enum EmojiSet : String {
7+
internal enum EmojiSet : String, CaseIterable {
88

99
/**
1010
The original set of emoji used in clt-logger.
1111
These work well in Terminal and Xcode (and on macOS generally, though not in VSCode). */
12-
case original = "ORIGINAL"
13-
case originalForWindowsTerminal = "ORIGINAL+WINDOWS_TERMINAL"
14-
case originalForVSCodeMacOS = "ORIGINAL+VSCODE_MACOS"
15-
case originalForVSCodeWindows = "ORIGINAL+VSCODE_WINDOWS"
12+
case original = "ORIGINAL"
13+
case swiftyBeaver = "SWIFTY_BEAVER"
14+
case cleanroomLogger = "CLEANROOM_LOGGER"
15+
case vaibhavsingh97EmojiLogger = "VAIBHAVSINGH97_EMOJI_LOGGER"
1616

17-
case vaibhavsingh97EmojiLogger = "VAIBHAVSINGH97_EMOJI_LOGGER"
18-
case vaibhavsingh97EmojiLoggerForVSCodeMacOS = "VAIBHAVSINGH97_EMOJI_LOGGER+VSCODE_MACOS"
17+
/** The emoji set that works on all platforms (no need for a replacement emoji because the original renders as text). */
18+
case noAlternates = "NO_ALTERNATES"
1919

2020
static func `default`(for environment: OutputEnvironment, _ envVars: [String: String] = ProcessInfo.processInfo.environment) -> EmojiSet {
2121
if let envStr = envVars["CLTLOGGER_EMOJI_SET_NAME"], let ret = EmojiSet(rawValue: envStr) {
2222
return ret
2323
}
24-
switch environment {
25-
case .xcode, .macOSTerminal, .macOSiTerm2, .macOSUnknown:
26-
return .original
27-
28-
case .macOSVSCode, .unknownVSCode, .unknown:
29-
return .originalForVSCodeMacOS
30-
31-
case .windowsTerminal, .windowsConsole, .windowsUnknown:
32-
return .originalForWindowsTerminal
33-
34-
case .windowsVSCode:
35-
return .originalForVSCodeWindows
36-
}
24+
return .original
3725
}
3826

39-
/* Exceptions:
40-
* - ⚙️ on VSCode macOS renders as text
41-
* - ⚠️ on VSCode macOS renders as text
42-
* - ‼️ on VSCode macOS renders as text
43-
* - ❤️ on VSCode macOS renders as text
44-
* - 🗣 on VSCode Windows renders as text (I think)
45-
* - ‼️ on VSCode Windows renders as text
46-
* - ❗️ on Windows Terminal is larger than the rest (negative padding would be needed)
47-
* - ‼️ on Windows Terminal renders as text */
48-
func emoji(for logLevel: Logger.Level) -> Emoji {
49-
let original: (Logger.Level) -> Emoji = {
50-
switch $0 {
51-
case .critical: return .doubleExclamationPoint
52-
case .error: return .exclamationPoint
53-
case .warning: return .warning
54-
case .notice: return .speaker
55-
case .info: return .notebook
56-
case .debug: return .cog
57-
case .trace: return .poo
58-
}
59-
}
60-
let vaibhavsingh97: (Logger.Level) -> Emoji = {
61-
switch $0 {
62-
case .critical: return .ambulance
63-
case .error: return .fearFace
64-
case .warning: return .warning
65-
case .notice: return .greenCheck /* Called success in upstream. */
66-
case .info: return .monocle
67-
case .debug: return .ladybug
68-
case .trace: return .poo /* Does not exist in upstream. */
69-
}
70-
}
71-
27+
func emoji(for logLevel: Logger.Level, in environment: OutputEnvironment) -> Emoji {
28+
let ret: Emoji
7229
switch self {
7330
case .original:
74-
return original(logLevel)
75-
76-
case .originalForWindowsTerminal:
7731
switch logLevel {
78-
case .critical: return .policeLight
79-
case .error: return .redCross
80-
default: return original(logLevel)
32+
case .critical: ret = .doubleExclamationPoint
33+
case .error: ret = .exclamationPoint
34+
case .warning: ret = .warning
35+
case .notice: ret = .speaker
36+
case .info: ret = .notebook
37+
case .debug: ret = .cog
38+
case .trace: ret = .poo
8139
}
8240

83-
case .originalForVSCodeMacOS:
41+
case .swiftyBeaver:
8442
switch logLevel {
85-
case .critical: return .policeLight
86-
case .warning: return .orangeDiamond
87-
case .debug: return .worm
88-
default: return original(logLevel)
43+
case .critical: ret = .redSquare
44+
case .error: ret = .redSquare
45+
case .warning: ret = .yellowSquare
46+
case .notice: ret = .blueSquare /* Log level does not exist in upstream. */
47+
case .info: ret = .blueSquare
48+
case .debug: ret = .greenSquare
49+
case .trace: ret = .whiteSquare
8950
}
9051

91-
case .originalForVSCodeWindows:
52+
case .cleanroomLogger:
9253
switch logLevel {
93-
case .critical: return .policeLight
94-
case .notice: return .eyebrow
95-
default: return original(logLevel)
54+
case .critical: ret = .redCross /* Log level does not exist in upstream. */
55+
case .error: ret = .redCross
56+
case .warning: ret = .orangeDiamond
57+
case .notice: ret = .blueDiamond /* Log level does not exist in upstream. */
58+
case .info: ret = .blueDiamond
59+
case .debug: ret = .blackSmallSquare
60+
case .trace: ret = .greySmallSquare
9661
}
9762

9863
case .vaibhavsingh97EmojiLogger:
99-
return vaibhavsingh97(logLevel)
64+
switch logLevel {
65+
case .critical: ret = .ambulance
66+
case .error: ret = .fearFace
67+
case .warning: ret = .warning
68+
case .notice: ret = .greenCheck /* Called success in upstream. */
69+
case .info: ret = .monocle
70+
case .debug: ret = .ladybug
71+
case .trace: ret = .ladybug /* Log level does not exist in upstream. */
72+
}
10073

101-
case .vaibhavsingh97EmojiLoggerForVSCodeMacOS:
74+
case .noAlternates:
10275
switch logLevel {
103-
case .warning: return .orangeDiamond
104-
default: return vaibhavsingh97(logLevel)
76+
case .critical: return .redCross
77+
case .error: return .redCircle
78+
case .warning: return .orangeCircle
79+
case .notice: return .yellowCircle
80+
case .info: return .greenCircle
81+
case .debug: return .purpleCircle
82+
case .trace: return .whiteCircle
10583
}
10684
}
85+
guard ret.rendersAsText(in: environment) else {
86+
return ret
87+
}
88+
/* The no alternates emoji set will not check if its returned emojis render as text so there will never be an infinite loop here. */
89+
return EmojiSet.noAlternates.emoji(for: logLevel, in: environment)
10790
}
10891

10992
func paddedEmoji(for logLevel: Logger.Level, in environment: OutputEnvironment) -> String {
110-
return emoji(for: logLevel).valueWithPadding(for: environment)
93+
return emoji(for: logLevel, in: environment).valueWithPadding(for: environment)
11194
}
11295

11396
}

Sources/OutputEnvironment.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import Foundation
22

33

44

5-
internal enum OutputEnvironment : String {
5+
internal enum OutputEnvironment : String, CaseIterable {
66

77
case xcode = "XCODE"
88

Tests/CLTLoggerTests/CLTLoggerTests.swift

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
11
import Foundation
22
import XCTest
33

4-
import Logging
5-
64
@testable import CLTLogger
5+
@testable import Logging
76

87

98

109
final class CLTLoggerTests : XCTestCase {
1110

11+
static let defaultBootstrapFactory: @Sendable (String) -> any LogHandler = { _ in CLTLogger(multilineMode: .allMultiline) }
12+
1213
override class func setUp() {
13-
LoggingSystem.bootstrap{ _ in CLTLogger(multilineMode: .allMultiline) }
14+
LoggingSystem.bootstrap(Self.defaultBootstrapFactory)
1415
}
1516

1617
/* From <https://apple.github.io/swift-log/docs/current/Logging/Protocols/LogHandler.html#treat-log-level-amp-metadata-as-values>. */
@@ -90,4 +91,24 @@ final class CLTLoggerTests : XCTestCase {
9091
logger.critical("YAM!\nhere is the second line\nand why not a third one", metadata: ["with": ["metadata", "again"], "because": "42"])
9192
}
9293

94+
func testBasicLogOutputWithAllEmojiSets() throws {
95+
XCTAssertTrue(true, "We only want to see how the log look, so please see the logs.")
96+
97+
for emojiSet in EmojiSet.allCases {
98+
LoggingSystem.bootstrapInternal{ _ in CLTLogger(multilineMode: .allMultiline, constantsByLevel: CLTLogger.defaultConstantsByLogLevelForEmoji(on: .standardError, forcedEmojiSet: emojiSet)) }
99+
try FileHandle.standardError.write(contentsOf: Data("\n***** \(emojiSet.rawValue) *****\n".utf8))
100+
var logger = Logger(label: "my logger")
101+
logger.logLevel = .trace
102+
logger.critical("critical: Example of text at this level.")
103+
logger.error( "error: Example of text at this level.")
104+
logger.warning( "warning: Example of text at this level.")
105+
logger.notice( "notice: Example of text at this level.")
106+
logger.info( "info: Example of text at this level.")
107+
logger.debug( "debug: Example of text at this level.")
108+
logger.trace( "trace: Example of text at this level.")
109+
}
110+
/* Reset factory. */
111+
LoggingSystem.bootstrapInternal(Self.defaultBootstrapFactory)
112+
}
113+
93114
}

0 commit comments

Comments
 (0)