laporan-generator 2.6.1
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 +132 -0
- package/.agents/skills/laporan-generator/agents/openai.yaml +3 -0
- package/.agents/skills/laporan-generator/references/campus-guide.md +120 -0
- package/.agents/skills/laporan-generator/references/metadata-schema.md +76 -0
- package/.agents/skills/laporan-generator/references/preset-schema.md +79 -0
- package/.agents/skills/laporan-generator/references/syntax-cheatsheet.md +122 -0
- package/.agents/skills/laporan-generator/references/template-guide.md +100 -0
- package/.agents/skills/laporan-generator/references/troubleshooting.md +105 -0
- package/.agents/skills/laporan-generator/resources/Makefile +90 -0
- package/.agents/skills/laporan-generator/resources/apa.csl +2273 -0
- package/.agents/skills/laporan-generator/resources/build-docx.sh +122 -0
- package/.agents/skills/laporan-generator/resources/build.sh +102 -0
- package/.agents/skills/laporan-generator/resources/chapters/bab1-pendahuluan.md +60 -0
- package/.agents/skills/laporan-generator/resources/chapters/bab2-tinjauan-pustaka.md +83 -0
- package/.agents/skills/laporan-generator/resources/chapters/bab3-metodologi.md +116 -0
- package/.agents/skills/laporan-generator/resources/chapters/bab4-hasil-dan-pembahasan.md +93 -0
- package/.agents/skills/laporan-generator/resources/chapters/bab5-penutup.md +27 -0
- package/.agents/skills/laporan-generator/resources/cover.md +50 -0
- package/.agents/skills/laporan-generator/resources/docx.lua +416 -0
- package/.agents/skills/laporan-generator/resources/laporan +464 -0
- package/.agents/skills/laporan-generator/resources/laporan.ps1 +552 -0
- package/.agents/skills/laporan-generator/resources/logo.jpg +0 -0
- package/.agents/skills/laporan-generator/resources/metadata.yml +35 -0
- package/.agents/skills/laporan-generator/resources/presets/itb-ta.yml +27 -0
- package/.agents/skills/laporan-generator/resources/presets/its-skripsi.yml +26 -0
- package/.agents/skills/laporan-generator/resources/presets/skripsi-4433.yml +26 -0
- package/.agents/skills/laporan-generator/resources/presets/standard.yml +26 -0
- package/.agents/skills/laporan-generator/resources/presets/ugm-skripsi.yml +26 -0
- package/.agents/skills/laporan-generator/resources/presets/ui-skripsi.yml +26 -0
- package/.agents/skills/laporan-generator/resources/presets/unpad-skripsi.yml +27 -0
- package/.agents/skills/laporan-generator/resources/reference.docx +0 -0
- package/.agents/skills/laporan-generator/resources/references.bib +115 -0
- package/.agents/skills/laporan-generator/resources/scripts/bundle.py +75 -0
- package/.agents/skills/laporan-generator/resources/scripts/docx-pagenum.py +192 -0
- package/.agents/skills/laporan-generator/resources/scripts/finalize-docx.py +291 -0
- package/.agents/skills/laporan-generator/resources/scripts/make-reference-docx.py +670 -0
- package/.agents/skills/laporan-generator/resources/scripts/report-doctor.py +211 -0
- package/.agents/skills/laporan-generator/resources/scripts/report-stats.py +149 -0
- package/.agents/skills/laporan-generator/resources/scripts/scan-preset.py +311 -0
- package/.agents/skills/laporan-generator/resources/scripts/setup-deps.ps1 +152 -0
- package/.agents/skills/laporan-generator/resources/scripts/setup-deps.sh +113 -0
- package/.agents/skills/laporan-generator/resources/scripts/test_scripts.py +128 -0
- package/.agents/skills/laporan-generator/resources/scripts/validate-preset.py +166 -0
- package/.agents/skills/laporan-generator/resources/template.typ +394 -0
- package/.agents/skills/laporan-generator/scripts/build.sh +78 -0
- package/.agents/skills/laporan-generator/scripts/bundle.py +75 -0
- package/.agents/skills/laporan-generator/scripts/docx-pagenum.py +192 -0
- package/.agents/skills/laporan-generator/scripts/finalize-docx.py +152 -0
- package/.agents/skills/laporan-generator/scripts/laporan +447 -0
- package/.agents/skills/laporan-generator/scripts/laporan.ps1 +529 -0
- package/.agents/skills/laporan-generator/scripts/make-reference-docx.py +630 -0
- package/.agents/skills/laporan-generator/scripts/report-doctor.py +211 -0
- package/.agents/skills/laporan-generator/scripts/report-stats.py +149 -0
- package/.agents/skills/laporan-generator/scripts/scan-preset.py +311 -0
- package/.agents/skills/laporan-generator/scripts/setup-deps.ps1 +152 -0
- package/.agents/skills/laporan-generator/scripts/setup-deps.sh +113 -0
- package/.agents/skills/laporan-generator/scripts/test_scripts.py +103 -0
- package/.agents/skills/laporan-generator/scripts/validate-preset.py +166 -0
- package/.agents/workflows/laporan-generator.md +8 -0
- package/LICENSE +21 -0
- package/Makefile +90 -0
- package/README.md +389 -0
- package/apa.csl +2273 -0
- package/bin/laporan-generator.js +71 -0
- package/build-docx.sh +122 -0
- package/build.sh +102 -0
- package/chapters/bab1-pendahuluan.md +60 -0
- package/chapters/bab2-tinjauan-pustaka.md +83 -0
- package/chapters/bab3-metodologi.md +116 -0
- package/chapters/bab4-hasil-dan-pembahasan.md +93 -0
- package/chapters/bab5-penutup.md +27 -0
- package/cover.md +50 -0
- package/docs/campus-guide.md +120 -0
- package/docs/metadata-schema.md +76 -0
- package/docs/preset-schema.md +79 -0
- package/docs/syntax-cheatsheet.md +122 -0
- package/docs/template-guide.md +100 -0
- package/docs/troubleshooting.md +105 -0
- package/docx.lua +416 -0
- package/laporan +464 -0
- package/laporan.ps1 +552 -0
- package/lib/init.js +118 -0
- package/lib/setup.js +33 -0
- package/lib/sync-hosts.js +194 -0
- package/lib/uninstall.js +123 -0
- package/logo.jpg +0 -0
- package/metadata.yml +35 -0
- package/package.json +65 -0
- package/presets/itb-ta.yml +27 -0
- package/presets/its-skripsi.yml +26 -0
- package/presets/skripsi-4433.yml +26 -0
- package/presets/standard.yml +26 -0
- package/presets/ugm-skripsi.yml +26 -0
- package/presets/ui-skripsi.yml +26 -0
- package/presets/unpad-skripsi.yml +27 -0
- package/reference.docx +0 -0
- package/references.bib +115 -0
- package/scripts/bundle.py +75 -0
- package/scripts/docx-pagenum.py +192 -0
- package/scripts/finalize-docx.py +291 -0
- package/scripts/make-reference-docx.py +670 -0
- package/scripts/report-doctor.py +211 -0
- package/scripts/report-stats.py +149 -0
- package/scripts/scan-preset.py +311 -0
- package/scripts/setup-deps.ps1 +152 -0
- package/scripts/setup-deps.sh +113 -0
- package/scripts/test_scripts.py +128 -0
- package/scripts/validate-preset.py +166 -0
- package/template.typ +394 -0
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
# setup-deps.ps1: Universal Dependency Installer untuk Windows
|
|
2
|
+
# Mendukung: Windows 10/11 (Winget) dan Fallback Portable Binary
|
|
3
|
+
|
|
4
|
+
[CmdletBinding()]
|
|
5
|
+
param (
|
|
6
|
+
[switch]$Force,
|
|
7
|
+
[switch]$CheckOnly
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
$ErrorActionPreference = "Stop"
|
|
11
|
+
|
|
12
|
+
# Pastikan folder localBin ada di PATH sesi saat ini jika ada
|
|
13
|
+
$localBin = Join-Path $env:LOCALAPPDATA "laporan-generator\bin"
|
|
14
|
+
if ((Test-Path $localBin) -and ($env:Path -notlike "*$localBin*")) {
|
|
15
|
+
$env:Path = "$localBin;$env:Path"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
Write-Host "==========================================================" -ForegroundColor Cyan
|
|
19
|
+
Write-Host " LAPORAN GENERATOR - WINDOWS DEPENDENCY SETUP " -ForegroundColor Cyan
|
|
20
|
+
Write-Host "==========================================================" -ForegroundColor Cyan
|
|
21
|
+
Write-Host ""
|
|
22
|
+
|
|
23
|
+
$packages = @(
|
|
24
|
+
@{ Id = "Typst.Typst"; Name = "Typst Typesetting Engine"; Cmd = "typst" },
|
|
25
|
+
@{ Id = "JohnMacFarlane.Pandoc"; Name = "Pandoc Document Converter"; Cmd = "pandoc" },
|
|
26
|
+
@{ Id = "ImageMagick.ImageMagick"; Name = "ImageMagick"; Cmd = "magick" }
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
Write-Host "[1/3] Memeriksa dependensi saat ini..." -ForegroundColor Yellow
|
|
30
|
+
$missing = @()
|
|
31
|
+
foreach ($pkg in $packages) {
|
|
32
|
+
$found = Get-Command $pkg.Cmd -ErrorAction SilentlyContinue
|
|
33
|
+
if (-not $found -and $pkg.Cmd -eq "magick" -and -not ($env:OS -match "Windows")) {
|
|
34
|
+
$found = Get-Command "convert" -ErrorAction SilentlyContinue
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if ($found) {
|
|
38
|
+
Write-Host " [OK] $($pkg.Name) ($($pkg.Cmd)) terpasang." -ForegroundColor Green
|
|
39
|
+
} else {
|
|
40
|
+
Write-Host " [MISSING] $($pkg.Name) belum terpasang." -ForegroundColor Red
|
|
41
|
+
$missing += $pkg
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if ($missing.Count -eq 0 -and -not $Force) {
|
|
46
|
+
Write-Host ""
|
|
47
|
+
Write-Host "[OK] Semua dependensi utama sudah lengkap dan siap digunakan!" -ForegroundColor Green
|
|
48
|
+
exit 0
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if ($CheckOnly) {
|
|
52
|
+
Write-Host ""
|
|
53
|
+
Write-Host "Ada $($missing.Count) dependensi yang belum terpasang." -ForegroundColor Red
|
|
54
|
+
exit 1
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
Write-Host ""
|
|
58
|
+
Write-Host "[2/3] Memasang dependensi yang dibutuhkan..." -ForegroundColor Yellow
|
|
59
|
+
|
|
60
|
+
$hasWinget = Get-Command "winget" -ErrorAction SilentlyContinue
|
|
61
|
+
|
|
62
|
+
if ($hasWinget) {
|
|
63
|
+
Write-Host "Menggunakan Windows Package Manager (Winget)..." -ForegroundColor Cyan
|
|
64
|
+
foreach ($pkg in $missing) {
|
|
65
|
+
Write-Host "--> Memasang $($pkg.Name) [$($pkg.Id)]..." -ForegroundColor White
|
|
66
|
+
try {
|
|
67
|
+
$process = Start-Process -FilePath "winget" -ArgumentList "install --id $($pkg.Id) --silent --accept-package-agreements --accept-source-agreements" -Wait -PassThru -NoNewWindow
|
|
68
|
+
if ($process.ExitCode -eq 0) {
|
|
69
|
+
Write-Host " [SUKSES] $($pkg.Name) berhasil dipasang." -ForegroundColor Green
|
|
70
|
+
} else {
|
|
71
|
+
Write-Host " [INFO] Winget exit code: $($process.ExitCode)." -ForegroundColor Yellow
|
|
72
|
+
}
|
|
73
|
+
} catch {
|
|
74
|
+
Write-Host " [GAGAL] Gagal memasang $($pkg.Name): $_" -ForegroundColor Red
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
# Periksa kembali apakah dependensi wajib (typst, pandoc) sudah tersedia setelah winget.
|
|
80
|
+
# Jika belum, unduh binary portable resmi dari GitHub release secara otomatis.
|
|
81
|
+
$typstAvailable = (Get-Command "typst" -ErrorAction SilentlyContinue) -or (Test-Path (Join-Path $localBin "typst.exe"))
|
|
82
|
+
$pandocAvailable = (Get-Command "pandoc" -ErrorAction SilentlyContinue) -or (Test-Path (Join-Path $localBin "pandoc.exe"))
|
|
83
|
+
|
|
84
|
+
if (-not $typstAvailable -or -not $pandocAvailable) {
|
|
85
|
+
Write-Host ""
|
|
86
|
+
Write-Host "Memulai pemasangan portable ke folder lokal ($localBin)..." -ForegroundColor Cyan
|
|
87
|
+
if (-not (Test-Path $localBin)) {
|
|
88
|
+
New-Item -ItemType Directory -Path $localBin -Force | Out-Null
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12
|
|
92
|
+
|
|
93
|
+
# Unduh Typst standalone jika belum ada
|
|
94
|
+
if (-not $typstAvailable) {
|
|
95
|
+
Write-Host "--> Mengunduh Typst standalone release..." -ForegroundColor White
|
|
96
|
+
$typstZip = Join-Path $env:TEMP "typst-x86_64-pc-windows-msvc.zip"
|
|
97
|
+
$typstUrl = "https://github.com/typst/typst/releases/latest/download/typst-x86_64-pc-windows-msvc.zip"
|
|
98
|
+
try {
|
|
99
|
+
Invoke-WebRequest -Uri $typstUrl -OutFile $typstZip -UseBasicParsing
|
|
100
|
+
Expand-Archive -Path $typstZip -DestinationPath (Join-Path $env:TEMP "typst-extracted") -Force
|
|
101
|
+
$typstExe = Get-ChildItem -Path (Join-Path $env:TEMP "typst-extracted") -Filter "typst.exe" -Recurse | Select-Object -First 1
|
|
102
|
+
if ($typstExe) {
|
|
103
|
+
Copy-Item -Path $typstExe.FullName -Destination (Join-Path $localBin "typst.exe") -Force
|
|
104
|
+
Write-Host " [SUKSES] Typst tersimpan di $localBin" -ForegroundColor Green
|
|
105
|
+
}
|
|
106
|
+
} catch {
|
|
107
|
+
Write-Host " [GAGAL] Mengunduh Typst: $_" -ForegroundColor Red
|
|
108
|
+
} finally {
|
|
109
|
+
Remove-Item -Path $typstZip -Force -ErrorAction SilentlyContinue
|
|
110
|
+
Remove-Item -Path (Join-Path $env:TEMP "typst-extracted") -Recurse -Force -ErrorAction SilentlyContinue
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
# Unduh Pandoc standalone jika belum ada
|
|
115
|
+
if (-not $pandocAvailable) {
|
|
116
|
+
Write-Host "--> Mengunduh Pandoc standalone release..." -ForegroundColor White
|
|
117
|
+
$pandocZip = Join-Path $env:TEMP "pandoc-windows-x86_64.zip"
|
|
118
|
+
$pandocUrl = "https://github.com/jgm/pandoc/releases/download/3.11/pandoc-3.11-windows-x86_64.zip"
|
|
119
|
+
try {
|
|
120
|
+
Invoke-WebRequest -Uri $pandocUrl -OutFile $pandocZip -UseBasicParsing
|
|
121
|
+
Expand-Archive -Path $pandocZip -DestinationPath (Join-Path $env:TEMP "pandoc-extracted") -Force
|
|
122
|
+
$pandocExe = Get-ChildItem -Path (Join-Path $env:TEMP "pandoc-extracted") -Filter "pandoc.exe" -Recurse | Select-Object -First 1
|
|
123
|
+
if ($pandocExe) {
|
|
124
|
+
Copy-Item -Path $pandocExe.FullName -Destination (Join-Path $localBin "pandoc.exe") -Force
|
|
125
|
+
Write-Host " [SUKSES] Pandoc tersimpan di $localBin" -ForegroundColor Green
|
|
126
|
+
}
|
|
127
|
+
} catch {
|
|
128
|
+
Write-Host " [GAGAL] Mengunduh Pandoc: $_" -ForegroundColor Red
|
|
129
|
+
} finally {
|
|
130
|
+
Remove-Item -Path $pandocZip -Force -ErrorAction SilentlyContinue
|
|
131
|
+
Remove-Item -Path (Join-Path $env:TEMP "pandoc-extracted") -Recurse -Force -ErrorAction SilentlyContinue
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# Tambahkan localBin ke User PATH jika belum terdaftar
|
|
136
|
+
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
|
137
|
+
if ($userPath -notlike "*$localBin*") {
|
|
138
|
+
[Environment]::SetEnvironmentVariable("Path", "$userPath;$localBin", "User")
|
|
139
|
+
Write-Host " [OK] Ditambahkan ke User PATH: $localBin" -ForegroundColor Green
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
Write-Host ""
|
|
144
|
+
Write-Host "[3/3] Memperbarui sesi Environment PATH..." -ForegroundColor Yellow
|
|
145
|
+
$machinePath = [Environment]::GetEnvironmentVariable("Path", "Machine")
|
|
146
|
+
$userPath = [Environment]::GetEnvironmentVariable("Path", "User")
|
|
147
|
+
$env:Path = "$localBin;$machinePath;$userPath"
|
|
148
|
+
|
|
149
|
+
Write-Host ""
|
|
150
|
+
Write-Host "==========================================================" -ForegroundColor Green
|
|
151
|
+
Write-Host " PEMASANGAN SELESAI! SILAKAN JALANKAN BUILD DOKUMEN " -ForegroundColor Green
|
|
152
|
+
Write-Host "==========================================================" -ForegroundColor Green
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# setup-deps.sh: Universal Dependency Installer untuk Linux & macOS
|
|
3
|
+
# Mendukung: apt (Debian/Ubuntu), pacman (Arch), dnf (Fedora), brew (macOS), nix, dan docker
|
|
4
|
+
|
|
5
|
+
set -euo pipefail
|
|
6
|
+
|
|
7
|
+
RED='\033[0;31m'
|
|
8
|
+
GREEN='\033[0;32m'
|
|
9
|
+
YELLOW='\033[1;33m'
|
|
10
|
+
CYAN='\033[0;36m'
|
|
11
|
+
BOLD='\033[1m'
|
|
12
|
+
NC='\033[0m'
|
|
13
|
+
|
|
14
|
+
echo -e "${CYAN}${BOLD}==========================================================${NC}"
|
|
15
|
+
echo -e "${CYAN}${BOLD} LAPORAN GENERATOR - DEPENDENCY SETUP (LINUX/MAC) ${NC}"
|
|
16
|
+
echo -e "${CYAN}${BOLD}==========================================================${NC}"
|
|
17
|
+
echo ""
|
|
18
|
+
|
|
19
|
+
# 1. Cek dependensi saat ini
|
|
20
|
+
has_cmd() { command -v "$1" >/dev/null 2>&1; }
|
|
21
|
+
|
|
22
|
+
echo -e "${YELLOW}[1/3] Memeriksa dependensi sistem...${NC}"
|
|
23
|
+
missing=()
|
|
24
|
+
|
|
25
|
+
if has_cmd typst; then
|
|
26
|
+
echo -e " ${GREEN}[OK]${NC} Typst Engine: $(typst --version 2>/dev/null || echo 'terpasang')"
|
|
27
|
+
else
|
|
28
|
+
echo -e " ${RED}[MISSING]${NC} Typst Engine belum terpasang."
|
|
29
|
+
missing+=("typst")
|
|
30
|
+
fi
|
|
31
|
+
|
|
32
|
+
if has_cmd pandoc; then
|
|
33
|
+
echo -e " ${GREEN}[OK]${NC} Pandoc Converter: $(pandoc --version 2>/dev/null | head -n1 || echo 'terpasang')"
|
|
34
|
+
else
|
|
35
|
+
echo -e " ${RED}[MISSING]${NC} Pandoc Converter belum terpasang."
|
|
36
|
+
missing+=("pandoc")
|
|
37
|
+
fi
|
|
38
|
+
|
|
39
|
+
if has_cmd magick || has_cmd convert; then
|
|
40
|
+
echo -e " ${GREEN}[OK]${NC} ImageMagick terpasang."
|
|
41
|
+
else
|
|
42
|
+
echo -e " ${RED}[MISSING]${NC} ImageMagick belum terpasang."
|
|
43
|
+
missing+=("imagemagick")
|
|
44
|
+
fi
|
|
45
|
+
|
|
46
|
+
if [ ${#missing[@]} -eq 0 ]; then
|
|
47
|
+
echo ""
|
|
48
|
+
echo -e "${GREEN}✔ Semua dependensi utama sudah lengkap dan siap digunakan!${NC}"
|
|
49
|
+
exit 0
|
|
50
|
+
fi
|
|
51
|
+
|
|
52
|
+
echo ""
|
|
53
|
+
echo -e "${YELLOW}[2/3] Memasang dependensi: ${missing[*]}...${NC}"
|
|
54
|
+
|
|
55
|
+
# Deteksi OS / Package Manager
|
|
56
|
+
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
57
|
+
if has_cmd brew; then
|
|
58
|
+
echo -e "${CYAN}--> Menggunakan Homebrew (macOS)...${NC}"
|
|
59
|
+
brew install typst pandoc imagemagick
|
|
60
|
+
else
|
|
61
|
+
echo -e "${RED}[ERROR] Homebrew tidak ditemukan. Silakan pasang brew (https://brew.sh) terlebih dahulu.${NC}"
|
|
62
|
+
exit 1
|
|
63
|
+
fi
|
|
64
|
+
elif has_cmd nix; then
|
|
65
|
+
echo -e "${CYAN}--> Lingkungan Nix terdeteksi! Kamu bisa langsung menjalankan:${NC}"
|
|
66
|
+
echo -e " ${BOLD}nix develop${NC}"
|
|
67
|
+
echo -e " Semua dependensi sudah otomatis terisolasi di flake.nix."
|
|
68
|
+
exit 0
|
|
69
|
+
elif has_cmd apt-get; then
|
|
70
|
+
echo -e "${CYAN}--> Menggunakan apt-get (Debian/Ubuntu)...${NC}"
|
|
71
|
+
sudo apt-get update -qq
|
|
72
|
+
sudo apt-get install -y imagemagick poppler-utils xz-utils unzip wget
|
|
73
|
+
|
|
74
|
+
# Install pandoc deb terbaru jika belum ada
|
|
75
|
+
if ! has_cmd pandoc; then
|
|
76
|
+
echo -e "--> Mengunduh Pandoc official deb..."
|
|
77
|
+
PANDOC_URL="https://github.com/jgm/pandoc/releases/download/3.7.0.2/pandoc-3.7.0.2-1-amd64.deb"
|
|
78
|
+
wget -q "$PANDOC_URL" -O /tmp/pandoc.deb
|
|
79
|
+
sudo dpkg -i /tmp/pandoc.deb
|
|
80
|
+
rm -f /tmp/pandoc.deb
|
|
81
|
+
fi
|
|
82
|
+
|
|
83
|
+
# Install typst binary jika belum ada
|
|
84
|
+
if ! has_cmd typst; then
|
|
85
|
+
echo -e "--> Mengunduh Typst standalone binary..."
|
|
86
|
+
TYPST_URL="https://github.com/typst/typst/releases/download/v0.13.0/typst-x86_64-unknown-linux-musl.tar.xz"
|
|
87
|
+
wget -q "$TYPST_URL" -O /tmp/typst.tar.xz
|
|
88
|
+
tar -xJf /tmp/typst.tar.xz -C /tmp
|
|
89
|
+
sudo mv /tmp/typst-x86_64-unknown-linux-musl/typst /usr/local/bin/typst
|
|
90
|
+
rm -rf /tmp/typst.tar.xz /tmp/typst-x86_64-unknown-linux-musl
|
|
91
|
+
fi
|
|
92
|
+
elif has_cmd pacman; then
|
|
93
|
+
echo -e "${CYAN}--> Menggunakan pacman (Arch Linux)...${NC}"
|
|
94
|
+
sudo pacman -S --noconfirm typst pandoc imagemagick
|
|
95
|
+
elif has_cmd dnf; then
|
|
96
|
+
echo -e "${CYAN}--> Menggunakan dnf (Fedora)...${NC}"
|
|
97
|
+
sudo dnf install -y typst pandoc ImageMagick
|
|
98
|
+
else
|
|
99
|
+
echo -e "${YELLOW}[WARN] Package manager native tidak dikenali.${NC}"
|
|
100
|
+
echo -e "Kamu dapat menggunakan Docker container bawaan:"
|
|
101
|
+
echo -e " ${BOLD}docker run --rm -v \"\$(pwd):/workspace\" muadzhdz/laporan-generator${NC}"
|
|
102
|
+
exit 1
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
echo ""
|
|
106
|
+
echo -e "${YELLOW}[3/3] Memverifikasi instalasi...${NC}"
|
|
107
|
+
if has_cmd typst && has_cmd pandoc; then
|
|
108
|
+
echo -e "${GREEN}==========================================================${NC}"
|
|
109
|
+
echo -e "${GREEN} PEMASANGAN SELESAI! SEMUA DEPENDENSI SIAP PAKAI! ${NC}"
|
|
110
|
+
echo -e "${GREEN}==========================================================${NC}"
|
|
111
|
+
else
|
|
112
|
+
echo -e "${RED}[WARN] Beberapa dependensi belum terdeteksi di PATH.${NC}"
|
|
113
|
+
fi
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""test_scripts.py: Automated unit tests for Laporan Generator Python helper scripts."""
|
|
3
|
+
|
|
4
|
+
import os
|
|
5
|
+
import sys
|
|
6
|
+
import unittest
|
|
7
|
+
|
|
8
|
+
# Tambahkan direktori scripts ke sys.path
|
|
9
|
+
SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__))
|
|
10
|
+
ROOT_DIR = os.path.dirname(SCRIPTS_DIR)
|
|
11
|
+
sys.path.insert(0, SCRIPTS_DIR)
|
|
12
|
+
|
|
13
|
+
scan_preset = __import__("scan-preset")
|
|
14
|
+
validate_preset = __import__("validate-preset")
|
|
15
|
+
docx_pagenum = __import__("docx-pagenum")
|
|
16
|
+
finalize_docx = __import__("finalize-docx")
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class TestScanPreset(unittest.TestCase):
|
|
20
|
+
def test_pdf_slug(self):
|
|
21
|
+
self.assertEqual(scan_preset.pdf_slug("pedoman-itb.pdf"), "itb")
|
|
22
|
+
self.assertEqual(scan_preset.pdf_slug("panduan-ugm.pdf"), "ugm")
|
|
23
|
+
self.assertEqual(scan_preset.pdf_slug("buku-ui.pdf"), "ui")
|
|
24
|
+
|
|
25
|
+
def test_detect_margins(self):
|
|
26
|
+
text_4433 = "Batas pengetikan adalah 4 - 4 - 3 - 3 cm."
|
|
27
|
+
margins = scan_preset.detect_margins(text_4433)
|
|
28
|
+
self.assertEqual(margins["top"], "4cm")
|
|
29
|
+
self.assertEqual(margins["bottom"], "3cm")
|
|
30
|
+
self.assertEqual(margins["left"], "4cm")
|
|
31
|
+
self.assertEqual(margins["right"], "3cm")
|
|
32
|
+
|
|
33
|
+
def test_detect_institution(self):
|
|
34
|
+
sample = "KEMENTERIAN PENDIDIKAN\nUNIVERSITAS INDONESIA\nFAKULTAS ILMU KOMPUTER"
|
|
35
|
+
inst = scan_preset.detect_institution(sample, "ui.pdf")
|
|
36
|
+
self.assertIn("UNIVERSITAS INDONESIA", inst)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class TestValidatePreset(unittest.TestCase):
|
|
40
|
+
def test_parse_simple_yaml(self):
|
|
41
|
+
test_yaml = os.path.join(ROOT_DIR, "presets", "standard.yml")
|
|
42
|
+
self.assertTrue(os.path.exists(test_yaml))
|
|
43
|
+
data = validate_preset.parse_simple_yaml(test_yaml)
|
|
44
|
+
self.assertEqual(data.get("preset_id"), "standard")
|
|
45
|
+
self.assertEqual(data.get("margin_top"), "2cm")
|
|
46
|
+
|
|
47
|
+
def test_validate_all_presets(self):
|
|
48
|
+
presets_dir = os.path.join(ROOT_DIR, "presets")
|
|
49
|
+
for f in os.listdir(presets_dir):
|
|
50
|
+
if f.endswith(".yml"):
|
|
51
|
+
path = os.path.join(presets_dir, f)
|
|
52
|
+
errors = validate_preset.validate_preset_file(path)
|
|
53
|
+
self.assertEqual(errors, [], f"Preset {f} gagal validasi: {errors}")
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TestDocxPagenum(unittest.TestCase):
|
|
57
|
+
def test_needle_of(self):
|
|
58
|
+
self.assertEqual(docx_pagenum.needle_of("BAB I PENDAHULUAN"), "PENDAHULUAN")
|
|
59
|
+
self.assertEqual(docx_pagenum.needle_of("1.1 Latar Belakang"), "1.1 Latar Belakang")
|
|
60
|
+
self.assertEqual(docx_pagenum.needle_of("KATA PENGANTAR"), "KATA PENGANTAR")
|
|
61
|
+
|
|
62
|
+
def test_roman_pattern(self):
|
|
63
|
+
self.assertTrue(docx_pagenum.ROMAN.match("iv"))
|
|
64
|
+
self.assertTrue(docx_pagenum.ROMAN.match("xii"))
|
|
65
|
+
self.assertFalse(docx_pagenum.ROMAN.match("123"))
|
|
66
|
+
|
|
67
|
+
def test_decimal_pattern(self):
|
|
68
|
+
self.assertTrue(docx_pagenum.DECIMAL.match("1"))
|
|
69
|
+
self.assertTrue(docx_pagenum.DECIMAL.match("42"))
|
|
70
|
+
self.assertFalse(docx_pagenum.DECIMAL.match("iv"))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
class TestFinalizeDocx(unittest.TestCase):
|
|
74
|
+
def test_classify_front_matter(self):
|
|
75
|
+
self.assertEqual(finalize_docx.classify("KATA PENGANTAR"), "cover")
|
|
76
|
+
self.assertEqual(finalize_docx.classify("ABSTRAK"), "cover")
|
|
77
|
+
self.assertEqual(finalize_docx.classify("DAFTAR ISI"), "cover")
|
|
78
|
+
|
|
79
|
+
def test_classify_standard_thesis_headings(self):
|
|
80
|
+
self.assertEqual(finalize_docx.classify("BAB I"), "roman")
|
|
81
|
+
self.assertEqual(finalize_docx.classify("BAB I PENDAHULUAN"), "roman")
|
|
82
|
+
self.assertEqual(finalize_docx.classify("BAB II"), "decimal-start")
|
|
83
|
+
self.assertEqual(
|
|
84
|
+
finalize_docx.classify("BAB II TINJAUAN PUSTAKA"), "decimal-start"
|
|
85
|
+
)
|
|
86
|
+
self.assertEqual(finalize_docx.classify("BAB III METODOLOGI"), "body")
|
|
87
|
+
self.assertEqual(finalize_docx.classify("DAFTAR PUSTAKA"), "body")
|
|
88
|
+
|
|
89
|
+
def test_classify_makalah_headings(self):
|
|
90
|
+
self.assertEqual(finalize_docx.classify("1. PENDAHULUAN"), "roman")
|
|
91
|
+
self.assertEqual(finalize_docx.classify("1 PENDAHULUAN"), "roman")
|
|
92
|
+
self.assertEqual(finalize_docx.classify("2. PEMBAHASAN"), "decimal-start")
|
|
93
|
+
self.assertEqual(finalize_docx.classify("2 PEMBAHASAN"), "decimal-start")
|
|
94
|
+
self.assertEqual(finalize_docx.classify("3. PENUTUP"), "body")
|
|
95
|
+
|
|
96
|
+
def test_classify_arabic_chapter_headings(self):
|
|
97
|
+
self.assertEqual(finalize_docx.classify("BAB 1 PENDAHULUAN"), "roman")
|
|
98
|
+
self.assertEqual(finalize_docx.classify("BAB 2 PEMBAHASAN"), "decimal-start")
|
|
99
|
+
self.assertEqual(finalize_docx.classify("BAB 3 PENUTUP"), "body")
|
|
100
|
+
|
|
101
|
+
def test_get_tab_pos(self):
|
|
102
|
+
sample_standard = '<w:sectPr><w:pgSz w:w="11906"/><w:pgMar w:left="1417" w:right="1417"/></w:sectPr>'
|
|
103
|
+
self.assertEqual(finalize_docx.get_tab_pos(sample_standard), 9072)
|
|
104
|
+
sample_4433 = '<w:sectPr><w:pgSz w:w="11906"/><w:pgMar w:left="2268" w:right="1701"/></w:sectPr>'
|
|
105
|
+
self.assertEqual(finalize_docx.get_tab_pos(sample_4433), 7937)
|
|
106
|
+
self.assertEqual(finalize_docx.get_tab_pos(""), 9072)
|
|
107
|
+
|
|
108
|
+
def test_fix_daftar_isi_title(self):
|
|
109
|
+
raw = '<w:p><w:pPr><w:pStyle w:val="Heading1"/><w:outlineLvl w:val="-1"/></w:pPr><w:r><w:t>DAFTAR ISI</w:t></w:r></w:p>'
|
|
110
|
+
fixed = finalize_docx.fix_daftar_isi_title(raw)
|
|
111
|
+
self.assertIn('w:val="TOCHeading"', fixed)
|
|
112
|
+
self.assertNotIn('w:val="Heading1"', fixed)
|
|
113
|
+
|
|
114
|
+
def test_fix_toc_styles(self):
|
|
115
|
+
styles = '<w:styles></w:styles>'
|
|
116
|
+
res = finalize_docx.fix_toc_styles(styles, 7937)
|
|
117
|
+
self.assertIn('w:styleId="TOCHeading"', res)
|
|
118
|
+
self.assertIn('w:styleId="TOC1"', res)
|
|
119
|
+
self.assertIn('w:styleId="TOC2"', res)
|
|
120
|
+
self.assertIn('w:styleId="TOC3"', res)
|
|
121
|
+
self.assertIn('w:pos="7937"', res)
|
|
122
|
+
self.assertIn('w:left="360"', res)
|
|
123
|
+
self.assertIn('w:left="720"', res)
|
|
124
|
+
|
|
125
|
+
|
|
126
|
+
if __name__ == "__main__":
|
|
127
|
+
unittest.main(verbosity=2)
|
|
128
|
+
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""validate-preset.py: Validator dan Linter untuk berkas preset format kampus (YAML).
|
|
3
|
+
|
|
4
|
+
Memeriksa integritas berkas preset terhadap spesifikasi skema resmi (docs/preset-schema.md).
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
python3 scripts/validate-preset.py [path-to-preset.yml ...]
|
|
8
|
+
python3 scripts/validate-preset.py --all
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
import argparse
|
|
12
|
+
import glob
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
DIM_REGEX = re.compile(r"^[0-9]+(\.[0-9]+)?(cm|mm|in|pt)$", re.I)
|
|
18
|
+
PT_REGEX = re.compile(r"^[0-9]+(\.[0-9]+)?pt$", re.I)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def parse_simple_yaml(filepath):
|
|
22
|
+
"""Parser YAML yang tangguh dengan PyYAML dan fallback aman tanpa dependensi."""
|
|
23
|
+
try:
|
|
24
|
+
import yaml
|
|
25
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
26
|
+
content = yaml.safe_load(f)
|
|
27
|
+
if isinstance(content, dict):
|
|
28
|
+
return {str(k): ("" if v is None else str(v)) for k, v in content.items()}
|
|
29
|
+
except (ImportError, ModuleNotFoundError):
|
|
30
|
+
pass
|
|
31
|
+
|
|
32
|
+
data = {}
|
|
33
|
+
with open(filepath, "r", encoding="utf-8") as f:
|
|
34
|
+
for line in f:
|
|
35
|
+
line = line.strip()
|
|
36
|
+
if not line or line.startswith("#"):
|
|
37
|
+
continue
|
|
38
|
+
if ":" in line:
|
|
39
|
+
key, val = line.split(":", 1)
|
|
40
|
+
key = key.strip()
|
|
41
|
+
val = val.strip()
|
|
42
|
+
if val.startswith('"'):
|
|
43
|
+
end_quote = val.find('"', 1)
|
|
44
|
+
val = val[1:end_quote] if end_quote != -1 else val.strip('"')
|
|
45
|
+
elif val.startswith("'"):
|
|
46
|
+
end_quote = val.find("'", 1)
|
|
47
|
+
val = val[1:end_quote] if end_quote != -1 else val.strip("'")
|
|
48
|
+
else:
|
|
49
|
+
val = val.split("#")[0].strip()
|
|
50
|
+
data[key] = val
|
|
51
|
+
return data
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def validate_preset_file(filepath):
|
|
55
|
+
"""Validasi sebuah file preset dan kembalikan daftar error."""
|
|
56
|
+
errors = []
|
|
57
|
+
if not os.path.isfile(filepath):
|
|
58
|
+
return [f"Berkas '{filepath}' tidak ditemukan."]
|
|
59
|
+
|
|
60
|
+
try:
|
|
61
|
+
data = parse_simple_yaml(filepath)
|
|
62
|
+
except Exception as e:
|
|
63
|
+
return [f"Gagal mengurai YAML: {e}"]
|
|
64
|
+
|
|
65
|
+
# 1. Identitas
|
|
66
|
+
expected_id = os.path.basename(filepath).rsplit(".", 1)[0]
|
|
67
|
+
pid = data.get("preset_id")
|
|
68
|
+
if not pid:
|
|
69
|
+
errors.append("Field wajib 'preset_id' tidak ditemukan.")
|
|
70
|
+
elif pid != expected_id:
|
|
71
|
+
errors.append(f"'preset_id' ('{pid}') tidak cocok dengan nama berkas ('{expected_id}').")
|
|
72
|
+
|
|
73
|
+
if not data.get("name"):
|
|
74
|
+
errors.append("Field wajib 'name' tidak ditemukan atau kosong.")
|
|
75
|
+
|
|
76
|
+
# 2. Margin
|
|
77
|
+
for m in ["margin_top", "margin_bottom", "margin_left", "margin_right"]:
|
|
78
|
+
val = data.get(m)
|
|
79
|
+
if not val:
|
|
80
|
+
errors.append(f"Field margin '{m}' wajib diisi.")
|
|
81
|
+
elif not DIM_REGEX.match(val):
|
|
82
|
+
errors.append(f"Format satuan '{m}' tidak valid: '{val}' (contoh valid: '4cm', '2.5cm', '12pt').")
|
|
83
|
+
|
|
84
|
+
# 3. Tipografi
|
|
85
|
+
if not data.get("font_family"):
|
|
86
|
+
errors.append("Field 'font_family' wajib diisi.")
|
|
87
|
+
|
|
88
|
+
fs = data.get("font_size")
|
|
89
|
+
if not fs:
|
|
90
|
+
errors.append("Field 'font_size' wajib diisi.")
|
|
91
|
+
elif not PT_REGEX.match(fs):
|
|
92
|
+
errors.append(f"Format 'font_size' tidak valid: '{fs}' (contoh: '12pt').")
|
|
93
|
+
|
|
94
|
+
ls = data.get("line_spacing")
|
|
95
|
+
if not ls:
|
|
96
|
+
errors.append("Field 'line_spacing' wajib diisi.")
|
|
97
|
+
|
|
98
|
+
ind = data.get("first_line_indent")
|
|
99
|
+
if not ind:
|
|
100
|
+
errors.append("Field 'first_line_indent' wajib diisi.")
|
|
101
|
+
elif not DIM_REGEX.match(ind):
|
|
102
|
+
errors.append(f"Format 'first_line_indent' tidak valid: '{ind}' (contoh: '1.25cm').")
|
|
103
|
+
|
|
104
|
+
# 4. Heading
|
|
105
|
+
fmt = data.get("heading_chapter_num_format")
|
|
106
|
+
if fmt and fmt not in ("roman", "arabic"):
|
|
107
|
+
errors.append(f"'heading_chapter_num_format' harus 'roman' atau 'arabic', bukan '{fmt}'.")
|
|
108
|
+
|
|
109
|
+
# 5. Booleans
|
|
110
|
+
for b in ["heading_sub_dot", "heading_subsub_dot", "cover_show_lecturer"]:
|
|
111
|
+
if b in data and str(data[b]).lower() not in ("true", "false"):
|
|
112
|
+
errors.append(f"Field '{b}' harus berupa boolean (true/false), bukan '{data[b]}'.")
|
|
113
|
+
|
|
114
|
+
return errors
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def main():
|
|
118
|
+
parser = argparse.ArgumentParser(description="Linter dan validator berkas preset format kampus.")
|
|
119
|
+
parser.add_argument("files", nargs="*", help="Path ke berkas preset YAML yang ingin divalidasi")
|
|
120
|
+
parser.add_argument("--all", action="store_true", help="Validasi seluruh preset di folder presets/")
|
|
121
|
+
parser.add_argument("--dir", default="presets", help="Direktori preset (default: presets/)")
|
|
122
|
+
|
|
123
|
+
args = parser.parse_args()
|
|
124
|
+
|
|
125
|
+
target_files = []
|
|
126
|
+
if args.all or not args.files:
|
|
127
|
+
target_files = sorted(glob.glob(os.path.join(args.dir, "*.yml")))
|
|
128
|
+
else:
|
|
129
|
+
for f in args.files:
|
|
130
|
+
if not f.endswith(".yml"):
|
|
131
|
+
f = os.path.join(args.dir, f"{f}.yml")
|
|
132
|
+
target_files.append(f)
|
|
133
|
+
|
|
134
|
+
if not target_files:
|
|
135
|
+
print(f"[WARN] Tidak ada berkas preset YAML yang ditemukan di '{args.dir}'.")
|
|
136
|
+
sys.exit(0)
|
|
137
|
+
|
|
138
|
+
print("=" * 60)
|
|
139
|
+
print(" LAPORAN GENERATOR PRESET VALIDATOR")
|
|
140
|
+
print("=" * 60)
|
|
141
|
+
|
|
142
|
+
total = len(target_files)
|
|
143
|
+
passed = 0
|
|
144
|
+
failed = 0
|
|
145
|
+
|
|
146
|
+
for f in target_files:
|
|
147
|
+
rel_path = os.path.relpath(f)
|
|
148
|
+
errors = validate_preset_file(f)
|
|
149
|
+
if not errors:
|
|
150
|
+
print(f" [OK] {rel_path}")
|
|
151
|
+
passed += 1
|
|
152
|
+
else:
|
|
153
|
+
print(f" [FAIL] {rel_path}:")
|
|
154
|
+
for err in errors:
|
|
155
|
+
print(f" - {err}")
|
|
156
|
+
failed += 1
|
|
157
|
+
|
|
158
|
+
print("=" * 60)
|
|
159
|
+
print(f"Hasil: {passed} valid, {failed} error dari total {total} preset.")
|
|
160
|
+
print("=" * 60)
|
|
161
|
+
|
|
162
|
+
sys.exit(1 if failed > 0 else 0)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
if __name__ == "__main__":
|
|
166
|
+
main()
|