I've already written a few apps using the swift package manager. What I haven't done so far was writing an application that uses the package manager for testing as well. Hence I decided to write a mac utility that could help me reduce the technical debt of my current project.
The utility lists all the files (objective-c, Swift or both) that have more than a given number of lines. Given it's purpose I decided to name it filesize. I'm sure someone can come up with some concatenation of various Unix command to achieve this but then where what would be the fun in doing that.
The utility basically traverses all the subdirectories from a given location, counts the line numbers of all files meeting the name filter and lists all those files whose line count exceeds a given number. So a call like
filesize . --limit 1000
would list all objective-c and swift files that have more than 999 lines.
Package manager configuration
// The swift-tools-version declares the minimum version of Swift required to build this package.
let package = Package( name: "filesize", dependencies: [ ], targets: [
.target( name: "filesize", dependencies: ["filesizeCore"], path: "Sources/filesize" ), .target( name: "filesizeCore", dependencies: , path: "Sources/filesizeCore" ), .testTarget( name: "filesizeTests", dependencies: ["filesizeCore"], path: "Tests" ) ]
It's a simple app with a simple configuration file. The configuration lists three targets.
One target to create a framework named filesizeCore: I had to wrap the command line parser for the various options along with the directory/file parser in a framework since I ran into a minor problem. When I started out writing tests, xcodebuild would refuse to compile the test target whenever main.swift was included in the app target filesize. So as a workaround I created a dedicated target named filesizeCore that allowed me to write tests for most of the implementation.
One target for the utility fittingly named filesize as mentioned before to compile main.swift and link it with filesizeCore and last but not least of course one dedicated target for all the tests.
The life of the app starts in main.swift. Here all the command line arguments are first handed over to CommandlineParser to translate those into an actionable enum type. After that the extracted path is used to instantiate a class of FileContentProvider. FileContentProvider doesn't scan all the files at instantiation. It only sets up the list of files to scan. The class also conforms to the Sequence protocol which can be either used to iterate over all files or as in the current implementation return them all at once. Either way what come back is a tuple of file url and filecontent or an array thereof. To make it easier to deal with the tuple there's also a typealias FileContentProviderResultType. The latter was used to extend protocol Sequence with a parse() method, which applies filters and threshold given at the command line. This approach made it also easy to test parse() which contains the core functionality of the whole application.
This project gave me an opportunity again to play around with the package manager. It's configuration format still keeps changing from version to version which made me wonder when it reaches a source code maturity level that doesn't require some year over year adjustment.
Apart from that I wish it was possible to provide build flags. Apart from the source code having a dependency on the macOS 10.12 API, it would also be nice to make it easy for everyone who wants to compile the project to get a usable release build with a linked-in Swift standard library right from the start. Lacking this feature everyone unfortunately needs to type
swift build -Xswiftc "-target" -Xswiftc "x86_64-apple-macosx10.12" -Xswiftc -static-stdlib -c release
It's a mouthful which hopefully becomes redundant with the next version.