<commit_before>//
//  Browser.swift
//  SwiftScraper
//
//  Created by <NAME> on 21/04/2017.
//  Copyright © 2017 <NAME>. All rights reserved.
//

import Foundation
import WebKit

// MARK: - Types

/// The result of the browser navigation.
typealias NavigationResult = Result<Void, SwiftScraperError>

/// Invoked when the page navigation has completed or failed.
typealias NavigationCallback = (_ result: NavigationResult) -> Void

/// The result of some JavaScript execution.
///
/// If successful, it contains the response from the JavaScript;
/// If it failed, it contains the error.
typealias ScriptResponseResult = Result<Any?, SwiftScraperError>

/// Invoked when the asynchronous call to some JavaScript is completed, containing the response or error.
typealias ScriptResponseResultCallback = (_ result: ScriptResponseResult) -> Void

// MARK: - Browser

/// The browser used to perform the web scraping.
///
/// This class encapsulates the webview and its delegates, providing an closure based API.
public class Browser: NSObject, WKNavigationDelegate, WKScriptMessageHandler {

    // MARK: - Constants

    private enum Constants {
        static let coreScript = "SwiftScraper"
        static let messageHandlerName = "swiftScraperResponseHandler"
    }

    // MARK: - Properties

    private let moduleName: String
    private (set) public var webView: WKWebView!
    private let userContentController = WKUserContentController()
    private var navigationCallback: NavigationCallback?
    private var asyncScriptCallback: ScriptResponseResultCallback?

    // MARK: - Setup

    /// Initialize the Browser object.
    ///
    /// - parameter moduleName: The name of the JavaScript module. By convention, the filename of the JavaScript file is the same as the module name.
    /// - parameter scriptBundle: The bundle from which to load the JavaScript file. Defaults to the main bundle.
    /// - parameter customUserAgent: The custom user agent string (only works for iOS 9+).
    init(moduleName: String, scriptBundle: Bundle = Bundle.main, customUserAgent: String? = nil) {
        self.moduleName = moduleName
        super.init()
        setupWebView(moduleName: moduleName, customUserAgent: customUserAgent, scriptBundle: scriptBundle)
    }

    private func setupWebView(moduleName: String, customUserAgent: String?, scriptBundle: Bundle) {

        let coreScriptURL = moduleResourceBundle().path(forResource: Constants.coreScript, ofType: "js")
        let coreScriptContent = try! String(contentsOfFile: coreScriptURL!)
        let coreScript = WKUserScript(source: coreScriptContent, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        userContentController.addUserScript(coreScript)

        let moduleScriptURL = scriptBundle.path(forResource: moduleName, ofType: "js")
        let moduleScriptContent = try! String(contentsOfFile: moduleScriptURL!)  // TODO: prevent force try, propagate error
        let moduleScript = WKUserScript(source: moduleScriptContent, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        userContentController.addUserScript(moduleScript)

        userContentController.add(self, name: Constants.messageHandlerName)

        let config = WKWebViewConfiguration()
        config.userContentController = userContentController

        webView = WKWebView(frame: CGRect.zero, configuration: config)
        webView.navigationDelegate = self
        webView.allowsBackForwardNavigationGestures = true
        if #available(iOS 9.0, *) {
            webView.customUserAgent = customUserAgent
        }
    }

    /// Returns the resource bundle for this Pod where all the resources are kept, 
    /// or defaulting to the framework module bundle (e.g. when running unit tests).
    private func moduleResourceBundle() -> Bundle {
        let moduleBundle = Bundle(for: Browser.self)
        guard let resourcesBundleURL = moduleBundle.url(forResource: "SwiftScraper", withExtension: ".bundle"),
            let resourcesBundle = Bundle(url: resourcesBundleURL) else {
            return moduleBundle
        }
        return resourcesBundle
    }

    // MARK: - WKNavigationDelegate

    public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        print("didFinishNavigation was called")
        callNavigationCompletion(result: .success())
    }

    public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        print("didFailProvisionalNavigation")
        let navigationError = SwiftScraperError.navigationFailed(error: error)
        callNavigationCompletion(result: .failure(navigationError))
    }

    public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        print("didFailNavigation was called")
        let nsError = error as NSError
        if nsError.domain == "NSURLErrorDomain" && nsError.code == NSURLErrorCancelled {
            return
        }
        let navigationError = SwiftScraperError.navigationFailed(error: error)
        callNavigationCompletion(result: .failure(navigationError))<commit_msg>Add void parameter to make Swift 4 happy<commit_after>//
//  Browser.swift
//  SwiftScraper
//
//  Created by <NAME> on 21/04/2017.
//  Copyright © 2017 <NAME>. All rights reserved.
//

import Foundation
import WebKit

// MARK: - Types

/// The result of the browser navigation.
typealias NavigationResult = Result<Void, SwiftScraperError>

