Skip to content

Instantly share code, notes, and snippets.

@abhibeckert
Created June 22, 2018 01:31
Show Gist options
  • Save abhibeckert/844d867f40bdd2da7706942cf6a5e30b to your computer and use it in GitHub Desktop.
Save abhibeckert/844d867f40bdd2da7706942cf6a5e30b to your computer and use it in GitHub Desktop.
Project File Linter
//
// main.swift
// Lint
//
// Created by Abhi Beckert on 18/6/18.
// Copyright © 2018 Abhi Beckert. All rights reserved.
//
import Foundation
if CommandLine.argc != 2 {
// you might like to do this instead for running in the debugger
// let inputDir = ("~/path/to/project" as NSString).expandingTildeInPath
print("Usage: lint path/to/project")
exit(1)
}
// how many files to process at once? Recommend 2 per CPU core you'd like to dedicate to this.
let concurrrentOperations = 8
// process args
let inputDir = CommandLine.arguments[1]
// subclass OperationQueue for running many shell command concurrently, and capturing their output, and processing the
// output on separate operation queue that is not concurrent (to prevent terminal output getting mixed up)
class ShellOperationQueue: OperationQueue
{
let processResutlsQueue = OperationQueue()
override init()
{
super.init()
processResutlsQueue.maxConcurrentOperationCount = 1
}
func addShellOperation(_ shellCmd: String, _ args: [String], completion: @escaping (_ terminationStatus: Int, _ stdOut: String, _ stdErr: String) -> Void)
{
self.addOperation {
let process = Process()
process.launchPath = shellCmd
process.arguments = args
let outPipe = Pipe()
process.standardOutput = outPipe
let errPipe = Pipe()
process.standardError = errPipe
do {
try process.run()
} catch {
self.processResutlsQueue.addOperation {
completion(-1, "", "Unknown Error returned by Process()")
}
}
let outData = outPipe.fileHandleForReading.readDataToEndOfFile()
let errData = errPipe.fileHandleForReading.readDataToEndOfFile()
process.waitUntilExit()
let outString: String
if let utf8String = String(data: outData, encoding: .utf8) {
outString = utf8String
} else if let asciiString = String(data: outData, encoding: .ascii) {
outString = asciiString
} else {
outString = "Unable to process Standard Output!!"
}
let errString: String
if let utf8String = String(data: errData, encoding: .utf8) {
errString = utf8String
} else if let asciiString = String(data: errData, encoding: .ascii) {
errString = asciiString
} else {
errString = "Unable to process Standard Output!!"
}
self.processResutlsQueue.addOperation {
completion(Int(process.terminationStatus), outString, errString)
}
}
}
override func waitUntilAllOperationsAreFinished()
{
super.waitUntilAllOperationsAreFinished()
processResutlsQueue.waitUntilAllOperationsAreFinished()
}
}
let processFilesQueue = ShellOperationQueue()
processFilesQueue.maxConcurrentOperationCount = concurrrentOperations
guard let dirEnumerator = FileManager.default.enumerator(atPath: inputDir) else {
print("⁉️ Unabel to scan \(inputDir)")
exit(1)
}
var successCount = 0
var failCount = 0
var failedFiles: [String] = []
var unrecognisedFilesCount = 0
var unrecognisedFiles: [String] = []
func success(_ file: String)
{
successCount += 1
print("🔹 \(file)")
}
func fail(_ file: String, _ stdOut: String, _ stdErr: String)
{
failCount += 1
failedFiles.append(file)
print("\n❗️ Failed: \(file)")
if stdOut != "" {
print(stdOut)
}
if stdOut != "" {
print(stdErr)
}
}
for case let file as String in dirEnumerator {
var fileExt = (file as NSString).pathExtension
if fileExt == "" {
fileExt = (file as NSString).lastPathComponent
}
let fullPath = (inputDir as NSString).appendingPathComponent(file)
switch fileExt {
case "php":
processFilesQueue.addShellOperation("/usr/bin/php", ["-l", fullPath]) { (status, stdOut, stdErr) in
if status == 0 {
success(file)
return
}
fail(file, stdOut, stdErr)
}
break;
case ".DS_Store", "LICENSE", "tpl":
break; // ignore these files
default:
unrecognisedFilesCount += 1
if (unrecognisedFilesCount < 15) {
unrecognisedFiles.append(file)
}
}
}
processFilesQueue.waitUntilAllOperationsAreFinished()
print("——————————————————————————————————————————————————————————")
if (successCount == 0 && failCount == 0) {
print("⁉️ Did not process any files")
} else if (failCount > 0) {
print("‼️ Failed to process \(failCount) files, successfully processed \(successCount).")
for file in failedFiles {
print(" \(file)")
}
} else {
print("👌 Processed \(successCount) files")
}
if unrecognisedFilesCount > 0 {
print("🤢 Didn't know how to process \(unrecognisedFilesCount) files:")
for file in unrecognisedFiles {
print(" \(file)")
}
if unrecognisedFiles.count < unrecognisedFilesCount {
print(" …and \(unrecognisedFilesCount - unrecognisedFiles.count) more")
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment