Skip to content

Commit 9ee7d79

Browse files
committed
Merge gist.cafe's Inspect support to remove a dep when exploring
1 parent 809fb02 commit 9ee7d79

File tree

2 files changed

+263
-0
lines changed

2 files changed

+263
-0
lines changed

Sources/ServiceStack/Inspect.swift

Lines changed: 208 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,208 @@
1+
import Foundation
2+
3+
public class Inspect {
4+
5+
public static var verbose = false
6+
7+
public static func log(to filePath: String, _ text: String) {
8+
let data = text.data(using: .utf8)!
9+
if let dir = FileManager.default.urls(for: .desktopDirectory, in: .userDomainMask).first {
10+
let logFile = dir.appendingPathComponent(filePath)
11+
if FileManager.default.fileExists(atPath: filePath) {
12+
if let fileHandle = try? FileHandle(forWritingTo: logFile) {
13+
fileHandle.seekToEndOfFile()
14+
fileHandle.write(data)
15+
fileHandle.closeFile()
16+
}
17+
} else {
18+
try? data.write(to: logFile, options: .atomic)
19+
}
20+
}
21+
}
22+
23+
public static func vars<T: Encodable>(_ obj: [String:T]) {
24+
25+
let encoder = JSONEncoder()
26+
if let data = try? encoder.encode(obj) {
27+
28+
if var inspectVarsPath = ProcessInfo.processInfo.environment["INSPECT_VARS"] {
29+
if let os = ProcessInfo.processInfo.environment["OS"], os.lowercased().contains("win") {
30+
inspectVarsPath = inspectVarsPath.replacingOccurrences(of:"/", with:"\\")
31+
} else {
32+
inspectVarsPath = inspectVarsPath.replacingOccurrences(of:"\\", with:"/")
33+
}
34+
35+
let filePath = URL(fileURLWithPath: FileManager.default.currentDirectoryPath).appendingPathComponent(inspectVarsPath)
36+
let dirPath = filePath.deletingLastPathComponent().relativePath
37+
38+
if !FileManager.default.fileExists(atPath: dirPath) {
39+
do {
40+
try FileManager.default.createDirectory(atPath: dirPath, withIntermediateDirectories: true, attributes: nil)
41+
} catch {
42+
if verbose { print("vars() createDirectory: \(error.localizedDescription)") }
43+
}
44+
}
45+
46+
do {
47+
try data.write(to:filePath, options:.atomic)
48+
} catch {
49+
if verbose { print("vars() data.write: \(error.localizedDescription)") }
50+
}
51+
}
52+
}
53+
}
54+
55+
public static func dump<T: Encodable>(_ obj: T) -> String {
56+
let encoder = JSONEncoder()
57+
encoder.outputFormatting = .prettyPrinted
58+
if let data = try? encoder.encode(obj) {
59+
let json = String(data: data, encoding: .utf8)!
60+
return json.replacingOccurrences(of:"\"", with: "")
61+
} else {
62+
var toStr = String()
63+
Swift.dump(obj, to:&toStr)
64+
return toStr
65+
}
66+
}
67+
68+
public static func printDump<T: Encodable>(_ obj: T) {
69+
print(Inspect.dump(obj))
70+
}
71+
72+
static func alignLeft(_ str: String, length: Int, pad: String = " ") -> String {
73+
if length < 0 {
74+
return ""
75+
}
76+
let alen = length + 1 - str.count
77+
if alen <= 0 {
78+
return str
79+
}
80+
return pad + str + String(repeating:pad, count:length + 1 - str.count)
81+
}
82+
83+
static func alignCenter(_ str: String, length: Int, pad: String = " ") -> String {
84+
if length < 0 {
85+
return ""
86+
}
87+
let nLen = str.count
88+
let half = Int(floor(Double(length) / 2.0 - Double(nLen) / 2.0))
89+
let odds = abs((nLen % 2) - (length % 2))
90+
return String(repeating:pad, count:half + 1) + str + String(repeating:pad, count:half + 1 + odds)
91+
}
92+
93+
static func alignRight(_ str: String, length: Int, pad: String = " ") -> String {
94+
if length < 0 {
95+
return ""
96+
}
97+
let alen = length + 1 - str.count
98+
if alen <= 0 {
99+
return str
100+
}
101+
return String(repeating:pad, count:length + 1 - str.count) + str + pad
102+
}
103+
104+
static func alignAuto(_ obj: AnyObject?, length: Int, pad: String = " ") -> String {
105+
let str = obj == nil ? "" : "\(obj!)"
106+
if str.count <= length {
107+
if let o = obj, isNumber(o) {
108+
return alignRight(fmtNumber(o)!, length:length, pad:pad)
109+
}
110+
return alignLeft(str, length:length, pad:pad)
111+
}
112+
return str
113+
}
114+
115+
public static func isNumber(_ obj:AnyObject) -> Bool {
116+
return obj is Int || obj is Double
117+
}
118+
119+
public static func fmtNumber(_ obj:AnyObject) -> String? {
120+
return "\(obj)"
121+
}
122+
123+
public static func dumpTable<S: Sequence>(_ objs: S, columns: [String]? = nil) -> String where S.Element : Codable {
124+
let rows = Array(objs)
125+
let mapRows = asArrayDictionary(rows)
126+
let keys = columns != nil
127+
? columns!
128+
: allKeys(mapRows)
129+
var colSizes = Dictionary<String,Int>()
130+
131+
for k in keys {
132+
var max = k.count
133+
for row in mapRows {
134+
if let col = row[k] {
135+
let valSize = "\(col)".count
136+
if valSize > max {
137+
max = valSize
138+
}
139+
}
140+
}
141+
colSizes[k] = max
142+
}
143+
144+
// sum + ' padding ' + |
145+
let rowWidth = colSizes.values.reduce(0,+) +
146+
(colSizes.count * 2) +
147+
(colSizes.count + 1)
148+
149+
var sb = [String]()
150+
151+
sb.append("+\(String(repeating:"-", count:rowWidth-2))+")
152+
var head = "|"
153+
for k in keys {
154+
head += alignCenter(k, length:colSizes[k]!) + "|"
155+
}
156+
sb.append(head)
157+
sb.append("+\(String(repeating:"-", count:rowWidth-2))+")
158+
159+
for row in mapRows {
160+
var to = "|"
161+
for k in keys {
162+
to += alignAuto(row[k], length:colSizes[k]!) + "|"
163+
}
164+
sb.append(to)
165+
}
166+
sb.append("+\(String(repeating:"-", count:rowWidth-2))+")
167+
168+
return sb.joined(separator: "\n")
169+
}
170+
171+
public static func printDumpTable<S: Sequence>(_ objs: S, columns: [String]? = nil) where S.Element : Codable {
172+
print(dumpTable(objs, columns:columns))
173+
}
174+
175+
176+
public static func asArrayDictionary<T: Codable>(_ objs: [T]) -> [Dictionary<String, AnyObject>] {
177+
return objs.map { asDictionary($0) }.filter { $0 != nil }.map { $0! }
178+
}
179+
180+
public static func asDictionary<T: Codable>(_ obj: T) -> Dictionary<String, AnyObject>? {
181+
do {
182+
let encoder = JSONEncoder()
183+
if let data = try? encoder.encode(obj),
184+
let dict = try JSONSerialization.jsonObject(
185+
with: data, options: JSONSerialization.ReadingOptions.allowFragments) as? [String: AnyObject] {
186+
return dict
187+
}
188+
} catch let error as NSError {
189+
print("Failed to convert to Dictionary: \(error.localizedDescription)")
190+
}
191+
return nil
192+
}
193+
194+
static func allKeys(_ objs: [Dictionary<String, AnyObject>]) -> [String] {
195+
var to = [String]()
196+
197+
for dict in objs {
198+
for (k,_) in dict {
199+
if !to.contains(k) {
200+
to.append(k)
201+
}
202+
}
203+
}
204+
205+
return to
206+
}
207+
208+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
import XCTest
2+
import Foundation
3+
4+
@testable import ServiceStack
5+
6+
struct GitHubRepo : Codable {
7+
let name: String
8+
let description: String?
9+
let language: String?
10+
let watchers: Int
11+
let forks: Int
12+
}
13+
14+
final class InspectSwiftTests: XCTestCase {
15+
16+
func testInspect() {
17+
let asyncTest = expectation(description: "asyncTest")
18+
19+
let orgName = "apple"
20+
21+
print("downloading orgRepos...")
22+
if let url = URL(string: "https://api.github.com/orgs/\(orgName)/repos") {
23+
URLSession.shared.dataTask(with: url) { data, response, error in
24+
if let data = data {
25+
do {
26+
defer { asyncTest.fulfill() }
27+
28+
let orgRepos = try JSONDecoder().decode([GitHubRepo].self, from: data)
29+
.sorted { $0.watchers > $1.watchers }
30+
31+
print("Top 3 \(orgName) GitHub Repos:")
32+
Inspect.printDump(Array(orgRepos.prefix(3)))
33+
34+
print("\nTop 10 \(orgName) GitHub Repos:")
35+
Inspect.printDumpTable(Array(orgRepos.prefix(10)),
36+
columns:["name","language","watchers","forks"])
37+
38+
Inspect.vars(["orgRepos":orgRepos])
39+
40+
} catch let error {
41+
print(error)
42+
}
43+
}
44+
}.resume()
45+
}
46+
47+
waitForExpectations(timeout: 5, handler: { error in
48+
XCTAssertNil(error, "Error")
49+
})
50+
}
51+
52+
static var allTests = [
53+
("testInspect", testInspect),
54+
]
55+
}

0 commit comments

Comments
 (0)