diff --git a/.swiftformat b/.swiftformat index 6045ea4..531ea33 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,18 +1,18 @@ --swiftversion 5.3 ---binarygrouping none ---decimalgrouping none ---hexgrouping none ---indent tab ---indentcase true ---nospaceoperators "...,..<" ---octalgrouping none ---self insert ---semicolons never ---stripunusedargs unnamed-only ---wraparguments before-first ---wrapparameters before-first +--binarygrouping none +--decimalgrouping none +--hexgrouping none +--indent tab +--indentcase true +--nospaceoperators "...,..<" +--octalgrouping none +--self insert +--semicolons never +--stripunusedargs unnamed-only +--wraparguments before-first +--wrapparameters before-first --disable trailingClosures, typeSugar --header "Copyright (c) 2021 Jeff Lebrun \n\n \ Licensed under the MIT License. \n\n \ -The full text license can be found in the file named LICENSE. +The full text of the license can be found in the file named LICENSE. " diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..c5d1a84 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,30 @@ +# Changelog + +All notable changes to this project will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), +and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased]() + +### Fixed + +- Fixed a bug that occurred when parsing a file in an archive that contained a `\n` after it's content. + +## [0.1.0](https://github.com/LebJe/ArArchiveKit/releases/tag/0.1.0) - 2021-04-09 + +### Added + +- Support for reading and writing [BSD variant](https://www.freebsd.org/cgi/man.cgi?query=ar&sektion=5) `ar` archives. + +## [0.0.2](https://github.com/LebJe/ArArchiveKit/releases/tag/0.0.2) - 2021-03-18 + +### Added + +- Support for reading `ar` archives. + +## [0.0.1](https://github.com/LebJe/ArArchiveKit/releases/tag/0.0.1) - 2021-03-17 + +### Added + +- Support for creating `ar` archives. diff --git a/Examples/Foundationless/Sources/Foundationless/main.swift b/Examples/Foundationless/Sources/Foundationless/main.swift index 48440a5..1524a72 100644 --- a/Examples/Foundationless/Sources/Foundationless/main.swift +++ b/Examples/Foundationless/Sources/Foundationless/main.swift @@ -2,7 +2,7 @@ // // Licensed under the MIT License. // -// The full text license can be found in the file named LICENSE. +// The full text of the license can be found in the file named LICENSE. #if os(macOS) import Darwin.C @@ -28,7 +28,7 @@ func parseHelpFlag(_ s: String) -> Bool { let usage = """ USAGE: \(CommandLine.arguments[0]) [--help, -h, -?] [-p] [-b] -Reads the archive at `file` prints information about each file in the archive. Note that `-b` must come AFTER `-p`. +Reads the archive at `file` prints information about each file in the archive. -h, --help, -? Prints this message. -p Print the contents of the files in the archive. @@ -44,11 +44,11 @@ func parseArgs() { exit(1) } - if CommandLine.arguments.count >= 3, CommandLine.arguments[2] == "-p" { + if CommandLine.arguments.firstIndex(of: "-p") != nil { shouldPrintFile = true } - if CommandLine.arguments.count >= 4, CommandLine.arguments[3] == "-b" { + if CommandLine.arguments.firstIndex(of: "-b") != nil { printInBinary = true } } @@ -81,9 +81,9 @@ for (header, file) in reader { print("File Size: " + String(header.size)) print("File Modification Time: " + String(header.modificationTime)) - print("Contents:\n") - if shouldPrintFile { + print("Contents:\n") + if printInBinary { file.forEach({ print($0) }) } else { diff --git a/Examples/Foundationless/Tests/FoundationlessTests/FoundationlessTests.swift b/Examples/Foundationless/Tests/FoundationlessTests/FoundationlessTests.swift index c2762b9..b790868 100644 --- a/Examples/Foundationless/Tests/FoundationlessTests/FoundationlessTests.swift +++ b/Examples/Foundationless/Tests/FoundationlessTests/FoundationlessTests.swift @@ -2,50 +2,13 @@ // // Licensed under the MIT License. // -// The full text license can be found in the file named LICENSE. +// The full text of the license can be found in the file named LICENSE. import class Foundation.Bundle import XCTest final class FoundationlessTests: XCTestCase { - func testExample() throws { - // This is an example of a functional test case. - // Use XCTAssert and related functions to verify your tests produce the correct - // results. - - // Some of the APIs that we use below are available in macOS 10.13 and above. - guard #available(macOS 10.13, *) else { - return - } - - let fooBinary = productsDirectory.appendingPathComponent("Foundationless") - - let process = Process() - process.executableURL = fooBinary - - let pipe = Pipe() - process.standardOutput = pipe - - try process.run() - process.waitUntilExit() - - let data = pipe.fileHandleForReading.readDataToEndOfFile() - let output = String(data: data, encoding: .utf8) - - XCTAssertEqual(output, "Hello, world!\n") - } - - /// Returns path to the built products directory. - var productsDirectory: URL { - #if os(macOS) - for bundle in Bundle.allBundles where bundle.bundlePath.hasSuffix(".xctest") { - return bundle.bundleURL.deletingLastPathComponent() - } - fatalError("couldn't find the products directory") - #else - return Bundle.main.bundleURL - #endif - } + func testExample() throws {} static var allTests = [ ("testExample", testExample), diff --git a/Examples/Foundationless/Tests/FoundationlessTests/XCTestManifests.swift b/Examples/Foundationless/Tests/FoundationlessTests/XCTestManifests.swift index d097c8c..d03de01 100644 --- a/Examples/Foundationless/Tests/FoundationlessTests/XCTestManifests.swift +++ b/Examples/Foundationless/Tests/FoundationlessTests/XCTestManifests.swift @@ -2,7 +2,7 @@ // // Licensed under the MIT License. // -// The full text license can be found in the file named LICENSE. +// The full text of the license can be found in the file named LICENSE. import XCTest diff --git a/Examples/Foundationless/Tests/LinuxMain.swift b/Examples/Foundationless/Tests/LinuxMain.swift index 9bec06f..3548e7b 100644 --- a/Examples/Foundationless/Tests/LinuxMain.swift +++ b/Examples/Foundationless/Tests/LinuxMain.swift @@ -2,7 +2,7 @@ // // Licensed under the MIT License. // -// The full text license can be found in the file named LICENSE. +// The full text of the license can be found in the file named LICENSE. import XCTest diff --git a/Sources/ArArchiveKit/ArArchiveReader.swift b/Sources/ArArchiveKit/ArArchiveReader.swift index d5cffdc..aa46d5c 100644 --- a/Sources/ArArchiveKit/ArArchiveReader.swift +++ b/Sources/ArArchiveKit/ArArchiveReader.swift @@ -2,7 +2,7 @@ // // Licensed under the MIT License. // -// The full text license can be found in the file named LICENSE. +// The full text of the license can be found in the file named LICENSE. /// `ArArchiveReader` reads `ar` files. public struct ArArchiveReader { @@ -48,12 +48,15 @@ public struct ArArchiveReader { while index < (self.data.count - 1), (index + (headerSize - 1)) < self.data.count - 1 { var h = try self.parseHeader(bytes: Array(self.data[index...(index + headerSize - 1)])) - index += headerSize + 1 - h.contentLocation = (index - 1) + (h.nameSize != nil ? h.nameSize! : 0) + h.contentLocation = (index + headerSize) + (h.nameSize != nil ? h.nameSize! : 0) + + // Jump past the header. + index += headerSize h.name = h.nameSize != nil ? String(Array(self.data[h.contentLocation - h.nameSize!.. [UInt8] { var s = str while s.count < size { s = s + " " } - self.bytes += s.asciiArray + return s.asciiArray } - private mutating func writeInt(_ int: I, size: Int, radix: Int? = nil, prefix: String? = nil) { + private func intToBytes(_ int: I, size: Int, radix: Int? = nil, prefix: String? = nil) -> [UInt8] { if let r = radix { - self.writeString((prefix != nil ? prefix! : "") + String(int, radix: r), size: size) + return self.stringToASCII((prefix != nil ? prefix! : "") + String(int, radix: r), size: size) } else { - self.writeString((prefix != nil ? prefix! : "") + String(int), size: size) + return self.stringToASCII((prefix != nil ? prefix! : "") + String(int), size: size) } } - /// Adds a `Header` to the archive. - private mutating func addHeader(header: Header, contentSize: Int) { + private mutating func writeString(_ str: String, size: Int) { + self.bytes += self.stringToASCII(str, size: size) + } + + private mutating func writeInt(_ int: I, size: Int, radix: Int? = nil, prefix: String? = nil) { + self.bytes += self.intToBytes(int, size: size, radix: radix, prefix: prefix) + } + + private func headerToBytes(header: Header, contentSize: Int) -> [UInt8] { + var header = header + var data: [UInt8] = [] + switch self.variant { - case .common: self.writeString(header.name.truncate(length: 16), size: 16) + case .common: data += self.stringToASCII(header.name.truncate(length: 16), size: 16) case .bsd: - self.writeString(header.name.count <= 16 && !header.name.contains(" ") ? header.name : "#1/\(header.name.count)", size: 16) + data += self.stringToASCII(header.name.count <= 16 && !header.name.contains(" ") ? header.name : "#1/\(header.name.count)", size: 16) } - self.writeInt(header.modificationTime, size: 12, radix: 10) - self.writeInt(header.userID, size: 6, radix: 10) - self.writeInt(header.groupID, size: 6, radix: 10) - self.writeInt(header.mode, size: 8, radix: 8, prefix: "100") + data += self.intToBytes(header.modificationTime, size: 12, radix: 10) + data += self.intToBytes(header.userID, size: 6, radix: 10) + data += self.intToBytes(header.groupID, size: 6, radix: 10) + data += self.intToBytes(header.mode, size: 8, radix: 8, prefix: "100") switch self.variant { case .common: - self.writeInt(contentSize, size: 10, radix: 10) - self.writeString("`\n", size: 2) + data += self.intToBytes(contentSize, size: 10, radix: 10) + data += self.stringToASCII("`\n", size: 2) case .bsd: if header.name.count > 16 || header.name.contains(" ") { - self.writeInt(contentSize + header.name.count, size: 10, radix: 10) - self.writeString("`\n", size: 2) - self.writeString(header.name, size: header.name.count) + data += self.intToBytes(contentSize + header.name.count, size: 10, radix: 10) + data += self.stringToASCII("`\n", size: 2) + data += self.stringToASCII(header.name, size: header.name.count) } else { - self.writeInt(contentSize, size: 10, radix: 10) - self.writeString("`\n", size: 2) + data += self.intToBytes(contentSize, size: 10, radix: 10) + data += self.stringToASCII("`\n", size: 2) } } + + header.nameSize = header.name.count > 16 || header.name.contains(" ") ? header.name.count : nil + header.contentLocation = (self.bytes.endIndex - 1) + contentSize + (contentSize % 2 != 0 ? 1 : 0) + + return data + } + + /// Adds a `Header` to the archive. + private mutating func addHeader(header: Header, contentSize: Int) { + var header = header + header.startingLocation = self.bytes.endIndex - 1 + self.bytes += self.headerToBytes(header: header, contentSize: contentSize) + header.endingLocation = (self.bytes.endIndex - 1) + contentSize + header.size = contentSize + + self.headers.append(header) } /// Add a file to the archive. diff --git a/Sources/ArArchiveKit/Constants.swift b/Sources/ArArchiveKit/Constants.swift index 878b6e1..64862c1 100644 --- a/Sources/ArArchiveKit/Constants.swift +++ b/Sources/ArArchiveKit/Constants.swift @@ -2,7 +2,7 @@ // // Licensed under the MIT License. // -// The full text license can be found in the file named LICENSE. +// The full text of the license can be found in the file named LICENSE. let globalHeader = "!\n" let headerSize = 60 diff --git a/Sources/ArArchiveKit/Errors.swift b/Sources/ArArchiveKit/Errors.swift index e4bf1de..01bb5e0 100644 --- a/Sources/ArArchiveKit/Errors.swift +++ b/Sources/ArArchiveKit/Errors.swift @@ -2,7 +2,7 @@ // // Licensed under the MIT License. // -// The full text license can be found in the file named LICENSE. +// The full text of the license can be found in the file named LICENSE. public enum ArArchiveError: Error { /// The archive was empty. diff --git a/Sources/ArArchiveKit/Extensions.swift b/Sources/ArArchiveKit/Extensions.swift index df2451f..89bb9f9 100644 --- a/Sources/ArArchiveKit/Extensions.swift +++ b/Sources/ArArchiveKit/Extensions.swift @@ -2,7 +2,7 @@ // // Licensed under the MIT License. // -// The full text license can be found in the file named LICENSE. +// The full text of the license can be found in the file named LICENSE. extension String { var utf8Array: [UInt8] { diff --git a/Sources/ArArchiveKit/Header.swift b/Sources/ArArchiveKit/Header.swift index bb081e3..f473eb4 100644 --- a/Sources/ArArchiveKit/Header.swift +++ b/Sources/ArArchiveKit/Header.swift @@ -2,7 +2,7 @@ // // Licensed under the MIT License. // -// The full text license can be found in the file named LICENSE. +// The full text of the license can be found in the file named LICENSE. /// The `ar` header. /// @@ -33,6 +33,8 @@ public struct Header: Equatable { internal var contentLocation: Int = 0 internal var nameSize: Int? + internal var startingLocation: Int? + internal var endingLocation: Int? public init( name: String, diff --git a/Sources/ArArchiveKit/Variant.swift b/Sources/ArArchiveKit/Variant.swift index e6dd50c..644141c 100644 --- a/Sources/ArArchiveKit/Variant.swift +++ b/Sources/ArArchiveKit/Variant.swift @@ -2,7 +2,7 @@ // // Licensed under the MIT License. // -// The full text license can be found in the file named LICENSE. +// The full text of the license can be found in the file named LICENSE. /// The different formats of the `ar` archive. public enum Variant { diff --git a/Tests/ArArchiveKitTests/ArArchiveKitTests.swift b/Tests/ArArchiveKitTests/ArArchiveKitTests.swift index 8c5f3d6..4afdd34 100644 --- a/Tests/ArArchiveKitTests/ArArchiveKitTests.swift +++ b/Tests/ArArchiveKitTests/ArArchiveKitTests.swift @@ -2,7 +2,7 @@ // // Licensed under the MIT License. // -// The full text license can be found in the file named LICENSE. +// The full text of the license can be found in the file named LICENSE. @testable import ArArchiveKit import Foundation diff --git a/Tests/ArArchiveKitTests/XCTestManifests.swift b/Tests/ArArchiveKitTests/XCTestManifests.swift index bbb2dee..eddd258 100644 --- a/Tests/ArArchiveKitTests/XCTestManifests.swift +++ b/Tests/ArArchiveKitTests/XCTestManifests.swift @@ -2,7 +2,7 @@ // // Licensed under the MIT License. // -// The full text license can be found in the file named LICENSE. +// The full text of the license can be found in the file named LICENSE. import XCTest diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index d449dee..997bdbf 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -2,7 +2,7 @@ // // Licensed under the MIT License. // -// The full text license can be found in the file named LICENSE. +// The full text of the license can be found in the file named LICENSE. import XCTest