<commit_before>/**
 *  Marathon
 *  Copyright (c) <NAME> 2017
 *  Licensed under the MIT license. See LICENSE file.
 */

import Foundation
import Files
import Require

// MARK: - Error

public enum ScriptManagerError {
    case scriptNotFound(String)
    case failedToCreatePackageFile(Folder)
    case failedToAddDependencyScript(String)
    case failedToRemoveScriptFolder(Folder)
    case failedToDownloadScript(URL)
}

extension ScriptManagerError: PrintableError {
    public var message: String {
        switch self {
        case .scriptNotFound(let path):
            return "Could not find a Swift script at '\(path)'"
        case .failedToCreatePackageFile(_):
            return "Failed to create a Package.swift file for the script"
        case .failedToAddDependencyScript(let path):
            return "Failed to add the dependency script at '\(path)'"
        case .failedToRemoveScriptFolder(_):
            return "Failed to remove script folder"
        case .failedToDownloadScript(let url):
            return "Failed to download script from '\(url.absoluteString)'"
        }
    }

    public var hints: [String] {
        switch self {
        case .scriptNotFound(_):
            return ["Please check that the path is valid and try again"]
        case .failedToCreatePackageFile(let folder),
             .failedToRemoveScriptFolder(let folder):
            return ["Make sure you have write permissions to the folder '\(folder.path)'"]
        case .failedToAddDependencyScript(_):
            return ["Make sure that the file exists and is readable"]
        case .failedToDownloadScript(_):
            return ["Make sure that the URL is reachable, and that it contains a valid Swift script"]
        }
    }
}

// MARK: - ScriptManager

internal final class ScriptManager {
    private typealias Error = ScriptManagerError

    var managedScriptPaths: [String] { return makeManagedScriptPathList() }

    private let cacheFolder: Folder
    private let temporaryFolder: Folder
    private lazy var temporaryScriptFiles = [File]()
    private let packageManager: PackageManager
    private let printer: Printer

    // MARK: - Lifecycle

    init(folder: Folder, packageManager: PackageManager, printer: Printer) throws {
        self.cacheFolder = try folder.createSubfolderIfNeeded(withName: "Cache")
        self.temporaryFolder = try folder.createSubfolderIfNeeded(withName: "Temp")
        self.packageManager = packageManager
        self.printer = printer
    }

    deinit {
        for file in temporaryScriptFiles {
            try? removeDataForScript(at: file.path)
            try? file.parent?.delete()
        }
    }

    // MARK: - API

    func script(at path: String) throws -> Script {
        let file = try perform(File(path: path), orThrow: Error.scriptNotFound(path))
        return try script(from: file)
    }

    func downloadScript(from url: URL) throws -> Script {
        do {
            let url = url.transformIfNeeded()

            printer.reportProgress("Downloading script...")
            let data = try Data(contentsOf: url)

            printer.reportProgress("Saving script...")
            let identifier = scriptIdentifier(from: url.absoluteString)
            let folder = try temporaryFolder.createSubfolderIfNeeded(withName: identifier)
            let fileName = scriptName(from: identifier) + ".swift"
            let file = try folder.createFile(named: fileName, contents: data)
            temporaryScriptFiles.append(file)

            printer.reportProgress("Resolving Marathonfile...")
            if let parentURL = url.parent {
                let marathonFileURL = URL(string: parentURL.absoluteString + "Marathonfile").require()

                if let marathonFileData = try? Data(contentsOf: marathonFileURL) {
                    printer.reportProgress("Saving Marathonfile...")
                    try folder.createFile(named: "Marathonfile", contents: marathonFileData)
                }
            }

            return try script(from: file)
        } catch {
            throw Error.failedToDownloadScript(url)
        }
    }

    func removeDataForScript(at path: String) throws {
        let identifier = scriptIdentifier(from: path)

        guard let folder = folderForScript(withIdentifier: identifier) else {
            return
        }

        try perform(folder.delete(), orThrow: Error.failedToRemoveScriptFolder(folder))
    }

    func removeAllScriptData() throws {
        for path in managedScriptPaths {
            try removeDataForScript(at: path)
        }
    }

    // MARK: - Private

