<commit_before>// Copyright 2016 The Tulsi Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Cocoa
import TulsiGenerator


/// Provides functionality to generate an Xcode project from a TulsiGeneratorConfig
class HeadlessXcodeProjectGenerator {
  enum Error: ErrorType {
    /// The given required commandline option was not provided.
    case MissingConfigOption(String)
    /// The config file path was invalid for the given reason.
    case InvalidConfigPath(String)
    /// The config file contents were invalid for the given reason.
    case InvalidConfigFileContents(String)
    /// The project file contents were invalid for the given reason.
    case InvalidProjectFileContents(String)
    /// The given configuration file requires that an explicit output path be given.
    case ExplicitOutputOptionRequired
    /// XCode project generation failed for the given reason.
    case GenerationFailed(String)
    /// The path to the Bazel binary given on the commandline is invalid.
    case InvalidBazelPath
    /// A workspace root override was given but references an invalid param.
    case InvalidWorkspaceRootOverride
  }

  let arguments: TulsiCommandlineParser.Arguments

  init(arguments: TulsiCommandlineParser.Arguments) {
    self.arguments = arguments
  }

  /// Performs project generation.
  func generate() throws {
    TulsiProjectDocument.showAlertsOnErrors = false
    TulsiProjectDocument.suppressWORKSPACECheck = arguments.suppressWORKSPACECheck

    let explicitBazelURL: NSURL?
    if let bazelPath = arguments.bazel {
      if !NSFileManager.defaultManager().isExecutableFileAtPath(bazelPath) {
        throw Error.InvalidBazelPath
      }
      explicitBazelURL = NSURL(fileURLWithPath: bazelPath)
      TulsiProjectDocument.suppressRuleEntryUpdateOnLoad = true
    } else {
      explicitBazelURL = nil
    }

    defer {
      TulsiProjectDocument.showAlertsOnErrors = true
      TulsiProjectDocument.suppressWORKSPACECheck = false
      TulsiProjectDocument.suppressRuleEntryUpdateOnLoad = false
    }

    guard let configPath = arguments.generatorConfig else {
      throw Error.MissingConfigOption(TulsiCommandlineParser.ParamGeneratorConfigLong)
    }

    let (projectURL, configURL, defaultOutputFolderURL) = try resolveConfigPath(configPath)

    let documentController = NSDocumentController.sharedDocumentController()
    let doc: NSDocument
    do {
      doc = try documentController.makeDocumentWithContentsOfURL(projectURL,
                                                                 ofType: "com.google.tulsi.project")
    } catch TulsiProjectDocument.Error.InvalidWorkspace(let info) {
      throw Error.InvalidProjectFileContents("Failed to load project due to invalid workspace: \(info)")
    } catch let e as NSError {
      throw Error.InvalidProjectFileContents("Failed to load project due to unexpected exception: \(e)")
    } catch {
      throw Error.InvalidProjectFileContents("Failed to load project due to unexpected exception.")
    }
    guard let projectDocument = doc as? TulsiProjectDocument else {
      throw Error.InvalidProjectFileContents("\(doc) is not of the expected type.")
    }

    let outputFolderURL: NSURL
    if let option = arguments.outputFolder {
      outputFolderURL = NSURL(fileURLWithPath: option, isDirectory: true)
    } else if let defaultOutputFolderURL = defaultOutputFolderURL {
      outputFolderURL = defaultOutputFolderURL
    } else {
      throw Error.ExplicitOutputOptionRequired
    }

    var config = try loadConfig(configURL, bazelURL: explicitBazelURL)
    config = config.configByAppendingPathFilters(arguments.additionalPathFilters)

    if let projectOptionSet = projectDocument.optionSet {
      config = config.configByResolvingInheritedOptions(projectOptionSet)
    }

    let workspaceRootURL: NSURL
    let projectWorkspaceRootURL = projectDocument.workspaceRootURL
    if let workspaceRootOverride = arguments.workspaceRootOverride {
      workspaceRootURL = NSURL(fileURLWithPath: workspaceRootOverride, isDirectory: true)
      if !isExistingDirectory(workspaceRootURL) {
        throw Error.InvalidWorkspaceRootOverride
      }
      if projectWorkspaceRootURL != nil {
        print("Overriding project workspace root (\(projectWorkspaceRootURL!.path!)) with " +
            "command-line parameter (\(workspaceRootOverride))")
      }
    } else {
      guard let projectWorkspaceRootURL = projectWorkspaceRootURL else {
        throw Error.InvalidProjectFileContents("Invalid workspaceRoot")
      }
      workspaceRootURL = projectWorkspaceRootURL
    }

    print("Generating project into '\(outputFolderURL.path!)' using:\n" +
              "\tconfig at '\(configURL.path!)'\n" +
              "\tBazel workspace at '\(workspaceRootURL.path!)'\n" +
              "\tBazel at '\(config.bazelURL.path!)'.\n" +
              "This may take a while.")

    let result = TulsiGeneratorConfigDocument.generateXcodeProjectInFolder(outputFolderURL,
                                                                           withGeneratorConfig: config,
                                                                           workspaceRootURL: workspaceRootURL,
                                                                           messageLog: nil)
    switch result {
      case .Success(let url):
        print("Generated project at \"\(url.path!)\"")
        if arguments.openXcodeOnSuccess {
          print("Opening generated project in Xcode")
          NSWorkspace.sharedWorkspace().openURL(url)
        }
      case .Failure(let errorInfo):
        throw Error.GenerationFailed(errorInfo)
    }
  }

