| /* |
| * 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 { |
| |
| |
| var atPath: String! |
| var toPath: String! |
| var projectFileName: NSString! |
| var targetName: String! |
| |
| public init(from: String, to: String, projectFile: NSString, target: String? ) { |
| atPath = from |
| toPath = to |
| if target != nil { |
| targetName = target! |
| } else { |
| targetName = "OpenWhiskActions" |
| } |
| self.projectFileName = projectFile |
| } |
| |
| func readTargetSourceFiles() -> [String]? { |
| let pbxProject = PBXProject(file: projectFileName as String, targetName: targetName) |
| |
| var filesForTarget = pbxProject.filesForTarget |
| for (target, files) in filesForTarget { |
| print("target:\(target), files:\(files)") |
| } |
| |
| return filesForTarget[targetName] |
| } |
| |
| 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]() |
| |
| if let fileList = readTargetSourceFiles() { |
| |
| print("There are \(fileList.count) files for target \(targetName)") |
| |
| 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 \(item.lastPathComponent)") |
| if dir.fileExists(atPath: fullPath, isDirectory: &isDir) == true { |
| if isDir.boolValue == true { |
| |
| } else if item.hasSuffix(".swift") { |
| |
| if fileList.contains(item.lastPathComponent as String) { |
| print("****Processing \(item.lastPathComponent)") |
| do { |
| let fileStr = try String(contentsOfFile: fullPath) |
| if let entityTuple = getWhiskEntities(str: fileStr) { |
| |
| for action in entityTuple.actions { |
| do { |
| |
| let actionDirPath = toPath+"/\(targetName!)" |
| |
| 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.triggers { |
| let whiskTrigger = Trigger(name: trigger.triggerName as NSString, feed: nil, parameters: nil) |
| whiskTriggerArray.append(whiskTrigger) |
| } |
| |
| for rule in entityTuple.rules { |
| let rule = Rule(name: rule.ruleName as NSString, trigger: rule.triggerName as NSString, action: rule.actionName as NSString) |
| whiskRuleArray.append(rule) |
| } |
| |
| for sequence in entityTuple.sequences { |
| let seq = Sequence(name: sequence.sequenceName as NSString, actions: sequence.actionNames) |
| whiskSequenceArray.append(seq) |
| } |
| |
| } |
| |
| } catch { |
| print("Error \(error)") |
| } |
| } |
| } |
| } |
| |
| } |
| } |
| |
| } else { |
| print("No files for given target \(targetName!)") |
| } |
| |
| |
| return (whiskActionArray, whiskTriggerArray, whiskRuleArray, whiskSequenceArray) |
| } |
| |
| func getWhiskEntities(str: String) -> (actions: [ActionToken], triggers: [TriggerToken], rules: [RuleToken], sequences: [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: "let") != nil && trimmedLine.range(of: "WhiskTrigger()") != nil { |
| |
| let classStr = trimmedLine.components(separatedBy: "=") |
| // get actionName |
| let letStr = classStr[0].components(separatedBy: .whitespaces) |
| let triggerName = letStr[1] |
| let triggerToken = TriggerToken(triggerName: triggerName) |
| triggerArray.append(triggerToken) |
| |
| } else if trimmedLine.range(of: "let") != nil && trimmedLine.range(of: "WhiskRule(") != nil && trimmedLine.range(of: "trigger:") != nil && trimmedLine.range(of: "action:") != nil{ |
| var classStr = trimmedLine.components(separatedBy: "=") |
| let letStr = classStr[0].components(separatedBy: .whitespaces) |
| let ruleName = letStr[1].trimmingCharacters(in: .whitespacesAndNewlines) |
| |
| let paramStr = classStr[1].replacingOccurrences(of: "WhiskRule", with: "").components(separatedBy: ",") |
| let triggerStr = paramStr[0].components(separatedBy: ":") |
| let triggerName = triggerStr[1].trimmingCharacters(in: .whitespacesAndNewlines) |
| var actionStr = paramStr[1].components(separatedBy: ":") |
| let actionName = actionStr[1].replacingOccurrences(of: "(", with: "").replacingOccurrences(of: ")", with: "").trimmingCharacters(in: .whitespaces) |
| |
| |
| |
| print("Got trigger:\(triggerName), action:\(actionName)") |
| |
| let rule = RuleToken(ruleName: ruleName, triggerName: triggerName, actionName: actionName) |
| ruleArray.append(rule) |
| |
| } else if trimmedLine.range(of: "let") != nil && trimmedLine.range(of: "WhiskSequence(") != nil { |
| var classStr = trimmedLine.components(separatedBy: "=") |
| let letStr = classStr[0].components(separatedBy: .whitespaces) |
| let sequenceName = letStr[1] |
| |
| let paramStr = classStr[1].replacingOccurrences(of: "WhiskSequence", with: "").components(separatedBy: ":") |
| |
| let actionNames = paramStr[1].replacingOccurrences(of: "[", with: "").replacingOccurrences(of: "]", with: "").replacingOccurrences(of: "(", with: "").replacingOccurrences(of: ")", with: "").replacingOccurrences(of: " ", with: "") |
| |
| print("Got sequence \(actionNames)") |
| let names = actionNames.components(separatedBy: ",") |
| var sequenceActions = [String]() |
| for name in names { |
| sequenceActions.append(name) |
| } |
| let sequence = SequenceToken(sequenceName: sequenceName, actionNames: sequenceActions) |
| sequenceArray.append(sequence) |
| |
| } 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 (actions: actionArray, triggers: triggerArray, rules: ruleArray, sequences: sequenceArray) |
| } |
| |
| } |
| |