用 Swift 来写命令行程序
这是探索 Swift 写 Linux 程序的系列文章中的一篇。 在上一个例子中,我们通过组合使用
翻译程序会显示它接受什么语言(源语言)以及翻译的目标语言。比如:
翻译程序默认是 如果用户输入的字符串不是命令的话,翻译程序会把输入逐字地发送到翻译的 web 服务。然后把返回的结果打印出来。 需要注意的几点如果你是系统或者运维程序员,并且以前也没接触过 Swift 的话,下面是一些你在代码里需要注意的事情。我想你会发现 Swift 为两种类型的工程师都提供了很多有用的特性,并且会成为 Linux 开发体系中一股很受欢迎的新力量。
程序设计我们的翻译程序可以拆分成一个主程序、两个类以及一个 translator/Sources/main.swift /Sources/CommandInterpreter.swift /Sources/... /Package.swift
import Foundation import Glibc let interpreter = CommandInterpreter() let translator = Translator() // Listen for events to translate nc.addObserverForName(INPUT_NOTIFICATION,object:nil,queue:nil) { (_) in let tc = translationCommand translator.translate(tc.text,from:tc.from,to:tc.to){ translation,error in guard error == nil && translation != nil else { print("Translation failure: (error!.code)") return } print(translation!) } } interpreter.start() select(0,nil,nil) 上面的代码表示我们的程序不接受命令行参数。具体的流程说明:
CommandInterpreter 类
// Import statements import Foundation import Glibc // Enumerations enum CommandType { case None case Translate case SetFrom case SetTo case Quit } // Structs struct Command { var type:CommandType var data:String } // Classes class CommandInterpreter { // Read-only computed property var prompt:String { return "(translationCommand.from)->(translationCommand.to)" } // Class constant let delim:Character = "n" init() { } func start() { let readThread = NSThread(){ var input:String = "" print("To set input language,type 'from LANG'") print("To set output language,type 'to LANG'") print("Type 'quit' to exit") self.displayPrompt() while true { let c = Character(UnicodeScalar(UInt32(fgetc(stdin)))) if c == self.delim { let command = self.parseInput(input) self.doCommand(command) input = "" // Clear input self.displayPrompt() } else { input.append(c) } } } readThread.start() } func displayPrompt() { print("(self.prompt): ",terminator:"") } func parseInput(input:String) -> Command { var commandType:CommandType var commandData:String = "" // Splitting a string let tokens = input.characters.split{$0 == " "}.map(String.init) // guard statement to validate that there are tokens guard tokens.count > 0 else { return Command(type:CommandType.None,data:"") } switch tokens[0] { case "quit": commandType = .Quit case "from": commandType = .SetFrom commandData = tokens[1] case "to": commandType = .SetTo commandData = tokens[1] default: commandType = .Translate commandData = input } return Command(type:commandType,data:commandData) } func doCommand(command:Command) { switch command.type { case .Quit: exit(0) case .SetFrom: translationCommand.from = command.data case .SetTo: translationCommand.to = command.data case .Translate: translationCommand.text = command.data nc.postNotificationName(INPUT_NOTIFICATION,object:nil) case .None: break } } }
我们的 // Listen for events to translate nc.addObserverForName(INPUT_NOTIFICATION,error in guard error == nil && translation != nil else { print("Translation failure: (error!.code)") return } print(translation!) } } 我在这篇文章中提到,在对
Translator
import Glibc import Foundation import CcURL import CJSONC class Translator { let BUFSIZE = 1024 init() { } func translate(text:String,from:String,to:String,completion:(translation:String?,error:NSError?) -> Void) { let curl = curl_easy_init() guard curl != nil else { completion(translation:nil,error:NSError(domain:"translator",code:1,userInfo:nil)) return } let escapedText = curl_easy_escape(curl,text,Int32(strlen(text))) guard escapedText != nil else { completion(translation:nil,code:2,userInfo:nil)) return } let langPair = from + "%7c" + to let wgetCommand = "wget -qO- http://api.mymemory.translated.net/get?q=" + String.fromCString(escapedText)! + "&langpair=" + langPair let pp = popen(wgetCommand,"r") var buf = [CChar](count:BUFSIZE,repeatedValue:CChar(0)) var response:String = "" while fgets(&buf,Int32(BUFSIZE),pp) != nil { response = response + String.fromCString(buf)! } let translation = getTranslatedText(response) guard translation.error == nil else { completion(translation:nil,error:translation.error) return } completion(translation:translation.translation,error:nil) } private func getTranslatedText(jsonString:String) -> (error:NSError?,translation:String?) { let obj = json_tokener_parse(jsonString) guard obj != nil else { return (NSError(domain:"translator",code:3,userInfo:nil),nil) } let responseData = json_object_object_get(obj,"responseData") guard responseData != nil else { return (NSError(domain:"translator",nil) } let translatedTextObj = json_object_object_get(responseData,"translatedText") guard translatedTextObj != nil else { return (NSError(domain:"translator",nil) } let translatedTextStr = json_object_get_string(translatedTextObj) return (nil,String.fromCString(translatedTextStr)!) } } 整合各个部分要把上面介绍的组件结合到一起,我们还需要创建额外的两个文件:
import Foundation let INPUT_NOTIFICATION = "InputNotification" let nc = NSNotificationCenter.defaultCenter() struct TranslationCommand { var from:String var to:String var text:String } var translationCommand:TranslationCommand = TranslationCommand(from:"en",to:"es",text:"")
import PackageDescription let package = Package( name: "translator",dependencies: [ .Package(url: "https://github.com/iachievedit/CJSONC",majorVersion: 1),.Package(url: "https://github.com/PureSwift/CcURL",majorVersion: 1) ] ) 如果一切都配置正确的话,最后执行 swift build Cloning https://github.com/iachievedit/CJSONC Using version 1.0.0 of package CJSONC Cloning https://github.com/PureSwift/CcURL Using version 1.0.0 of package CcURL Compiling Swift Module 'translator' (4 sources) Linking Executable: .build/debug/translator 如果你打算从现成的代码开始学习,可以从 Github 上获取本站的代码,然后找到 试试自己动手现在的翻译程序还有很多可以优化的地方。下面是一个你可以尝试的列表:
结束语我从来不掩饰我是一个狂热的 Swift 爱好者,我坚信它很可能既能像 Perl、Python 和 Ruby 这样语言一样出色的完成运维工作,也能像 C、C++ 和 Java 一样出色的完成系统编程的任务。我知道现在和那些个单文件脚本语言相比,Swift 比较蛋疼的一点就是必须得编译成二进制文件。我真诚的希望这一点能够改善,这样我就能不再关注语言层面的东西而是去做一些新,酷酷的东西。一些朋友已经在 Swift 的邮件列表中讨论这一点,具体可以看这个帖子。
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |