Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Handle reporters with absolute path as output path #49

Merged
merged 1 commit into from
Dec 29, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
41 changes: 26 additions & 15 deletions TaylorFramework/Modules/temper/Output/JSONCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,35 +8,46 @@

import Foundation

private typealias JSON = [String:[[String:AnyObject]]]

struct JSONCoordinator: WritingCoordinator {

func writeViolations(_ violations: [Violation], atPath path: String) {
FileManager().removeFileAtPath(path)
let json = generateJSON(violations)
var jsonData: Data? = nil
do {
jsonData = try JSONSerialization.data(withJSONObject: json, options: JSONSerialization.WritingOptions.prettyPrinted)
} catch {
print("Error while creating the JSON object.")
}
do {
try jsonData?.write(to: URL(fileURLWithPath: path), options: NSData.WritingOptions.withoutOverwriting)
} catch {
print("Error while writing the JSON object to file.")
let json = generateJSON(from: violations)
let data = try generateData(from: json)
try write(data: data, at: path)
} catch let error {
let message = "Error while creating JSON report." + "\n" +
"Reason: " + error.localizedDescription
Printer(verbosityLevel: .error).printError(message)
}
}

fileprivate func generateJSON(_ violations: [Violation]) -> [String:[[String:AnyObject]]] {
private func generateJSON(from violations: [Violation]) -> JSON {
return ["violation" : violations.map() { $0.toDictionary() }]
}
}

private func generateData(from json: JSON) throws -> Data {
return try JSONSerialization.data(withJSONObject: json, options: .prettyPrinted)
}

private func write(data: Data, at path: String) throws {
try data.write(to: URL(fileURLWithPath: path), options: .atomic)
}
}

extension FileManager {
func removeFileAtPath(_ path: String) {
var isDirectory: ObjCBool = false
if !FileManager.default.fileExists(atPath: path, isDirectory: &isDirectory) && !isDirectory.boolValue { return }
guard fileExists(atPath: path, isDirectory: &isDirectory) && !isDirectory.boolValue else { return }
do {
try FileManager.default.removeItem(atPath: path)
} catch { print("Error while removing item at path: \(path)") }
try removeItem(atPath: path)
} catch let error {
let message = "Error while removing file at path: " + path + "\n" +
"Reason: " + error.localizedDescription
Printer(verbosityLevel: .error).printError(message)
}
}
}
10 changes: 7 additions & 3 deletions TaylorFramework/Modules/temper/Output/OutputCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,15 @@ final class OutputCoordinator {
func writeTheOutput(_ violations: [Violation], reporters: [Reporter]) {
self.reporters = reporters
for reporter in reporters {
if reporter.fileName.isEmpty && (reporter.concreteReporter as? XcodeReporter) == nil { continue }
let path = (filePath as NSString).appendingPathComponent(reporter.fileName)
reporter.coordinator().writeViolations(violations, atPath: path)
write(violations: violations, with: reporter)
}
}

private func write(violations: [Violation], with reporter: Reporter) {
if reporter.fileName.isEmpty && (reporter.concreteReporter as? XcodeReporter) == nil { return }
let outputPath = reporter.fileName.absolutePath(filePath)
reporter.coordinator().writeViolations(violations, atPath: outputPath)
}
}