    private func script(from file: File) throws -> Script {
        let identifier = scriptIdentifier(from: file.path)
        let name = scriptName(from: identifier)
        let folder = try createFolderIfNeededForScript(withIdentifier: identifier, file: file)<commit_msg>ScriptManager: Output underlying download error<commit_after>/**
 *  Marathon
 *  Copyright (c) <NAME> 2017
 *  Licensed under the MIT license. See LICENSE file.
 */

import Foundation
import Files
import Require

// MARK: - Error

public enum ScriptManagerError {
    case scriptNotFound(String)
    case failedToCreatePackageFile(Folder)
    case failedToAddDependencyScript(String)
    case failedToRemoveScriptFolder(Folder)
    case failedToDownloadScript(URL, Error)
}

extension ScriptManagerError: PrintableError {
    public var message: String {
        switch self {
        case .scriptNotFound(let path):
            return "Could not find a Swift script at '\(path)'"
        case .failedToCreatePackageFile(_):
            return "Failed to create a Package.swift file for the script"
        case .failedToAddDependencyScript(let path):
            return "Failed to add the dependency script at '\(path)'"
        case .failedToRemoveScriptFolder(_):
            return "Failed to remove script folder"
        case .failedToDownloadScript(let url, let error):
            return "Failed to download script from '\(url.absoluteString)' (\(error))"
        }
    }

    public var hints: [String] {
        switch self {
        case .scriptNotFound(_):
            return ["Please check that the path is valid and try again"]
        case .failedToCreatePackageFile(let folder),
             .failedToRemoveScriptFolder(let folder):
            return ["Make sure you have write permissions to the folder '\(folder.path)'"]
        case .failedToAddDependencyScript(_):
            return ["Make sure that the file exists and is readable"]
        case .failedToDownloadScript(_):
            return ["Make sure that the URL is reachable, and that it contains a valid Swift script"]
        }
    }
}

// MARK: - ScriptManager

internal final class ScriptManager {
    private typealias Error = ScriptManagerError

    var managedScriptPaths: [String] { return makeManagedScriptPathList() }

    private let cacheFolder: Folder
    private let temporaryFolder: Folder
    private lazy var temporaryScriptFiles = [File]()
    private let packageManager: PackageManager
    private let printer: Printer

    // MARK: - Lifecycle

    init(folder: Folder, packageManager: PackageManager, printer: Printer) throws {
        self.cacheFolder = try folder.createSubfolderIfNeeded(withName: "Cache")
        self.temporaryFolder = try folder.createSubfolderIfNeeded(withName: "Temp")
        self.packageManager = packageManager
        self.printer = printer
    }

    deinit {
        for file in temporaryScriptFiles {
            try? removeDataForScript(at: file.path)
            try? file.parent?.delete()
        }
    }

    // MARK: - API

    func script(at path: String) throws -> Script {
        let file = try perform(File(path: path), orThrow: Error.scriptNotFound(path))
        return try script(from: file)
    }

    func downloadScript(from url: URL) throws -> Script {
        do {
            let url = url.transformIfNeeded()

            printer.reportProgress("Downloading script...")
            let data = try Data(contentsOf: url)

            printer.reportProgress("Saving script...")
            let identifier = scriptIdentifier(from: url.absoluteString)
            let folder = try temporaryFolder.createSubfolderIfNeeded(withName: identifier)
            let fileName = scriptName(from: identifier) + ".swift"
            let file = try folder.createFile(named: fileName, contents: data)
            temporaryScriptFiles.append(file)

            printer.reportProgress("Resolving Marathonfile...")
            if let parentURL = url.parent {
                let marathonFileURL = URL(string: parentURL.absoluteString + "Marathonfile").require()

                if let marathonFileData = try? Data(contentsOf: marathonFileURL) {
                    printer.reportProgress("Saving Marathonfile...")
                    try folder.createFile(named: "Marathonfile", contents: marathonFileData)
                }
            }

            return try script(from: file)
        } catch {
            throw Error.failedToDownloadScript(url, error)
        }
    }

    func removeDataForScript(at path: String) throws {
        let identifier = scriptIdentifier(from: path)

        guard let folder = folderForScript(withIdentifier: identifier) else {
            return
        }

        try perform(folder.delete(), orThrow: Error.failedToRemoveScriptFolder(folder))
    }

    func removeAllScriptData() throws {
        for path in managedScriptPaths {
            try removeDataForScript(at: path)
        }
    }

    // MARK: - Private

    private func script(from file: File) throws -> Script {
        let identifier = scriptIdentifier(from: file.path)
        let name = scriptName(from: identifier)
        let folder = try createFolderIfNeededForScript(withIdentifier: identifier, file: file)
{'fraction_non_alphanumeric': 0.08372329603255341, 'fraction_numerical': 0.0008138351983723296, 'mean_word_length': 2.535059331175836, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 15, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1354613', 'n_tokens_mistral': 2502, 'n_tokens_neox': 2420, 'n_words': 758}