Skrip dengan Swift : Ekstraksi Berkas String

Belum lama, saya diberikan tugas di tempat kerja saya untuk mengekstraksi kopi dari Localizable.strings ke format excel. Pikir saya ini tugas agaknya membosankan bila dikerjakan dengan cara manual. Maka saya ambil kesempatan ini untuk kembali bermain dengan Swift.

Terilhami dari presentasi Ayaka di Swift Summit, tiap tugas kecil seperti ini ternyata bisa lho dikerjakan dengan Swift. Intinya, walaupun belum ada kode Swift dalam proyek utama kita, bukan berarti kita tidak bisa menyentuh Swift sama sekali.

Membuat dan Menjalankan Skrip dengan Swift

1
#!/usr/bin/env xcrun swift
1
print("Teks ini dibuat dengan Swift!")
1
2
// pastikan kita berada di direktorinya xtractr.swift
$ chmod +x xtractr.swift
1
2
$ ./xtractr.swift
"Teks ini dibuat dengan Swift!"

Cukup mudah kan? Setelah hanya menampilkan contoh teks, mari kita buat pengurai Localizable.strings yang sebenarnya.

Membaca parameter dari terminal

Untuk mendapatkan nama file dari terminal, kita bisa gunakan variabel Process.arguments. Variabel ini berisi semua parameter yang diketikkan saat dijalankan dari terminal.

Bila ingin melihat parameter yang kita dapatkan dari terminal, ubah skrip kita menjadi seperti di bawah

1
2
#!/usr/bin/env xcrun swift
print(Process.arguments)

Jalankan kembali di terminal

1
2
$ ./xtractr.swift tes argumen 123
["./xtractr.swift", "tes", "argumen", "123"]

Bisa kita lihat, kata per kata dirangkum dalam list Process.arguments (termasuk nama programmnya sendiri). Skrip kita hanya akan mengharapkan satu buah parameter, yaitu alamat dari berkas Localizable.strings itu sendiri. Kita bisa rangkum menjadi sebuah fungsi yang akan mengecek jumlah parameter dan mengambil parameter pertama sebagai filepath.

1
2
3
4
5
6
7
8
9
10
#!/usr/bin/env xcrun swift

func getParameter() -> String {
    guard Process.arguments.count >= 2 else {
        return "File tidak ditemukan"
    }
    return Process.arguments[1]
}

print(getParameter())

Sebelum mencoba kembali, cari suatu file Localizable.strings dari salah satu proyek yang sudah ada ke dalam direktori yang aktif di terminal (saya taruh di ~/Desktop). Jalankan kembali dengan menggunakan nama file yang benar. Coba cek juga tanpa menggunakan parameter dab menggunakan lebih dari satu parameter.

1
2
3
4
5
6
$ ./xtractr.swift Localizable.strings
Localizable.strings
$ ./xtractr.swift     
File tidak ditemukan
$ ./xtractr.swift Localizable.strings 123 heyho
Localizable.strings

Membaca teks dari file

Untuk membaca konten teks dari sebuah file, kita bisa gunakan fungsi konstruktor dari NSString yang menerima parameter filepath dan encoding.

1
let content = try NSString(contentsOfFile: filepath, encoding: NSUTF8StringEncoding)

Bisa kita periksa bahwa tipe yang digunakan adalah NSString dan bukan String dari Swift. Kita bisa konversi dari tipe satu ke tipe lainnya, namun karena kita menggunakan beberapa perilaku dan method dari NSString maka kita biarkan dulu dan konversi ke String hanya saat diperlukan.

Mengurai teks dari Localizable.strings

Penguraian akan menggunakan NSRegularExpression yang akan menemukan semua string dalam file yang berformat : "<key>" = "<value>";.

1
2
let regex = try NSRegularExpression(pattern: "\"(.+?)\"\\s*=\\s*\"(.+?)\"\\s*;", options: .CaseInsensitive)
let matches = regex.matchesInString((query as String), options: .WithTransparentBounds, range: NSMakeRange(0, query.length))

Variabel matches akan berisi array dari objek NSTextCheckingResult. NSTextCheckingResult mempunyai sebuah list lagi yang berisi NSRange yang menunjukan lokasi-lokasi dari string yang cocok dengan format regex yang kita berikan. Untuk mendapatkan semua string yang dari sebuah NSTextCheckingResult kita harus mengaksesnya dengan rangeAtIndex

1
2
3
4
5
6
var strings = [String]()
for index in 0..<(match.numberOfRanges) {
    let range = match.rangeAtIndex(index)
    strings.append(query.substringWithRange(range) as String)
}
return strings

Setiap array string yang kita dapatkan akan berjumlah tiga, yaitu dalam kesatuan format ("<key>" = "<value>";), key-nya (<key>) dan terakhir value dari key tersebut (<value>). Kita hanya akan menggunakan 2 string terakhir dari setiap array string kita.

Terakhir kita akan mengkonversi semua pasangan <key, value> kita ke dalam format yang bisa dibaca Excel. Format yang paling mudah adalah format csv atau comma separated value. Intinya tiap kolom akan dipisahkan dengan koma; “<key>,<value>\n

Bila kita gabung seluruh proses ini menjadi satu kesatuan, fungsi pengurai akan terlihat seperti berikut

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
func parse(query: NSString) throws -> String {
    let regex = try NSRegularExpression(pattern: "\"(.+?)\"\\s*=\\s*\"(.+?)\"\\s*;", options: .CaseInsensitive)
    let matches = regex.matchesInString((query as String), options: .WithTransparentBounds, range: NSMakeRange(0, query.length))

    let results = matches

        // transformasi dari NSTextCheckingResult menjadi array String
        .map { match -> [String] in
            var strings = [String]()
            for index in 0..<(match.numberOfRanges) {
                let range = match.rangeAtIndex(index)
                strings.append(query.substringWithRange(range) as String)
            }
            return strings
        }

        // periksa apakah pasti ada tiga atau lebih string
        .filter { $0.count >= 3 }

        // transformasi dari array string menjadi key value yang terpisah oleh koma
        .reduce("") { (initial, strings) -> String in
            return initial + "\(strings[1]),\(strings[2])\n"
        }

    return results
}

Hasil akhir

Kode akhir bisa lihat di sini, berikut dengan penanganan error yang lebih lengkap.

Namun saat kita jalankan, skrip ini hanya akan menampikan konten csv di dalam terminal. Untuk menyimpannya ke dalam file, kita harus pass hasil cetak tersebut langsung di terminal dengan format “> nama_file.csv”.

1
2
$ ./xtractr.swift Localizable.strings # hanya mencetak di terminal
$ ./xtractr.swift Localizable.strings > extracted.csv # mencetak ke dalam sebuah file extracted.csv

Walau tugas saya saat itu cukup remeh namun tugas ini jadi lebih menarik dan lebih menyenangkan karena saya menantang diri saya dengan menggunakan Swift. Sampai jumpa di skrip-skrip Swift selanjutnya!


Update 10-11-2015

Kalau perlu contoh lain yang lebih advance, bisa cek postingan Ayaka tentang bagaimana dia membuat halaman acknowledgement dengan membaca file Cocoapods and Carthage. Keren cuy!