laporan-generator 2.7.0 → 2.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/laporan-generator/SKILL.md +30 -15
- package/.agents/skills/laporan-generator/references/campus-guide.md +8 -2
- package/.agents/skills/laporan-generator/references/metadata-schema.md +39 -11
- package/.agents/skills/laporan-generator/references/template-guide.md +20 -14
- package/.agents/skills/laporan-generator/resources/cover.md +18 -14
- package/.agents/skills/laporan-generator/resources/docx.lua +208 -5
- package/.agents/skills/laporan-generator/resources/laporan +2 -2
- package/.agents/skills/laporan-generator/resources/laporan.ps1 +55 -25
- package/.agents/skills/laporan-generator/resources/metadata.yml +13 -16
- package/.agents/skills/laporan-generator/resources/presets/ipb-ppki.yml +27 -0
- package/.agents/skills/laporan-generator/resources/presets/its-skripsi.yml +6 -6
- package/.agents/skills/laporan-generator/resources/presets/telkom-ta.yml +26 -0
- package/.agents/skills/laporan-generator/resources/presets/ub-skripsi.yml +26 -0
- package/.agents/skills/laporan-generator/resources/presets/ui-skripsi.yml +6 -6
- package/.agents/skills/laporan-generator/resources/presets/unair-skripsi.yml +26 -0
- package/.agents/skills/laporan-generator/resources/presets/undip-skripsi.yml +26 -0
- package/.agents/skills/laporan-generator/resources/scripts/bundle.py +7 -1
- package/.agents/skills/laporan-generator/resources/scripts/docx-pagenum.py +6 -2
- package/.agents/skills/laporan-generator/resources/scripts/finalize-docx.py +13 -0
- package/.agents/skills/laporan-generator/resources/scripts/setup-deps.sh +9 -3
- package/.agents/skills/laporan-generator/resources/scripts/test_scripts.py +130 -0
- package/.agents/skills/laporan-generator/resources/template.typ +3 -0
- package/.agents/skills/laporan-generator/scripts/build-docx.sh +122 -0
- package/.agents/skills/laporan-generator/scripts/build.sh +32 -8
- package/.agents/skills/laporan-generator/scripts/bundle.py +7 -1
- package/.agents/skills/laporan-generator/scripts/docx-pagenum.py +6 -2
- package/.agents/skills/laporan-generator/scripts/finalize-docx.py +13 -0
- package/.agents/skills/laporan-generator/scripts/laporan +55 -5
- package/.agents/skills/laporan-generator/scripts/laporan.ps1 +628 -529
- package/.agents/skills/laporan-generator/scripts/setup-deps.sh +9 -3
- package/.agents/skills/laporan-generator/scripts/test_scripts.py +130 -0
- package/README.md +29 -19
- package/bin/laporan-generator.js +34 -6
- package/cover.md +18 -14
- package/docs/campus-guide.md +8 -2
- package/docs/metadata-schema.md +39 -11
- package/docs/template-guide.md +20 -14
- package/docx.lua +208 -5
- package/laporan +2 -2
- package/laporan.ps1 +55 -25
- package/lib/init.js +28 -3
- package/lib/mcp.js +21 -9
- package/lib/sync-hosts.js +26 -2
- package/metadata.yml +13 -16
- package/package.json +3 -2
- package/presets/ipb-ppki.yml +27 -0
- package/presets/its-skripsi.yml +6 -6
- package/presets/telkom-ta.yml +26 -0
- package/presets/ub-skripsi.yml +26 -0
- package/presets/ui-skripsi.yml +6 -6
- package/presets/unair-skripsi.yml +26 -0
- package/presets/undip-skripsi.yml +26 -0
- package/scripts/bundle.py +7 -1
- package/scripts/docx-pagenum.py +6 -2
- package/scripts/finalize-docx.py +13 -0
- package/scripts/setup-deps.sh +9 -3
- package/scripts/test_scripts.py +130 -0
- package/template.typ +3 -0
|
@@ -40,7 +40,7 @@ function Get-PythonCommand {
|
|
|
40
40
|
|
|
41
41
|
function Show-Banner {
|
|
42
42
|
Write-Host " ========================================================" -ForegroundColor Cyan
|
|
43
|
-
Write-Host " LAPORAN GENERATOR CLI v2.
|
|
43
|
+
Write-Host " LAPORAN GENERATOR CLI v2.9.0 " -ForegroundColor Cyan
|
|
44
44
|
Write-Host " Otomatisasi Dokumen Akademik (Typst + DOCX Engine) " -ForegroundColor Cyan
|
|
45
45
|
Write-Host " ========================================================" -ForegroundColor Cyan
|
|
46
46
|
Write-Host ""
|
|
@@ -60,7 +60,9 @@ function Show-Help {
|
|
|
60
60
|
Write-Host " doctor Audit kesehatan proyek (broken images, sitasi hilang, dll.)" -ForegroundColor Green
|
|
61
61
|
Write-Host " bundle Kemas seluruh laporan (PDF, DOCX, MD) menjadi arsip zip" -ForegroundColor Green
|
|
62
62
|
Write-Host " setup Pasang dependensi sistem otomatis Windows (Typst, Pandoc, Magick)" -ForegroundColor Green
|
|
63
|
-
Write-Host "
|
|
63
|
+
Write-Host " mcp Jalankan Model Context Protocol (MCP) Server untuk AI Agent" -ForegroundColor Green
|
|
64
|
+
Write-Host " sync-skills Sinkronkan skill AI agent & MCP ke Antigravity, Claude, OpenCode" -ForegroundColor Green
|
|
65
|
+
Write-Host " uninstall Hapus integrasi skill dan perintah AI agent dari seluruh sistem" -ForegroundColor Green
|
|
64
66
|
Write-Host " check Audit dependensi sistem dan struktur proyek" -ForegroundColor Green
|
|
65
67
|
Write-Host " test Jalankan suite pengujian otomatis" -ForegroundColor Green
|
|
66
68
|
Write-Host " view Buka dokumen Laporan.pdf di PDF viewer" -ForegroundColor Green
|
|
@@ -336,29 +338,29 @@ preset: "$escPreset"
|
|
|
336
338
|
Write-Host "Jalankan '.\laporan.ps1 build' untuk mulai menghasilkan laporan." -ForegroundColor Cyan
|
|
337
339
|
}
|
|
338
340
|
|
|
339
|
-
function Cmd-Test {
|
|
340
|
-
Show-Banner
|
|
341
|
-
Write-Host "Menjalankan Test Suite..." -ForegroundColor Blue
|
|
342
|
-
$gitBash = "C:\Program Files\Git\bin\bash.exe"
|
|
343
|
-
$bashCmd = if (Test-Path $gitBash) { $gitBash } elseif ((Get-Command "bash" -ErrorAction SilentlyContinue) -and (Get-Command "bash").Source -notmatch "system32\\bash\.exe") { "bash" } else { "" }
|
|
344
|
-
if ($bashCmd -and (Test-Path "test.sh")) {
|
|
345
|
-
& $bashCmd test.sh
|
|
346
|
-
} else {
|
|
347
|
-
$pyCmd = Get-PythonCommand
|
|
348
|
-
if ($pyCmd) {
|
|
349
|
-
Write-Host "Menjalankan validasi skema preset..." -ForegroundColor Blue
|
|
350
|
-
if (Test-Path "scripts/validate-preset.py") {
|
|
351
|
-
& $pyCmd scripts/validate-preset.py --all
|
|
352
|
-
}
|
|
353
|
-
if (Test-Path "scripts/test_scripts.py") {
|
|
354
|
-
Write-Host ""
|
|
355
|
-
Write-Host "Menjalankan unit test skrip Python..." -ForegroundColor Blue
|
|
356
|
-
& $pyCmd scripts/test_scripts.py
|
|
357
|
-
}
|
|
358
|
-
} else {
|
|
359
|
-
Write-Host "Test suite membutuhkan bash atau python3." -ForegroundColor Yellow
|
|
360
|
-
}
|
|
361
|
-
}
|
|
341
|
+
function Cmd-Test {
|
|
342
|
+
Show-Banner
|
|
343
|
+
Write-Host "Menjalankan Test Suite..." -ForegroundColor Blue
|
|
344
|
+
$gitBash = "C:\Program Files\Git\bin\bash.exe"
|
|
345
|
+
$bashCmd = if (Test-Path $gitBash) { $gitBash } elseif ((Get-Command "bash" -ErrorAction SilentlyContinue) -and (Get-Command "bash").Source -notmatch "system32\\bash\.exe") { "bash" } else { "" }
|
|
346
|
+
if ($bashCmd -and (Test-Path "test.sh")) {
|
|
347
|
+
& $bashCmd test.sh
|
|
348
|
+
} else {
|
|
349
|
+
$pyCmd = Get-PythonCommand
|
|
350
|
+
if ($pyCmd) {
|
|
351
|
+
Write-Host "Menjalankan validasi skema preset..." -ForegroundColor Blue
|
|
352
|
+
if (Test-Path "scripts/validate-preset.py") {
|
|
353
|
+
& $pyCmd scripts/validate-preset.py --all
|
|
354
|
+
}
|
|
355
|
+
if (Test-Path "scripts/test_scripts.py") {
|
|
356
|
+
Write-Host ""
|
|
357
|
+
Write-Host "Menjalankan unit test skrip Python..." -ForegroundColor Blue
|
|
358
|
+
& $pyCmd scripts/test_scripts.py
|
|
359
|
+
}
|
|
360
|
+
} else {
|
|
361
|
+
Write-Host "Test suite membutuhkan bash atau python3." -ForegroundColor Yellow
|
|
362
|
+
}
|
|
363
|
+
}
|
|
362
364
|
}
|
|
363
365
|
|
|
364
366
|
function Cmd-Preset {
|
|
@@ -587,6 +589,34 @@ switch ($Command.ToLower()) {
|
|
|
587
589
|
Write-Host "Node.js diperlukan untuk sinkronisasi multi-host AI agent." -ForegroundColor Yellow
|
|
588
590
|
}
|
|
589
591
|
}
|
|
592
|
+
"uninstall" {
|
|
593
|
+
if (Get-Command "node" -ErrorAction SilentlyContinue) {
|
|
594
|
+
$genPath = Join-Path $PSScriptRoot "bin\laporan-generator.js"
|
|
595
|
+
if (Test-Path $genPath) {
|
|
596
|
+
node $genPath uninstall @extraArgs
|
|
597
|
+
} elseif (Get-Command "npx" -ErrorAction SilentlyContinue) {
|
|
598
|
+
npx -y laporan-generator uninstall @extraArgs
|
|
599
|
+
} else {
|
|
600
|
+
Write-Host "bin\laporan-generator.js tidak ditemukan dan npx tidak tersedia." -ForegroundColor Red
|
|
601
|
+
}
|
|
602
|
+
} else {
|
|
603
|
+
Write-Host "Node.js diperlukan untuk membersihkan integrasi AI agent." -ForegroundColor Yellow
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
"clean-skills" {
|
|
607
|
+
if (Get-Command "node" -ErrorAction SilentlyContinue) {
|
|
608
|
+
$genPath = Join-Path $PSScriptRoot "bin\laporan-generator.js"
|
|
609
|
+
if (Test-Path $genPath) {
|
|
610
|
+
node $genPath uninstall @extraArgs
|
|
611
|
+
} elseif (Get-Command "npx" -ErrorAction SilentlyContinue) {
|
|
612
|
+
npx -y laporan-generator uninstall @extraArgs
|
|
613
|
+
} else {
|
|
614
|
+
Write-Host "bin\laporan-generator.js tidak ditemukan dan npx tidak tersedia." -ForegroundColor Red
|
|
615
|
+
}
|
|
616
|
+
} else {
|
|
617
|
+
Write-Host "Node.js diperlukan untuk membersihkan integrasi AI agent." -ForegroundColor Yellow
|
|
618
|
+
}
|
|
619
|
+
}
|
|
590
620
|
"check" { Cmd-Check }
|
|
591
621
|
"clean" { Cmd-Clean }
|
|
592
622
|
"view" { Cmd-View }
|
|
@@ -15,21 +15,18 @@ institution: "Universitas Example"
|
|
|
15
15
|
faculty: "Program Studi Informatika"
|
|
16
16
|
year: "2026/2027"
|
|
17
17
|
date: "Agustus 2026"
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
abstract_en: |
|
|
27
|
-
|
|
28
|
-
keywords_en:
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
- Typst
|
|
32
|
-
- OpenXML
|
|
33
|
-
- Software Engineering
|
|
18
|
+
daftar_gambar: false
|
|
19
|
+
daftar_tabel: false
|
|
20
|
+
# Komponen halaman depan opsional (aktifkan jika diperlukan):
|
|
21
|
+
# abstract_id: |
|
|
22
|
+
# Ringkasan atau abstrak laporan dalam Bahasa Indonesia...
|
|
23
|
+
# keywords_id:
|
|
24
|
+
# - Kata Kunci 1
|
|
25
|
+
# - Kata Kunci 2
|
|
26
|
+
# abstract_en: |
|
|
27
|
+
# Summary or abstract of the report in English...
|
|
28
|
+
# keywords_en:
|
|
29
|
+
# - Keyword 1
|
|
30
|
+
# - Keyword 2
|
|
34
31
|
preset: "standard"
|
|
35
32
|
...
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Preset: IPB University (Institut Pertanian Bogor)
|
|
2
|
+
preset_id: "ipb-ppki"
|
|
3
|
+
name: "IPB University (PPKI)"
|
|
4
|
+
description: "Format Tugas Akhir sesuai Pedoman Penulisan Karya Ilmiah (PPKI) IPB University (Margin Kiri 4cm, Atas/Kanan/Bawah 3cm)."
|
|
5
|
+
|
|
6
|
+
# 1. Geometri & Margin Halaman (Standar Resmi PPKI IPB: Kiri 4cm, Atas 3cm, Bawah 3cm, Kanan 3cm)
|
|
7
|
+
margin_top: 3cm
|
|
8
|
+
margin_bottom: 3cm
|
|
9
|
+
margin_left: 4cm
|
|
10
|
+
margin_right: 3cm
|
|
11
|
+
|
|
12
|
+
# 2. Tipografi & Spasi
|
|
13
|
+
font_family: "Libertinus Serif"
|
|
14
|
+
font_size: 12pt
|
|
15
|
+
line_spacing: 0.75em
|
|
16
|
+
first_line_indent: 1.0cm
|
|
17
|
+
|
|
18
|
+
# 3. Penomoran Heading
|
|
19
|
+
heading_chapter_prefix: "BAB "
|
|
20
|
+
heading_chapter_num_format: "roman"
|
|
21
|
+
heading_sub_dot: false
|
|
22
|
+
heading_subsub_dot: false
|
|
23
|
+
|
|
24
|
+
# 4. Pengaturan Halaman Sampul
|
|
25
|
+
cover_show_lecturer: false
|
|
26
|
+
cover_hide_lecturer: true
|
|
27
|
+
cover_logo_width: 4cm
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Preset: Institut Teknologi Sepuluh Nopember (ITS)
|
|
2
2
|
preset_id: "its-skripsi"
|
|
3
3
|
name: "Institut Teknologi Sepuluh Nopember (ITS)"
|
|
4
|
-
description: "Format Tugas Akhir & Skripsi
|
|
4
|
+
description: "Format Tugas Akhir & Skripsi sesuai Pedoman Resmi ITS (Margin Atas 3cm, Kiri 3cm, Bawah 2.5cm, Kanan 2cm)."
|
|
5
5
|
|
|
6
|
-
# Margin halaman (Standar ITS:
|
|
7
|
-
margin_top:
|
|
8
|
-
margin_bottom:
|
|
9
|
-
margin_left:
|
|
10
|
-
margin_right:
|
|
6
|
+
# Margin halaman (Standar Resmi Pedoman TA ITS: Atas 3.0cm, Bawah 2.5cm, Kiri 3.0cm, Kanan 2.0cm)
|
|
7
|
+
margin_top: 3cm
|
|
8
|
+
margin_bottom: 2.5cm
|
|
9
|
+
margin_left: 3cm
|
|
10
|
+
margin_right: 2cm
|
|
11
11
|
|
|
12
12
|
# Tipografi
|
|
13
13
|
font_family: "Libertinus Serif"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Preset: Telkom University (Tel-U)
|
|
2
|
+
preset_id: "telkom-ta"
|
|
3
|
+
name: "Telkom University (Tel-U)"
|
|
4
|
+
description: "Format Buku Tugas Akhir sesuai Pedoman Akademik Telkom University (Margin Kiri 4cm, Atas/Kanan/Bawah 3cm)."
|
|
5
|
+
|
|
6
|
+
# 1. Geometri & Margin Halaman (Standar TA Telkom University: Kiri 4cm, Atas 3cm, Bawah 3cm, Kanan 3cm)
|
|
7
|
+
margin_top: 3cm
|
|
8
|
+
margin_bottom: 3cm
|
|
9
|
+
margin_left: 4cm
|
|
10
|
+
margin_right: 3cm
|
|
11
|
+
|
|
12
|
+
# 2. Tipografi & Spasi
|
|
13
|
+
font_family: "Libertinus Serif"
|
|
14
|
+
font_size: 12pt
|
|
15
|
+
line_spacing: 0.75em
|
|
16
|
+
first_line_indent: 1.25cm
|
|
17
|
+
|
|
18
|
+
# 3. Penomoran Heading
|
|
19
|
+
heading_chapter_prefix: "BAB "
|
|
20
|
+
heading_chapter_num_format: "roman"
|
|
21
|
+
heading_sub_dot: true
|
|
22
|
+
heading_subsub_dot: false
|
|
23
|
+
|
|
24
|
+
# 4. Pengaturan Halaman Sampul
|
|
25
|
+
cover_show_lecturer: true
|
|
26
|
+
cover_logo_width: 4cm
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Preset: Universitas Brawijaya (UB)
|
|
2
|
+
preset_id: "ub-skripsi"
|
|
3
|
+
name: "Universitas Brawijaya (UB)"
|
|
4
|
+
description: "Format Skripsi & Tugas Akhir sesuai Pedoman Penulisan Karya Ilmiah Universitas Brawijaya (Margin 4-4-3-3)."
|
|
5
|
+
|
|
6
|
+
# 1. Geometri & Margin Halaman (Standar UB: Atas 4cm, Kiri 4cm, Bawah 3cm, Kanan 3cm)
|
|
7
|
+
margin_top: 4cm
|
|
8
|
+
margin_bottom: 3cm
|
|
9
|
+
margin_left: 4cm
|
|
10
|
+
margin_right: 3cm
|
|
11
|
+
|
|
12
|
+
# 2. Tipografi & Spasi
|
|
13
|
+
font_family: "Libertinus Serif"
|
|
14
|
+
font_size: 12pt
|
|
15
|
+
line_spacing: 0.75em
|
|
16
|
+
first_line_indent: 1.25cm
|
|
17
|
+
|
|
18
|
+
# 3. Penomoran Heading
|
|
19
|
+
heading_chapter_prefix: "BAB "
|
|
20
|
+
heading_chapter_num_format: "roman"
|
|
21
|
+
heading_sub_dot: true
|
|
22
|
+
heading_subsub_dot: false
|
|
23
|
+
|
|
24
|
+
# 4. Pengaturan Halaman Sampul
|
|
25
|
+
cover_show_lecturer: true
|
|
26
|
+
cover_logo_width: 4cm
|
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
# Preset: Universitas Indonesia (UI)
|
|
2
2
|
preset_id: "ui-skripsi"
|
|
3
3
|
name: "Universitas Indonesia (UI)"
|
|
4
|
-
description: "Format Tugas Akhir & Skripsi sesuai
|
|
4
|
+
description: "Format Tugas Akhir & Skripsi sesuai Keputusan Rektor UI (Margin Kiri 4cm, Atas/Kanan/Bawah 3cm, TNR 12pt)."
|
|
5
5
|
|
|
6
|
-
# Margin halaman (Standar
|
|
7
|
-
margin_top:
|
|
8
|
-
margin_bottom:
|
|
9
|
-
margin_left:
|
|
10
|
-
margin_right:
|
|
6
|
+
# Margin halaman (Standar Resmi Pedoman Teknis TA UI: Kiri 4cm, Atas 3cm, Bawah 3cm, Kanan 3cm)
|
|
7
|
+
margin_top: 3cm
|
|
8
|
+
margin_bottom: 3cm
|
|
9
|
+
margin_left: 4cm
|
|
10
|
+
margin_right: 3cm
|
|
11
11
|
|
|
12
12
|
# Tipografi
|
|
13
13
|
font_family: "Libertinus Serif"
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Preset: Universitas Airlangga (UNAIR)
|
|
2
|
+
preset_id: "unair-skripsi"
|
|
3
|
+
name: "Universitas Airlangga (UNAIR)"
|
|
4
|
+
description: "Format Skripsi & Karya Ilmiah sesuai Pedoman Penulisan Universitas Airlangga (Margin 4-4-3-3)."
|
|
5
|
+
|
|
6
|
+
# 1. Geometri & Margin Halaman (Standar UNAIR: Atas 4cm, Kiri 4cm, Bawah 3cm, Kanan 3cm)
|
|
7
|
+
margin_top: 4cm
|
|
8
|
+
margin_bottom: 3cm
|
|
9
|
+
margin_left: 4cm
|
|
10
|
+
margin_right: 3cm
|
|
11
|
+
|
|
12
|
+
# 2. Tipografi & Spasi
|
|
13
|
+
font_family: "Libertinus Serif"
|
|
14
|
+
font_size: 12pt
|
|
15
|
+
line_spacing: 0.75em
|
|
16
|
+
first_line_indent: 1.25cm
|
|
17
|
+
|
|
18
|
+
# 3. Penomoran Heading
|
|
19
|
+
heading_chapter_prefix: "BAB "
|
|
20
|
+
heading_chapter_num_format: "roman"
|
|
21
|
+
heading_sub_dot: true
|
|
22
|
+
heading_subsub_dot: false
|
|
23
|
+
|
|
24
|
+
# 4. Pengaturan Halaman Sampul
|
|
25
|
+
cover_show_lecturer: true
|
|
26
|
+
cover_logo_width: 4cm
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# Preset: Universitas Diponegoro (UNDIP)
|
|
2
|
+
preset_id: "undip-skripsi"
|
|
3
|
+
name: "Universitas Diponegoro (UNDIP)"
|
|
4
|
+
description: "Format Skripsi & Tugas Akhir sesuai Pedoman Akademik Universitas Diponegoro (Margin 4-4-3-3)."
|
|
5
|
+
|
|
6
|
+
# 1. Geometri & Margin Halaman (Standar UNDIP: Atas 4cm, Kiri 4cm, Bawah 3cm, Kanan 3cm)
|
|
7
|
+
margin_top: 4cm
|
|
8
|
+
margin_bottom: 3cm
|
|
9
|
+
margin_left: 4cm
|
|
10
|
+
margin_right: 3cm
|
|
11
|
+
|
|
12
|
+
# 2. Tipografi & Spasi
|
|
13
|
+
font_family: "Libertinus Serif"
|
|
14
|
+
font_size: 12pt
|
|
15
|
+
line_spacing: 0.75em
|
|
16
|
+
first_line_indent: 1.25cm
|
|
17
|
+
|
|
18
|
+
# 3. Penomoran Heading
|
|
19
|
+
heading_chapter_prefix: "BAB "
|
|
20
|
+
heading_chapter_num_format: "roman"
|
|
21
|
+
heading_sub_dot: true
|
|
22
|
+
heading_subsub_dot: false
|
|
23
|
+
|
|
24
|
+
# 4. Pengaturan Halaman Sampul
|
|
25
|
+
cover_show_lecturer: true
|
|
26
|
+
cover_logo_width: 4cm
|
|
@@ -36,7 +36,13 @@ def main():
|
|
|
36
36
|
# Cek apakah PDF & DOCX sudah ada
|
|
37
37
|
if not os.path.isfile("Laporan.pdf") or not os.path.isfile("Laporan.docx"):
|
|
38
38
|
print(f" {BLUE}[1/3] Menjalankan build PDF dan DOCX...{NC}")
|
|
39
|
-
|
|
39
|
+
import subprocess
|
|
40
|
+
if sys.platform == "win32":
|
|
41
|
+
ret = subprocess.run(["powershell", "-ExecutionPolicy", "Bypass", "-File", "laporan.ps1", "build"]).returncode
|
|
42
|
+
else:
|
|
43
|
+
ret_pdf = subprocess.run(["bash", "build.sh"]).returncode
|
|
44
|
+
ret_docx = subprocess.run(["bash", "build-docx.sh"]).returncode
|
|
45
|
+
ret = ret_pdf or ret_docx
|
|
40
46
|
if ret != 0:
|
|
41
47
|
print("Gagal mengompilasi dokumen untuk bundling.")
|
|
42
48
|
return 1
|
|
@@ -73,6 +73,10 @@ def first_decimal_page(pages, start):
|
|
|
73
73
|
|
|
74
74
|
|
|
75
75
|
def find_page(pages, needle, start, end):
|
|
76
|
+
for p in range(start, end + 1):
|
|
77
|
+
lines = pages.get(p, [])
|
|
78
|
+
if any(l == needle for l in lines):
|
|
79
|
+
return p
|
|
76
80
|
for p in range(start, end + 1):
|
|
77
81
|
text = "\n".join(pages.get(p, []))
|
|
78
82
|
if needle in text:
|
|
@@ -160,8 +164,8 @@ def main():
|
|
|
160
164
|
entries = parse_entries(doc)
|
|
161
165
|
injected = 0
|
|
162
166
|
for para, pid, text in entries:
|
|
163
|
-
if pid
|
|
164
|
-
page = find_page(pages,
|
|
167
|
+
if pid in ("kata-pengantar", "lembar-pengesahan", "abstrak", "abstract"):
|
|
168
|
+
page = find_page(pages, text, 1, toc_start - 1)
|
|
165
169
|
else:
|
|
166
170
|
page = find_page(pages, needle_of(text), first_dec, max(pages))
|
|
167
171
|
if not page:
|
|
@@ -43,6 +43,8 @@ def next_heading_text(doc, start):
|
|
|
43
43
|
def is_front_matter_heading(t):
|
|
44
44
|
norm = t.strip().upper()
|
|
45
45
|
return norm in (
|
|
46
|
+
"LEMBAR PENGESAHAN",
|
|
47
|
+
"HALAMAN PENGESAHAN",
|
|
46
48
|
"KATA PENGANTAR",
|
|
47
49
|
"PRAKATA",
|
|
48
50
|
"ABSTRAK",
|
|
@@ -103,7 +105,18 @@ def add_pgnum(sect, fmt, start):
|
|
|
103
105
|
return sect + tag
|
|
104
106
|
|
|
105
107
|
|
|
108
|
+
def sync_section_margins(doc):
|
|
109
|
+
first_mar = re.search(
|
|
110
|
+
r'<w:sectPr[^>]*>.*?(<w:pgMar\b[^>]*/>).*?</w:sectPr>', doc, re.S
|
|
111
|
+
)
|
|
112
|
+
if not first_mar:
|
|
113
|
+
return doc
|
|
114
|
+
target_pgmar = first_mar.group(1)
|
|
115
|
+
return re.sub(r'<w:pgMar\b[^>]*/>', target_pgmar, doc)
|
|
116
|
+
|
|
117
|
+
|
|
106
118
|
def process(doc):
|
|
119
|
+
doc = sync_section_margins(doc)
|
|
107
120
|
matches = list(SECT_IN_PPR.finditer(doc))
|
|
108
121
|
if not matches:
|
|
109
122
|
return doc, 0
|
|
@@ -83,11 +83,17 @@ elif has_cmd apt-get; then
|
|
|
83
83
|
# Install typst binary jika belum ada
|
|
84
84
|
if ! has_cmd typst; then
|
|
85
85
|
echo -e "--> Mengunduh Typst standalone binary..."
|
|
86
|
-
|
|
86
|
+
ARCH=$(uname -m)
|
|
87
|
+
if [ "$ARCH" = "aarch64" ] || [ "$ARCH" = "arm64" ]; then
|
|
88
|
+
TYPST_DIR="typst-aarch64-unknown-linux-musl"
|
|
89
|
+
else
|
|
90
|
+
TYPST_DIR="typst-x86_64-unknown-linux-musl"
|
|
91
|
+
fi
|
|
92
|
+
TYPST_URL="https://github.com/typst/typst/releases/download/v0.15.1/${TYPST_DIR}.tar.xz"
|
|
87
93
|
wget -q "$TYPST_URL" -O /tmp/typst.tar.xz
|
|
88
94
|
tar -xJf /tmp/typst.tar.xz -C /tmp
|
|
89
|
-
sudo mv /tmp/typst
|
|
90
|
-
rm -rf /tmp/typst.tar.xz /tmp
|
|
95
|
+
sudo mv "/tmp/${TYPST_DIR}/typst" /usr/local/bin/typst
|
|
96
|
+
rm -rf /tmp/typst.tar.xz "/tmp/${TYPST_DIR}"
|
|
91
97
|
fi
|
|
92
98
|
elif has_cmd pacman; then
|
|
93
99
|
echo -e "${CYAN}--> Menggunakan pacman (Arch Linux)...${NC}"
|
|
@@ -84,6 +84,13 @@ class TestDocxPagenum(unittest.TestCase):
|
|
|
84
84
|
self.assertEqual(docx_pagenum.needle_of("1.1 Latar Belakang"), "1.1 Latar Belakang")
|
|
85
85
|
self.assertEqual(docx_pagenum.needle_of("KATA PENGANTAR"), "KATA PENGANTAR")
|
|
86
86
|
|
|
87
|
+
def test_find_page_exact_line_priority(self):
|
|
88
|
+
pages = {
|
|
89
|
+
10: ["Mencegah penomoran ganda pada KATA PENGANTAR dan DAFTAR PUSTAKA.", "6"],
|
|
90
|
+
23: ["DAFTAR PUSTAKA", "American Psychological Association. (2020).", "19"],
|
|
91
|
+
}
|
|
92
|
+
self.assertEqual(docx_pagenum.find_page(pages, "DAFTAR PUSTAKA", 5, 25), 23)
|
|
93
|
+
|
|
87
94
|
def test_roman_pattern(self):
|
|
88
95
|
self.assertTrue(docx_pagenum.ROMAN.match("iv"))
|
|
89
96
|
self.assertTrue(docx_pagenum.ROMAN.match("xii"))
|
|
@@ -97,10 +104,20 @@ class TestDocxPagenum(unittest.TestCase):
|
|
|
97
104
|
|
|
98
105
|
class TestFinalizeDocx(unittest.TestCase):
|
|
99
106
|
def test_classify_front_matter(self):
|
|
107
|
+
self.assertEqual(finalize_docx.classify("LEMBAR PENGESAHAN"), "cover")
|
|
100
108
|
self.assertEqual(finalize_docx.classify("KATA PENGANTAR"), "cover")
|
|
101
109
|
self.assertEqual(finalize_docx.classify("ABSTRAK"), "cover")
|
|
102
110
|
self.assertEqual(finalize_docx.classify("DAFTAR ISI"), "cover")
|
|
103
111
|
|
|
112
|
+
def test_sync_section_margins(self):
|
|
113
|
+
raw_xml = (
|
|
114
|
+
'<w:sectPr><w:pgSz w:w="11906"/><w:pgMar w:top="2268" w:right="1701" w:bottom="1701" w:left="2268"/></w:sectPr>'
|
|
115
|
+
'<w:p><w:pPr><w:sectPr><w:pgMar w:top="1134" w:right="1417" w:bottom="1701" w:left="1417"/></w:sectPr></w:pPr></w:p>'
|
|
116
|
+
)
|
|
117
|
+
synced = finalize_docx.sync_section_margins(raw_xml)
|
|
118
|
+
self.assertEqual(synced.count('w:top="2268"'), 2)
|
|
119
|
+
self.assertEqual(synced.count('w:left="2268"'), 2)
|
|
120
|
+
|
|
104
121
|
def test_classify_standard_thesis_headings(self):
|
|
105
122
|
self.assertEqual(finalize_docx.classify("BAB I"), "roman")
|
|
106
123
|
self.assertEqual(finalize_docx.classify("BAB I PENDAHULUAN"), "roman")
|
|
@@ -184,6 +201,119 @@ class TestReportDoctor(unittest.TestCase):
|
|
|
184
201
|
self.assertIn("citations", data)
|
|
185
202
|
|
|
186
203
|
|
|
204
|
+
class TestFrontMatterOptIn(unittest.TestCase):
|
|
205
|
+
def test_default_metadata_structure(self):
|
|
206
|
+
meta_path = os.path.join(get_project_root(), "metadata.yml")
|
|
207
|
+
with open(meta_path, "r", encoding="utf-8") as f:
|
|
208
|
+
content = f.read()
|
|
209
|
+
# Verify abstract is commented or empty by default
|
|
210
|
+
self.assertNotIn("\nabstract_id: |", content, "abstract_id should not be active by default in metadata.yml")
|
|
211
|
+
self.assertNotIn("\nabstract_en: |", content, "abstract_en should not be active by default in metadata.yml")
|
|
212
|
+
# Verify opt-in flags for daftar_gambar & daftar_tabel are explicitly false or commented
|
|
213
|
+
has_dg = "daftar_gambar: false" in content
|
|
214
|
+
has_dt = "daftar_tabel: false" in content
|
|
215
|
+
self.assertTrue(has_dg, "metadata.yml must explicitly specify daftar_gambar: false by default")
|
|
216
|
+
self.assertTrue(has_dt, "metadata.yml must explicitly specify daftar_tabel: false by default")
|
|
217
|
+
|
|
218
|
+
def test_cover_md_gated_outlines(self):
|
|
219
|
+
cover_path = os.path.join(get_project_root(), "cover.md")
|
|
220
|
+
with open(cover_path, "r", encoding="utf-8") as f:
|
|
221
|
+
content = f.read()
|
|
222
|
+
# Unconditional outline queries must not exist
|
|
223
|
+
self.assertNotIn("let imgs = query(figure.where(kind: image))\n if imgs.len() > 0", content,
|
|
224
|
+
"cover.md must not unconditionally emit DAFTAR GAMBAR outline")
|
|
225
|
+
self.assertNotIn("let tbls = query(figure.where(kind: table))\n if tbls.len() > 0", content,
|
|
226
|
+
"cover.md must not unconditionally emit DAFTAR TABEL outline")
|
|
227
|
+
# Must be gated by opt-in check
|
|
228
|
+
self.assertIn("opt-daftar-gambar", content, "cover.md must gate DAFTAR GAMBAR with opt-daftar-gambar")
|
|
229
|
+
self.assertIn("opt-daftar-tabel", content, "cover.md must gate DAFTAR TABEL with opt-daftar-tabel")
|
|
230
|
+
|
|
231
|
+
def test_lua_meta_bool_coercion(self):
|
|
232
|
+
"""Verify docx.lua handles YAML boolean scalars and string equivalents safely."""
|
|
233
|
+
import shutil
|
|
234
|
+
lua_bin = (
|
|
235
|
+
shutil.which("lua")
|
|
236
|
+
or shutil.which("luajit")
|
|
237
|
+
or shutil.which("lua5.4")
|
|
238
|
+
or shutil.which("lua5.3")
|
|
239
|
+
or shutil.which("lua5.2")
|
|
240
|
+
or shutil.which("lua5.1")
|
|
241
|
+
)
|
|
242
|
+
if not lua_bin:
|
|
243
|
+
self.skipTest("Standalone Lua interpreter not found in PATH")
|
|
244
|
+
|
|
245
|
+
lua_code = """
|
|
246
|
+
local pandoc = {
|
|
247
|
+
utils = {
|
|
248
|
+
stringify = function(x)
|
|
249
|
+
if type(x) ~= 'string' and type(x) ~= 'table' then
|
|
250
|
+
error('pandoc.utils.stringify expects table or string, got ' .. type(x))
|
|
251
|
+
end
|
|
252
|
+
return tostring(x)
|
|
253
|
+
end
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
local function meta_str(meta, key)
|
|
257
|
+
local v = meta[key]
|
|
258
|
+
if v == nil then return "" end
|
|
259
|
+
if type(v) == "boolean" then return tostring(v) end
|
|
260
|
+
return pandoc.utils.stringify(v)
|
|
261
|
+
end
|
|
262
|
+
local function meta_bool(meta, key)
|
|
263
|
+
local v = meta[key]
|
|
264
|
+
if v == nil then return false end
|
|
265
|
+
if type(v) == "boolean" then return v end
|
|
266
|
+
local s = meta_str(meta, key):lower():gsub("%s+", "")
|
|
267
|
+
return s == "true" or s == "1" or s == "yes"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
local meta = {
|
|
271
|
+
dg_bool_false = false,
|
|
272
|
+
dg_bool_true = true,
|
|
273
|
+
dg_str_false = "false",
|
|
274
|
+
dg_str_true = "true",
|
|
275
|
+
dg_nil = nil
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
assert(meta_str(meta, "dg_bool_false") == "false")
|
|
279
|
+
assert(meta_bool(meta, "dg_bool_false") == false)
|
|
280
|
+
assert(meta_str(meta, "dg_bool_true") == "true")
|
|
281
|
+
assert(meta_bool(meta, "dg_bool_true") == true)
|
|
282
|
+
assert(meta_bool(meta, "dg_str_false") == false)
|
|
283
|
+
assert(meta_bool(meta, "dg_str_true") == true)
|
|
284
|
+
assert(meta_bool(meta, "dg_nil") == false)
|
|
285
|
+
print("LUA_OK")
|
|
286
|
+
"""
|
|
287
|
+
proc = subprocess.run([lua_bin, "-e", lua_code], capture_output=True, text=True)
|
|
288
|
+
self.assertEqual(proc.returncode, 0, f"Lua helper test failed: {proc.stderr}")
|
|
289
|
+
self.assertIn("LUA_OK", proc.stdout)
|
|
290
|
+
|
|
291
|
+
def test_init_project_boolean_coercion(self):
|
|
292
|
+
"""Verify lib/init.js does not treat string 'false' as truthy."""
|
|
293
|
+
import shutil
|
|
294
|
+
import tempfile
|
|
295
|
+
if not shutil.which("node"):
|
|
296
|
+
self.skipTest("Node.js runtime not found in PATH")
|
|
297
|
+
|
|
298
|
+
with tempfile.TemporaryDirectory() as tmpdir:
|
|
299
|
+
node_code = f"""
|
|
300
|
+
const path = require('path');
|
|
301
|
+
const {{ initProject }} = require('{os.path.join(ROOT_DIR, "lib/init.js")}');
|
|
302
|
+
initProject({{ targetDir: '{tmpdir}/test_f', daftar_gambar: 'false', daftar_tabel: false, silent: true }});
|
|
303
|
+
initProject({{ targetDir: '{tmpdir}/test_t', daftar_gambar: 'true', daftar_tabel: true, silent: true }});
|
|
304
|
+
"""
|
|
305
|
+
proc = subprocess.run(["node", "-e", node_code], capture_output=True, text=True)
|
|
306
|
+
self.assertEqual(proc.returncode, 0, f"Node initProject failed: {proc.stderr}")
|
|
307
|
+
with open(os.path.join(tmpdir, "test_f", "metadata.yml"), "r", encoding="utf-8") as f:
|
|
308
|
+
meta_f = f.read()
|
|
309
|
+
with open(os.path.join(tmpdir, "test_t", "metadata.yml"), "r", encoding="utf-8") as f:
|
|
310
|
+
meta_t = f.read()
|
|
311
|
+
self.assertIn("daftar_gambar: false", meta_f)
|
|
312
|
+
self.assertIn("daftar_tabel: false", meta_f)
|
|
313
|
+
self.assertIn("daftar_gambar: true", meta_t)
|
|
314
|
+
self.assertIn("daftar_tabel: true", meta_t)
|
|
315
|
+
|
|
316
|
+
|
|
187
317
|
if __name__ == "__main__":
|
|
188
318
|
unittest.main(verbosity=2)
|
|
189
319
|
|
|
@@ -391,4 +391,7 @@ $endif$
|
|
|
391
391
|
#pagebreak()
|
|
392
392
|
$endif$
|
|
393
393
|
|
|
394
|
+
#let opt-daftar-gambar = $if(daftar_gambar)$$daftar_gambar$$else$$if(list_of_figures)$$list_of_figures$$else$false$endif$$endif$
|
|
395
|
+
#let opt-daftar-tabel = $if(daftar_tabel)$$daftar_tabel$$else$$if(list_of_tables)$$list_of_tables$$else$false$endif$$endif$
|
|
396
|
+
|
|
394
397
|
$body$
|