back home

How to clean your Address Book with Swift script?

I have this problem that my Address Book is a mess :-( There are many entries that have an email only, no name, no anything, just a plain email.

What's even worse that some of those emails are duplicated or match people that already exists in my address book.

I decided to give it a stop and clean it a bit. I'm on holidays, what else would I be doing than cleaning my address book? ;-)

I thought it would be a good idea to: play with Xcode playgrounds, Swift and run this as a script!

So far it was a smooth ride, I read a few books on Swift before (without much coding). I found API for AddressBook and voila, I have a script that cleaned my address book.

What it does exactly?

It will take all the first.last@something emails. It will skip others.

It will try to locate an existing first last entry. If that's possible it will add the email there.

If there's no such entry it will update the existing entry and set proper names on it. So at least it looks more humane ;-)

The script is available as gist. Grab it!

#!/usr/bin/env swift

import Cocoa import AddressBook

var ab = ABAddressBook.sharedAddressBook()

func findByFirstAndLastName(firstName: String, lastName: String) -> [ABRecord] { let comparison: ABSearchConjunction = CFIndex(kABContainsSubStringCaseInsensitive.rawValue)

let searchForEmptyLastName = ABPerson.searchElementForProperty(kABLastNameProperty, label: nil, key: nil, value: lastName, comparison: comparison)

let searchForEmptyFirstName = ABPerson.searchElementForProperty(kABFirstNameProperty, label: nil, key: nil, value: firstName, comparison: comparison)

let andComparison = ABSearchElement(forConjunction: CFIndex(kABSearchAnd.rawValue), children: [searchForEmptyLastName, searchForEmptyFirstName])

return ab.recordsMatchingSearchElement(andComparison) as! [ABRecord]

}

func findWithoutNames() -> [ABRecord] { let comparison: ABSearchConjunction = CFIndex(kABContainsSubStringCaseInsensitive.rawValue)

let searchForEmptyLastName = ABPerson.searchElementForProperty(kABLastNameProperty, label: nil, key: nil, value: "", comparison: comparison)

let searchForEmptyFirstName = ABPerson.searchElementForProperty(kABFirstNameProperty, label: nil, key: nil, value: "", comparison: comparison)

let andComparison = ABSearchElement(forConjunction: CFIndex(kABSearchAnd.rawValue), children: [searchForEmptyLastName, searchForEmptyFirstName])

return ab.recordsMatchingSearchElement(andComparison) as! [ABRecord]

}

for person in findWithoutNames() { let emailsProperty = person.valueForProperty(kABEmailProperty) as! ABMultiValue? if let emails = emailsProperty { for i in 0 ..< emails.count() { let email = emails.valueAtIndex(i) as! String let name = email.characters.split("@")[0] if name.contains(".") { let parts = name.split(".") if parts.count == 2 { let firstName = String(parts[0]).capitalizedString let lastName = String(parts[1]).capitalizedString let matching = findByFirstAndLastName(firstName, lastName: lastName)

                if matching.count &gt; 0 {
                    for another in matching {
                        let anotherEmails = another.valueForProperty(kABEmailProperty)?.mutableCopy() ?? ABMutableMultiValue()
                        anotherEmails.addValue(email, withLabel: kABEmailWorkLabel)
                    }
                    ab.removeRecord(person)
                } else {
                    print (firstName, lastName, email, matching.count)

                    person.setValue(firstName, forProperty: kABFirstNameProperty)
                    person.setValue(lastName, forProperty: kABLastNameProperty)
                }
            }
        }
    }
}

}

ab.save()

If you remove ab.save() from the end you can play safely with it as whatever you do will not be persisted.

You can add as many print lines as you want to make sure you know what you are doing.

You can download it and run as a script if you want too! I love that about Swift!

Who knows maybe it will become my scripting go to language? :-)