Skip to content

Commit f21f36c

Browse files
committed
Add decodeFile(_:)
1 parent 2280d8f commit f21f36c

File tree

7 files changed

+72
-34
lines changed

7 files changed

+72
-34
lines changed

BencodeKit.xcodeproj/xcshareddata/xcschemes/BencodeKit.xcscheme

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
buildConfiguration = "Debug"
2727
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
2828
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
29-
shouldUseLaunchSchemeArgsEnv = "YES">
29+
shouldUseLaunchSchemeArgsEnv = "YES"
30+
codeCoverageEnabled = "YES">
3031
<Testables>
3132
<TestableReference
3233
skipped = "NO">

Sources/Bencode.swift

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
public enum BencodingError: Error {
1010
case emptyString
11+
case nonExistentFile(String)
1112
case unexpectedCharacter(Character, Data.Index)
1213
case negativeZeroEncountered(Data.Index)
1314
case invalidStringLength(Data.Index)
@@ -20,6 +21,7 @@ public enum BencodingError: Error {
2021
case nonStringDictionaryKey(Data.Index)
2122
case nonAsciiDictionaryKey(Data.Index)
2223
case unterminatedDictionary(Data.Index)
24+
case nonAsciiString
2325
}
2426

2527
public indirect enum Bencode: Equatable {
@@ -32,10 +34,8 @@ public indirect enum Bencode: Equatable {
3234
self = .integer(integer)
3335
}
3436

35-
init?(_ string: String) {
36-
guard let value = string.data(using: .ascii) else {
37-
return nil
38-
}
37+
init(_ string: String) throws {
38+
guard let value = string.data(using: .ascii) else { throw BencodingError.nonAsciiString }
3939
self = .bytes(value)
4040
}
4141

@@ -51,16 +51,17 @@ public indirect enum Bencode: Equatable {
5151
self = .dictionary(dictionary)
5252
}
5353

54-
var description: String {
54+
func stringRepresentation() throws -> String {
5555
switch self {
5656
case .integer(let integer):
5757
return "i\(String(integer))e"
5858
case .bytes(let bytes):
59-
return "\(bytes.count):\(String(bytes: bytes, encoding: .ascii) ?? "")"
59+
guard let string = String(bytes: bytes, encoding: .ascii) else { throw BencodingError.nonAsciiString }
60+
return "\(string.characters.count):\(string)"
6061
case .list(let list):
61-
return "l\(list.map({ $0.description }).joined())e"
62+
return try "l\(list.map({ try $0.stringRepresentation() }).joined())e"
6263
case .dictionary(let dictionary):
63-
return "d\(dictionary.map({ "\($0.characters.count):\($0)\($1.description)" }).joined())e"
64+
return try "d\(dictionary.map({ "\($0.characters.count):\($0)\(try $1.stringRepresentation())" }).joined())e"
6465
}
6566
}
6667
}
@@ -71,6 +72,15 @@ public extension Bencode {
7172
}
7273
}
7374

75+
public extension Bencode {
76+
public static func decodeFile(atPath path: String) throws -> Bencode {
77+
guard let data = FileManager.default.contents(atPath: path) else {
78+
throw BencodingError.nonExistentFile(path)
79+
}
80+
return try decode(data)
81+
}
82+
}
83+
7484
public extension Bencode {
7585
public func encoded() -> Data {
7686
switch self {

Sources/Types/Dictionary.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// Copyright © 2017 Monadic Consulting. All rights reserved.
77
//
88

9-
func bdecodeDictionary(_ data: Data, _ index: Data.Index) throws -> (Bencode, Data.Index) {
9+
internal func bdecodeDictionary(_ data: Data, _ index: Data.Index) throws -> (Bencode, Data.Index) {
1010
guard data[index] == "d" else {
1111
throw BencodingError.invalidDictionary(index)
1212
}

Sources/Types/Integer.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
// Copyright © 2017 Monadic Consulting. All rights reserved.
77
//
88

9-
func bdecodeInteger(_ data: Data, _ index: Data.Index) throws -> (match: Bencode, index: Data.Index) {
9+
internal func bdecodeInteger(_ data: Data, _ index: Data.Index) throws -> (match: Bencode, index: Data.Index) {
1010
guard data[index] == "i" else {
1111
throw BencodingError.invalidInteger(index)
1212
}

Sources/Types/List.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,14 @@
66
// Copyright © 2017 Monadic Consulting. All rights reserved.
77
//
88

9-
func bdecodeList(_ data: Data, _ index: Data.Index) throws -> (Bencode, Data.Index) {
9+
internal func bdecodeList(_ data: Data, _ index: Data.Index) throws -> (Bencode, Data.Index) {
1010
guard data[index] == "l" else {
1111
throw BencodingError.invalidList(index)
1212
}
1313

1414
var currentIndex = data.index(after: index)
1515
guard currentIndex != data.endIndex else {
16-
throw BencodingError.unterminatedDictionary(index)
16+
throw BencodingError.unterminatedList(index)
1717
}
1818

1919
var values: [Bencode] = []

Sources/Types/String.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ internal func bdecodeString(_ data: Data, _ index: Data.Index) throws -> (match:
1515
let restIndex = data.index(after: colonIndex)
1616
let lengthString = String(bytes: data.subdata(in: Range(uncheckedBounds: (index, colonIndex))), encoding: .ascii)
1717

18-
guard let length = lengthString.flatMap({UInt($0)}).map({ Int($0) }) else {
18+
guard let length = lengthString.flatMap({ UInt($0) }).map({ Int($0) }) else {
1919
throw BencodingError.invalidStringLength(index)
2020
}
2121

Tests/Tests.swift

Lines changed: 47 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,37 +12,50 @@ import XCTest
1212

1313
class BencodeKitTests: XCTestCase {
1414

15+
func testDecoding() {
16+
expectException("")
17+
expectException("k")
18+
_ = Bencode("hello".asciiData)
19+
XCTAssertNotEqual(Bencode(0), try! Bencode("4:null"))
20+
expectException(try Bencode.decodeFile(atPath: ""))
21+
}
22+
1523
func testStringParsing() {
16-
let data = "12:123456789123".data(using: .ascii)!
17-
let (match, _) = try! bdecodeString(data, data.startIndex)
18-
print(match.description)
19-
// testParsing("12:123456789123", .bytes("123456789123"))
24+
testParsing("12:123456789123", try! Bencode("123456789123"))
2025

21-
expectException("-13:40")
22-
expectException("-13:40")
23-
expectException("-13:40")
24-
expectException("-13:40")
26+
expectException("-13:40", bdecodeString)
2527
expectException("4:lkd")
28+
expectException("4e:lkd")
29+
expectException("i0e", bdecodeString)
2630
}
2731

2832
func testListParsing() {
29-
testParsing("li-123456789e4:nulle", Bencode([.integer(-123456789), Bencode("null")!]))
33+
testParsing("li-123456789e4:nulle", try! Bencode([.integer(-123456789), Bencode("null")]))
3034
compareToReencoding("li-123456789e4:nulle")
35+
expectException("l")
36+
expectException("li0e")
37+
expectException("i0e", bdecodeList)
3138
}
3239

3340
func testDictionaryParsing() {
41+
_ = Bencode([("", Bencode(0))])
3442
testParsing("d4:nulli-123456789e2:hilee", .dictionary([("null", .integer(-123456789)), ("hi", .list([]))]))
43+
testParsing("de", .dictionary([]))
3544
compareToReencoding("d4:nulli-123456789e2:hilee")
45+
compareToReencoding("de")
46+
compareToReencoding("d5:hello5:theree")
3647

3748
expectException("d")
49+
expectException("di0e4:nulle")
50+
expectException("d4:nulli0e")
51+
expectException("i0e", bdecodeDictionary)
3852
}
3953

4054
func testIntegerParsing() {
41-
let data = "i-123456789e".data(using: .ascii)!
42-
print(try! bdecodeInteger(data, data.startIndex))
43-
55+
_ = Bencode(0)
4456
testParsing("i-123456789e", .integer(-123456789))
45-
57+
compareToReencoding("i-123456789e")
58+
expectException("de", bdecodeInteger)
4659
expectException("ie")
4760
expectException("i-0e")
4861
expectException("ioe")
@@ -52,31 +65,45 @@ class BencodeKitTests: XCTestCase {
5265
}
5366

5467
func testTorrentFiles() {
55-
Bundle(for: type(of: self))
56-
.paths(forResourcesOfType: "torrent", inDirectory: "Torrents")
68+
let filePaths = Bundle(for: type(of: self)).paths(forResourcesOfType: "torrent", inDirectory: "Torrents")
69+
filePaths
5770
.flatMap(FileManager.default.contents)
5871
.forEach { encoded in
5972
let decoded = try! Bencode.decode(encoded)
60-
print(decoded.description)
6173
let reEncoded = decoded.encoded()
6274
XCTAssertEqual(encoded, reEncoded)
75+
_ = try! decoded.stringRepresentation()
76+
}
77+
filePaths
78+
.map { try! Bencode.decodeFile(atPath: $0) }
79+
.forEach { decoded in
80+
_ = try! decoded.stringRepresentation()
6381
}
6482
}
6583
}
6684

6785
func compareToReencoding(_ param: String) {
6886
let data = param.asciiData
69-
let returnedData = try! Bencode.decode(data).encoded()
70-
XCTAssertEqual(returnedData, data)
87+
let decoded = try! Bencode.decode(data)
88+
XCTAssertEqual(data, decoded.encoded())
89+
XCTAssertEqual(param, try! decoded.stringRepresentation())
7190
}
7291

7392
func testParsing(_ param: String, _ compareTo: Bencode) {
7493
XCTAssertEqual(try! Bencode.decode(param.asciiData), compareTo)
7594
}
7695

77-
func expectException(_ param: String) {
96+
func expectException(_ param: String, _ f: (Data, Data.Index) throws -> (Bencode, Data.Index) = { data, index in try (Bencode.decode(data), 0) }) {
97+
do {
98+
let data = param.asciiData
99+
_ = try f(data, data.startIndex)
100+
XCTFail()
101+
} catch {}
102+
}
103+
104+
func expectException<T>(_ f: @autoclosure () throws -> T) {
78105
do {
79-
_ = try Bencode.decode(param.asciiData)
106+
_ = try f()
80107
XCTFail()
81108
} catch {}
82109
}

0 commit comments

Comments
 (0)