  // MARK: - Private methods

  private func resolveConfigPath(path: String) throws -> (projectURL: NSURL,
                                                          configURL: NSURL,
                                                          defaultOutputFolderURL: NSURL?) {
    let tulsiProjExtension = TulsiProjectDocument.getTulsiBundleExtension()
    let components = path.componentsSeparatedByString(":")
    if components.count == 2 {
      var pathString = components[0] as NSString
      let projectExtension = pathString.pathExtension
      if projectExtension != tulsiProjExtension {<commit_msg>Remove quotes around the generated project path.<commit_after>// Copyright 2016 The Tulsi Authors. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//      http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

import Cocoa
import TulsiGenerator


/// Provides functionality to generate an Xcode project from a TulsiGeneratorConfig
class HeadlessXcodeProjectGenerator {
  enum Error: ErrorType {
    /// The given required commandline option was not provided.
    case MissingConfigOption(String)
    /// The config file path was invalid for the given reason.
    case InvalidConfigPath(String)
    /// The config file contents were invalid for the given reason.
    case InvalidConfigFileContents(String)
    /// The project file contents were invalid for the given reason.
    case InvalidProjectFileContents(String)
    /// The given configuration file requires that an explicit output path be given.
    case ExplicitOutputOptionRequired
    /// XCode project generation failed for the given reason.
    case GenerationFailed(String)
    /// The path to the Bazel binary given on the commandline is invalid.
    case InvalidBazelPath
    /// A workspace root override was given but references an invalid param.
    case InvalidWorkspaceRootOverride
  }

  let arguments: TulsiCommandlineParser.Arguments

  init(arguments: TulsiCommandlineParser.Arguments) {
    self.arguments = arguments
  }

  /// Performs project generation.
  func generate() throws {
    TulsiProjectDocument.showAlertsOnErrors = false
    TulsiProjectDocument.suppressWORKSPACECheck = arguments.suppressWORKSPACECheck

    let explicitBazelURL: NSURL?
    if let bazelPath = arguments.bazel {
      if !NSFileManager.defaultManager().isExecutableFileAtPath(bazelPath) {
        throw Error.InvalidBazelPath
      }
      explicitBazelURL = NSURL(fileURLWithPath: bazelPath)
      TulsiProjectDocument.suppressRuleEntryUpdateOnLoad = true
    } else {
      explicitBazelURL = nil
    }

    defer {
      TulsiProjectDocument.showAlertsOnErrors = true
      TulsiProjectDocument.suppressWORKSPACECheck = false
      TulsiProjectDocument.suppressRuleEntryUpdateOnLoad = false
    }

    guard let configPath = arguments.generatorConfig else {
      throw Error.MissingConfigOption(TulsiCommandlineParser.ParamGeneratorConfigLong)
    }

    let (projectURL, configURL, defaultOutputFolderURL) = try resolveConfigPath(configPath)

    let documentController = NSDocumentController.sharedDocumentController()
    let doc: NSDocument
    do {
      doc = try documentController.makeDocumentWithContentsOfURL(projectURL,
                                                                 ofType: "com.google.tulsi.project")
    } catch TulsiProjectDocument.Error.InvalidWorkspace(let info) {
      throw Error.InvalidProjectFileContents("Failed to load project due to invalid workspace: \(info)")
    } catch let e as NSError {
      throw Error.InvalidProjectFileContents("Failed to load project due to unexpected exception: \(e)")
    } catch {
      throw Error.InvalidProjectFileContents("Failed to load project due to unexpected exception.")
    }
    guard let projectDocument = doc as? TulsiProjectDocument else {
      throw Error.InvalidProjectFileContents("\(doc) is not of the expected type.")
    }

    let outputFolderURL: NSURL
    if let option = arguments.outputFolder {
      outputFolderURL = NSURL(fileURLWithPath: option, isDirectory: true)
    } else if let defaultOutputFolderURL = defaultOutputFolderURL {
      outputFolderURL = defaultOutputFolderURL
    } else {
      throw Error.ExplicitOutputOptionRequired
    }

    var config = try loadConfig(configURL, bazelURL: explicitBazelURL)
    config = config.configByAppendingPathFilters(arguments.additionalPathFilters)

    if let projectOptionSet = projectDocument.optionSet {
      config = config.configByResolvingInheritedOptions(projectOptionSet)
    }

    let workspaceRootURL: NSURL
    let projectWorkspaceRootURL = projectDocument.workspaceRootURL
    if let workspaceRootOverride = arguments.workspaceRootOverride {
      workspaceRootURL = NSURL(fileURLWithPath: workspaceRootOverride, isDirectory: true)
      if !isExistingDirectory(workspaceRootURL) {
        throw Error.InvalidWorkspaceRootOverride
      }
      if projectWorkspaceRootURL != nil {
        print("Overriding project workspace root (\(projectWorkspaceRootURL!.path!)) with " +
            "command-line parameter (\(workspaceRootOverride))")
      }
    } else {
      guard let projectWorkspaceRootURL = projectWorkspaceRootURL else {
        throw Error.InvalidProjectFileContents("Invalid workspaceRoot")
      }
      workspaceRootURL = projectWorkspaceRootURL
    }

    print("Generating project into '\(outputFolderURL.path!)' using:\n" +
              "\tconfig at '\(configURL.path!)'\n" +
              "\tBazel workspace at '\(workspaceRootURL.path!)'\n" +
              "\tBazel at '\(config.bazelURL.path!)'.\n" +
              "This may take a while.")

    let result = TulsiGeneratorConfigDocument.generateXcodeProjectInFolder(outputFolderURL,
                                                                           withGeneratorConfig: config,
                                                                           workspaceRootURL: workspaceRootURL,
                                                                           messageLog: nil)
    switch result {
      case .Success(let url):
        print("Generated project at \(url.path!)")
        if arguments.openXcodeOnSuccess {
          print("Opening generated project in Xcode")
          NSWorkspace.sharedWorkspace().openURL(url)
        }
      case .Failure(let errorInfo):
        throw Error.GenerationFailed(errorInfo)
    }
  }

  // MARK: - Private methods

  private func resolveConfigPath(path: String) throws -> (projectURL: NSURL,
                                                          configURL: NSURL,
                                                          defaultOutputFolderURL: NSURL?) {
    let tulsiProjExtension = TulsiProjectDocument.getTulsiBundleExtension()
    let components = path.componentsSeparatedByString(":")
    if components.count == 2 {
      var pathString = components[0] as NSString
      let projectExtension = pathString.pathExtension
      if projectExtension != tulsiProjExtension {
{'fraction_non_alphanumeric': 0.06977428130284538, 'fraction_numerical': 0.0014704801117564885, 'mean_word_length': 2.797319932998325, 'pattern_counts': {'":': 2, '<': 3, '<?xml version=': 0, '>': 5, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 30, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5822168', 'n_tokens_mistral': 3335, 'n_tokens_neox': 3315, 'n_words': 980}