{"id":2344,"date":"2015-12-28T16:36:24","date_gmt":"2015-12-28T22:36:24","guid":{"rendered":"http:\/\/dev.iachieved.it\/iachievedit\/?p=2344"},"modified":"2015-12-28T19:19:08","modified_gmt":"2015-12-29T01:19:08","slug":"command-line-utilities-in-swift","status":"publish","type":"post","link":"https:\/\/dev.iachieved.it\/iachievedit\/command-line-utilities-in-swift\/","title":{"rendered":"Command Line Utilities in Swift"},"content":{"rendered":"<p>This is another post in a series designed to explore the range of applications that Swift can be applied to on Linux server systems.  <\/p>\n<p>Our application will build upon a previous example that uses <code>popen<\/code> combined with the <code>wget<\/code> command to call a <a href=\"http:\/\/mymemory.translated.net\/doc\/spec.php\">natural language translation service<\/a> and translate strings from one language to another like <a href=\"https:\/\/translate.google.com\/\">Google Translate<\/a>.  Rather than having a one-shot command line that translates a single string, we&#8217;re going to create an interactive &#8220;shell&#8221; that translates each string entered at a prompt.  Here&#8217;s a screenshot of the application in action:<\/p>\n<figure id=\"attachment_2355\" aria-describedby=\"caption-attachment-2355\" style=\"width: 564px\" class=\"wp-caption aligncenter\"><a href=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2015\/12\/swifty_006.png\" rel=\"attachment wp-att-2355\"><img loading=\"lazy\" decoding=\"async\" src=\"http:\/\/dev.iachieved.it\/iachievedit\/wp-content\/uploads\/2015\/12\/swifty_006.png\" alt=\"Translate all the things!\" width=\"564\" height=\"340\" class=\"size-full wp-image-2355\" \/><\/a><figcaption id=\"caption-attachment-2355\" class=\"wp-caption-text\">Translate all the things!<\/figcaption><\/figure>\n<p>The translator prompt indicates what language it is expecting to receive, as well as what language it will attempt to translate to.  For example:<\/p>\n<ul>\n<li><code>en->es<\/code> Translate from English to Spanish\n<li><code>es->it<\/code> Translate from Spanish to Italian\n<li><code>it->ru<\/code> Translate from Italian to Russian\n<\/ul>\n<p>The program defaults to <code>en->es<\/code> and provides the commands <code>to<\/code> and <code>from<\/code> to change the languages.  For example, typing <code>to es<\/code> will change the language to translate to to Spanish.  Typing <code>quit<\/code> will exit the application.<\/p>\n<p>If the string entered is not a command it is taken verbatim and passed to the translation web service.  The result of the translation is then echoed back.<\/p>\n<h2>What to Note<\/h2>\n<p>If you&#8217;re coming from a systems or devops programming background and have never been exposed to Swift before, here are the things you should be taking a look at in the code.  I think you will find Swift offers a lot for both types of programmers and will become a welcome addition to the Linux programming toolbox.<\/p>\n<ul>\n<li><code>let variableName = value<\/code> for assigning constants\n<li><a href=\"https:\/\/www.weheartswift.com\/tuples-enums\/\">tuples<\/a>\n<li>strings in <code>switch-case<\/code> statements\n<li><code>switch-case<\/code> statements must be exhaustive\n<li>computed <a href=\"https:\/\/developer.apple.com\/library\/ios\/documentation\/Swift\/Conceptual\/Swift_Programming_Language\/Properties.html\">properties<\/a>\n<li><code>import Glibc<\/code> which provides access to standard C functions\n<li>the <a href=\"http:\/\/www.codingexplorer.com\/the-guard-statement-in-swift-2\/\"><code>guard<\/code> statement<\/a>\n<li>Foundation classes such as <code>NSThread<\/code> and <code>NSNotificationCenter<\/code>\n<li>posting notifications to trigger code execution in a different object or thread\n<\/ul>\n<h2>Application Design<\/h2>\n<p>Our translator is broken into a main routine, two classes, and a <code>globals.swift<\/code> file.  If you are going to follow along you should use the <a href=\"http:\/\/dev.iachieved.it\/iachievedit\/introducing-the-swift-package-manager\/\">Swift Package Manager<\/a> and lay out your files in a directory like this:<\/p>\n<pre class=\"crayon:false\">\r\ntranslator\/Sources\/main.swift\r\n          \/Sources\/CommandInterpreter.swift\r\n          \/Sources\/...\r\n          \/Package.swift\r\n<\/pre>\n<p>The <code>main.swift<\/code> file is a Swift application&#8217;s entry point and the only file that should have executable statements in it (assigning a variable or declaring a class is not an &#8220;executable statement&#8221; in this context).<\/p>\n<p><code>main.swift<\/code>:<\/p>\n<pre class=\"lang:swift\">\r\nimport Foundation\r\nimport Glibc\r\n\r\nlet interpreter = CommandInterpreter()\r\nlet translator  = Translator()\r\n\r\n\/\/ Listen for events to translate\r\nnc.addObserverForName(INPUT_NOTIFICATION, object:nil, queue:nil) {\r\n  (_) in\r\n  let tc = translationCommand\r\n  translator.translate(tc.text, from:tc.from, to:tc.to){\r\n    translation, error in\r\n    guard error == nil && translation != nil else {\r\n      print(\"Translation failure:  \\(error!.code)\")\r\n      return\r\n    }\r\n    print(translation!)\r\n  }\r\n}\r\n\r\ninterpreter.start()\r\n\r\nselect(0, nil, nil, nil, nil)\r\n<\/pre>\n<p>As it stands our application has no arguments to process off of the command line, so it:<\/p>\n<p><b>1.<\/b>  Creates an instance of both the <code>CommandInterpreter<\/code> and <code>Translator<\/code> class<\/p>\n<p><b>2.<\/b>  Adds an observer for a notification named <code>InputNotification<\/code> (we use a constant <code>INPUT_NOTIFICATION<\/code> defined in <code>globals.swift<\/code>)<\/p>\n<p><b>3.<\/b>  Provides a block of code to execute when the notification is received<\/p>\n<p><b>4.<\/b>  Starts the interpreter<\/p>\n<p><b>5.<\/b>  Calls <code>select<\/code> which blocks the main thread while other threads in the application continue to run<\/p>\n<h3>CommandInterpreter<\/h3>\n<p>The class <code>CommandInterpreter<\/code> is responsible for reading characters from standard input, parsing the input string to determine what action should be taken, and then taking that action.  If you&#8217;re new to Swift we&#8217;ve commented some of the basic constructs of the language.<\/p>\n<p><code>CommandInterpreter.swift<\/code>:<\/p>\n<pre class=\"lang:swift\">\r\n\/\/ Import statements\r\nimport Foundation\r\nimport Glibc\r\n\r\n\/\/ Enumerations\r\nenum CommandType {\r\ncase None\r\ncase Translate\r\ncase SetFrom\r\ncase SetTo\r\ncase Quit\r\n}\r\n\r\n\/\/ Structs\r\nstruct Command {\r\n  var type:CommandType\r\n  var data:String\r\n}\r\n\r\n\/\/ Classes\r\nclass CommandInterpreter {\r\n\r\n  \/\/ Read-only computed property\r\n  var prompt:String {\r\n    return \"\\(translationCommand.from)->\\(translationCommand.to)\"\r\n  }\r\n\r\n  \/\/ Class constant\r\n  let delim:Character = \"\\n\"\r\n\r\n  init() {\r\n  }\r\n\r\n  func start() {\r\n    let readThread = NSThread(){\r\n      var input:String    = \"\"\r\n      \r\n      print(\"To set input language, type 'from LANG'\")\r\n      print(\"To set output language, type 'to LANG'\")\r\n      print(\"Type 'quit' to exit\")\r\n      self.displayPrompt()\r\n\r\n      while true {\r\n        let c = Character(UnicodeScalar(UInt32(fgetc(stdin))))\r\n        if c == self.delim {\r\n          let command = self.parseInput(input)\r\n          self.doCommand(command)\r\n          input = \"\" \/\/ Clear input\r\n          self.displayPrompt()\r\n        } else {\r\n          input.append(c)\r\n        }\r\n      }\r\n    }\r\n    \r\n    readThread.start()\r\n  }\r\n\r\n  func displayPrompt() {\r\n    print(\"\\(self.prompt):  \", terminator:\"\")\r\n  }\r\n\r\n  func parseInput(input:String) -> Command {\r\n    var commandType:CommandType\r\n    var commandData:String = \"\"\r\n    \r\n    \/\/ Splitting a string\r\n    let tokens = input.characters.split{$0 == \" \"}.map(String.init)\r\n\r\n    \/\/ guard statement to validate that there are tokens\r\n    guard tokens.count > 0 else {\r\n      return Command(type:CommandType.None, data:\"\")\r\n    }\r\n\r\n    switch tokens[0] {\r\n    case \"quit\":\r\n      commandType = .Quit\r\n    case \"from\":\r\n      commandType = .SetFrom\r\n      commandData = tokens[1]\r\n    case \"to\":\r\n      commandType = .SetTo\r\n      commandData = tokens[1]\r\n    default:\r\n      commandType = .Translate\r\n      commandData = input\r\n    }\r\n    return Command(type:commandType,data:commandData)\r\n  }\r\n\r\n  func doCommand(command:Command) {\r\n    switch command.type {\r\n    case .Quit:\r\n      exit(0)\r\n    case .SetFrom:\r\n      translationCommand.from = command.data\r\n    case .SetTo:\r\n      translationCommand.to   = command.data\r\n    case .Translate:\r\n      translationCommand.text = command.data\r\n      nc.postNotificationName(INPUT_NOTIFICATION, object:nil)   \r\n    case .None:\r\n      break\r\n    }\r\n  }\r\n}\r\n<\/pre>\n<p>The <code>CommandInterpreter<\/code> logic is straightforward.  When the <code>start<\/code> function is called an <code>NSThread<\/code> is created with a block of code that calls <code>fgetc<\/code> collecting characters on <code>stdin<\/code>.  When the newline character is encountered (the user hits <code>RETURN<\/code>) the input is parsed and structured into a <code>Command<\/code>.  This in turn is handed over to the <code>doCommand<\/code> function to be processed.<\/p>\n<p>Our <code>doCommand<\/code> function is a simple <code>switch-case<\/code> statement.  If a <code>.Quit<\/code> command is encountered the application simply calls <code>exit(0)<\/code>.  The <code>.SetFrom<\/code> and <code>.SetTo<\/code> commands are self-explanatory as well.  When it comes to the <code>.Translate<\/code> command, here is where the Foundation notification system is used.  <code>doCommand<\/code> itself doesn&#8217;t translate anything but instead <i>posts<\/i> an application-wide notification named <code>InputNotification<\/code>.  Anyone listening for this notification (our <code>main<\/code> thread!) will have its associated callback invoked:<\/p>\n<pre class=\"lang:swift\">\r\n\/\/ Listen for events to translate\r\nnc.addObserverForName(INPUT_NOTIFICATION, object:nil, queue:nil) {\r\n  (_) in\r\n  let tc = translationCommand\r\n  translator.translate(tc.text, from:tc.from, to:tc.to){\r\n    translation, error in\r\n    guard error == nil && translation != nil else {\r\n      print(\"Translation failure:  \\(error!.code)\")\r\n      return\r\n    }\r\n    print(translation!)\r\n  }\r\n}\r\n<\/pre>\n<p>As I mentioned in <a href=\"http:\/\/dev.iachieved.it\/iachievedit\/foundation-on-linux\/\">this post<\/a>, there is a SILgen crash working with <code>NSNotification<\/code> <code>userInfo<\/code> dictionaries, so we are cheating with a global variable named <code>translationCommand<\/code>.  In our block we:<\/p>\n<p><b>1.<\/b>  Decrease our verbosity a little bit by assigning <code>tc<\/code> to the contents of <code>translationCommand<\/code><\/p>\n<p><b>2.<\/b>  Call the <code>translate<\/code> function of our <code>Translator<\/code> object, passing in the required arguments<\/p>\n<p><b>3.<\/b>  Provide a block to run with the translation is complete, which<\/p>\n<p><b>4.<\/b>  Uses the nice <code>guard<\/code> statement of Swift to bail quickly if there was an error<\/p>\n<p><b>5.<\/b>  Print our translation!<\/p>\n<h3>Translator<\/h3>\n<p>The <code>Translator<\/code> was originally introduced in <a href=\"http:\/\/dev.iachieved.it\/iachievedit\/more-swift-on-linux\/\">this post<\/a>, and we have reused it here.<\/p>\n<p><code>Translator.swift<\/code>:<\/p>\n<pre class=\"lang:swift\">\r\nimport Glibc\r\nimport Foundation\r\nimport CcURL\r\nimport CJSONC\r\n\r\nclass Translator {\r\n\r\n  let BUFSIZE = 1024\r\n\r\n  init() {\r\n  }\r\n\r\n  func translate(text:String, from:String, to:String,\r\n                        completion:(translation:String?, error:NSError?) -> Void) {\r\n\r\n    let curl = curl_easy_init()\r\n\r\n    guard curl != nil else {\r\n      completion(translation:nil,\r\n                 error:NSError(domain:\"translator\", code:1, userInfo:nil))\r\n      return\r\n    }\r\n\r\n    let escapedText = curl_easy_escape(curl, text, Int32(strlen(text)))\r\n\r\n    guard escapedText != nil else {\r\n      completion(translation:nil,\r\n                 error:NSError(domain:\"translator\", code:2, userInfo:nil))\r\n      return\r\n    }\r\n    \r\n    let langPair = from + \"%7c\" + to\r\n    let wgetCommand = \"wget -qO- http:\/\/api.mymemory.translated.net\/get\\\\?q\\\\=\" + String.fromCString(escapedText)! + \"\\\\&langpair\\\\=\" + langPair\r\n    \r\n    let pp      = popen(wgetCommand, \"r\")\r\n    var buf     = [CChar](count:BUFSIZE, repeatedValue:CChar(0))\r\n    \r\n    var response:String = \"\"\r\n    while fgets(&buf, Int32(BUFSIZE), pp) != nil {\r\n      response = response + String.fromCString(buf)!\r\n    }\r\n    \r\n    let translation = getTranslatedText(response)\r\n\r\n    guard translation.error == nil else {\r\n      completion(translation:nil, error:translation.error)\r\n      return\r\n    }\r\n\r\n    completion(translation:translation.translation, error:nil)\r\n  }\r\n\r\n  private func getTranslatedText(jsonString:String) -> (error:NSError?, translation:String?) {\r\n\r\n    let obj = json_tokener_parse(jsonString)\r\n\r\n    guard obj != nil else {\r\n      return (NSError(domain:\"translator\", code:3, userInfo:nil),\r\n             nil)\r\n    }\r\n\r\n    let responseData = json_object_object_get(obj, \"responseData\")\r\n\r\n    guard responseData != nil else {\r\n      return (NSError(domain:\"translator\", code:3, userInfo:nil),\r\n              nil)\r\n    }\r\n\r\n    let translatedTextObj = json_object_object_get(responseData,\r\n                                                   \"translatedText\")\r\n\r\n    guard translatedTextObj != nil else {\r\n      return (NSError(domain:\"translator\", code:3, userInfo:nil),\r\n              nil)\r\n    }\r\n\r\n    let translatedTextStr = json_object_get_string(translatedTextObj)\r\n\r\n    return (nil, String.fromCString(translatedTextStr)!)\r\n           \r\n  }\r\n\r\n}\r\n<\/pre>\n<h3>Putting it All Together<\/h3>\n<p>To pull everything together we have two more files to create:  <code>globals.swift<\/code> and <code>Package.swift<\/code>:<\/p>\n<p><code>globals.swift<\/code>:<\/p>\n<pre class=\"lang:swift\">\r\nimport Foundation\r\n\r\nlet INPUT_NOTIFICATION   = \"InputNotification\"\r\nlet nc = NSNotificationCenter.defaultCenter()\r\n\r\nstruct TranslationCommand {\r\n  var from:String\r\n  var to:String\r\n  var text:String\r\n}\r\n\r\nvar translationCommand:TranslationCommand = TranslationCommand(from:\"en\",\r\n                                                               to:\"es\",\r\n                                                               text:\"\")\r\n<\/pre>\n<p><code>Package.swift<\/code>:<\/p>\n<pre class=\"lang:swift\">\r\nimport PackageDescription\r\n\r\nlet package = Package(\r\n  name:  \"translator\",\r\n  dependencies: [\r\n    .Package(url:  \"https:\/\/github.com\/iachievedit\/CJSONC\", majorVersion: 1),\r\n    .Package(url:  \"https:\/\/github.com\/PureSwift\/CcURL\", majorVersion: 1)\r\n  ]\r\n)\r\n<\/pre>\n<p>If everything is in its proper place, a <code>swift build<\/code> should give you a functioning translator application.<\/p>\n<pre class=\"crayon:false\">\r\nswift build\r\nCloning https:\/\/github.com\/iachievedit\/CJSONC\r\nUsing version 1.0.0 of package CJSONC\r\nCloning https:\/\/github.com\/PureSwift\/CcURL\r\nUsing version 1.0.0 of package CcURL\r\nCompiling Swift Module 'translator' (4 sources)\r\nLinking Executable:  .build\/debug\/translator\r\n<\/pre>\n<p>If you want to start off with the code already there, grab it from <a href=\"https:\/\/github.com\/iachievedit\/moreswift\">Github<\/a> and check out the <code>cmdline_translator<\/code> directory.<\/p>\n<h2>Things to Try!<\/h2>\n<p>There are a lot of improvements that can be made to the application.  Here&#8217;s a list of things you might like to try:<\/p>\n<ul>\n<li>add command line arguments to set the default from and to language\n<li>add command line arguments to run in a non-interactive mode\n<li>add a <code>swap<\/code> command which swaps the from and to language\n<li>add a <code>help<\/code> command\n<li>collapse the <code>from<\/code> and <code>to<\/code> commands into line, like <code>from en to es<\/code>\n<li>fix the bug where typing <code>from<\/code> or <code>to<\/code> with no arguments results in a crash\n<li>require the commands to be proceeded with a character like <code>\\<\/code> (otherwise you can&#8217;t translate &#8220;quit&#8221;)\n<li>add <code>localizedDescription<\/code> strings to our <code>Translator<\/code> errors, or\n<li>implement <code>throws<\/code> on the <code>Translator<\/code> for when errors occur\n<\/ul>\n<h2>Closing Thoughts<\/h2>\n<p>I am unabashedly a Swift enthusiast and believe it has a great opportunity to stand along side Perl, Python, and Ruby for &#8220;devops tasks&#8221; <i>as well as<\/i> compete with C, C++, and Java for &#8220;systems programming.&#8221;  At the moment it is true that for other than the most trivial single file scripts, you must compile your Swift code into a binary.  I am hopeful that situation will change and I can move on from languages that start new blocks with whitespace.  Folks are already talking about it on the Swift evolution mailing list in <a href=\"https:\/\/lists.swift.org\/pipermail\/swift-evolution\/Week-of-Mon-20151207\/000983.html\">this thread<\/a>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This is another post in a series designed to explore the range of applications that Swift can be applied to on Linux server systems. Our application will build upon a previous example that uses popen combined with the wget command to call a natural language translation service and translate strings from one language to another [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[19,5],"tags":[],"class_list":["post-2344","post","type-post","status-publish","format-standard","hentry","category-linux","category-swift"],"_links":{"self":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/2344"}],"collection":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/comments?post=2344"}],"version-history":[{"count":23,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/2344\/revisions"}],"predecessor-version":[{"id":2366,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/posts\/2344\/revisions\/2366"}],"wp:attachment":[{"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/media?parent=2344"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/categories?post=2344"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/dev.iachieved.it\/iachievedit\/wp-json\/wp\/v2\/tags?post=2344"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}