blob: a799b512d5ed516d31c70c96f691a53671a16caa [file] [log] [blame]
/*
* Copyright 2015-2016 IBM Corporation
*
* 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 Foundation
public struct ActionToken {
var actionName: String
var actionCode: String
}
public struct TriggerToken {
var triggerName: String
}
public struct RuleToken {
var ruleName: String
var triggerName: String
var actionName: String
}
public struct SequenceToken {
var sequenceName: String
var actionNames: [String]
}
enum TokenState {
case inStarComment
case inSlashComment
case initial
case inClass
case inClassName
case inClassQualifier
case skippingBlock
case parseAction
}
open class WhiskTokenizer {
let OpenWhiskActionTarget = "OpenWhiskActions"
var atPath: String!
var toPath: String!
var projectFileName: NSString!
public init(from: String, to: String, projectFile: NSString) {
atPath = from
toPath = to
self.projectFileName = projectFile
}
func readTargetSourceFiles() -> [String] {
let pbxProject = PBXProject(file: projectFileName as String, targetName: OpenWhiskActionTarget)
var filesForTarget = pbxProject.filesForTarget
for (target, files) in filesForTarget {
print("target:\(target), files:\(files)")
}
return filesForTarget[OpenWhiskActionTarget]!
}
open func readXCodeProjectDirectory() throws -> (actions: [Action],triggers: [Trigger], rules: [Rule], sequences: [Sequence]) {
let dir: FileManager = FileManager.default
var whiskActionArray = [Action]()
var whiskTriggerArray = [Trigger]()
var whiskRuleArray = [Rule]()
var whiskSequenceArray = [Sequence]()
let fileList = readTargetSourceFiles()
if let enumerator: FileManager.DirectoryEnumerator = dir.enumerator(atPath: atPath) {
while let item = enumerator.nextObject() as? NSString {
var isDir = ObjCBool(false)
let fullPath = atPath+"/\(item)"
//print("===== inspecting \(fullPath)")
if dir.fileExists(atPath: fullPath, isDirectory: &isDir) == true {
if isDir.boolValue == true {
} else if item.hasSuffix(".swift") {
if fileList.contains(item.lastPathComponent as String) {
do {
let fileStr = try String(contentsOfFile: fullPath)
if let entityTuple = getWhiskEntities(str: fileStr) {
for action in entityTuple.0 {
do {
let actionDirPath = toPath+"/\(OpenWhiskActionTarget)"
try FileManager.default.createDirectory(atPath: actionDirPath, withIntermediateDirectories: true, attributes: nil)
let actionPath = actionDirPath+"/\(action.actionName).swift"
let fileUrl = URL(fileURLWithPath: actionPath)
try action.actionCode.write(to: fileUrl, atomically: false, encoding: String.Encoding.utf8)
let whiskAction = Action(name: action.actionName as NSString, path: actionPath as NSString, runtime: Runtime.swift, parameters: nil)
whiskActionArray.append(whiskAction)
} catch {
print("Error writing actions from Xcode \(error)")
}
}
for trigger in entityTuple.1 {
let whiskTrigger = Trigger(name: trigger.triggerName as NSString, feed: nil, parameters: nil)
whiskTriggerArray.append(whiskTrigger)
}
}
} catch {
print("Error \(error)")
}
}
}
}
}
}
return (whiskActionArray, whiskTriggerArray, whiskRuleArray, whiskSequenceArray)
}
func getWhiskEntities(str: String) -> ([ActionToken], [TriggerToken], [RuleToken], [SequenceToken])? {
let scanner = Scanner(string: str)
var line: NSString?
var state = TokenState.initial
var actionArray = [ActionToken]()
var triggerArray = [TriggerToken]()
var ruleArray = [RuleToken]()
var sequenceArray = [SequenceToken]()
var actionName = ""
var actionCode = ""
var leftBracketCount = 0
var rightBracketCount = 0
while scanner.scanUpToCharacters(from: CharacterSet.newlines, into: &line) {
// print("Scan location is \(scanner.scanLocation)")
guard let line = line else {
print("Xcode To Whisk: Error, line from tokenizer is nil, aborting.")
return nil
}
var trimmedLine = line.trimmingCharacters(in: CharacterSet.whitespaces)
if trimmedLine.hasPrefix("//") {
//print("Skipping comment")
} else if trimmedLine.hasPrefix("/*") {
state = TokenState.inStarComment
} else {
switch state {
case .initial:
if trimmedLine.range(of: "class") != nil && trimmedLine.range(of: "WhiskTrigger") != nil && trimmedLine.range(of: ":") != nil {
let classStr = trimmedLine.components(separatedBy: ":")
// get actionName
let classIndex = classStr[0].characters.index(classStr[0].startIndex, offsetBy: 6)
let triggerName = classStr[0].substring(from: classIndex).trimmingCharacters(in: CharacterSet.whitespaces)
print("Trigger name is \(triggerName)")
let triggerToken = TriggerToken(triggerName: triggerName)
triggerArray.append(triggerToken)
} else if trimmedLine.range(of: "WhiskRule") != nil {
} else if trimmedLine.range(of: "WhiskSequence") != nil {
} else if trimmedLine.range(of: "OpenWhiskAction") == nil && trimmedLine.range(of: "class") != nil && trimmedLine.range(of: ":") != nil && trimmedLine.range(of: "WhiskAction") != nil {
let classStr = trimmedLine.components(separatedBy: ":")
// get actionName
let classIndex = classStr[0].characters.index(classStr[0].startIndex, offsetBy: 6)
actionName = classStr[0].substring(from: classIndex).trimmingCharacters(in: CharacterSet.whitespaces)
state = TokenState.parseAction
var tok = trimmedLine.components(separatedBy: "{")
leftBracketCount = tok.count - 1
tok = trimmedLine.components(separatedBy: "}")
rightBracketCount = tok.count - 1
}
case .parseAction:
var lookingForLeftBracket = false
if leftBracketCount == 0 {
lookingForLeftBracket = true
}
var tok = trimmedLine.components(separatedBy: "{")
leftBracketCount = leftBracketCount + (tok.count - 1)
tok = trimmedLine.components(separatedBy: "}")
rightBracketCount = rightBracketCount + (tok.count - 1)
if leftBracketCount == rightBracketCount {
// drop extra bracket
let lastLine = String(trimmedLine.characters.dropLast())
if lookingForLeftBracket == false {
actionCode = actionCode + "\n" + lastLine
}
let newAction = ActionToken(actionName: actionName, actionCode: actionCode)
actionArray.append(newAction)
state = TokenState.initial
actionName = ""
actionCode = ""
leftBracketCount = 0
rightBracketCount = 0
state = TokenState.initial
} else {
let range = trimmedLine.range(of: "func run(")
if range != nil {
trimmedLine.replaceSubrange(range!, with: "func main(")
}
if lookingForLeftBracket == false {
actionCode = actionCode + "\n"+trimmedLine
}
}
case .inStarComment:
if trimmedLine.hasSuffix("*/") {
state = TokenState.initial
}
default:
//print("Don't care")
break
}
}
}
if state == .parseAction {
let code = String(actionCode.characters.dropLast())
let newAction = ActionToken(actionName: actionName, actionCode: code)
actionArray.append(newAction)
}
return (actionArray, triggerArray, ruleArray, sequenceArray)
}
}