protocol WritingCoordinator {
Expand Down
8 changes: 5 additions & 3 deletions TaylorFramework/Modules/temper/Output/PLAINCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,11 @@ final class PLAINCoordinator: WritingCoordinator {
FileManager().removeFileAtPath(path)
let content = generateFileContentFromViolations(violations)
do {
try content.write(toFile: path, atomically: true, encoding: String.Encoding.utf8)
} catch _ {
print("Error while trying to write the content of string in file.")
try content.write(toFile: path, atomically: true, encoding: .utf8)
} catch let error {
let message = "Error while writing content of string at path: " + path + "\n" +
"Reason: " + error.localizedDescription
Printer(verbosityLevel: .error).printError(message)
}
}

Expand Down
64 changes: 43 additions & 21 deletions TaylorFramework/Modules/temper/Output/PMDCoordinator.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,43 +8,65 @@

import Foundation

private typealias ViolationsMap = [Path: [Violation]]

final class PMDCoordinator: WritingCoordinator {

func writeViolations(_ violations: [Violation], atPath path: String) {
FileManager().removeFileAtPath(path)
let xml = generateXML(violations)
let map = mapViolations(violations)
let xml = generateXML(from: map)
let xmlData = xml.xmlData(withOptions: Int(XMLNode.Options.nodePrettyPrint.rawValue))
do {
try xmlData.write(to: URL(fileURLWithPath: path), options: NSData.WritingOptions.withoutOverwriting)
} catch {
print("Error while writing the XML object to file.")
try xmlData.write(to: URL(fileURLWithPath: path), options: .atomic)
} catch let error {
let message = "Error while creating PMD report." + "\n" +
"Reason: " + error.localizedDescription
Printer(verbosityLevel: .error).printError(message)
}
}

private func mapViolations(_ violations: [Violation]) -> ViolationsMap {
return violations.reduce([Path : [Violation]]()) { result, violation in
let path = violation.path

// Add violation to the group with the same path
var violations = result[path] ?? []
violations.append(violation)

// Update the result
var nextResult = result
nextResult[path] = violations

return nextResult
}
}

fileprivate func generateXML(_ violations: [Violation]) -> XMLDocument {
private func generateXML(from violationsMap: ViolationsMap) -> XMLDocument {
let xml = XMLDocument(rootElement: XMLElement(name: "pmd"))
xml.version = "1.0"
xml.characterEncoding = "UTF-8"
addElementsToXML(xml, fromViolations: violations)

let fileElements = violationsMap.map(generateFileElement)
xml.rootElement()?.addChildren(fileElements)

return xml
}

fileprivate func addElementsToXML(_ xml: XMLDocument, fromViolations violations: [Violation]) {
for filePath in filePathsFromViolations(violations) {
let fileElement = XMLElement(name: "file")
let attributeNode = XMLNode.attribute(withName: "name", stringValue: filePath) as? XMLNode
guard let attribute = attributeNode else {
return
}
fileElement.addAttribute(attribute)
let _ = violations.filter({ $0.path == filePath }).map({ fileElement.addChild($0.toXMLElement()) })
if let root = xml.rootElement() {
root.addChild(fileElement)
}
private func generateFileElement(path: Path, violations: [Violation]) -> XMLElement {
let element = XMLElement(name: "file")
if let attribute = XMLNode.attribute(withName: "name", stringValue: path) as? XMLNode {
element.addAttribute(attribute)
}

violations.forEach { element.addChild($0.toXMLElement()) }

return element
}

fileprivate func filePathsFromViolations(_ violations: [Violation]) -> [String] {
return violations.map({ $0.path }).unique
}

extension XMLElement {
func addChildren(_ children: [XMLElement]) {
children.forEach(addChild)
}
}
11 changes: 7 additions & 4 deletions TaylorFramework/Resources/Help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ options:
Path to recursive analyse (default current path).
Must be maximum one path option

-r, --reporter type:file_name
Saves report in file with given "file_name" and "type" at analyze path.
Each "type" must be indicated no more than one time.
Supported types : json, pmd, plain, xcode(without path).
-r, --reporter type:output_path
Types of reports to be created after analyzing.
Currently we support "json", "pmd", "plain" and "xcode" (without "output_path") types or reports.
Each "type" must be indicated only once.
"output_path" can be a relative path to analyze path, see -p/--path option,
or an absolute path, and should also contain reporter name.
E.g: "-r json:report.json" or "-r pmd:~/Desktop/Taylor/report.pmd"

-rc, --ruleCustomization rule=value
Rules for code analisys.
Expand Down
24 changes: 23 additions & 1 deletion TaylorFrameworkTests/TemperTests/NSFileManagerTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,16 @@ import Nimble
import Quick
@testable import TaylorFramework

fileprivate final class MockFileManagerNotRemovable: FileManager {
enum FileManagerError: Error {
case notRemovable
}

override func removeItem(atPath path: String) throws {
throw FileManagerError.notRemovable
}
}

class NSFileManagerTests: QuickSpec {
override func spec() {
describe("NSFileManager") {
Expand All @@ -26,7 +36,19 @@ class NSFileManagerTests: QuickSpec {
manager.removeFileAtPath(filePath)
expect(manager.fileExists(atPath: filePath)).to(beFalse())
}
it("should't remove directory at path") {

it("shouldn't remove file at path if it's not removable") {
let manager = MockFileManagerNotRemovable()
let path = manager.currentDirectoryPath as NSString
let filePath = path.appendingPathComponent("file.txt") as String
manager.createFile(atPath: filePath, contents: nil, attributes: nil)
expect(manager.fileExists(atPath: filePath)).to(beTrue())
manager.removeFileAtPath(filePath)
expect(manager.fileExists(atPath: filePath)).to(beTrue())
FileManager.default.removeFileAtPath(filePath)
}

it("shouldn't remove directory at path") {
let manager = FileManager.default
expect(manager.fileExists(atPath: manager.currentDirectoryPath)).to(beTrue())
if manager.fileExists(atPath: (manager.currentDirectoryPath as NSString).appendingPathComponent("blablabla")) {
Expand Down
Loading