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.
Files changed (109) hide show
  1. package/.agents/skills/laporan-generator/SKILL.md +132 -0
  2. package/.agents/skills/laporan-generator/agents/openai.yaml +3 -0
  3. package/.agents/skills/laporan-generator/references/campus-guide.md +120 -0
  4. package/.agents/skills/laporan-generator/references/metadata-schema.md +76 -0
  5. package/.agents/skills/laporan-generator/references/preset-schema.md +79 -0
  6. package/.agents/skills/laporan-generator/references/syntax-cheatsheet.md +122 -0
  7. package/.agents/skills/laporan-generator/references/template-guide.md +100 -0
  8. package/.agents/skills/laporan-generator/references/troubleshooting.md +105 -0
  9. package/.agents/skills/laporan-generator/resources/Makefile +90 -0
  10. package/.agents/skills/laporan-generator/resources/apa.csl +2273 -0
  11. package/.agents/skills/laporan-generator/resources/build-docx.sh +122 -0
  12. package/.agents/skills/laporan-generator/resources/build.sh +102 -0
  13. package/.agents/skills/laporan-generator/resources/chapters/bab1-pendahuluan.md +60 -0
  14. package/.agents/skills/laporan-generator/resources/chapters/bab2-tinjauan-pustaka.md +83 -0
  15. package/.agents/skills/laporan-generator/resources/chapters/bab3-metodologi.md +116 -0
  16. package/.agents/skills/laporan-generator/resources/chapters/bab4-hasil-dan-pembahasan.md +93 -0
  17. package/.agents/skills/laporan-generator/resources/chapters/bab5-penutup.md +27 -0
  18. package/.agents/skills/laporan-generator/resources/cover.md +50 -0
  19. package/.agents/skills/laporan-generator/resources/docx.lua +416 -0
  20. package/.agents/skills/laporan-generator/resources/laporan +464 -0
  21. package/.agents/skills/laporan-generator/resources/laporan.ps1 +552 -0
  22. package/.agents/skills/laporan-generator/resources/logo.jpg +0 -0
  23. package/.agents/skills/laporan-generator/resources/metadata.yml +35 -0
  24. package/.agents/skills/laporan-generator/resources/presets/itb-ta.yml +27 -0
  25. package/.agents/skills/laporan-generator/resources/presets/its-skripsi.yml +26 -0
  26. package/.agents/skills/laporan-generator/resources/presets/skripsi-4433.yml +26 -0
  27. package/.agents/skills/laporan-generator/resources/presets/standard.yml +26 -0
  28. package/.agents/skills/laporan-generator/resources/presets/ugm-skripsi.yml +26 -0
  29. package/.agents/skills/laporan-generator/resources/presets/ui-skripsi.yml +26 -0
  30. package/.agents/skills/laporan-generator/resources/presets/unpad-skripsi.yml +27 -0
  31. package/.agents/skills/laporan-generator/resources/reference.docx +0 -0
  32. package/.agents/skills/laporan-generator/resources/references.bib +115 -0
  33. package/.agents/skills/laporan-generator/resources/scripts/bundle.py +75 -0
  34. package/.agents/skills/laporan-generator/resources/scripts/docx-pagenum.py +192 -0
  35. package/.agents/skills/laporan-generator/resources/scripts/finalize-docx.py +291 -0
  36. package/.agents/skills/laporan-generator/resources/scripts/make-reference-docx.py +670 -0
  37. package/.agents/skills/laporan-generator/resources/scripts/report-doctor.py +211 -0
  38. package/.agents/skills/laporan-generator/resources/scripts/report-stats.py +149 -0
  39. package/.agents/skills/laporan-generator/resources/scripts/scan-preset.py +311 -0
  40. package/.agents/skills/laporan-generator/resources/scripts/setup-deps.ps1 +152 -0
  41. package/.agents/skills/laporan-generator/resources/scripts/setup-deps.sh +113 -0
  42. package/.agents/skills/laporan-generator/resources/scripts/test_scripts.py +128 -0
  43. package/.agents/skills/laporan-generator/resources/scripts/validate-preset.py +166 -0
  44. package/.agents/skills/laporan-generator/resources/template.typ +394 -0
  45. package/.agents/skills/laporan-generator/scripts/build.sh +78 -0
  46. package/.agents/skills/laporan-generator/scripts/bundle.py +75 -0
  47. package/.agents/skills/laporan-generator/scripts/docx-pagenum.py +192 -0
  48. package/.agents/skills/laporan-generator/scripts/finalize-docx.py +152 -0
  49. package/.agents/skills/laporan-generator/scripts/laporan +447 -0
  50. package/.agents/skills/laporan-generator/scripts/laporan.ps1 +529 -0
  51. package/.agents/skills/laporan-generator/scripts/make-reference-docx.py +630 -0
  52. package/.agents/skills/laporan-generator/scripts/report-doctor.py +211 -0
  53. package/.agents/skills/laporan-generator/scripts/report-stats.py +149 -0
  54. package/.agents/skills/laporan-generator/scripts/scan-preset.py +311 -0
  55. package/.agents/skills/laporan-generator/scripts/setup-deps.ps1 +152 -0
  56. package/.agents/skills/laporan-generator/scripts/setup-deps.sh +113 -0
  57. package/.agents/skills/laporan-generator/scripts/test_scripts.py +103 -0
  58. package/.agents/skills/laporan-generator/scripts/validate-preset.py +166 -0
  59. package/.agents/workflows/laporan-generator.md +8 -0
  60. package/LICENSE +21 -0
  61. package/Makefile +90 -0
  62. package/README.md +389 -0
  63. package/apa.csl +2273 -0
  64. package/bin/laporan-generator.js +71 -0
  65. package/build-docx.sh +122 -0
  66. package/build.sh +102 -0
  67. package/chapters/bab1-pendahuluan.md +60 -0
  68. package/chapters/bab2-tinjauan-pustaka.md +83 -0
  69. package/chapters/bab3-metodologi.md +116 -0
  70. package/chapters/bab4-hasil-dan-pembahasan.md +93 -0
  71. package/chapters/bab5-penutup.md +27 -0
  72. package/cover.md +50 -0
  73. package/docs/campus-guide.md +120 -0
  74. package/docs/metadata-schema.md +76 -0
  75. package/docs/preset-schema.md +79 -0
  76. package/docs/syntax-cheatsheet.md +122 -0
  77. package/docs/template-guide.md +100 -0
  78. package/docs/troubleshooting.md +105 -0
  79. package/docx.lua +416 -0
  80. package/laporan +464 -0
  81. package/laporan.ps1 +552 -0
  82. package/lib/init.js +118 -0
  83. package/lib/setup.js +33 -0
  84. package/lib/sync-hosts.js +194 -0
  85. package/lib/uninstall.js +123 -0
  86. package/logo.jpg +0 -0
  87. package/metadata.yml +35 -0
  88. package/package.json +65 -0
  89. package/presets/itb-ta.yml +27 -0
  90. package/presets/its-skripsi.yml +26 -0
  91. package/presets/skripsi-4433.yml +26 -0
  92. package/presets/standard.yml +26 -0
  93. package/presets/ugm-skripsi.yml +26 -0
  94. package/presets/ui-skripsi.yml +26 -0
  95. package/presets/unpad-skripsi.yml +27 -0
  96. package/reference.docx +0 -0
  97. package/references.bib +115 -0
  98. package/scripts/bundle.py +75 -0
  99. package/scripts/docx-pagenum.py +192 -0
  100. package/scripts/finalize-docx.py +291 -0
  101. package/scripts/make-reference-docx.py +670 -0
  102. package/scripts/report-doctor.py +211 -0
  103. package/scripts/report-stats.py +149 -0
  104. package/scripts/scan-preset.py +311 -0
  105. package/scripts/setup-deps.ps1 +152 -0
  106. package/scripts/setup-deps.sh +113 -0
  107. package/scripts/test_scripts.py +128 -0
  108. package/scripts/validate-preset.py +166 -0
  109. package/template.typ +394 -0