/// Invoked when the page navigation has completed or failed.
typealias NavigationCallback = (_ result: NavigationResult) -> Void

/// The result of some JavaScript execution.
///
/// If successful, it contains the response from the JavaScript;
/// If it failed, it contains the error.
typealias ScriptResponseResult = Result<Any?, SwiftScraperError>

/// Invoked when the asynchronous call to some JavaScript is completed, containing the response or error.
typealias ScriptResponseResultCallback = (_ result: ScriptResponseResult) -> Void

// MARK: - Browser

/// The browser used to perform the web scraping.
///
/// This class encapsulates the webview and its delegates, providing an closure based API.
public class Browser: NSObject, WKNavigationDelegate, WKScriptMessageHandler {

    // MARK: - Constants

    private enum Constants {
        static let coreScript = "SwiftScraper"
        static let messageHandlerName = "swiftScraperResponseHandler"
    }

    // MARK: - Properties

    private let moduleName: String
    private (set) public var webView: WKWebView!
    private let userContentController = WKUserContentController()
    private var navigationCallback: NavigationCallback?
    private var asyncScriptCallback: ScriptResponseResultCallback?

    // MARK: - Setup

    /// Initialize the Browser object.
    ///
    /// - parameter moduleName: The name of the JavaScript module. By convention, the filename of the JavaScript file is the same as the module name.
    /// - parameter scriptBundle: The bundle from which to load the JavaScript file. Defaults to the main bundle.
    /// - parameter customUserAgent: The custom user agent string (only works for iOS 9+).
    init(moduleName: String, scriptBundle: Bundle = Bundle.main, customUserAgent: String? = nil) {
        self.moduleName = moduleName
        super.init()
        setupWebView(moduleName: moduleName, customUserAgent: customUserAgent, scriptBundle: scriptBundle)
    }

    private func setupWebView(moduleName: String, customUserAgent: String?, scriptBundle: Bundle) {

        let coreScriptURL = moduleResourceBundle().path(forResource: Constants.coreScript, ofType: "js")
        let coreScriptContent = try! String(contentsOfFile: coreScriptURL!)
        let coreScript = WKUserScript(source: coreScriptContent, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        userContentController.addUserScript(coreScript)

        let moduleScriptURL = scriptBundle.path(forResource: moduleName, ofType: "js")
        let moduleScriptContent = try! String(contentsOfFile: moduleScriptURL!)  // TODO: prevent force try, propagate error
        let moduleScript = WKUserScript(source: moduleScriptContent, injectionTime: .atDocumentEnd, forMainFrameOnly: true)
        userContentController.addUserScript(moduleScript)

        userContentController.add(self, name: Constants.messageHandlerName)

        let config = WKWebViewConfiguration()
        config.userContentController = userContentController

        webView = WKWebView(frame: CGRect.zero, configuration: config)
        webView.navigationDelegate = self
        webView.allowsBackForwardNavigationGestures = true
        if #available(iOS 9.0, *) {
            webView.customUserAgent = customUserAgent
        }
    }

    /// Returns the resource bundle for this Pod where all the resources are kept, 
    /// or defaulting to the framework module bundle (e.g. when running unit tests).
    private func moduleResourceBundle() -> Bundle {
        let moduleBundle = Bundle(for: Browser.self)
        guard let resourcesBundleURL = moduleBundle.url(forResource: "SwiftScraper", withExtension: ".bundle"),
            let resourcesBundle = Bundle(url: resourcesBundleURL) else {
            return moduleBundle
        }
        return resourcesBundle
    }

    // MARK: - WKNavigationDelegate

    public func webView(_ webView: WKWebView, didFinish navigation: WKNavigation!) {
        print("didFinishNavigation was called")
        callNavigationCompletion(result: .success(()))
    }

    public func webView(_ webView: WKWebView, didFailProvisionalNavigation navigation: WKNavigation!, withError error: Error) {
        print("didFailProvisionalNavigation")
        let navigationError = SwiftScraperError.navigationFailed(error: error)
        callNavigationCompletion(result: .failure(navigationError))
    }

    public func webView(_ webView: WKWebView, didFail navigation: WKNavigation!, withError error: Error) {
        print("didFailNavigation was called")
        let nsError = error as NSError
        if nsError.domain == "NSURLErrorDomain" && nsError.code == NSURLErrorCancelled {
            return
        }
        let navigationError = SwiftScraperError.navigationFailed(error: error)
        callNavigationCompletion(result: .failure(navigationError))
{'fraction_non_alphanumeric': 0.0815812421277008, 'fraction_numerical': 0.0030035849239414786, 'mean_word_length': 3.515310586176728, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2090789', 'n_tokens_mistral': 2705, 'n_tokens_neox': 2546, 'n_words': 846}