Skip to content

Konverter Markdown yang bisa dipercaya justru pilih menolak

Adityo Guni Waluyo

Parser Markdown dua arah di alat sync repo ke Plane Pages sengaja menolak sintaks di luar subset, biar mirror nggak rusak diam-diam.

Ringkasan

Pull dokumen ke Plane gagal total cuma gara gara raw HTML di baris 57 langsung error. Parsernya emang dibikin strict cuma mau subset kecil kayak heading list tabel jadi sisanya ditolak pakai nomor baris. Galak di awal tapi enak banget soalnya mending error cepet daripada konten kepotong diam diam baru ketahuan belakangan.

Saya jalankan perintah pull di alat sinkronisasi dokumen dari repo ke Plane, dan yang keluar bukan isi halaman, melainkan satu baris error: line 57: raw HTML is not supported in markdown. Nggak ada render setengah jadi, nggak ada konten yang hilang diam-diam. Konverternya mogok kerja penuh, lengkap dengan nomor baris tempat dia menyerah.

Reaksi saya dulu beda. Parser Markdown yang baik itu pemaaf, begitu keyakinan saya bertahun-tahun. Kalo ada sintaks aneh, dia cuek aja atau baca seadanya, kayak renderer Markdown di kebanyakan tempat. Akibatnya halus tapi mahal: konten yang kepotong diam-diam baru ketahuan pas dibandingin sama sumbernya, kadang jauh setelah data itu dikonsumsi sistem lain, dan yang ngasih tahu sering kali pembaca, bukan alatnya.

Modul mdhtml.py di skill plane-doc-sync sengaja dibangun dengan cara pikir sebaliknya. Fungsi parse_md, render_blocks, dan normalize_md cuma nerima subset tertutup: heading ATX sampai h4, paragraf, list bersarang lewat indentasi, tabel gaya GitHub lengkap dengan pipe yang di-escape dan baris pendek yang di-padding, bold, italic, inline code, link, gambar yang wajib di baris sendiri, fenced code block dengan bahasa yang dipertahankan, blockquote satu tingkat, dan garis pemisah. Gambar sendiri dirender jadi penunjuk repo yang kelihatan, karena halaman Community Edition nggak punya lampiran. Sisanya dilempar keluar sebagai UnsupportedMarkdownError, kelas error sendiri yang pesannya selalu berawalan nomor baris, persis kayak pesan di pembuka. Frontmatter YAML juga nggak ikut dikonversi: dia metadata sisi repo, dipisah sebelum parsing, nggak pernah di-push, lalu dipasang balik pas pull.

# mdhtml.py: isi code span dilindungi dulu supaya
# contoh HTML literal nggak dianggap markup
if _RAW_HTML_RE.search(_CODE_SPAN_RE.sub("\x00", line)):
    raise UnsupportedMarkdownError(
        "raw HTML is not supported in markdown", line_no,
    )

Bentuk penolakannya bervariasi sesuai kejadiannya, dan semuanya bisa direproduksi dari test suite: heading setext disuruh pake tanda pagar, blockquote bertingkat ditolak, code fence yang nggak ditutup dilaporin di baris terakhir file, list yang campur marker juga. Pesannya singkat, tapi isinya petunjuk perbaikan, bukan cuma keluhan. Waktu migrasi dokumen lama, daftar error kayak gini jadi checklist: perbaiki barisnya, jalanin lagi, beres. Bandingkan sama sesi debugging yang dimulai dari "kok kontennya beda ya" tanpa satu petunjuk pun.

Ambiguitas Markdown bukan cerita baru. Deskripsi sintaks aslinya nggak tegas, dan dalam sepuluh tahun lebih implementasinya saling beda; yang bikin parah, karena nggak ada satu pun konstruksi Markdown yang dianggap syntax error, hasil yang beda antar-tools sering baru ketahuan belakangan [1]. Nggak semua implementasi juga menghasilkan output yang sama persis untuk input yang sama [3]. CommonMark menjawabnya lewat jalan besar: membakukan tata bahasa secara unambiguous [2]. Konverter ini pilih jalan satunya: grammar justru ditutup sampai tinggal subset kecil, jadi parser nggak punya ruang buat nebak. Kontraknya jujur: yang belum didukung ditolak di tempat kejadian, lengkap dengan barisnya, bukan ditebak pelan-pelan.

Kanonisasi tinggal di sisi repo

Sisi Plane nggak pernah netral soal HTML yang kita kirim: payload bakal disanitasi, diubah ke format editor, dan seluruh body diganti saat update; dokumentasinya sampai menyuruh kita fetch ulang halaman kalau mau lihat HTML akhir yang tersimpan [4]. Kesetaraan byte lewat kabel jadi mustahil, dan justru karena itu normalize_md hidup di sisi repo: perbandingan dilakukan lewat bentuk kanonik, bukan byte mentah. Jaminannya pun diuji sebagai properti di test suite, bukan dibuktikan pakai satu contoh doang: normalize_md(html_to_md(md_to_html(teks))) harus sama persis dengan normalize_md(teks).

Pengalaman saya, alat mirror yang bengis saat parsing justru paling nyaman dipakai dalam jangka panjang. Error tiga detik di terminal jauh lebih murah daripada mengaudit isi dokumen yang kepotong diam-diam di tracker. Jadi kalau lagi bangun alat yang output-nya dipercaya mesin lain, bikin parser-nya berani menolak. Yang agak lucu, sejak subset-nya ketat saya hampir nggak pernah ketemu error-nya, karena dokumen yang disiplin emang nggak nyentuh batas.

Sumber

Artikel terkait