ligarb 0.1.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 6cc0976cad1fb731ff5ad56f38d7ee7142a0deecc158b70482f017df2611b678
4
+ data.tar.gz: 802ec24c33f96792addf6939cf92e04e7163e1b6864958dbfef7b8a02d224645
5
+ SHA512:
6
+ metadata.gz: 58807bf5f3088e399d395761e75e65d52933db36ce116326e30a88b0ccb87425d4cde2644c89912e10b772f5565238b15384ada2fdd2ea261509eceded09ab97
7
+ data.tar.gz: 36069084a2b167996bf2310aa2bd17ee0689d59b52941fbb295b2c623eeb3f34e24a8ccd95bb12751ce23782c9df1374f3c8df8eaa46d24742f63beed146ec87
data/assets/style.css ADDED
@@ -0,0 +1,592 @@
1
+ /* === Reset & Base === */
2
+ *, *::before, *::after {
3
+ box-sizing: border-box;
4
+ margin: 0;
5
+ padding: 0;
6
+ }
7
+
8
+ :root {
9
+ --sidebar-width: 280px;
10
+ --color-bg: #ffffff;
11
+ --color-text: #1a1a1a;
12
+ --color-text-muted: #666666;
13
+ --color-border: #e0e0e0;
14
+ --color-sidebar-bg: #f5f5f5;
15
+ --color-sidebar-hover: #e8e8e8;
16
+ --color-accent: #2563eb;
17
+ --color-accent-light: #dbeafe;
18
+ --color-code-bg: #f8f8f8;
19
+ --color-code-border: #e5e5e5;
20
+ --font-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
21
+ --font-mono: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
22
+ }
23
+
24
+ html {
25
+ font-size: 16px;
26
+ scroll-behavior: smooth;
27
+ }
28
+
29
+ body {
30
+ font-family: var(--font-sans);
31
+ color: var(--color-text);
32
+ background: var(--color-bg);
33
+ line-height: 1.7;
34
+ display: flex;
35
+ min-height: 100vh;
36
+ }
37
+
38
+ /* === Sidebar === */
39
+ .sidebar {
40
+ position: fixed;
41
+ top: 0;
42
+ left: 0;
43
+ width: var(--sidebar-width);
44
+ height: 100vh;
45
+ background: var(--color-sidebar-bg);
46
+ border-right: 1px solid var(--color-border);
47
+ overflow-y: auto;
48
+ display: flex;
49
+ flex-direction: column;
50
+ z-index: 100;
51
+ }
52
+
53
+ .sidebar-header {
54
+ padding: 1.5rem 1rem 0.5rem;
55
+ border-bottom: 1px solid var(--color-border);
56
+ }
57
+
58
+ .book-title {
59
+ font-size: 1.1rem;
60
+ font-weight: 700;
61
+ line-height: 1.3;
62
+ color: var(--color-text);
63
+ }
64
+
65
+ .book-title a {
66
+ color: inherit;
67
+ text-decoration: none;
68
+ }
69
+
70
+ .book-title a:hover {
71
+ color: var(--color-accent);
72
+ }
73
+
74
+ .book-author {
75
+ font-size: 0.85rem;
76
+ color: var(--color-text-muted);
77
+ margin-top: 0.25rem;
78
+ }
79
+
80
+ .search-box {
81
+ padding: 0.75rem 1rem;
82
+ border-bottom: 1px solid var(--color-border);
83
+ position: relative;
84
+ }
85
+
86
+ .search-box input {
87
+ width: 100%;
88
+ padding: 0.4rem 1.8rem 0.4rem 0.6rem;
89
+ border: 1px solid var(--color-border);
90
+ border-radius: 4px;
91
+ font-size: 0.85rem;
92
+ font-family: var(--font-sans);
93
+ outline: none;
94
+ }
95
+
96
+ .search-clear {
97
+ display: none;
98
+ position: absolute;
99
+ right: 0.3rem;
100
+ top: 50%;
101
+ transform: translateY(-50%);
102
+ background: none;
103
+ border: none;
104
+ font-size: 1.1rem;
105
+ color: var(--color-text-muted);
106
+ cursor: pointer;
107
+ padding: 0.2rem 0.4rem;
108
+ line-height: 1;
109
+ }
110
+
111
+ .search-clear:hover {
112
+ color: var(--color-text);
113
+ }
114
+
115
+ .search-box input:focus {
116
+ border-color: var(--color-accent);
117
+ box-shadow: 0 0 0 2px var(--color-accent-light);
118
+ }
119
+
120
+ /* TOC */
121
+ .toc {
122
+ flex: 1;
123
+ overflow-y: auto;
124
+ padding: 0.5rem 0;
125
+ }
126
+
127
+ .toc ul {
128
+ list-style: none;
129
+ }
130
+
131
+ .toc > ul > li {
132
+ margin-bottom: 0.25rem;
133
+ }
134
+
135
+ .toc a {
136
+ display: block;
137
+ padding: 0.3rem 1rem;
138
+ color: var(--color-text);
139
+ text-decoration: none;
140
+ font-size: 0.88rem;
141
+ border-left: 3px solid transparent;
142
+ transition: background 0.15s, border-color 0.15s;
143
+ }
144
+
145
+ .toc a:hover {
146
+ background: var(--color-sidebar-hover);
147
+ }
148
+
149
+ .toc a.toc-h1 {
150
+ font-weight: 600;
151
+ }
152
+
153
+ .toc a.toc-h2 {
154
+ padding-left: 1.8rem;
155
+ font-size: 0.84rem;
156
+ }
157
+
158
+ .toc a.toc-h3 {
159
+ padding-left: 2.6rem;
160
+ font-size: 0.8rem;
161
+ color: var(--color-text-muted);
162
+ }
163
+
164
+ .toc-chapter.active > a.toc-h1 {
165
+ border-left-color: var(--color-accent);
166
+ color: var(--color-accent);
167
+ background: var(--color-accent-light);
168
+ }
169
+
170
+ /* Part & Appendix titles in TOC */
171
+ .toc-part-title {
172
+ font-weight: 700 !important;
173
+ font-size: 0.9rem !important;
174
+ color: var(--color-text) !important;
175
+ text-transform: uppercase;
176
+ letter-spacing: 0.03em;
177
+ padding-top: 0.6rem !important;
178
+ padding-bottom: 0.3rem !important;
179
+ border-left-color: transparent !important;
180
+ }
181
+
182
+ .toc-part > ul {
183
+ margin-bottom: 0.5rem;
184
+ }
185
+
186
+ .toc-appendix-title {
187
+ display: block;
188
+ padding: 0.6rem 1rem 0.3rem;
189
+ font-weight: 700;
190
+ font-size: 0.9rem;
191
+ color: var(--color-text);
192
+ text-transform: uppercase;
193
+ letter-spacing: 0.03em;
194
+ }
195
+
196
+ .toc-appendix > ul {
197
+ margin-bottom: 0.5rem;
198
+ }
199
+
200
+ /* Sidebar toggle */
201
+ .sidebar-toggle {
202
+ display: none;
203
+ position: fixed;
204
+ top: 0.75rem;
205
+ left: 0.75rem;
206
+ z-index: 200;
207
+ background: var(--color-bg);
208
+ border: 1px solid var(--color-border);
209
+ border-radius: 4px;
210
+ padding: 0.4rem 0.6rem;
211
+ font-size: 1.2rem;
212
+ cursor: pointer;
213
+ line-height: 1;
214
+ }
215
+
216
+ /* === Main Content === */
217
+ .content {
218
+ margin-left: var(--sidebar-width);
219
+ flex: 1;
220
+ max-width: 800px;
221
+ padding: 2.5rem 3rem;
222
+ }
223
+
224
+ .chapter h1 {
225
+ font-size: 2rem;
226
+ font-weight: 700;
227
+ margin-bottom: 1.5rem;
228
+ padding-bottom: 0.5rem;
229
+ border-bottom: 2px solid var(--color-border);
230
+ }
231
+
232
+ .chapter h2 {
233
+ font-size: 1.5rem;
234
+ font-weight: 600;
235
+ margin-top: 2.5rem;
236
+ margin-bottom: 1rem;
237
+ }
238
+
239
+ .chapter h3 {
240
+ font-size: 1.2rem;
241
+ font-weight: 600;
242
+ margin-top: 2rem;
243
+ margin-bottom: 0.75rem;
244
+ }
245
+
246
+ .chapter h4,
247
+ .chapter h5,
248
+ .chapter h6 {
249
+ font-size: 1rem;
250
+ font-weight: 600;
251
+ margin-top: 1.5rem;
252
+ margin-bottom: 0.5rem;
253
+ }
254
+
255
+ .chapter p {
256
+ margin-bottom: 1rem;
257
+ }
258
+
259
+ .chapter ul,
260
+ .chapter ol {
261
+ margin-bottom: 1rem;
262
+ padding-left: 1.5rem;
263
+ }
264
+
265
+ .chapter li {
266
+ margin-bottom: 0.3rem;
267
+ }
268
+
269
+ .chapter li > ul,
270
+ .chapter li > ol {
271
+ margin-top: 0.3rem;
272
+ margin-bottom: 0;
273
+ }
274
+
275
+ /* Links */
276
+ .chapter a {
277
+ color: var(--color-accent);
278
+ text-decoration: none;
279
+ }
280
+
281
+ .chapter a:hover {
282
+ text-decoration: underline;
283
+ }
284
+
285
+ /* Code */
286
+ .chapter code {
287
+ font-family: var(--font-mono);
288
+ font-size: 0.88em;
289
+ background: var(--color-code-bg);
290
+ border: 1px solid var(--color-code-border);
291
+ border-radius: 3px;
292
+ padding: 0.15em 0.35em;
293
+ }
294
+
295
+ .chapter pre {
296
+ background: var(--color-code-bg);
297
+ border: 1px solid var(--color-code-border);
298
+ border-radius: 6px;
299
+ padding: 1rem 1.2rem;
300
+ overflow-x: auto;
301
+ margin-bottom: 1.2rem;
302
+ line-height: 1.5;
303
+ }
304
+
305
+ .chapter pre code {
306
+ background: none;
307
+ border: none;
308
+ padding: 0;
309
+ font-size: 0.85rem;
310
+ }
311
+
312
+ /* Tables */
313
+ .chapter table {
314
+ width: 100%;
315
+ border-collapse: collapse;
316
+ margin-bottom: 1.2rem;
317
+ font-size: 0.92rem;
318
+ }
319
+
320
+ .chapter th,
321
+ .chapter td {
322
+ border: 1px solid var(--color-border);
323
+ padding: 0.5rem 0.75rem;
324
+ text-align: left;
325
+ }
326
+
327
+ .chapter th {
328
+ background: var(--color-sidebar-bg);
329
+ font-weight: 600;
330
+ }
331
+
332
+ /* Blockquote */
333
+ .chapter blockquote {
334
+ border-left: 4px solid var(--color-accent);
335
+ padding: 0.5rem 1rem;
336
+ margin-bottom: 1rem;
337
+ color: var(--color-text-muted);
338
+ background: var(--color-code-bg);
339
+ border-radius: 0 4px 4px 0;
340
+ }
341
+
342
+ .chapter blockquote p:last-child {
343
+ margin-bottom: 0;
344
+ }
345
+
346
+ /* Images */
347
+ .chapter img {
348
+ max-width: 100%;
349
+ height: auto;
350
+ border-radius: 4px;
351
+ margin: 0.5rem 0;
352
+ }
353
+
354
+ /* Horizontal rule */
355
+ .chapter hr {
356
+ border: none;
357
+ border-top: 1px solid var(--color-border);
358
+ margin: 2rem 0;
359
+ }
360
+
361
+ /* Task lists */
362
+ .chapter .task-list-item {
363
+ list-style: none;
364
+ margin-left: -1.5rem;
365
+ }
366
+
367
+ .chapter .task-list-item input[type="checkbox"] {
368
+ margin-right: 0.5rem;
369
+ }
370
+
371
+ /* Search highlight */
372
+ mark.search-highlight {
373
+ background: #fef08a;
374
+ color: inherit;
375
+ padding: 0.1em 0;
376
+ border-radius: 2px;
377
+ }
378
+
379
+ /* === Dark Mode === */
380
+ [data-theme="dark"] {
381
+ --color-bg: #1a1a2e;
382
+ --color-text: #e0e0e0;
383
+ --color-text-muted: #a0a0a0;
384
+ --color-border: #333;
385
+ --color-sidebar-bg: #16213e;
386
+ --color-sidebar-hover: #1a1a3a;
387
+ --color-accent: #4dabf7;
388
+ --color-accent-light: #1c3a5c;
389
+ --color-code-bg: #0f172a;
390
+ --color-code-border: #2d3748;
391
+ }
392
+
393
+ [data-theme="dark"] .search-box input {
394
+ background: var(--color-code-bg);
395
+ color: var(--color-text);
396
+ }
397
+
398
+ [data-theme="dark"] mark.search-highlight {
399
+ background: #854d0e;
400
+ color: #fef08a;
401
+ }
402
+
403
+ [data-theme="dark"] .hljs {
404
+ background: var(--color-code-bg) !important;
405
+ color: #e0e0e0 !important;
406
+ }
407
+
408
+ /* Theme toggle */
409
+ .sidebar-header-top {
410
+ display: flex;
411
+ justify-content: space-between;
412
+ align-items: flex-start;
413
+ }
414
+
415
+ .theme-toggle {
416
+ background: none;
417
+ border: 1px solid var(--color-border);
418
+ border-radius: 4px;
419
+ padding: 0.2rem 0.4rem;
420
+ font-size: 1rem;
421
+ cursor: pointer;
422
+ color: var(--color-text-muted);
423
+ line-height: 1;
424
+ flex-shrink: 0;
425
+ }
426
+
427
+ .theme-toggle:hover {
428
+ color: var(--color-text);
429
+ background: var(--color-sidebar-hover);
430
+ }
431
+
432
+ /* === Part / Appendix TOC === */
433
+ .toc-part {
434
+ margin-top: 0.5rem;
435
+ }
436
+
437
+ .toc-part-title {
438
+ display: block;
439
+ font-weight: 700;
440
+ font-size: 0.9rem;
441
+ text-transform: uppercase;
442
+ letter-spacing: 0.03em;
443
+ color: var(--color-text-muted);
444
+ padding: 0.6rem 1rem 0.3rem;
445
+ text-decoration: none;
446
+ cursor: pointer;
447
+ }
448
+
449
+ .toc-part-title:hover {
450
+ color: var(--color-text);
451
+ }
452
+
453
+ .toc-appendix {
454
+ margin-top: 0.5rem;
455
+ }
456
+
457
+ .toc-appendix-title {
458
+ display: block;
459
+ font-weight: 700;
460
+ font-size: 0.9rem;
461
+ text-transform: uppercase;
462
+ letter-spacing: 0.03em;
463
+ color: var(--color-text-muted);
464
+ padding: 0.6rem 1rem 0.3rem;
465
+ border-top: 1px solid var(--color-border);
466
+ }
467
+
468
+ /* === Chapter Navigation === */
469
+ .chapter-nav {
470
+ display: flex;
471
+ justify-content: space-between;
472
+ align-items: center;
473
+ margin-top: 3rem;
474
+ padding-top: 1.5rem;
475
+ border-top: 1px solid var(--color-border);
476
+ gap: 1rem;
477
+ }
478
+
479
+ .nav-prev,
480
+ .nav-next {
481
+ display: inline-block;
482
+ padding: 0.5rem 1rem;
483
+ color: var(--color-accent);
484
+ text-decoration: none;
485
+ font-size: 0.9rem;
486
+ border: 1px solid var(--color-border);
487
+ border-radius: 4px;
488
+ transition: background 0.15s, border-color 0.15s;
489
+ max-width: 45%;
490
+ }
491
+
492
+ .nav-prev:hover,
493
+ .nav-next:hover {
494
+ background: var(--color-accent-light);
495
+ border-color: var(--color-accent);
496
+ text-decoration: none;
497
+ }
498
+
499
+ .nav-next {
500
+ text-align: right;
501
+ margin-left: auto;
502
+ }
503
+
504
+ /* === Edit Link === */
505
+ .edit-link {
506
+ margin-top: 2rem;
507
+ text-align: right;
508
+ }
509
+
510
+ .edit-link a {
511
+ color: var(--color-text-muted);
512
+ text-decoration: none;
513
+ font-size: 0.85rem;
514
+ }
515
+
516
+ .edit-link a:hover {
517
+ color: var(--color-accent);
518
+ }
519
+
520
+ /* === Footnotes === */
521
+ .chapter .footnotes {
522
+ margin-top: 2rem;
523
+ padding-top: 1rem;
524
+ border-top: 1px solid var(--color-border);
525
+ font-size: 0.88rem;
526
+ }
527
+
528
+ .chapter .footnotes ol {
529
+ padding-left: 1.5rem;
530
+ }
531
+
532
+ .chapter sup a {
533
+ color: var(--color-accent);
534
+ text-decoration: none;
535
+ font-weight: 600;
536
+ }
537
+
538
+ .chapter sup a:hover {
539
+ text-decoration: underline;
540
+ }
541
+
542
+ /* === Responsive === */
543
+ @media (max-width: 768px) {
544
+ .sidebar {
545
+ transform: translateX(-100%);
546
+ transition: transform 0.3s ease;
547
+ }
548
+
549
+ .sidebar.open {
550
+ transform: translateX(0);
551
+ }
552
+
553
+ .sidebar-toggle {
554
+ display: block;
555
+ }
556
+
557
+ .content {
558
+ margin-left: 0;
559
+ padding: 2rem 1.5rem;
560
+ padding-top: 3.5rem;
561
+ }
562
+ }
563
+
564
+ /* === Print === */
565
+ @media print {
566
+ .sidebar,
567
+ .sidebar-toggle,
568
+ .chapter-nav,
569
+ .edit-link {
570
+ display: none !important;
571
+ }
572
+
573
+ .content {
574
+ margin-left: 0;
575
+ max-width: 100%;
576
+ padding: 0;
577
+ }
578
+
579
+ .chapter {
580
+ display: block !important;
581
+ page-break-before: always;
582
+ }
583
+
584
+ .chapter:first-child {
585
+ page-break-before: avoid;
586
+ }
587
+
588
+ .chapter pre {
589
+ white-space: pre-wrap;
590
+ word-wrap: break-word;
591
+ }
592
+ }
data/exe/ligarb ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require_relative "../lib/ligarb/cli"
5
+
6
+ Ligarb::CLI.run(ARGV)
@@ -0,0 +1,90 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "net/http"
4
+ require "uri"
5
+ require "fileutils"
6
+ require "set"
7
+
8
+ module Ligarb
9
+ class AssetManager
10
+ ASSETS = {
11
+ highlight: {
12
+ fence_pattern: /language-(?!mermaid|math)(\w+)/,
13
+ files: {
14
+ "js/highlight.min.js" => "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/highlight.min.js",
15
+ "css/highlight.css" => "https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.11.1/styles/github.min.css",
16
+ },
17
+ },
18
+ mermaid: {
19
+ fence_pattern: /class="mermaid"/,
20
+ files: {
21
+ "js/mermaid.min.js" => "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js",
22
+ },
23
+ },
24
+ katex: {
25
+ fence_pattern: /class="math-block"/,
26
+ files: {
27
+ "js/katex.min.js" => "https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.js",
28
+ "css/katex.min.css" => "https://cdn.jsdelivr.net/npm/katex@0.16/dist/katex.min.css",
29
+ },
30
+ },
31
+ }.freeze
32
+
33
+ def initialize(output_path)
34
+ @output_path = output_path
35
+ @needed = Set.new
36
+ end
37
+
38
+ # Scan chapter HTML to detect which assets are needed
39
+ def detect(chapters)
40
+ combined_html = chapters.map(&:html).join
41
+ ASSETS.each do |name, config|
42
+ @needed << name if combined_html.match?(config[:fence_pattern])
43
+ end
44
+ @needed
45
+ end
46
+
47
+ # Download assets if not already present
48
+ def provision!
49
+ @needed.each do |name|
50
+ ASSETS[name][:files].each do |dest_rel, url|
51
+ dest = File.join(@output_path, dest_rel)
52
+ download(url, dest) unless File.exist?(dest)
53
+ end
54
+ end
55
+ end
56
+
57
+ def need?(name)
58
+ @needed.include?(name)
59
+ end
60
+
61
+ private
62
+
63
+ def download(url, dest)
64
+ FileUtils.mkdir_p(File.dirname(dest))
65
+ $stderr.print "Downloading #{File.basename(dest)}... "
66
+
67
+ uri = URI(url)
68
+ response = fetch_with_redirects(uri)
69
+
70
+ if response.is_a?(Net::HTTPSuccess)
71
+ File.write(dest, response.body)
72
+ $stderr.puts "done"
73
+ else
74
+ abort "Error: failed to download #{url} (#{response.code})"
75
+ end
76
+ end
77
+
78
+ def fetch_with_redirects(uri, limit = 5)
79
+ raise "Too many redirects" if limit == 0
80
+
81
+ response = Net::HTTP.get_response(uri)
82
+ case response
83
+ when Net::HTTPRedirection
84
+ fetch_with_redirects(URI(response["location"]), limit - 1)
85
+ else
86
+ response
87
+ end
88
+ end
89
+ end
90
+ end