@@ -0,0 +1,394 @@
1
+ #set document(
2
+ title: "$title$",
3
+ author: ($for(author)$"$author.name$"$sep$,$endfor$),
4
+ )
5
+
6
+ $if(margin_top)$
7
+ #let doc-margin = (
8
+ top: $margin_top$,
9
+ bottom: $margin_bottom$,
10
+ left: $margin_left$,
11
+ right: $margin_right$,
12
+ )
13
+ $else$
14
+ #let doc-margin = if "$margin_preset$" == "skripsi-4433" or "$margin_preset$" == "4-4-3-3" {
15
+ (top: 4cm, bottom: 3cm, left: 4cm, right: 3cm)
16
+ } else {
17
+ (top: 2cm, bottom: 3cm, left: 2.5cm, right: 2.5cm)
18
+ }
19
+ $endif$
20
+
21
+ #set page(
22
+ paper: "a4",
23
+ margin: doc-margin,
24
+ numbering: none,
25
+ )
26
+
27
+ $if(font_family)$
28
+ #set text(font: "$font_family$", size: $if(font_size)$$font_size$$else$12pt$endif$)
29
+ $else$
30
+ #set text(font: "Libertinus Serif", size: 12pt)
31
+ $endif$
32
+
33
+ $if(line_spacing)$
34
+ #set par(justify: true, leading: $line_spacing$, first-line-indent: $if(first_line_indent)$$first_line_indent$$else$1.25cm$endif$)
35
+ $else$
36
+ #set par(justify: true, leading: 0.75em, first-line-indent: 1.25cm)
37
+ $endif$
38
+ #show bibliography: set par(hanging-indent: $if(first_line_indent)$$first_line_indent$$else$1.25cm$endif$, first-line-indent: 0cm)
39
+
40
+ #let raw-prefix = "$if(heading_chapter_prefix)$$heading_chapter_prefix$$else$BAB $endif$"
41
+ #let chapter-prefix = if raw-prefix == "" {
42
+ ""
43
+ } else if raw-prefix.ends-with(" ") {
44
+ raw-prefix
45
+ } else {
46
+ raw-prefix + " "
47
+ }
48
+ #let chapter-fmt = "$if(heading_chapter_num_format)$$heading_chapter_num_format$$else$roman$endif$"
49
+ #let format-chapter-num(n) = {
50
+ if chapter-fmt == "arabic" {
51
+ numbering("1", n)
52
+ } else {
53
+ numbering("I", n)
54
+ }
55
+ }
56
+
57
+ #set heading(numbering: (..ns) => {
58
+ if ns.len() == 1 {
59
+ if chapter-prefix != "" {
60
+ chapter-prefix + format-chapter-num(ns.at(0))
61
+ } else {
62
+ format-chapter-num(ns.at(0))
63
+ }
64
+ } else if ns.len() == 2 {
65
+ numbering("$if(heading_sub_dot)$1.1.$else$1.1$endif$", ..ns)
66
+ } else {
67
+ numbering("$if(heading_subsub_dot)$1.1.1.$else$1.1.1$endif$", ..ns)
68
+ }
69
+ })
70
+
71
+ #show figure.where(kind: image): set figure(
72
+ supplement: [Gambar],
73
+ numbering: it => {
74
+ let ch = counter(heading).get().at(0, default: 1)
75
+ str(ch) + "." + str(it)
76
+ }
77
+ )
78
+
79
+ #show figure.where(kind: table): set figure(
80
+ supplement: [Tabel],
81
+ numbering: it => {
82
+ let ch = counter(heading).get().at(0, default: 1)
83
+ str(ch) + "." + str(it)
84
+ }
85
+ )
86
+
87
+ #show figure.where(kind: raw): set figure(
88
+ supplement: [Listing],
89
+ numbering: it => {
90
+ let ch = counter(heading).get().at(0, default: 1)
91
+ str(ch) + "." + str(it)
92
+ }
93
+ )
94
+
95
+ #set math.equation(numbering: it => "(" + str(counter(heading).get().at(0, default: 1)) + "." + str(it) + ")")
96
+
97
+ #show heading.where(level: 1): it => {
98
+ counter(figure.where(kind: image)).update(0)
99
+ counter(figure.where(kind: table)).update(0)
100
+ counter(figure.where(kind: raw)).update(0)
101
+ counter(math.equation).update(0)
102
+ pagebreak(weak: true)
103
+ align(center)[
104
+ #block(inset: (top: 0.5em, bottom: 1.8em))[
105
+ #text(size: 14pt, weight: "bold")[
106
+ #if it.numbering != none [
107
+ #if chapter-prefix != "" [
108
+ #(chapter-prefix + format-chapter-num(counter(heading).get().at(0)))
109
+ #linebreak()
110
+ ] else [
111
+ #(format-chapter-num(counter(heading).get().at(0)) + ". ")
112
+ ]
113
+ #if it.body.has("text") [
114
+ #upper(it.body.text)
115
+ ] else [
116
+ #upper(it.body)
117
+ ]
118
+ ] else [
119
+ #it.body
120
+ ]
121
+ ]
122
+ ]
123
+ ]
124
+ }
125
+
126
+ #show heading.where(level: 2): it => {
127
+ block(above: 1.0em, below: 0.5em)[
128
+ #text(size: 12pt, weight: "bold")[#it]
129
+ ]
130
+ par(leading: 0.75em)[]
131
+ }
132
+
133
+ #show heading.where(level: 3): it => {
134
+ block(above: 0.8em, below: 0.4em)[
135
+ #text(size: 12pt, weight: "bold")[#it]
136
+ ]
137
+ par(leading: 0.75em)[]
138
+ }
139
+
140
+ #show figure: it => it + par[]
141
+ #show list: it => it + par[]
142
+ #show enum: it => it + par[]
143
+ #show quote: it => {
144
+ block(
145
+ fill: rgb("#f3f4f6"),
146
+ stroke: (left: 3.5pt + rgb("#2563eb")),
147
+ inset: (x: 12pt, y: 8pt),
148
+ radius: (right: 4pt),
149
+ width: 100%,
150
+ )[
151
+ #set text(size: 10.5pt, style: "italic")
152
+ #set par(justify: true, leading: 0.65em)
153
+ #it.body
154
+ ]
155
+ }
156
+ #show table: it => it + par[]
157
+ #show raw.where(block: true): it => {
158
+ block(
159
+ fill: rgb("#f8f9fa"),
160
+ stroke: 0.5pt + rgb("#d1d5db"),
161
+ inset: (x: 10pt, y: 8pt),
162
+ radius: 4pt,
163
+ width: 100%,
164
+ )[
165
+ #set text(font: "DejaVu Sans Mono", size: 8.5pt)
166
+ #set par(justify: false, leading: 0.55em)
167
+ #it
168
+ ]
169
+ }
170
+
171
+ #show outline.entry.where(level: 1): set text(weight: "bold")
172
+ #set par(leading: 0.75em)
173
+
174
+ #let balance-split(title, k) = {
175
+ let words = title.split(" ")
176
+ let n = words.len()
177
+ if n <= 1 {
178
+ return (title,)
179
+ }
180
+ if n <= k {
181
+ return words
182
+ }
183
+ let single-count = lines => lines.map(l => if l.split(" ").len() <= 1 { 1 } else { 0 }).sum()
184
+ let candidates = ()
185
+ if k == 2 {
186
+ for c in range(1, n) {
187
+ let line1 = words.slice(0, c).join(" ")
188
+ let line2 = words.slice(c, n).join(" ")
189
+ let mx = calc.max(line1.len(), line2.len())
190
+ candidates = candidates + (
191
+ (mx + 100 * single-count((line1, line2)), (line1, line2)),
192
+ )
193
+ }
194
+ } else if k == 3 {
195
+ for c1 in range(1, n - 1) {
196
+ for c2 in range(c1 + 1, n) {
197
+ let line1 = words.slice(0, c1).join(" ")
198
+ let line2 = words.slice(c1, c2).join(" ")
199
+ let line3 = words.slice(c2, n).join(" ")
200
+ let mx = calc.max(line1.len(), line2.len(), line3.len())
201
+ candidates = candidates + (
202
+ (mx + 100 * single-count((line1, line2, line3)), (line1, line2, line3)),
203
+ )
204
+ }
205
+ }
206
+ }
207
+ let best = candidates.at(0)
208
+ for cand in candidates {
209
+ if cand.at(0) < best.at(0) {
210
+ best = cand
211
+ }
212
+ }
213
+ best.at(1)
214
+ }
215
+
216
+ #let title-lines = layout(size => {
217
+ let t = upper("$title$".replace("\n", " ").replace("\r", " "))
218
+ let lines = if measure(text(t, size: 14pt, weight: "bold")).width <= size.width {
219
+ (t,)
220
+ } else {
221
+ let two = balance-split(t, 2)
222
+ let w-two = two.map(l => measure(text(l, size: 14pt, weight: "bold")).width)
223
+ .fold(0pt, calc.max)
224
+ let orphan = two.any(l => l.split(" ").len() <= 1)
225
+ if (not orphan) and w-two <= size.width {
226
+ two
227
+ } else {
228
+ balance-split(t, 3)
229
+ }
230
+ }
231
+ let out = ()
232
+ for i in range(lines.len()) {
233
+ if i > 0 {
234
+ out.push(linebreak())
235
+ }
236
+ out.push(lines.at(i))
237
+ }
238
+ out.join()
239
+ })
240
+
241
+ #set table(stroke: 0.5pt + luma(140), inset: 4pt)
242
+ #show table.header: set text(weight: "bold")
243
+ #set figure.caption(position: bottom)
244
+ #show raw.where(block: true): set text(font: "DejaVu Sans Mono", size: 9pt)
245
+
246
+ #v(1cm)
247
+ #align(center)[
248
+ #text(size: 14pt, weight: "bold")[#title-lines]
249
+ ]
250
+ $if(subtitle)$
251
+ #v(0.3cm)
252
+ #align(center)[
253
+ #text(size: 14pt, weight: "bold")[#upper("$subtitle$".replace("\n", " "))]
254
+ ]
255
+ $endif$
256
+ $if(course)$
257
+ #v(0.8cm)
258
+ #align(center)[
259
+ #text(size: 12pt, weight: "bold")[$course$]
260
+ ]
261
+ $endif$
262
+ $if(lecturer)$
263
+ #let hide-lecturer = ("$cover_hide_lecturer$" == "true" or "$cover_show_lecturer$" == "false")
264
+ #if not hide-lecturer [
265
+ #v(0.4cm)
266
+ #align(center)[
267
+ #text(size: 11pt)[$if(lecturer_label)$$lecturer_label$$else$Dosen Pengampu:$endif$]
268
+ #text(size: 12pt, weight: "bold")[$lecturer$]
269
+ ]
270
+ ]
271
+ $endif$
272
+
273
+ #v(1fr)
274
+ #align(center)[
275
+ #image("logo.jpg", width: $if(cover_logo_width)$$cover_logo_width$$else$4cm$endif$)
276
+ ]
277
+ #v(1fr)
278
+
279
+ #align(center)[
280
+ #text(size: 12pt)[Disusun oleh:]
281
+ ]
282
+ $for(author)$
283
+ #v(0.3cm)
284
+ #align(center)[
285
+ #text(size: 12pt, weight: "bold")[$author.name$] \
286
+ #text(size: 11pt)[$author.nim$]
287
+ ]
288
+ $endfor$
289
+
290
+ #v(1fr)
291
+ #align(center)[
292
+ #text(size: 14pt, weight: "bold")[#upper("$faculty$".replace("\n", " "))]
293
+ #linebreak()
294
+ #text(size: 14pt, weight: "bold")[#upper("$institution$".replace("\n", " "))]
295
+ #linebreak()
296
+ #text(size: 14pt, weight: "bold")[#upper("$year$".replace("\n", " "))]
297
+ ]
298
+ #v(1cm)
299
+
300
+ #pagebreak()
301
+ #set page(numbering: "i")
302
+ #counter(page).update(1)
303
+
304
+ $if(approval)$
305
+ #align(center)[
306
+ #text(size: 14pt, weight: "bold")[LEMBAR PENGESAHAN]
307
+ #v(0.8cm)
308
+ #text(size: 12pt, weight: "bold")[#title-lines]
309
+ ]
310
+ #v(0.5cm)
311
+ #align(center)[
312
+ #text(size: 11pt)[Disusun oleh:] \
313
+ $for(author)$
314
+ #text(size: 11pt, weight: "bold")[$author.name$] #text(size: 11pt)[($author.nim$)] \
315
+ $endfor$
316
+ ]
317
+ #v(0.5cm)
318
+ #align(center)[
319
+ #text(size: 11pt)[Telah diperiksa dan disetujui untuk diujikan pada:] \
320
+ #text(size: 11pt)[$if(approval.city)$$approval.city$, $endif$$if(approval.date)$$approval.date$$else$$date$$endif$]
321
+ ]
322
+ #v(1cm)
323
+ #grid(
324
+ columns: (1fr, 1fr),
325
+ gutter: 20pt,
326
+ $for(approval.supervisors)$
327
+ align(center)[
328
+ #text(size: 10.5pt)[$it.role$] \
329
+ #v(2cm)
330
+ #text(size: 10.5pt, weight: "bold")[$it.name$] \
331
+ $if(it.nip)$#text(size: 9pt)[NIP. $it.nip$]$endif$
332
+ ],
333
+ $endfor$
334
+ )
335
+ $if(approval.dean)$
336
+ #v(1cm)
337
+ #align(center)[
338
+ #text(size: 10.5pt)[Mengetahui,] \
339
+ #text(size: 10.5pt)[$approval.dean.role$] \
340
+ #v(2cm)
341
+ #text(size: 10.5pt, weight: "bold")[$approval.dean.name$] \
342
+ $if(approval.dean.nip)$#text(size: 9pt)[NIP. $approval.dean.nip$]$endif$
343
+ ]
344
+ $endif$
345
+ #pagebreak()
346
+ $endif$
347
+
348
+ $if(abstract_id)$
349
+ #align(center)[
350
+ #text(size: 14pt, weight: "bold")[ABSTRAK]
351
+ ]
352
+ #v(0.5cm)
353
+ #align(center)[
354
+ #text(size: 11pt, weight: "bold")[#title-lines]
355
+ ]
356
+ #v(0.5cm)
357
+ #set par(justify: true, leading: 0.65em, first-line-indent: 1cm)
358
+ #text(size: 11pt)[
359
+ $abstract_id$
360
+ ]
361
+ $if(keywords_id)$
362
+ #v(0.5cm)
363
+ #text(size: 10.5pt)[
364
+ #set par(first-line-indent: 0cm)
365
+ #strong[Kata Kunci:] #emph[$for(keywords_id)$$keywords_id$$sep$, $endfor$]
366
+ ]
367
+ $endif$
368
+ #pagebreak()
369
+ $endif$
370
+
371
+ $if(abstract_en)$
372
+ #align(center)[
373
+ #text(size: 14pt, weight: "bold")[ABSTRACT]
374
+ ]
375
+ #v(0.5cm)
376
+ #align(center)[
377
+ #text(size: 11pt, weight: "bold", style: "italic")[#title-lines]
378
+ ]
379
+ #v(0.5cm)
380
+ #set par(justify: true, leading: 0.65em, first-line-indent: 1cm)
381
+ #text(size: 11pt, style: "italic")[
382
+ $abstract_en$
383
+ ]
384
+ $if(keywords_en)$
385
+ #v(0.5cm)
386
+ #text(size: 10.5pt)[
387
+ #set par(first-line-indent: 0cm)
388
+ #strong[Keywords:] #emph[$for(keywords_en)$$keywords_en$$sep$, $endfor$]
389
+ ]
390
+ $endif$
391
+ #pagebreak()
392
+ $endif$
393
+
394
+ $body$
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bash
2
+ set -e
3
+
4
+ DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ OUTDIR="$DIR"
6
+ REPORT="$OUTDIR/Laporan.pdf"
7
+
8
+ TMPDIR=$(mktemp -d)
9
+ trap 'rm -rf "$TMPDIR"' EXIT
10
+
11
+ cp "$OUTDIR/cover.md" "$TMPDIR/"
12
+ cp "$OUTDIR/template.typ" "$TMPDIR/"
13
+ cp "$OUTDIR/logo.jpg" "$TMPDIR/"
14
+ cp "$OUTDIR/metadata.yml" "$TMPDIR/" 2>/dev/null || true
15
+ cp "$OUTDIR/references.bib" "$TMPDIR/" 2>/dev/null || true
16
+ cp "$OUTDIR/apa.csl" "$TMPDIR/" 2>/dev/null || true
17
+ cp -r "$OUTDIR/presets" "$TMPDIR/" 2>/dev/null || true
18
+
19
+ if [ -d "$OUTDIR/chapters" ]; then
20
+ cp "$OUTDIR/chapters"/*.md "$TMPDIR/" 2>/dev/null || true
21
+ CHAPTER_FILES=$(find "$TMPDIR" -maxdepth 1 -name "bab*.md" | sort -V)
22
+ if [ -z "$CHAPTER_FILES" ]; then
23
+ echo "ERROR: Tidak ada berkas bab*.md di direktori chapters/."
24
+ exit 1
25
+ fi
26
+ INPUT_FILES=("$TMPDIR/cover.md")
27
+ while IFS= read -r f; do
28
+ [ -n "$f" ] && INPUT_FILES+=("$f")
29
+ done < <(find "$TMPDIR" -maxdepth 1 -name "bab*.md" | sort -V)
30
+ else
31
+ echo "ERROR: Direktori chapters/ tidak ditemukan."
32
+ echo "Buat folder chapters/ dengan file bab laporan (contoh: bab1-pendahuluan.md dst)."
33
+ exit 1
34
+ fi
35
+
36
+ if [ -d "$OUTDIR/gambar" ]; then
37
+ cp -r "$OUTDIR/gambar" "$TMPDIR/"
38
+ IM_CONV="convert"
39
+ command -v magick >/dev/null 2>&1 && IM_CONV="magick"
40
+ find "$TMPDIR/gambar" -type f \( -name "*.png" -o -name "*.PNG" \) -exec $IM_CONV {} -alpha off {} \; 2>/dev/null || true
41
+ fi
42
+
43
+ cd "$TMPDIR"
44
+
45
+ PRESET_NAME=$(grep -E '^[[:space:]]*(preset|margin_preset):' "$TMPDIR/metadata.yml" 2>/dev/null | head -n 1 | cut -d: -f2- | tr -d '"'\''\r\n ')
46
+ PRESET_OPTS=()
47
+ if [ -n "$PRESET_NAME" ] && [ -f "$TMPDIR/presets/${PRESET_NAME}.yml" ]; then
48
+ PRESET_OPTS=("--metadata-file=$TMPDIR/presets/${PRESET_NAME}.yml")
49
+ elif [ -f "$TMPDIR/presets/standard.yml" ]; then
50
+ PRESET_OPTS=("--metadata-file=$TMPDIR/presets/standard.yml")
51
+ fi
52
+
53
+ if ! pandoc \
54
+ "${INPUT_FILES[@]}" \
55
+ --template="template.typ" \
56
+ "${PRESET_OPTS[@]}" \
57
+ --metadata-file="metadata.yml" \
58
+ --citeproc \
59
+ --bibliography="references.bib" \
60
+ --csl="apa.csl" \
61
+ --metadata=reference-section-title="DAFTAR PUSTAKA" \
62
+ --top-level-division=chapter \
63
+ --pdf-engine=typst \
64
+ --no-highlight \
65
+ -o "$REPORT" 2>&1; then
66
+ echo ""
67
+ echo "[ERROR] BUILD GAGAL: Terjadi kesalahan saat kompilasi Pandoc/Typst."
68
+ echo "Kemungkinan penyebab & solusi:"
69
+ echo " 1. Sintaks YAML di metadata.yml tidak valid -> Cek docs/metadata-schema.md"
70
+ echo " 2. Berkas gambar tidak ditemukan atau rusak -> Cek path gambar di chapters/"
71
+ echo " 3. Sintaks Markdown tidak didukung -> Cek docs/troubleshooting.md"
72
+ echo " 4. Gunakan Docker jika ada masalah dependensi lokal: docker compose run --rm laporan-generator"
73
+ exit 1
74
+ fi
75
+
76
+ echo ""
77
+ echo "=== PDF BERHASIL DIBUAT ==="
78
+ echo "Lokasi: $REPORT"
@@ -0,0 +1,75 @@
1
+ #!/usr/bin/env python3
2
+ """bundle.py: Mengemas hasil laporan PDF, DOCX, dan sumber Markdown menjadi arsip rilis zip."""
3
+
4
+ import glob
5
+ import os
6
+ import shutil
7
+ import sys
8
+ import zipfile
9
+
10
+ GREEN = "\033[0;32m"
11
+ BLUE = "\033[0;34m"
12
+ CYAN = "\033[0;36m"
13
+ BOLD = "\033[1m"
14
+ NC = "\033[0m"
15
+
16
+
17
+ def main():
18
+ root = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
19
+ os.chdir(root)
20
+
21
+ print(f"{CYAN}{BOLD}")
22
+ print(" ========================================================")
23
+ print(" LAPORAN GENERATOR - BUNDLE PACKAGER ")
24
+ print(" ========================================================")
25
+ print(f"{NC}")
26
+
27
+ # Cek apakah PDF & DOCX sudah ada
28
+ if not os.path.isfile("Laporan.pdf") or not os.path.isfile("Laporan.docx"):
29
+ print(f" {BLUE}[1/3] Menjalankan build PDF dan DOCX...{NC}")
30
+ ret = os.system("./build.sh && make docx")
31
+ if ret != 0:
32
+ print("Gagal mengompilasi dokumen untuk bundling.")
33
+ return 1
34
+ else:
35
+ print(f" {GREEN}[OK]{NC} Berkas Laporan.pdf dan Laporan.docx siap dikemas.")
36
+
37
+ out_dir = "dist"
38
+ os.makedirs(out_dir, exist_ok=True)
39
+ zip_path = os.path.join(out_dir, "Laporan-Akademik-Lengkap.zip")
40
+
41
+ files_to_pack = [
42
+ "Laporan.pdf",
43
+ "Laporan.docx",
44
+ "metadata.yml",
45
+ "references.bib",
46
+ "cover.md",
47
+ ]
48
+
49
+ for f in glob.glob("chapters/*.md"):
50
+ files_to_pack.append(f)
51
+
52
+ if os.path.isdir("gambar"):
53
+ for f in glob.glob("gambar/**/*", recursive=True):
54
+ if os.path.isfile(f):
55
+ files_to_pack.append(f)
56
+
57
+ print(f" {BLUE}[2/3] Mengompresi berkas ke dalam {zip_path}...{NC}")
58
+ with zipfile.ZipFile(zip_path, "w", zipfile.ZIP_DEFLATED) as zf:
59
+ for f in files_to_pack:
60
+ if os.path.isfile(f):
61
+ zf.write(f, arcname=f)
62
+ sz = os.path.getsize(f) / 1024
63
+ print(f" + {f:<30} ({sz:.1f} KB)")
64
+
65
+ total_sz = os.path.getsize(zip_path) / 1024
66
+ print("")
67
+ print(f" {GREEN}{BOLD}[3/3] BUNDLE SELESAI DIBUAT!{NC}")
68
+ print(f" * Lokasi Arsip : {CYAN}{BOLD}{os.path.abspath(zip_path)}{NC}")
69
+ print(f" * Ukuran Arsip : {total_sz:.1f} KB ({len(files_to_pack)} berkas)")
70
+ print(" ========================================================")
71
+ return 0
72
+
73
+
74
+ if __name__ == "__main__":
75
+ sys.exit(main())