Skip to content

Commit

Permalink
Add decodeFile(_:)
Browse files Browse the repository at this point in the history
  • Loading branch information
Qata committed Jan 30, 2017
1 parent 2280d8f commit f21f36c
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
shouldUseLaunchSchemeArgsEnv = "YES"
codeCoverageEnabled = "YES">
<Testables>
<TestableReference
skipped = "NO">
Expand Down
26 changes: 18 additions & 8 deletions Sources/Bencode.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

public enum BencodingError: Error {
case emptyString
case nonExistentFile(String)
case unexpectedCharacter(Character, Data.Index)
case negativeZeroEncountered(Data.Index)
case invalidStringLength(Data.Index)
Expand All @@ -20,6 +21,7 @@ public enum BencodingError: Error {
case nonStringDictionaryKey(Data.Index)
case nonAsciiDictionaryKey(Data.Index)
case unterminatedDictionary(Data.Index)
case nonAsciiString
}

public indirect enum Bencode: Equatable {
Expand All @@ -32,10 +34,8 @@ public indirect enum Bencode: Equatable {
self = .integer(integer)
}

init?(_ string: String) {
guard let value = string.data(using: .ascii) else {
return nil
}
init(_ string: String) throws {
guard let value = string.data(using: .ascii) else { throw BencodingError.nonAsciiString }
self = .bytes(value)
}

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

var description: String {
func stringRepresentation() throws -> String {
switch self {
case .integer(let integer):
return "i\(String(integer))e"
case .bytes(let bytes):
return "\(bytes.count):\(String(bytes: bytes, encoding: .ascii) ?? "")"
guard let string = String(bytes: bytes, encoding: .ascii) else { throw BencodingError.nonAsciiString }
return "\(string.characters.count):\(string)"
case .list(let list):
return "l\(list.map({ $0.description }).joined())e"
return try "l\(list.map({ try $0.stringRepresentation() }).joined())e"
case .dictionary(let dictionary):
return "d\(dictionary.map({ "\($0.characters.count):\($0)\($1.description)" }).joined())e"
return try "d\(dictionary.map({ "\($0.characters.count):\($0)\(try $1.stringRepresentation())" }).joined())e"
}
}
}
Expand All @@ -71,6 +72,15 @@ public extension Bencode {
}
}

public extension Bencode {
public static func decodeFile(atPath path: String) throws -> Bencode {
guard let data = FileManager.default.contents(atPath: path) else {
throw BencodingError.nonExistentFile(path)
}
return try decode(data)
}
}

public extension Bencode {
public func encoded() -> Data {
switch self {
Expand Down
2 changes: 1 addition & 1 deletion Sources/Types/Dictionary.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright © 2017 Monadic Consulting. All rights reserved.
//

func bdecodeDictionary(_ data: Data, _ index: Data.Index) throws -> (Bencode, Data.Index) {
internal func bdecodeDictionary(_ data: Data, _ index: Data.Index) throws -> (Bencode, Data.Index) {
guard data[index] == "d" else {
throw BencodingError.invalidDictionary(index)
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Types/Integer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
// Copyright © 2017 Monadic Consulting. All rights reserved.
//

func bdecodeInteger(_ data: Data, _ index: Data.Index) throws -> (match: Bencode, index: Data.Index) {
internal func bdecodeInteger(_ data: Data, _ index: Data.Index) throws -> (match: Bencode, index: Data.Index) {
guard data[index] == "i" else {
throw BencodingError.invalidInteger(index)
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/Types/List.swift
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
// Copyright © 2017 Monadic Consulting. All rights reserved.
//

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

var currentIndex = data.index(after: index)
guard currentIndex != data.endIndex else {
throw BencodingError.unterminatedDictionary(index)
throw BencodingError.unterminatedList(index)
}

var values: [Bencode] = []
Expand Down
2 changes: 1 addition & 1 deletion Sources/Types/String.swift
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ internal func bdecodeString(_ data: Data, _ index: Data.Index) throws -> (match:
let restIndex = data.index(after: colonIndex)
let lengthString = String(bytes: data.subdata(in: Range(uncheckedBounds: (index, colonIndex))), encoding: .ascii)

guard let length = lengthString.flatMap({UInt($0)}).map({ Int($0) }) else {
guard let length = lengthString.flatMap({ UInt($0) }).map({ Int($0) }) else {
throw BencodingError.invalidStringLength(index)
}

Expand Down
67 changes: 47 additions & 20 deletions Tests/Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,50 @@ import XCTest

class BencodeKitTests: XCTestCase {

func testDecoding() {
expectException("")
expectException("k")
_ = Bencode("hello".asciiData)
XCTAssertNotEqual(Bencode(0), try! Bencode("4:null"))
expectException(try Bencode.decodeFile(atPath: ""))
}

func testStringParsing() {
let data = "12:123456789123".data(using: .ascii)!
let (match, _) = try! bdecodeString(data, data.startIndex)
print(match.description)
// testParsing("12:123456789123", .bytes("123456789123"))
testParsing("12:123456789123", try! Bencode("123456789123"))

expectException("-13:40")
expectException("-13:40")
expectException("-13:40")
expectException("-13:40")
expectException("-13:40", bdecodeString)
expectException("4:lkd")
expectException("4e:lkd")
expectException("i0e", bdecodeString)
}

func testListParsing() {
testParsing("li-123456789e4:nulle", Bencode([.integer(-123456789), Bencode("null")!]))
testParsing("li-123456789e4:nulle", try! Bencode([.integer(-123456789), Bencode("null")]))
compareToReencoding("li-123456789e4:nulle")
expectException("l")
expectException("li0e")
expectException("i0e", bdecodeList)
}

func testDictionaryParsing() {
_ = Bencode([("", Bencode(0))])
testParsing("d4:nulli-123456789e2:hilee", .dictionary([("null", .integer(-123456789)), ("hi", .list([]))]))
testParsing("de", .dictionary([]))
compareToReencoding("d4:nulli-123456789e2:hilee")
compareToReencoding("de")
compareToReencoding("d5:hello5:theree")

expectException("d")
expectException("di0e4:nulle")
expectException("d4:nulli0e")
expectException("i0e", bdecodeDictionary)
}

func testIntegerParsing() {
let data = "i-123456789e".data(using: .ascii)!
print(try! bdecodeInteger(data, data.startIndex))

_ = Bencode(0)
testParsing("i-123456789e", .integer(-123456789))

compareToReencoding("i-123456789e")
expectException("de", bdecodeInteger)
expectException("ie")
expectException("i-0e")
expectException("ioe")
Expand All @@ -52,31 +65,45 @@ class BencodeKitTests: XCTestCase {
}

func testTorrentFiles() {
Bundle(for: type(of: self))
.paths(forResourcesOfType: "torrent", inDirectory: "Torrents")
let filePaths = Bundle(for: type(of: self)).paths(forResourcesOfType: "torrent", inDirectory: "Torrents")
filePaths
.flatMap(FileManager.default.contents)
.forEach { encoded in
let decoded = try! Bencode.decode(encoded)
print(decoded.description)
let reEncoded = decoded.encoded()
XCTAssertEqual(encoded, reEncoded)
_ = try! decoded.stringRepresentation()
}
filePaths
.map { try! Bencode.decodeFile(atPath: $0) }
.forEach { decoded in
_ = try! decoded.stringRepresentation()
}
}
}

func compareToReencoding(_ param: String) {
let data = param.asciiData
let returnedData = try! Bencode.decode(data).encoded()
XCTAssertEqual(returnedData, data)
let decoded = try! Bencode.decode(data)
XCTAssertEqual(data, decoded.encoded())
XCTAssertEqual(param, try! decoded.stringRepresentation())
}

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

func expectException(_ param: String) {
func expectException(_ param: String, _ f: (Data, Data.Index) throws -> (Bencode, Data.Index) = { data, index in try (Bencode.decode(data), 0) }) {
do {
let data = param.asciiData
_ = try f(data, data.startIndex)
XCTFail()
} catch {}
}

func expectException<T>(_ f: @autoclosure () throws -> T) {
do {
_ = try Bencode.decode(param.asciiData)
_ = try f()
XCTFail()
} catch {}
}

0 comments on commit f21f36c

Please sign in to comment.