forked from zeitlings/alfred-workflows
-
Notifications
You must be signed in to change notification settings - Fork 0
/
WhatIsIt.swift
executable file
·280 lines (260 loc) · 8.38 KB
/
WhatIsIt.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
#!/usr/bin/swift
//
// WhatIsIt.swift
// Inspect Unicode Characters
//
// Created by Patrick Sy on 27/01/2023.
//
import Foundation.NSJSONSerialization
struct Workflow {
static let inputv: String = CommandLine.arguments[1]
static func run() {
let input: String = inputv.prepared
let response: Response = input.unicodeScalars
.reduce(into: .init(), { response, scalar in
dissect(scalar, &response)
})
print(response.output())
}
}
extension Workflow {
typealias Mod = Item.Mod
typealias ModWrapper = Item.ModWrapper
typealias Text = Item.Text
static func dissect(_ scalar: UnicodeScalar, _ response: inout Response) {
let codePoint: String = String(scalar.value, radix: 16, uppercase: true)
let padding: String = String(repeating: "0", count: Swift.max(4 - codePoint.count, 0))
let full: String = padding + codePoint
let uni: String = "U+\(full)"
let hex: String = "0x\(codePoint)"
let swi: String = "\\u{\(full)}" // swift, es6, js
let pyg: String = "\\u\(full)" // python, go
let htm: String = "&#x\(full);" // html
var scalarString: String = .init(scalar)
let scalarName: String = {
if let sn: String = scalar.properties.name {
return sn
}
if let an: String = scalar.addedName {
scalarString = scalar.escaped(asASCII: false)
return an
}
return "[no name found]"
}()
let generalCategory: String = scalar.properties.generalCategory.description
let title: String = "\(scalarString)\t\u{203A}\t\(scalarName)"
let subtitle: String = "\(uni) | \(generalCategory)"
let arg: String = scalarString
let text: Text = .init(copy: scalarString)
let mods: ModWrapper = {
let cmd: Mod = .init(arg: swi, subtitle: swi)
let alt: Mod = .init(arg: pyg, subtitle: pyg)
let ctrl: Mod = .init(arg: htm, subtitle: "\(htm) (HTML entity)")
let shift: Mod = .init(arg: hex, subtitle: "\(hex) (Hex literal)")
return ModWrapper(cmd: cmd, alt: alt, ctrl: ctrl, shift: shift)
}()
let item: Item = .init(title, subtitle, arg, text, mods)
response.items.append(item)
}
}
struct Response: Encodable {
var items: [Item] = []
func output(encoder: JSONEncoder = .init()) -> String {
do {
let encoded: Data = try encoder.encode(self)
return String(data: encoded, encoding: .utf8)!
} catch let error {
let error: String = error.panicResponse
print(error)
fatalError()
}
}
}
struct Item: Encodable {
let title: String
let subtitle: String
let arg: String
let text: Text
let mods: ModWrapper
init(_ title: String, _ subtitle: String, _ arg: String, _ text: Text, _ mods: ModWrapper) {
self.title = title
self.subtitle = subtitle
self.arg = arg
self.text = text
self.mods = mods
}
}
extension Item {
struct Text: Encodable {
let copy: String
}
struct ModWrapper: Encodable {
let cmd: Mod
let alt: Mod
let ctrl: Mod
let shift: Mod
}
struct Mod: Encodable {
let arg: String
let subtitle: String
let valid: Bool = true
}
}
extension String {
var prepared: String {
func cast(_ hex: String) -> Optional<String> {
guard let decimal: UInt32 = .init(hex, radix: 16),
let scalar: UnicodeScalar = .init(decimal) else {
return nil
}
return scalar.description
}
let converted: Optional<String> = {
switch true {
case isSwf: return cast(swtm)
case isHtm: return cast(swtm)
case isPyg: return cast(pygx)
case isHex: return cast(pygx)
case isRaw:
/// Prevent inputs like "C" to be cast to hex, which will succeed but is not intended.
/// Enforce the hex input to always be zero padded to 4 digits if not, e.g. 0x prefixed
guard count >= 4 else {
return nil
}
return cast(self)
default: return nil
}
}()
return converted ?? self
}
var swtm: String { String(dropFirst(3).dropLast()) } // swift + html
var pygx: String { String(dropFirst(2)) } // pyg + hex
var isSwf: Bool { hasPrefix("\\u{") && hasSuffix("}") }
var isHtm: Bool { hasPrefix("&#x") && hasSuffix(";") }
var isRaw: Bool { UInt32(self, radix: 16) != nil }
var isPyg: Bool { hasPrefix("\\u") }
var isHex: Bool { hasPrefix("0x") }
}
extension Error {
var panicResponse: String {
"""
{"items" : [{
"title" : "Failure encoding response",
"arg" : "\(localizedDescription)",
"text" : {
"copy" : "\(localizedDescription)",
"largetype" : "\(localizedDescription)"
},
"valid" : true
}]}
"""
}
}
extension Unicode.GeneralCategory: CustomStringConvertible {
public var description: String {
switch self {
case .connectorPunctuation: return "Connector Punctuation"
case .initialPunctuation: return "Initial Punctuation"
case .paragraphSeparator: return "Paragraph Separator"
case .closePunctuation: return "Close Punctuation"
case .finalPunctuation: return "Final Punctuation"
case .otherPunctuation: return "Other Punctuation"
case .uppercaseLetter: return "Uppercase Letter"
case .lowercaseLetter: return "Lowercase Letter"
case .titlecaseLetter: return "Titlecase Letter"
case .dashPunctuation: return "Dash Punctuation"
case .openPunctuation: return "Open Punctuation"
case .currencySymbol: return "Currency Symbol"
case .modifierSymbol: return "Modifier Symbol"
case .modifierLetter: return "Modifier Letter"
case .nonspacingMark: return "Nonspacing Mark"
case .spaceSeparator: return "Space Separator"
case .lineSeparator: return "Line Separator"
case .enclosingMark: return "Enclosing Mark"
case .decimalNumber: return "Decimal Number"
case .letterNumber: return "Letter Number"
case .otherLetter: return "Other Letter"
case .spacingMark: return "Spacing Mark"
case .otherNumber: return "Other Number"
case .otherSymbol: return "Other Symbol"
case .mathSymbol: return "Math Symbol"
case .privateUse: return "Private Use"
case .unassigned: return "Unassigned"
case .surrogate: return "Surrogate"
case .control: return "Control"
case .format: return "Format"
@unknown default: return "Unknown"
}
}
}
extension UnicodeScalar {
var addedName: String? { UnicodeScalar.missing[self] }
static let missing: [UnicodeScalar:String] = [
"\u{0000}": "NULL",
"\u{0001}": "START OF HEADING",
"\u{0002}": "START OF TEXT",
"\u{0003}": "END OF TEXT",
"\u{0004}": "END OF TRANSMISSION",
"\u{0005}": "ENQUIRY",
"\u{0006}": "ACKNOWLEDGE",
"\u{0007}": "ALERT",
"\u{0008}": "BACKSPACE",
"\u{0009}": "CHARACTER TABULATION",
"\u{000A}": "LINE FEED (alias: NEW LINE, END OF LINE)",
"\u{000B}": "LINE TABULATION",
"\u{000C}": "FORM FEED",
"\u{000D}": "CARRIAGE RETURN",
"\u{000E}": "SHIFT OUT (alias: LOCKING-SHIFT ONE)",
"\u{000F}": "SHIFT IN (alias: LOCKING-SHIFT ZERO)",
"\u{0010}": "DATA LINK ESCAPE",
"\u{0011}": "DEVICE CONTROL ONE",
"\u{0012}": "DEVICE CONTROL TWO",
"\u{0013}": "DEVICE CONTROL THREE",
"\u{0014}": "DEVICE CONTROL FOUR",
"\u{0015}": "NEGATIVE ACKNOWLEDGE",
"\u{0016}": "SYNCHRONOUS IDLE",
"\u{0017}": "END OF TRANSMISSION BLOCK",
"\u{0018}": "CANCEL",
"\u{0019}": "END OF MEDIUM",
"\u{001A}": "SUBSTITUTE",
"\u{001B}": "ESCAPE",
"\u{001C}": "FILE SEPARATOR | INFORMATION SEPARATOR FOUR",
"\u{001D}": "GROUP SEPARATOR | INFORMATION SEPARATOR THREE",
"\u{001E}": "RECORD SEPARATOR | INFORMATION SEPARATOR TWO",
"\u{001F}": "UNIT SEPARATOR | INFORMATION SEPARATOR ONE",
"\u{007F}": "DELETE",
"\u{0080}": "PADDING CHARACTER",
"\u{0081}": "HIGH OCTET PRESET",
"\u{0082}": "BREAK PERMITTED HERE",
"\u{0083}": "NO BREAK HERE",
"\u{0084}": "INDEX",
"\u{0085}": "NEXT LINE",
"\u{0086}": "START OF SELECTED AREA",
"\u{0087}": "END OF SELECTED AREA",
"\u{0088}": "CHARACTER TABULATION SET",
"\u{0089}": "CHARACTER TABULATION WITH JUSTIFICATION",
"\u{008A}": "LINE TABULATION SET",
"\u{008B}": "PARTIAL LINE FORWARD",
"\u{008C}": "PARTIAL LINE BACKWARD",
"\u{008D}": "REVERSE LINE FEED (alt: REVERSE INDEX)",
"\u{008E}": "SINGLE SHIFT TWO",
"\u{008F}": "SINGLE SHIFT THREE",
"\u{0090}": "DEVICE CONTROL STRING",
"\u{0091}": "PRIVATE USE ONE",
"\u{0092}": "PRIVATE USE TWO",
"\u{0093}": "SET TRANSMIT STATE",
"\u{0094}": "CANCEL CHARACTER",
"\u{0095}": "MESSAGE WAITING",
"\u{0096}": "START OF GUARDED AREA",
"\u{0097}": "END OF GUARDED AREA",
"\u{0098}": "START OF STRING",
"\u{0099}": "SINGLE GRAPHIC CHARACTER INTRODUCER",
"\u{009A}": "SINGLE CHARACTER INTRODUCER",
"\u{009B}": "CONTROL SEQUENCE INTRODUCER",
"\u{009C}": "STRING TERMINATOR",
"\u{009D}": "OPERATING SYSTEM COMMAND",
"\u{009E}": "PRIVACY MESSAGE",
"\u{009F}": "APPLICATION PROGRAM COMMAND",
]
}
Workflow.run()