/
pbv.swift
executable file
·197 lines (172 loc) · 6.26 KB
/
pbv.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
#!/usr/bin/env swift
import Cocoa
import Foundation
let newline = Data([0x0A] as [UInt8])
let typemap = [
"html": "public.html",
"txt": "public.utf8-plain-text",
"png": "public.png"
]
/**
Write the given string to STDERR.
- parameter str: native string to write encode in utf-8.
- parameter appendNewline: whether or not to write a newline (U+000A) after the given string (defaults to true)
*/
func printErr(_ str: String, appendNewline: Bool = true) {
// writing to STDERR takes a bit of boilerplate, compared to print()
if let data = str.data(using: .utf8) {
FileHandle.standardError.write(data)
if appendNewline {
FileHandle.standardError.write(newline)
}
}
}
/**
Write the given string to STDOUT.
- parameter str: native string to write encode in utf-8.
- parameter appendNewline: whether or not to write a newline (U+000A) after the given string (defaults to true)
*/
func print(_ str: String, appendNewline: Bool = true) {
// writing to STDERR takes a bit of boilerplate, compared to print()
if let data = str.data(using: .utf8) {
FileHandle.standardOutput.write(data)
if appendNewline {
FileHandle.standardOutput.write(newline)
}
}
}
/**
Read Data from NSPasteboard.
- parameter pasteboard: specific pasteboard to read from
- parameter dataTypeName: name of pasteboard data type to read as
- throws: `NSError` if data cannot be read as given dataType
*/
func pasteboardData(_ pasteboard: NSPasteboard, dataTypeName: String) throws -> Data {
let dataType = NSPasteboard.PasteboardType(rawValue: dataTypeName)
if let string = pasteboard.string(forType: dataType) {
let data = string.data(using: .utf8)! // supposedly force-unwrapping using UTF-8 never fails
return data
}
if let data = pasteboard.data(forType: dataType) {
return data
}
throw NSError(
domain: "pbv",
code: 0,
userInfo: [
NSLocalizedDescriptionKey: "Could not access pasteboard contents as String or Data for type: '\(dataTypeName)'",
]
)
}
/**
Read Data from NSPasteboard, trying each dataType in turn.
- parameter pasteboard: specific pasteboard to read from
- parameter dataTypeNames: names of pasteboard data type to try reading as
- throws: `NSError` if data cannot be read as _any_ of the given dataTypes
*/
func bestPasteboardData(_ pasteboard: NSPasteboard, dataTypeNames: [String]) throws -> Data {
for dataTypeName in dataTypeNames {
let type = typemap[dataTypeName] ?? dataTypeName
if let data = try? pasteboardData(pasteboard, dataTypeName: type) {
return data
}
}
let dataTypeNamesJoined = dataTypeNames.map { "'\($0)'" }.joined(separator: ", ")
throw NSError(
domain: "pbv",
code: 0,
userInfo: [
NSLocalizedDescriptionKey: "Could not access pasteboard contents as String or Data for types: \(dataTypeNamesJoined)",
]
)
}
func streamBestPasteboardData(_ pasteboard: NSPasteboard, dataTypeNames: [String], timeInterval: TimeInterval = 0.1) -> Timer {
var lastChangeCount = pasteboard.changeCount
return Timer.scheduledTimer(withTimeInterval: timeInterval, repeats: true) { _ in
let changeCount = pasteboard.changeCount
if changeCount != lastChangeCount {
printErr("#\(changeCount)")
lastChangeCount = changeCount
do {
let data = try bestPasteboardData(pasteboard, dataTypeNames: dataTypeNames)
FileHandle.standardOutput.write(data)
} catch {
printErr(error.localizedDescription)
}
}
}
}
func printTypes(_ pasteboard: NSPasteboard) {
// printErr("Available types for the '\(pasteboard.name.rawValue)' pasteboard:")
// Apple documentation says `types` "is an array NSString objects",
// but that's wrong: they are PasteboardType structures.
if let types = pasteboard.types {
for type in types {
print("\(type.rawValue)")
}
} else {
printErr("(not available)")
}
}
// CLI helpers
private func basename(_ pathOption: String?) -> String? {
if let path = pathOption {
return URL(fileURLWithPath: path).lastPathComponent
}
return nil
}
private func printUsage(_ pasteboard: NSPasteboard) {
let process = basename(CommandLine.arguments.first) ?? "pbv"
printErr("""
Usage: \(process) [-h|--help]
\(process) [dataType [dataType [...]]] [-s|--stream]
Read contents of pasteboard as 'dataType'. If multiple types are specified,
tries each from left to right, stopping at first success. If omitted,
defaults to 'public.utf8-plain-text'.
Options:
-h|--help Show this help and exit
-s|--stream Start an infinite loop polling the Pasteboard 'changeCount',
running as usual whenever it changes
-t|--types Print types of current clipboard content
""")
}
// CLI entry point
func main() {
let pasteboard: NSPasteboard = .general
// CommandLine.arguments[0] is the fullpath to this file
// CommandLine.arguments[1+] should be the desired type(s)
var args = Array(CommandLine.arguments.dropFirst())
if args.contains("-h") || args.contains("--help") {
printUsage(pasteboard)
exit(0)
}
let typesIndex = args.firstIndex { arg in arg == "-t" || arg == "--types" }
if let index = typesIndex {
args.remove(at: index)
}
let streamIndex = args.firstIndex { arg in arg == "-s" || arg == "--stream" }
if let index = streamIndex {
args.remove(at: index)
}
let types = args.isEmpty ? ["public.utf8-plain-text"] : args
if typesIndex != nil {
printTypes(pasteboard)
exit(0)
} else if streamIndex != nil {
let timer = streamBestPasteboardData(pasteboard, dataTypeNames: types)
let runLoop = RunLoop.main
runLoop.add(timer, forMode: .default)
runLoop.run()
} else {
do {
let data = try bestPasteboardData(pasteboard, dataTypeNames: types)
FileHandle.standardOutput.write(data)
exit(0)
} catch {
printErr(error.localizedDescription)
printTypes(pasteboard)
exit(1)
}
}
}
main()