8
Pipeline Generate dengan Go Tools
Seluruh isi api/ dibuat oleh binary Go kecil di tools/. main.go membaca os.Args[1], memilih satu cabang switch, lalu memanggil fungsi run* yang sesuai (tools/main.go:34-711). Tidak ada subcommand yang memanggil subcommand lain, jadi keenam langkah pipeline berjalan sebagai enam proses go run terpisah dan urutannya ditentukan oleh pemanggil, bukan oleh kode. printUsage mencetak daftar perintah beserta deskripsi satu barisnya (tools/main.go:8-232).
Setiap cabang memakai pola error yang sama: kegagalan di dalam fungsi run* dicetak sebagai error: %v ke stderr lalu proses keluar dengan kode 1. Perintah yang tidak dikenal menghasilkan unknown command %q diikuti usage.
Urutan eksekusi
sequenceDiagram
autonumber
participant OP as Operator atau CI
participant MN as tools/main.go
participant XC as extract-csv
participant GS as generate-static-api
participant GB as generate-boundaries
participant AH as add-has-path
participant GM as generate-missings
participant US as update-stats
participant D as data/
participant A as api/
OP->>MN: go run ./tools extract-csv URL
MN->>XC: runExtractCSV
XC->>D: writeCSV per tabel
OP->>MN: go run ./tools generate-static-api
MN->>GS: runGenerateStaticAPI
GS->>A: phase A, phase B, stats.json
OP->>MN: go run ./tools generate-boundaries
MN->>GB: runGenerateBoundaries
GB->>A: api/paths/*.json dan lat/lng
OP->>MN: go run ./tools add-has-path
MN->>AH: runAddHasPath
AH->>A: has_path pada setiap object
OP->>MN: go run ./tools generate-missings
MN->>GM: runGenerateMissings
GM->>A: missings.json
OP->>MN: go run ./tools update-stats
MN->>US: runUpdateStats
US->>A: patch stats.json
%% fig: Urutan enam subcommand pipeline
%% desc: Setiap langkah adalah proses go run terpisah, main.go hanya meng-dispatch satu subcommand, sehingga urutan di sini ditentukan oleh pemanggil.Rantai ini bukan saran, melainkan syarat. generate-static-api menulis ulang provinces.json, provinces/{kode}.json, dan daftar regencies/{kode}.json, sehingga langkah yang menambal berkas itu harus dijalankan sesudahnya. add-has-path dan generate-missings sama-sama membaca api/paths/, sehingga generate-boundaries harus selesai lebih dulu. update-stats menghitung seluruh berkas *.json di bawah api/, jadi ia harus menjadi langkah terakhir.
Nama perintah dan alias
| Subcommand | Alias yang diterima |
|---|---|
extract-csv | extract-sql |
generate-static-api | generate-api |
generate-boundaries | — |
add-has-path | add_has_path |
generate-missings | generate-missing, generate_missings, missings |
update-stats | update_stats, generate-stats, generate_stats |
Alias di atas berasal langsung dari label case di switch (tools/main.go:34-711).
Peta flag
| Subcommand | Flag | Default | Keterangan |
|---|---|---|---|
extract-csv | -d | data | direktori keluaran CSV; argumen posisional pertama wajib berisi path berkas SQL atau URL |
generate-static-api | -data | data | direktori sumber CSV |
generate-static-api | -out | api | direktori keluaran pohon JSON |
generate-static-api | -v | false | log setiap berkas yang ditulis |
generate-boundaries | -data-boundaries | data/boundaries | direktori CSV hasil konversi boundary |
generate-boundaries | -out | api | direktori keluaran static JSON API |
generate-boundaries | -cache | .cache | tempat zip dan SQL hasil ekstraksi |
generate-boundaries | -prov | kosong | filter satu kode provinsi, misalnya 31 |
generate-boundaries | -limit | 0 | batasi jumlah berkas SQL yang diproses, 0 berarti tanpa batas |
generate-boundaries | -skip-download | false | pakai zip yang sudah ada di cache |
add-has-path | -out, -paths, -v | api, kosong, false | -paths kosong berarti <out>/paths |
generate-missings | -out, -paths, -v | api, kosong, false | -paths kosong berarti <out>/paths |
update-stats | -out, -v | api, false | stats.json dibaca dari <out>/stats.json |
Flag diambil dari definisi flag.NewFlagSet masing-masing berkas: (tools/extract_csv.go:40-573), (tools/generate_static_api.go:41-494), (tools/generate_boundaries.go:46-54), (tools/add_has_path.go:29-43), (tools/generate_missings.go:38-47), dan (tools/update_stats.go:30-43). Semua path flag yang relatif diselesaikan terhadap akar repository, bukan terhadap direktori kerja: repoRoot menaiki direktori sampai menemukan .git (tools/generate_static_api.go:633-648). Karena itu perintah yang sama bisa dijalankan dari subdirektori mana pun.
1. extract-csv: SQL dump menjadi CSV
Sumber SQL boleh berupa path lokal atau URL http(s); openSource memilih http.Get bila string dimulai http:// atau https://, menolak status selain 200 dengan GET %s: HTTP %d, dan membungkus body unduhan dengan progressReader (tools/extract_csv.go:74-9210). Berkas dibaca per baris lewat bufio.Scanner dengan batas token 64 * 1024 * 1024 agar kolom GeoJSON yang sangat panjang tetap masuk (tools/extract_csv.go:36-3811, tools/extract_csv.go:138-14912).
Setiap tabel yang muncul di INSERT ditulis sebagai satu CSV bernama sanitize(t) + ".csv" di dalam direktori -d; setelah semua tabel selesai, ringkasan dicetak sebagai Summary: %d table(s), %d row(s) (tools/extract_csv.go:253-26013). Baris header diambil dari daftar kolom pada pernyataan INSERT; bila INSERT tidak menyebut kolom, header dibuat sintetis col1, col2, dan seterusnya mengikuti jumlah nilai pada baris pertama (tools/extract_csv.go:402-42114).
Tiga berkas yang dibutuhkan tahap berikutnya diekstrak dari tiga dump berbeda: wilayah.csv (seluruh level), wilayah_level_1_2.csv (field kaya plus geometri), dan wilayah_kodepos.csv (peta kode ke kode pos) (tools/generate_static_api.go:1-1815).
2. generate-static-api: CSV menjadi pohon JSON
Perintah ini bekerja dalam dua fase supaya kolom geometri yang besar tidak pernah ditahan untuk semua wilayah sekaligus: fase A men-stream wilayah.csv dan membaca wilayah_kodepos.csv, fase B men-stream wilayah_level_1_2.csv lalu memperkaya provinsi dan kabupaten/kota (tools/generate_static_api.go:1-1815).
graph LR
WC["data/wilayah.csv"] --> PA["generator.phaseA"]
KP["data/wilayah_kodepos.csv"] --> PA
L12["data/wilayah_level_1_2.csv"] --> PB["generator.phaseB"]
PA --> BASE["api/provinces, regencies, districts, villages, postal-codes"]
PB --> PATHS["api/paths/*.json"]
PB --> REGEN["regenerate provinces + regencies"]
BASE --> STAT["generator.writeStats"]
REGEN --> STAT
STAT --> SJ["api/stats.json"]
style WC fill:#336791,color:#fff
style KP fill:#336791,color:#fff
style L12 fill:#336791,color:#fff
style PA fill:#8b5cf6,color:#fff
style PB fill:#8b5cf6,color:#fff
style PATHS fill:#8b5cf6,color:#fff
style SJ fill:#336791,color:#fff
style STAT fill:#f59e0b,color:#000
%% fig: Dua fase generate-static-api
%% desc: phaseA membaca wilayah.csv dan wilayah_kodepos.csv, phaseB membaca wilayah_level_1_2.csv lalu menulis paths dan menulis ulang provinces serta regencies sebelum writeStats menutup dengan stats.json.runGenerateStaticAPI menyiapkan enam subdirektori keluaran — provinces, regencies, districts, villages, postal-codes, paths — lalu memanggil phaseA, phaseB, dan writeStats berurutan. Tiga baris log penutupnya adalah phase A: %d files, phase B: %d paths written, %d level-1/2 files regenerated, dan total: %d files yang menghitung nA+nB+g.pathCount+1 (tools/generate_static_api.go:70-8616).
Fase A mengisi api/provinces.json, api/provinces/{kode}.json, daftar regencies/{provinsi}.json, districts/{kabupaten}.json, villages/{kecamatan}.json, detail tiap wilayah, serta postal-codes/{kode_pos}.json (tools/generate_static_api.go:214-24617). Fase B mewajibkan 11 kolom per baris dan berhenti dengan phase B: %s: expected %d fields, got %d bila kurang. Baris dengan kolom path yang bukan JSON valid tidak fatal; ia hanya dilaporkan sebagai phase B: skipping invalid path for %s. Setiap koordinat dibulatkan ke 6 angka desimal lewat roundPathCoords sebelum ditulis (tools/generate_static_api.go:294-35318, tools/generate_static_api.go:653-66519). Setelah itu fase B menulis ulang provinces.json, provinces/{kode}.json, daftar regencies/{kode}.json, dan detail regencies/{kode}.json supaya berisi field kaya (tools/generate_static_api.go:355-38220).
writeStats mengisi api/stats.json dengan total_provinces, total_regencies, total_districts, total_villages, total_postal_codes, total_paths, total_population, dan total_area (tools/generate_static_api.go:384-41821).
⚠️
writeGenJSONIndentdanwriteGenJSONCompactsama-sama memanggilwriteGenJSONdenganindent: false, sehingga seluruh keluaran — termasuk berkas yang namanya menyiratkan indentasi — ditulis satu baris tanpa pretty-print (tools/generate_static_api.go:569-59522).
3. generate-boundaries: unduh, konversi, tulis paths
Perintah ini mengunduh defaultZipURL ke .cache/wilayah_boundaries.zip, mengekstraknya ke .cache/extracted, lalu mencari direktori db di dalamnya; tanpa -skip-download unduhan selalu diulang (tools/generate_boundaries.go:98-12423). Untuk setiap berkas SQL, ia menulis CSV hasil konversi ke -data-boundaries, menulis api/paths/{kode}.json dengan path yang sudah dibulatkan, dan memperbarui lat/lng pada berkas detail maupun list (tools/generate_boundaries.go:228-25224). Di akhir ia hanya menghitung ulang total_paths di api/stats.json (tools/generate_boundaries.go:307-31125, tools/generate_boundaries.go:665-70526). Rincian bentuk geometri dan cakupan wilayah ada di Polygon dan Boundary.
4. add-has-path: menempelkan has_path
buildHasPathSet membaca api/paths/, mengambil nama berkas tanpa .json sebagai kode, dan mengembalikan set kosong bila direktori itu belum ada (tools/add_has_path.go:110-12727). Setiap target lalu dibaca, has_path diisi hasPathSet[id], meta.updated_at disegarkan, meta.generated_at dihapus, dan berkas ditulis ulang secara compact (tools/add_has_path.go:143-17628). Kegagalan pada satu berkas tidak menghentikan proses: ia hanya dicatat sebagai warning: patch %s: %v, dan hasil akhirnya dilaporkan sebagai [has_path] patched %d/%d files (tools/add_has_path.go:94-10829).
5. generate-missings: daftar wilayah yang kurang
Wilayah masuk daftar bila !has_path || !has_latlng (tools/generate_missings.go:109-11630). Hasilnya api/missings.json berisi data, meta, dan summary, dengan summary memuat total_missing, total_missing_path, total_missing_latlng, total_missing_both, serta by_level untuk province, regency, district, dan village (tools/generate_missings.go:151-17531). Arti setiap medan diuraikan di Kelengkapan Data dan missings.json.
6. update-stats: hitung endpoint dan ukuran
stats.json wajib sudah ada; bila tidak, perintah berhenti dengan stats file not found %s: %w (tools/update_stats.go:51-5532). filepath.WalkDir menyusuri -out, melewati direktori berawalan titik, dan hanya menghitung berkas berakhiran .json sebagai endpoint sambil menjumlahkan ukuran logis dan ukuran disk tiap berkas (tools/update_stats.go:62-9833). Lima kunci ditulis ke data: total_endpoints, total_filesize, total_filesize_human, total_disk_usage, total_disk_usage_human, dan meta.updated_at diperbarui (tools/update_stats.go:121-13034).
Karena menulis stats.json sendiri mengubah ukuran berkas, fungsi ini mengukur ulang berkas itu dan mengoreksi total_filesize serta total_disk_usage dengan selisih ukuran lama dan baru, lalu menulis ulang stats.json sekali lagi (tools/update_stats.go:136-15735).
Jebakan urutan
⚠️ Menjalankan
update-statslebih dulu membuat angkatotal_endpointsdan ukuran tidak mencakup berkas yang lahir sesudahnya, sehinggastats.jsonharus jadi langkah terakhir.
⚠️ Struktur
genShortItemdangenPlacememang punya medanHasPath, tetapi fase A dan fase B tidak pernah mengisinya, sehingga menjalankangenerate-static-apisetelahadd-has-pathmengembalikanhas_pathkefalsedi berkas yang ditulis ulang (tools/generate_static_api.go:100-12436).
⚠️
generate-boundaries -skip-downloadmemakai zip apa pun yang ada di.cache; zip kosong atau usang membuatpathsyang dihasilkan tidak sesuai dengandata/saat itu (tools/generate_boundaries.go:98-12423).