Created
June 22, 2018 01:31
-
-
Save abhibeckert/844d867f40bdd2da7706942cf6a5e30b to your computer and use it in GitHub Desktop.
Project File Linter
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// 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