How I’m Using Swift Scripting To Create Newsletter For iOS Cookies

bardonadam
4 min readDec 7, 2015

--

It’s been almost 8 weeks since I’ve launched iOS Cookies, a curated collection of iOS libraries written in Swift. It’s a side project, and besides improving it and working on new features, it needs a lot of attention. Adding new libraries(many suggested by some of you), posting them on twitter and probably most time consuming — creating newsletter, which I’ve failed to do few times. Those tasks are very boring and mundane, so I thought there must be a better way.

Let’s automate boring tasks!

iOS Cookies are built on flat-file CMS Grav. All libraries are stored in .yaml file, so if I want to add a new one, I need to write something like this:

- title: "SnapKit"
category: autolayout
description: "SnapKit is a DSL to make Auto Layout easy on both iOS and OS X."
link: https://github.com/SnapKit/SnapKit

Whenever I’ve added new library, I’d tweet about it like this:

New library in [category] category: [title] — [description][link to GitHub]

Simple pattern to follow, even simpler to automate.

At the beginning, I opened XCode playground, created simple struct:

struct Library {
let title: String
let description: String
let category: String
let link: String
}

and started to playing with strings, parsing, etc.

Parsing .yaml file

This is the biggest function in my script. Go through each line in file and store data to Library struct. Finally return Array<Library>. But hold on! Description and category are not in shape to be tweeted yet.

Each category needs better look, so let’s improve above struct.

struct Library {
enum Category: String {
case database = "Database"
case xmljson = "XML/JSON"
...

static let allValues = [database, networking, ...]
...
}

Now just push it through switch-statement:

switch category {
case “database”:
library.category = .database
case "xml-json":
library.category = .xmljson
case ...
}

What about description? Well sometimes it can contain a link, which in .yaml looks for example like this:

inspired by the popular [SDWebImage](https://github.com/rs/SDWebImage).

Combination of regular expressions and stringByReplacing OccurrencesOfString()will help use to loose those “[]” and “(link)”, leaving just a title.

let matches = matchesForRegexInText(“\\[(.*?)\\]\\(“, text: description)
if !matches.isEmpty {
let linkTitleRange = matches[0].startIndex.advancedBy(1)..<matches[0].endIndex.advancedBy(-2)
linkTitle = matches[0][linkTitleRange]

let hrefMatches = matchesForRegexInText("\\]\\((.*?)\\)", text: description)
if !hrefMatches.isEmpty {
let linkHrefRange = hrefMatches[0].startIndex.advancedBy(2)..<hrefMatches[0].endIndex.advancedBy(-1)
linkHref = hrefMatches[0][linkHrefRange]
description = description.stringByReplacingOccurrencesOfString("[\(linkTitle)]", withString: "\(linkTitle)")
description = descriptionForTweet.stringByReplacingOccurrencesOfString("(\(linkHref))", withString: "")
}
}

Can I get a tweet please?

So far we have array of parsed libraries, what now? Ready-to-use string would be fine.

func libIntoTweet(library: Library) -> String {
guard let category = library.category?.rawValue
else {
return “”
}
return “New library in \(category) category: \(library.title) — \(library.description)\n\(library.link)”
}

Didn’t I promised newsletter?

Having all this done, I realised that I can write newsletter directly in HTML and so this could be automated as well. Parsing is already done, all we need is string representation, which can be done in similar manner.

Every library has this HTML body:

<p><a href=”[link to GitHub]" target=”_blank”>[title]</a>&nbsp;-&nbsp;[description]</p>

What about category? Well I group all new libraries by category, so having a script which would go through all new libraries and grouped them for me would be awesome! Using some for-loop, it’s a very easy to do.

for category in Library.Category.allValues{
var categoryLibsCounter = 0
var libsHtml = Array<String>()
var categoryLink = “”
for lib in libraries {
if lib.category == category {
categoryLibsCounter++
libsHtml.append(“<p><a href=\”\(lib.link)\” target=\”_blank\”>\(lib.title)</a>&nbsp;-&nbsp;\(lib.descriptionForNewsletter)</p>”) categoryLink = lib.categoryLink
}
}
if categoryLibsCounter > 0 {
html.append(“<br />”)
html.append(“<h2 class=\”null\” style=\”text-align: center;\”>”)
html.append(“<strong><span style=\”font-size:20px\”><a href=\”http://www.ioscookies.com/\(categoryLink)/\"><span style=\”color:#30d1b5\”>\(category.rawValue)</span></a></span></strong>”)
html.append(“</h2>”)
html.appendContentsOf(libsHtml)
}
}

That’s final look of generated HTML, when put into my mailchimp template:

Scrap of iOS Cookies Newsletter

How to actually make a Swift script and write result to file?

There’s a great article about Swift scripting, written by @ayanonagon, which was big help.

Here’s my method to write output to file:

func writeToFile(fileType: FileType, string: String) {
switch fileType {
case .newsletter:
do {
try string.writeToFile(FileName.newsletter.rawValue, atomically: false, encoding: NSUTF8StringEncoding)
}
catch let error as NSError {
print(“\(error)”)
}
case .tweet:
do {
try string.writeToFile(FileName.tweets.rawValue, atomically: false, encoding: NSUTF8StringEncoding)
}
catch let error as NSError {
print(“\(error)”)
}
}
}

For tweets I call:

writeToFile(.tweet, string: libraries.map(libIntoTweet).joinWithSeparator(“\n”))

And for newsletter:

writeToFile(.newsletter, string: html.joinWithSeparator(“\n”))

That’s all! Writing this script was fun, saves me a lot of time and I don’t need to do those boring tasks manually anymore. You can check the script on GitHub. I hope you liked this and tomorrow will be new issue of iOS Cookies Newsletter ;)

--

--