~cytrogen/.emacs.d

ref: f8d92bae3251c172bfda2f570da2886112018e60 .emacs.d/elpa/ox-hugo-20251206.1738/ox-hugo.el -rw-r--r-- 243.5 KiB
f8d92bae — Cytrogen feat(dictionary): 添加 sdcv 离线词典和 GoldenDict 集成 a month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
2520
2521
2522
2523
2524
2525
2526
2527
2528
2529
2530
2531
2532
2533
2534
2535
2536
2537
2538
2539
2540
2541
2542
2543
2544
2545
2546
2547
2548
2549
2550
2551
2552
2553
2554
2555
2556
2557
2558
2559
2560
2561
2562
2563
2564
2565
2566
2567
2568
2569
2570
2571
2572
2573
2574
2575
2576
2577
2578
2579
2580
2581
2582
2583
2584
2585
2586
2587
2588
2589
2590
2591
2592
2593
2594
2595
2596
2597
2598
2599
2600
2601
2602
2603
2604
2605
2606
2607
2608
2609
2610
2611
2612
2613
2614
2615
2616
2617
2618
2619
2620
2621
2622
2623
2624
2625
2626
2627
2628
2629
2630
2631
2632
2633
2634
2635
2636
2637
2638
2639
2640
2641
2642
2643
2644
2645
2646
2647
2648
2649
2650
2651
2652
2653
2654
2655
2656
2657
2658
2659
2660
2661
2662
2663
2664
2665
2666
2667
2668
2669
2670
2671
2672
2673
2674
2675
2676
2677
2678
2679
2680
2681
2682
2683
2684
2685
2686
2687
2688
2689
2690
2691
2692
2693
2694
2695
2696
2697
2698
2699
2700
2701
2702
2703
2704
2705
2706
2707
2708
2709
2710
2711
2712
2713
2714
2715
2716
2717
2718
2719
2720
2721
2722
2723
2724
2725
2726
2727
2728
2729
2730
2731
2732
2733
2734
2735
2736
2737
2738
2739
2740
2741
2742
2743
2744
2745
2746
2747
2748
2749
2750
2751
2752
2753
2754
2755
2756
2757
2758
2759
2760
2761
2762
2763
2764
2765
2766
2767
2768
2769
2770
2771
2772
2773
2774
2775
2776
2777
2778
2779
2780
2781
2782
2783
2784
2785
2786
2787
2788
2789
2790
2791
2792
2793
2794
2795
2796
2797
2798
2799
2800
2801
2802
2803
2804
2805
2806
2807
2808
2809
2810
2811
2812
2813
2814
2815
2816
2817
2818
2819
2820
2821
2822
2823
2824
2825
2826
2827
2828
2829
2830
2831
2832
2833
2834
2835
2836
2837
2838
2839
2840
2841
2842
2843
2844
2845
2846
2847
2848
2849
2850
2851
2852
2853
2854
2855
2856
2857
2858
2859
2860
2861
2862
2863
2864
2865
2866
2867
2868
2869
2870
2871
2872
2873
2874
2875
2876
2877
2878
2879
2880
2881
2882
2883
2884
2885
2886
2887
2888
2889
2890
2891
2892
2893
2894
2895
2896
2897
2898
2899
2900
2901
2902
2903
2904
2905
2906
2907
2908
2909
2910
2911
2912
2913
2914
2915
2916
2917
2918
2919
2920
2921
2922
2923
2924
2925
2926
2927
2928
2929
2930
2931
2932
2933
2934
2935
2936
2937
2938
2939
2940
2941
2942
2943
2944
2945
2946
2947
2948
2949
2950
2951
2952
2953
2954
2955
2956
2957
2958
2959
2960
2961
2962
2963
2964
2965
2966
2967
2968
2969
2970
2971
2972
2973
2974
2975
2976
2977
2978
2979
2980
2981
2982
2983
2984
2985
2986
2987
2988
2989
2990
2991
2992
2993
2994
2995
2996
2997
2998
2999
3000
3001
3002
3003
3004
3005
3006
3007
3008
3009
3010
3011
3012
3013
3014
3015
3016
3017
3018
3019
3020
3021
3022
3023
3024
3025
3026
3027
3028
3029
3030
3031
3032
3033
3034
3035
3036
3037
3038
3039
3040
3041
3042
3043
3044
3045
3046
3047
3048
3049
3050
3051
3052
3053
3054
3055
3056
3057
3058
3059
3060
3061
3062
3063
3064
3065
3066
3067
3068
3069
3070
3071
3072
3073
3074
3075
3076
3077
3078
3079
3080
3081
3082
3083
3084
3085
3086
3087
3088
3089
3090
3091
3092
3093
3094
3095
3096
3097
3098
3099
3100
3101
3102
3103
3104
3105
3106
3107
3108
3109
3110
3111
3112
3113
3114
3115
3116
3117
3118
3119
3120
3121
3122
3123
3124
3125
3126
3127
3128
3129
3130
3131
3132
3133
3134
3135
3136
3137
3138
3139
3140
3141
3142
3143
3144
3145
3146
3147
3148
3149
3150
3151
3152
3153
3154
3155
3156
3157
3158
3159
3160
3161
3162
3163
3164
3165
3166
3167
3168
3169
3170
3171
3172
3173
3174
3175
3176
3177
3178
3179
3180
3181
3182
3183
3184
3185
3186
3187
3188
3189
3190
3191
3192
3193
3194
3195
3196
3197
3198
3199
3200
3201
3202
3203
3204
3205
3206
3207
3208
3209
3210
3211
3212
3213
3214
3215
3216
3217
3218
3219
3220
3221
3222
3223
3224
3225
3226
3227
3228
3229
3230
3231
3232
3233
3234
3235
3236
3237
3238
3239
3240
3241
3242
3243
3244
3245
3246
3247
3248
3249
3250
3251
3252
3253
3254
3255
3256
3257
3258
3259
3260
3261
3262
3263
3264
3265
3266
3267
3268
3269
3270
3271
3272
3273
3274
3275
3276
3277
3278
3279
3280
3281
3282
3283
3284
3285
3286
3287
3288
3289
3290
3291
3292
3293
3294
3295
3296
3297
3298
3299
3300
3301
3302
3303
3304
3305
3306
3307
3308
3309
3310
3311
3312
3313
3314
3315
3316
3317
3318
3319
3320
3321
3322
3323
3324
3325
3326
3327
3328
3329
3330
3331
3332
3333
3334
3335
3336
3337
3338
3339
3340
3341
3342
3343
3344
3345
3346
3347
3348
3349
3350
3351
3352
3353
3354
3355
3356
3357
3358
3359
3360
3361
3362
3363
3364
3365
3366
3367
3368
3369
3370
3371
3372
3373
3374
3375
3376
3377
3378
3379
3380
3381
3382
3383
3384
3385
3386
3387
3388
3389
3390
3391
3392
3393
3394
3395
3396
3397
3398
3399
3400
3401
3402
3403
3404
3405
3406
3407
3408
3409
3410
3411
3412
3413
3414
3415
3416
3417
3418
3419
3420
3421
3422
3423
3424
3425
3426
3427
3428
3429
3430
3431
3432
3433
3434
3435
3436
3437
3438
3439
3440
3441
3442
3443
3444
3445
3446
3447
3448
3449
3450
3451
3452
3453
3454
3455
3456
3457
3458
3459
3460
3461
3462
3463
3464
3465
3466
3467
3468
3469
3470
3471
3472
3473
3474
3475
3476
3477
3478
3479
3480
3481
3482
3483
3484
3485
3486
3487
3488
3489
3490
3491
3492
3493
3494
3495
3496
3497
3498
3499
3500
3501
3502
3503
3504
3505
3506
3507
3508
3509
3510
3511
3512
3513
3514
3515
3516
3517
3518
3519
3520
3521
3522
3523
3524
3525
3526
3527
3528
3529
3530
3531
3532
3533
3534
3535
3536
3537
3538
3539
3540
3541
3542
3543
3544
3545
3546
3547
3548
3549
3550
3551
3552
3553
3554
3555
3556
3557
3558
3559
3560
3561
3562
3563
3564
3565
3566
3567
3568
3569
3570
3571
3572
3573
3574
3575
3576
3577
3578
3579
3580
3581
3582
3583
3584
3585
3586
3587
3588
3589
3590
3591
3592
3593
3594
3595
3596
3597
3598
3599
3600
3601
3602
3603
3604
3605
3606
3607
3608
3609
3610
3611
3612
3613
3614
3615
3616
3617
3618
3619
3620
3621
3622
3623
3624
3625
3626
3627
3628
3629
3630
3631
3632
3633
3634
3635
3636
3637
3638
3639
3640
3641
3642
3643
3644
3645
3646
3647
3648
3649
3650
3651
3652
3653
3654
3655
3656
3657
3658
3659
3660
3661
3662
3663
3664
3665
3666
3667
3668
3669
3670
3671
3672
3673
3674
3675
3676
3677
3678
3679
3680
3681
3682
3683
3684
3685
3686
3687
3688
3689
3690
3691
3692
3693
3694
3695
3696
3697
3698
3699
3700
3701
3702
3703
3704
3705
3706
3707
3708
3709
3710
3711
3712
3713
3714
3715
3716
3717
3718
3719
3720
3721
3722
3723
3724
3725
3726
3727
3728
3729
3730
3731
3732
3733
3734
3735
3736
3737
3738
3739
3740
3741
3742
3743
3744
3745
3746
3747
3748
3749
3750
3751
3752
3753
3754
3755
3756
3757
3758
3759
3760
3761
3762
3763
3764
3765
3766
3767
3768
3769
3770
3771
3772
3773
3774
3775
3776
3777
3778
3779
3780
3781
3782
3783
3784
3785
3786
3787
3788
3789
3790
3791
3792
3793
3794
3795
3796
3797
3798
3799
3800
3801
3802
3803
3804
3805
3806
3807
3808
3809
3810
3811
3812
3813
3814
3815
3816
3817
3818
3819
3820
3821
3822
3823
3824
3825
3826
3827
3828
3829
3830
3831
3832
3833
3834
3835
3836
3837
3838
3839
3840
3841
3842
3843
3844
3845
3846
3847
3848
3849
3850
3851
3852
3853
3854
3855
3856
3857
3858
3859
3860
3861
3862
3863
3864
3865
3866
3867
3868
3869
3870
3871
3872
3873
3874
3875
3876
3877
3878
3879
3880
3881
3882
3883
3884
3885
3886
3887
3888
3889
3890
3891
3892
3893
3894
3895
3896
3897
3898
3899
3900
3901
3902
3903
3904
3905
3906
3907
3908
3909
3910
3911
3912
3913
3914
3915
3916
3917
3918
3919
3920
3921
3922
3923
3924
3925
3926
3927
3928
3929
3930
3931
3932
3933
3934
3935
3936
3937
3938
3939
3940
3941
3942
3943
3944
3945
3946
3947
3948
3949
3950
3951
3952
3953
3954
3955
3956
3957
3958
3959
3960
3961
3962
3963
3964
3965
3966
3967
3968
3969
3970
3971
3972
3973
3974
3975
3976
3977
3978
3979
3980
3981
3982
3983
3984
3985
3986
3987
3988
3989
3990
3991
3992
3993
3994
3995
3996
3997
3998
3999
4000
4001
4002
4003
4004
4005
4006
4007
4008
4009
4010
4011
4012
4013
4014
4015
4016
4017
4018
4019
4020
4021
4022
4023
4024
4025
4026
4027
4028
4029
4030
4031
4032
4033
4034
4035
4036
4037
4038
4039
4040
4041
4042
4043
4044
4045
4046
4047
4048
4049
4050
4051
4052
4053
4054
4055
4056
4057
4058
4059
4060
4061
4062
4063
4064
4065
4066
4067
4068
4069
4070
4071
4072
4073
4074
4075
4076
4077
4078
4079
4080
4081
4082
4083
4084
4085
4086
4087
4088
4089
4090
4091
4092
4093
4094
4095
4096
4097
4098
4099
4100
4101
4102
4103
4104
4105
4106
4107
4108
4109
4110
4111
4112
4113
4114
4115
4116
4117
4118
4119
4120
4121
4122
4123
4124
4125
4126
4127
4128
4129
4130
4131
4132
4133
4134
4135
4136
4137
4138
4139
4140
4141
4142
4143
4144
4145
4146
4147
4148
4149
4150
4151
4152
4153
4154
4155
4156
4157
4158
4159
4160
4161
4162
4163
4164
4165
4166
4167
4168
4169
4170
4171
4172
4173
4174
4175
4176
4177
4178
4179
4180
4181
4182
4183
4184
4185
4186
4187
4188
4189
4190
4191
4192
4193
4194
4195
4196
4197
4198
4199
4200
4201
4202
4203
4204
4205
4206
4207
4208
4209
4210
4211
4212
4213
4214
4215
4216
4217
4218
4219
4220
4221
4222
4223
4224
4225
4226
4227
4228
4229
4230
4231
4232
4233
4234
4235
4236
4237
4238
4239
4240
4241
4242
4243
4244
4245
4246
4247
4248
4249
4250
4251
4252
4253
4254
4255
4256
4257
4258
4259
4260
4261
4262
4263
4264
4265
4266
4267
4268
4269
4270
4271
4272
4273
4274
4275
4276
4277
4278
4279
4280
4281
4282
4283
4284
4285
4286
4287
4288
4289
4290
4291
4292
4293
4294
4295
4296
4297
4298
4299
4300
4301
4302
4303
4304
4305
4306
4307
4308
4309
4310
4311
4312
4313
4314
4315
4316
4317
4318
4319
4320
4321
4322
4323
4324
4325
4326
4327
4328
4329
4330
4331
4332
4333
4334
4335
4336
4337
4338
4339
4340
4341
4342
4343
4344
4345
4346
4347
4348
4349
4350
4351
4352
4353
4354
4355
4356
4357
4358
4359
4360
4361
4362
4363
4364
4365
4366
4367
4368
4369
4370
4371
4372
4373
4374
4375
4376
4377
4378
4379
4380
4381
4382
4383
4384
4385
4386
4387
4388
4389
4390
4391
4392
4393
4394
4395
4396
4397
4398
4399
4400
4401
4402
4403
4404
4405
4406
4407
4408
4409
4410
4411
4412
4413
4414
4415
4416
4417
4418
4419
4420
4421
4422
4423
4424
4425
4426
4427
4428
4429
4430
4431
4432
4433
4434
4435
4436
4437
4438
4439
4440
4441
4442
4443
4444
4445
4446
4447
4448
4449
4450
4451
4452
4453
4454
4455
4456
4457
4458
4459
4460
4461
4462
4463
4464
4465
4466
4467
4468
4469
4470
4471
4472
4473
4474
4475
4476
4477
4478
4479
4480
4481
4482
4483
4484
4485
4486
4487
4488
4489
4490
4491
4492
4493
4494
4495
4496
4497
4498
4499
4500
4501
4502
4503
4504
4505
4506
4507
4508
4509
4510
4511
4512
4513
4514
4515
4516
4517
4518
4519
4520
4521
4522
4523
4524
4525
4526
4527
4528
4529
4530
4531
4532
4533
4534
4535
4536
4537
4538
4539
4540
4541
4542
4543
4544
4545
4546
4547
4548
4549
4550
4551
4552
4553
4554
4555
4556
4557
4558
4559
4560
4561
4562
4563
4564
4565
4566
4567
4568
4569
4570
4571
4572
4573
4574
4575
4576
4577
4578
4579
4580
4581
4582
4583
4584
4585
4586
4587
4588
4589
4590
4591
4592
4593
4594
4595
4596
4597
4598
4599
4600
4601
4602
4603
4604
4605
4606
4607
4608
4609
4610
4611
4612
4613
4614
4615
4616
4617
4618
4619
4620
4621
4622
4623
4624
4625
4626
4627
4628
4629
4630
4631
4632
4633
4634
4635
4636
4637
4638
4639
4640
4641
4642
4643
4644
4645
4646
4647
4648
4649
4650
4651
4652
4653
4654
4655
4656
4657
4658
4659
4660
4661
4662
4663
4664
4665
4666
4667
4668
4669
4670
4671
4672
4673
4674
4675
4676
4677
4678
4679
4680
4681
4682
4683
4684
4685
4686
4687
4688
4689
4690
4691
4692
4693
4694
4695
4696
4697
4698
4699
4700
4701
4702
4703
4704
4705
4706
4707
4708
4709
4710
4711
4712
4713
4714
4715
4716
4717
4718
4719
4720
4721
4722
4723
4724
4725
4726
4727
4728
4729
4730
4731
4732
4733
4734
4735
4736
4737
4738
4739
4740
4741
4742
4743
4744
4745
4746
4747
4748
4749
4750
4751
4752
4753
4754
4755
4756
4757
4758
4759
4760
4761
4762
4763
4764
4765
4766
4767
4768
4769
4770
4771
4772
4773
4774
4775
4776
4777
4778
4779
4780
4781
4782
4783
4784
4785
4786
4787
4788
4789
4790
4791
4792
4793
4794
4795
4796
4797
4798
4799
4800
4801
4802
4803
4804
4805
4806
4807
4808
4809
4810
4811
4812
4813
4814
4815
4816
4817
4818
4819
4820
4821
4822
4823
4824
4825
4826
4827
4828
4829
4830
4831
4832
4833
4834
4835
4836
4837
4838
4839
4840
4841
4842
4843
4844
4845
4846
4847
4848
4849
4850
4851
4852
4853
4854
4855
4856
4857
4858
4859
4860
4861
4862
4863
4864
4865
4866
4867
4868
4869
4870
4871
4872
4873
4874
4875
4876
4877
4878
4879
4880
4881
4882
4883
4884
4885
4886
4887
4888
4889
4890
4891
4892
4893
4894
4895
4896
4897
4898
4899
4900
4901
4902
4903
4904
4905
4906
4907
4908
4909
4910
4911
4912
4913
4914
4915
4916
4917
4918
4919
4920
4921
4922
4923
4924
4925
4926
4927
4928
4929
4930
4931
4932
4933
4934
4935
4936
4937
4938
4939
4940
4941
4942
4943
4944
4945
4946
4947
4948
4949
4950
4951
4952
4953
4954
4955
4956
4957
4958
4959
4960
4961
4962
4963
4964
4965
4966
4967
4968
4969
4970
4971
4972
4973
4974
4975
4976
4977
4978
4979
4980
4981
4982
4983
4984
4985
4986
4987
4988
4989
4990
4991
4992
4993
4994
4995
4996
4997
4998
4999
5000
5001
5002
5003
5004
5005
5006
5007
5008
5009
5010
5011
5012
5013
5014
5015
5016
5017
5018
5019
5020
5021
5022
5023
5024
5025
5026
5027
5028
5029
5030
5031
5032
5033
5034
5035
5036
5037
5038
5039
5040
5041
;;; ox-hugo.el --- Hugo Markdown Back-End for Org Export Engine  -*- lexical-binding: t -*-

;; Author: Kaushal Modi <kaushal.modi@gmail.com>
;;         Matt Price <moptop99@gmail.com>
;; Package-Version: 20251206.1738
;; Package-Revision: b7dc44dc2891
;; Package-Requires: ((emacs "26.3") (tomelr "0.4.3"))
;; Keywords: Org, markdown, docs
;; URL: https://ox-hugo.scripter.co

;; This file is not part of GNU Emacs.

;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.

;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
;; GNU General Public License for more details.

;; You should have received a copy of the GNU General Public License
;; along with this program.  If not, see <https://www.gnu.org/licenses/>.

;;; Commentary:

;; ox-hugo implements a Markdown back-end for Org exporter.  The
;; exported Markdown is compatible with the Hugo static site generator
;; (https://gohugo.io/).  This exporter also generates the post
;; front-matter in TOML or YAML.

;; To start using this exporter, add the below to your Emacs config:
;;
;;   (with-eval-after-load 'ox
;;     (require 'ox-hugo))
;;
;; With the above evaluated, the ox-hugo exporter options will be
;; available in the Org Export Dispatcher.  The ox-hugo export
;; commands have bindings beginning with "H" (for Hugo).
;;
;; # Blogging Flows
;;
;; 1. one-post-per-subtree flow :: A single Org file can have multiple
;;      Org subtrees which export to individual Hugo posts.  Each of
;;      those subtrees that has the EXPORT_FILE_NAME property set is
;;      called a 'valid Hugo post subtree' in this package and its
;;      documentation.
;;
;; 2. one-post-per-file flow :: A single Org file exports to only
;;      *one* Hugo post.  An Org file intended to be exported by this
;;      flow must not have any 'valid Hugo post subtrees', and instead
;;      must have the #+title property set.
;;
;; # Commonly used export commands
;;
;; ## For both one-post-per-subtree and one-post-per-file flows
;;
;;    - C-c C-e H H  -> Export "What I Mean".
;;                      - If point is in a 'valid Hugo post subtree',
;;                        export that subtree to a Hugo post in
;;                        Markdown.
;;                      - If the file is intended to be exported as a
;;                        whole (i.e. has the #+title keyword),
;;                        export the whole Org file to a Hugo post in
;;                        Markdown.
;;
;;    - C-c C-e H A  -> Export *all* "What I Mean"
;;                      - If the Org file has one or more 'valid Hugo
;;                        post subtrees', export them to Hugo posts in
;;                        Markdown.
;;                      - If the file is intended to be exported as a
;;                        whole (i.e. no 'valid Hugo post subtrees'
;;                        at all, and has the #+title keyword),
;;                        export the whole Org file to a Hugo post in
;;                        Markdown.
;;
;; ## For only the one-post-per-file flow
;;
;;    - C-c C-e H h  -> Export the Org file to a Hugo post in Markdown.

;; Do M-x customize-group, and select `org-export-hugo' to see the
;; available customization options for this package.

;; See this package's website for more instructions and examples:
;;
;;   https://ox-hugo.scripter.co

;;; Code:

(require 'tomelr)                       ;For `tomelr-encode'

(require 'ox-blackfriday)

(require 'ffap)                         ;For `ffap-url-regexp'
(require 'ob-core)                      ;For `org-babel-parse-header-arguments'
;; `org-refile.el' is new in Org 9.4
;; https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=f636cf91b6cbe322eca56e23283f4614548c9d65
(require 'org-refile nil :noerror)      ;For `org-get-outline-path'

(require 'org)
(require 'org-id)                       ;For `org-id-find'

;; For `org-info-emacs-documents', `org-info-other-documents'
;; org-info.el got renamed to ol-info.el in Org version 9.3.  Remove
;; below if condition after the minimum emacs dependency is raised to
;; emacs 27.x. The Org version shipped with Emacs 26.3 is 9.1.9.
(if (version< (org-version) "9.3")
    (require 'org-info)
  (require 'ol-info))

(declare-function org-hugo-pandoc-cite--parse-citations-maybe "ox-hugo-pandoc-cite")
(declare-function org-hugo-pandoc-cite--meta-data-generator "ox-hugo-pandoc-cite")

(require 'ox-hugo-deprecated)


(defvar ffap-url-regexp)                ;Silence byte-compiler


;; Using the correct function for getting inherited Org tags.
;; Starting Org 9.2, `org-get-tags' returns all the inherited tags
;; instead of returning only the local tags i.e. only the current
;; heading tags.
;; https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=fbe56f89f75a8979e0ba48001a822518df2c66fe

;; For Org <= 9.1, `org-get-tags' returned a list of tags *only* at
;; the current heading, while `org-get-tags-at' returned inherited
;; tags too.
(with-no-warnings
  (if (fboundp #'org--get-local-tags)   ;If using Org 9.2+
      (defalias 'org-hugo--get-tags 'org-get-tags)
    (defalias 'org-hugo--get-tags 'org-get-tags-at)))

;; `org-back-to-heading-or-point-min' was introduced in Org 9.5 in
;; https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=1bdff9f73dc1e7ff625a90e3e61350bdea99f29c.
;; If a user is using a slightly older version of Org (like 9.3),
;; define it.
(unless (fboundp #'org-back-to-heading-or-point-min)
  (defun org-back-to-heading-or-point-min (&optional invisible-ok)
    "Go back to heading or first point in buffer.
If point is before first heading go to first point in buffer
instead of back to heading."
    (if (org-before-first-heading-p)
        (goto-char (point-min))
      (org-back-to-heading invisible-ok))))

(defvar org-hugo--subtree-coord nil
  "Variable to store the current valid Hugo subtree coordinates.
It holds the value returned by
`org-hugo--get-post-subtree-coordinates'.")

(defvar org-hugo--subtree-count 0
  "Variable to count of number of subtrees getting exported.
This variable is used when exporting all subtrees in a file.")

(defvar org-hugo--fm nil
  "Variable to store the current Hugo post's front-matter string.

This variable is used to cache the original ox-hugo generated
front-matter that's used after Pandoc Citation parsing.")

(defvar org-hugo--fm-yaml nil
  "Variable to store the current Hugo post's front-matter string in YAML format.

Pandoc understands meta-data only in YAML format.  So when Pandoc
Citations are enabled, Pandoc is handed over the file with this
YAML front-matter.")

(defvar org-hugo--internal-list-separator "\n"
  "String used to separate elements in list variables.

Examples are internal variables holding Hugo tags, categories and
keywords.

This variable is for internal use only, and must not be
modified.")

(defvar org-hugo--date-time-regexp (concat "\\`[[:digit:]]\\{4\\}-[[:digit:]]\\{2\\}-[[:digit:]]\\{2\\}"
                                           "\\(?:T[[:digit:]]\\{2\\}:[[:digit:]]\\{2\\}:[[:digit:]]\\{2\\}"
                                           "\\(?:Z\\|[+-][[:digit:]]\\{2\\}:[[:digit:]]\\{2\\}\\)*\\)*\\'")
  "Regexp to match the Hugo time stamp strings.

Reference: https://tools.ietf.org/html/rfc3339#section-5.8

Examples:
  2017-07-31
  2017-07-31T17:05:38
  2017-07-31T17:05:38Z
  2017-07-31T17:05:38+04:00
  2017-07-31T17:05:38-04:00.")

(defvar org-hugo--trim-pre-marker "<!-- trim-pre -->"
  "Special string to mark where whitespace should be trimmed before an element.")

(defvar org-hugo--trim-post-marker "<!-- trim-post -->"
  "Special string to mark where whitespace should be trimmed after an element.")

(defvar org-hugo--opened-buffers '()
  "List of buffers opened during an export, which will be auto-closed at the end.

An export operation might need to open files for resolving links
pointing to other Org files or temporary buffers for
pre-processing an Org file.  Each buffer opened during an Ox-Hugo
export gets added to this list, and they all are auto-closed at
the end of the export in `org-hugo--after-all-exports-function'.")

(defvar org-hugo--disable-after-all-exports-hook nil
  "If set, `org-hugo--after-all-exports-function' function is not called.

This variable is set internally by `org-hugo-export-wim-to-md'
when its ALL-SUBTREES arg is set to a non-nil value.

Setting this to non-nil will lead to slow or incorrect
exports.  This variable is for internal use only, and must not be
modified.")

(defvar org-hugo--all-subtrees-export--functions-to-silence
  '(org-babel-exp-src-block ;Don't print "org-babel-exp process .." messages
    write-region            ;Don't print "Wrote .." messages
    table-generate-source   ;Don't print "Generating source..." messages
    )
  "List of functions to silence in Echo and Messages buffers.

These functions are silenced only when ALL-SUBTREES export is done.")

(defconst org-hugo--preprocess-buffer t
  "Enable pre-processing of the current Org buffer.

This variable needs to be non-nil for the support of
cross-subtree Org internal links when using the subtree-based
export flow.")

(defvar org-hugo--preprocessed-buffer nil
  "Name of the pre-processed buffer.")

(defconst org-hugo--preprocessed-buffer-dummy-file-suffix ".pre-processed.org"
  "Dummy suffix (including file extension) for pre-processed buffers.

Dummy Org file paths are created in
`org-hugo--get-pre-processed-buffer' by appending this variable
to the link targets out of the current subtree scope.")


;;; Obsoletions

(define-obsolete-variable-alias 'org-hugo-default-section-directory 'org-hugo-section "Oct 31, 2018")
(define-obsolete-function-alias 'org-hugo-headline 'org-hugo-heading "Jan 3, 2022")



;;; User-Configurable Variables

(defgroup org-export-hugo nil
  "Options for exporting Org mode files to Hugo-compatible Markdown."
  :tag "Org Export Hugo"
  :group 'org-export
  :version "25.2")

(defcustom org-hugo-base-dir nil
  "Base directory for Hugo.

Set either this value, or the HUGO_BASE_DIR global property for
export."
  :group 'org-export-hugo
  :type 'directory)
;;;###autoload (put 'org-hugo-base-dir 'safe-local-variable 'stringp)

(defcustom org-hugo-content-folder "content"
  "Content folder for Hugo.

Set either this value, or the HUGO_BASE_CONTENT_FOLDER global property for
export."
  :group 'org-export-hugo
  :type 'string)
;;;###autoload (put 'org-hugo-content-folder 'safe-local-variable 'stringp)

(defcustom org-hugo-goldmark t
  "Enable Goldmark or Commonmark compatible Markdown export.

When nil, the hacks necessary for Blackfriday Markdown
processing are enabled.

If using Hugo v0.60.0 (released Nov 2019), keep the default
value.

https://github.com/kaushalmodi/ox-hugo/discussions/485."
  :group 'org-export-hugo
  :type 'boolean)
;;;###autoload (put 'org-hugo-goldmark 'safe-local-variable 'booleanp)

(defcustom org-hugo-headline-anchor t
  "Enable anchor for headline"
  :group 'org-export-hugo
  :type 'boolean)
;;;###autoload (put 'org-hugo-headline-anchor 'safe-local-variable 'booleanp)

(defcustom org-hugo-section "posts"
  "Default section for Hugo posts.

This variable is the name of the directory under the \"content/\"
directory where all Hugo posts should go by default."
  :group 'org-export-hugo
  :type 'directory)
;;;###autoload (put 'org-hugo-section 'safe-local-variable 'stringp)

(defcustom org-hugo-front-matter-format "toml"
  "Front-matter format.
This variable can be set to either \"toml\" or \"yaml\"."
  :group 'org-export-hugo
  :type '(choice
          (const :tag "TOML" "toml")
          (const :tag "YAML" "yaml")))
;;;###autoload (put 'org-hugo-front-matter-format 'safe-local-variable 'stringp)

(defcustom org-hugo-footer ""
  "String to be appended at the end of each Hugo post.

The string needs to be in a Hugo-compatible Markdown format or HTML."
  :group 'org-export-hugo
  :type 'string)
;;;###autoload (put 'org-hugo-footer 'safe-local-variable 'stringp)

(defcustom org-hugo-preserve-filling t
  "When non-nil, text filling done in Org will be retained in Markdown."
  :group 'org-export-hugo
  :type 'boolean)
;;;###autoload (put 'org-hugo-preserve-filling 'safe-local-variable 'booleanp)

(defcustom org-hugo-delete-trailing-ws t
  "When non-nil, delete trailing whitespace in Markdown output.
Trailing empty lines at the end of the Markdown output are also deleted.

One might want to set this variable to nil if they want to
preserve the trailing whitespaces in Markdown for the purpose of
forcing line-breaks.

The trailing whitespace deleting is skipped if
`org-export-preserve-breaks' is set to non-nil; either via that
variable or via the OPTIONS keyword \"\\n:t\" (See (org) Export
settings).

\(In below Markdown, underscores are used to represent spaces.)

    abc__
    def__

Those trailing whitespaces render to \"<br />\" tags in the Hugo
generated HTML.  But the same result can also be achived by using the
Org Verse block or Blackfriday hardLineBreak extension."
  :group 'org-export-hugo
  :type 'boolean)
;;;###autoload (put 'org-hugo-delete-trailing-ws 'safe-local-variable 'booleanp)

(defcustom org-hugo-use-code-for-kbd nil
  "When non-nil, ~text~ will translate to <kbd>text</kbd>."
  :group 'org-export-hugo
  :type 'boolean)
;;;###autoload (put 'org-hugo-use-code-for-kbd 'safe-local-variable 'booleanp)

(defcustom org-hugo-allow-spaces-in-tags t
  "When non-nil, replace double underscores in Org tags with spaces.

See `org-hugo--tag-processing-fn-replace-with-spaces-maybe' for
more information.

This variable affects the Hugo tags and categories (set via Org
tags using the \"@\" prefix)."
  :group 'org-export-hugo
  :type 'boolean)
;;;###autoload (put 'org-hugo-allow-spaces-in-tags 'safe-local-variable 'booleanp)

(defcustom org-hugo-prefer-hyphen-in-tags t
  "When non-nil, replace single underscores in Org tags with hyphens.

See `org-hugo--tag-processing-fn-replace-with-hyphens-maybe' for
more information.

This variable affects the Hugo tags and categories (set via Org
tags using the \"@\" prefix)."
  :group 'org-export-hugo
  :type 'boolean)
;;;###autoload (put 'org-hugo-prefer-hyphen-in-tags 'safe-local-variable 'booleanp)

(defcustom org-hugo-tag-processing-functions '(org-hugo--tag-processing-fn-replace-with-spaces-maybe
                                               org-hugo--tag-processing-fn-replace-with-hyphens-maybe)
  "List of functions that are called in order to process the Org tags.
Each function has to accept two arguments:

Arg 1: TAG-LIST which is a list of Org tags of the type
       \(\"TAG1\" \"TAG2\" ..).
Arg 2: INFO which is a plist holding contextual information.

Each function should then return a list of strings, which would
be processed form of TAG-LIST.

All the functions are called in order, and the output of one
function is fed as the TAG-LIST input of the next called
function.

The `org-hugo--tag-processing-fn-replace-with-spaces-maybe'
function skips any processing and returns its input TAG-LIST as
it is if `org-hugo-allow-spaces-in-tags' is nil.

The `org-hugo--tag-processing-fn-replace-with-hyphens-maybe'
function skips any processing and returns its input TAG-LIST as
it is if `org-hugo-prefer-hyphen-in-tags' is nil."
  :group 'org-export-hugo
  :type '(repeat (function)))

(defcustom org-hugo-auto-set-lastmod nil
  "When non-nil, set the lastmod field in front-matter to current time."
  :group 'org-export-hugo
  :type 'boolean)
;;;###autoload (put 'org-hugo-auto-set-lastmod 'safe-local-variable 'booleanp)

(defcustom org-hugo-suppress-lastmod-period 0.0
  "Suppressing period (in seconds) for adding the lastmod front-matter.

The suppressing period is calculated as a delta between the
\"date\" and auto-calculated \"lastmod\" values.  This value can
be 0.0 or a positive float.

The default value is 0.0 (seconds), which means that the lastmod
parameter will be added to front-matter even if the post is
modified within just 0.1 seconds after the initial creation of
it (when the \"date\" is set).

If the value is 86400.0, the lastmod parameter will not be added
to the front-matter within 24 hours from the initial exporting.

This variable is effective only if auto-setting of the
\"lastmod\" parameter is enabled i.e. if
`org-hugo-auto-set-lastmod' or `EXPORT_HUGO_AUTO_SET_LASTMOD' is
non-nil."
  :group 'org-export-hugo
  :type 'float)
;;;###autoload (put 'org-hugo-suppress-lastmod-period 'safe-local-variable 'floatp)

(defcustom org-hugo-export-with-toc nil
  "When non-nil, Markdown format TOC will be inserted.

The TOC contains headings with levels up
to`org-export-headline-levels'.  When an integer, include levels
up to N in the toc, this may then be different from
`org-export-headline-levels', but it will not be allowed to be
larger than the number of heading levels.  When nil, no table of
contents is made.

This option can also be set with the OPTIONS keyword,
e.g. \"toc:nil\", \"toc:t\" or \"toc:3\"."
  :group 'org-export-hugo
  :type '(choice
          (const :tag "No Table of Contents" nil)
          (const :tag "Full Table of Contents" t)
          (integer :tag "TOC to level")))
;;;###autoload (put 'org-hugo-export-with-toc 'safe-local-variable (lambda (x) (or (booleanp x) (integerp x))))

(defcustom org-hugo-export-with-section-numbers nil
  "Configuration for adding section numbers to headings.

When set to `onlytoc', none of the headings will be numbered in
the exported post body, but TOC generation will use the section
numbers.

When set to an integer N, numbering will only happen for
headings whose relative level is higher or equal to N.

When set to any other non-nil value, numbering will happen for
all the headings.

This option can also be set with the OPTIONS keyword,
e.g. \"num:onlytoc\", \"num:nil\", \"num:t\" or \"num:3\"."
  :group 'org-export-hugo
  :type '(choice
          (const :tag "Don't number only in body" onlytoc)
          (const :tag "Don't number any heading" nil)
          (const :tag "Number all headings" t)
          (integer :tag "Number to level")))
;;;###autoload (put 'org-hugo-export-with-section-numbers 'safe-local-variable (lambda (x) (or (booleanp x) (equal 'onlytoc x) (integerp x))))

(defcustom org-hugo-default-static-subdirectory-for-externals "ox-hugo"
  "Default sub-directory in Hugo static directory for external files.
If the source path for external files does not contain
\"static\", `ox-hugo` cannot know what directory structure to
create inside the Hugo static directory.  So all such files are
copied to this sub-directory inside the Hugo static directory."
  :group 'org-export-hugo
  :type 'string)
;;;###autoload (put 'org-hugo-default-static-subdirectory-for-externals 'safe-local-variable 'stringp)

(defcustom org-hugo-external-file-extensions-allowed-for-copying
  '("jpg" "jpeg" "tiff" "png" "svg" "gif" "bmp"
    "mp4"
    "pdf" "odt"
    "doc" "ppt" "xls"
    "docx" "pptx" "xlsx")
  "List of external file extensions allowed for copying to Hugo static dir.
If an Org link references a file with one of these extensions,
and if that file is not in the Hugo static directory, that file
is copied over to the static directory.

The auto-copying behavior is disabled if this variable is set to
nil."
  :group 'org-export-hugo
  :type '(repeat string))

(defcustom org-hugo-export-creator-string
  (format "Emacs %s (Org mode%s + ox-hugo)"
          emacs-version
          (if (fboundp 'org-version)
              (concat " " (org-version))
            ""))
  "Information about the creator of the document.
This option can also be set on with the CREATOR keyword."
  :group 'org-export-hugo
  :type '(string :tag "Creator string"))
;;;###autoload (put 'org-hugo-export-creator-string 'safe-local-variable 'stringp)

(defcustom org-hugo-date-format "%Y-%m-%dT%T%z"
  "Date format used for exporting date in front-matter.

Front-matter date parameters: `date', `publishDate',
`expiryDate', `lastmod'.

Note that the date format must match the date specification from
RFC3339.  See `org-hugo--date-time-regexp' for reference and
examples of compatible date strings.

Examples of RFC3339-compatible values for this variable:

  - %Y-%m-%dT%T%z (default) -> 2017-07-31T17:05:38-04:00
  - %Y-%m-%dT%T             -> 2017-07-31T17:05:38
  - %Y-%m-%d                -> 2017-07-31

Note that \"%Y-%m-%dT%T%z\" actually produces a date string like
\"2017-07-31T17:05:38-0400\"; notice the missing colon in the
time-zone portion.

A colon is needed to separate the hours and minutes in the
time-zone as per RFC3339.  This gets fixed in the
`org-hugo--format-date' function, so that \"%Y-%m-%dT%T%z\" now
results in a date string like \"2017-07-31T17:05:38-04:00\".

See `format-time-string' to learn about the date format string
expression."
  :group 'org-export-hugo
  :type 'string)
;;;###autoload (put 'org-hugo-date-format 'safe-local-variable 'stringp)

(defcustom org-hugo-paired-shortcodes ""
  "Space-separated string of paired shortcode strings.

Shortcode string convention:

  - Begin the string with \"%\" for shortcodes whose content can
    contain Markdown, and thus needs to be passed through the
    Hugo Markdown processor.  The content can also contain HTML.

    Example of a paired markdown shortcode:

      {{% mdshortcode %}}Content **bold** <i>italics</i>{{% /mdshortcode %}}

  - Absence of the \"%\" prefix would imply that the shortcode's
    content should not be passed to the Markdown parser.  The
    content can contain HTML though.

    Example of a paired non-markdown (default) shortcode:

      {{< myshortcode >}}Content <b>bold</b> <i>italics</i>{{< /myshortcode >}}

For example these shortcode strings:

  - %mdshortcode : Paired markdown shortcode
  - myshortcode  : Paired default shortcode

would be collectively added to this variable as:

   \"%mdshortcode myshortcode\"

Hugo shortcodes documentation:
https://gohugo.io/content-management/shortcodes/."
  :group 'org-export-hugo
  :type 'string)
;;;###autoload (put 'org-hugo-paired-shortcodes 'safe-local-variable 'stringp)

(defcustom org-hugo-link-desc-insert-type nil
  "Insert the element type in link descriptions for numbered elements.

String representing the type is inserted for these Org elements
if they are numbered (i.e. both \"#+name\" and \"#+caption\" are
specified for them):

- src-block : \"Code Snippet\"
- table: \"Table\"
- figure: \"Figure\"."
  :group 'org-export-hugo
  :type 'boolean)
;;;###autoload (put 'org-hugo-link-desc-insert-type 'safe-local-variable 'booleanp)

(defcustom org-hugo-container-element ""
  "HTML element to use for wrapping top level sections.
Can be set with the in-buffer HTML_CONTAINER property.

When set to \"\", the top level sections are not wrapped in any
HTML element."
  :group 'org-export-hugo
  :type 'string)
;;;###autoload (put 'org-hugo-container-element 'safe-local-variable 'stringp)

(defcustom org-hugo-special-block-type-properties '(("audio" . (:raw t))
                                                    ("katex" . (:raw t))
                                                    ("mark" . (:trim-pre t :trim-post t))
                                                    ("tikzjax" . (:raw t))
                                                    ("video" . (:raw t)))
  "Alist for storing default properties for special block types.

Each element of the alist is of the form (TYPE . PLIST) where
TYPE is a string holding the special block's type and PLIST is a
property list for that TYPE.

The TYPE string could be any special block type like an HTML
inline or block tag, or name of a Hugo shortcode, or any random
string.

Properties recognized in the PLIST:

- :raw :: When set to t, the contents of the special block as
          exported raw i.e. as typed in the Org buffer.

- :trim-pre :: When set to t, the whitespace before the special
               block is removed.

- :trim-pre :: When set to t, the whitespace after the special
               block is removed.

For the special block types not specified in this variable, the
default behavior is same as if (:raw nil :trim-pre nil :trim-post
nil) plist were associated with them."
  :group 'org-export-hugo
  :type '(alist :key-type string :value-type (plist :key-type symbol :value-type boolean)))

(defcustom org-hugo-anchor-functions '(org-hugo-get-page-or-bundle-name
                                       org-hugo-get-custom-id
                                       org-hugo-get-heading-slug
                                       org-hugo-get-md5)
  "A list of functions for deriving the anchor of current Org heading.

The functions will be run in the order added to this variable
until the first one returns a non-nil value.  So the functions in
this list are order-sensitive.

For example, if `org-hugo-get-page-or-bundle-name' is the first
element in this list, the heading's `:EXPORT_FILE_NAME' property
will have the highest precedence in determining the heading's
anchor string.

This variable is used in the `org-hugo--get-anchor' internal
function.

Functions added to this list should have 2 arguments (which could
even be declared as optional):

1. ELEMENT : Org element
2. INFO    : General plist used as a communication channel

Some of the inbuilt functions that can be added to this list:
- `org-hugo-get-page-or-bundle-name'
- `org-hugo-get-custom-id'
- `org-hugo-get-heading-slug'
- `org-hugo-get-md5'
- `org-hugo-get-id'"
  :group 'org-export-hugo
  :type '(repeat function))

(defcustom org-hugo-citations-plist '(:bibliography-section-heading "References")
  "Property list for storing default properties for citation exports.

Properties recognized in the PLIST:

- :bibliography-section-heading :: Heading to insert before the bibliography
                                   section.

Auto-detection of bibliography section requires installing the
`citations' package from Melpa and adding `#+cite_export: csl' at
the top of the Org file.

If `:bibliography-section-heading' set to an empty string,
bibliography heading auto-injection is not done."
  :group 'org-export-hugo
  :type '(plist :key-type symbol :value-type string))

(defcustom org-hugo-info-gnu-software '("3dldf" "8sync"
                                        "a2ps" "acct" "acm" "adns" "alive" "anubis" "apl"
                                        "archimedes" "aris" "artanis" "aspell" "auctex" "autoconf" "autoconf-archive"
                                        "autogen" "automake" "avl"
                                        "ballandpaddle" "barcode" "bash" "bayonne" "bazaar" "bc" "behistun"
                                        "bfd" "binutils" "bison" "bool" "bpel2owfn"
                                        "c-graph" "ccaudio" "ccd2cue" "ccide" "ccrtp" "ccscript" "cflow"
                                        "cgicc" "chess" "cim" "classpath" "classpathx" "clisp" "combine"
                                        "commoncpp" "complexity" "config" "consensus" "coreutils" "cpio" "cppi"
                                        "cssc" "cursynth"
                                        "dap" "datamash" "dc" "ddd" "ddrescue" "dejagnu" "denemo"
                                        "dia" "dico" "diction" "diffutils" "direvent" "djgpp" "dominion"
                                        "dr-geo"
                                        "easejs" "ed" "edma" "electric" "emacs" "emacs-muse" "emms"
                                        "enscript" "epsilon"
                                        "fdisk" "ferret" "findutils" "fisicalab" "foliot" "fontopia" "fontutils"
                                        "freedink" "freefont" "freeipmi" "freetalk" "fribidi"
                                        "g-golf" "gama" "garpd" "gawk" "gcal" "gcc" "gcide"
                                        "gcl" "gcompris" "gdb" "gdbm" "gengen" "gengetopt" "gettext"
                                        "gforth" "ggradebook" "ghostscript" "gift" "gimp" "glean" "global"
                                        "glpk" "glue" "gmediaserver" "gmp" "gnash" "gnat" "gnats"
                                        "gnatsweb" "gnowsys" "gnu-c-manual" "gnu-crypto" "gnu-pw-mgr" "gnuae" "gnuastro"
                                        "gnubatch" "gnubg" "gnubiff" "gnubik" "gnucap" "gnucash" "gnucobol"
                                        "gnucomm" "gnudos" "gnufm" "gnugo" "gnuit" "gnujdoc" "gnujump"
                                        "gnukart" "gnulib" "gnumach" "gnumed" "gnumeric" "gnump3d" "gnun"
                                        "gnunet" "gnupg" "gnupod" "gnuprologjava" "gnuradio" "gnurobots" "gnuschool"
                                        "gnushogi" "gnusound" "gnuspeech" "gnuspool" "gnustandards" "gnustep" "gnutls"
                                        "gnutrition" "gnuzilla" "goptical" "gorm" "gpaint" "gperf" "gprolog"
                                        "grabcomics" "greg" "grep" "gretl" "groff" "grub" "gsasl"
                                        "gsegrafix" "gsl" "gslip" "gsrc" "gss" "gtick" "gtypist"
                                        "guile" "guile-cv" "guile-dbi" "guile-gnome" "guile-ncurses" "guile-opengl"
                                        "guile-rpc" "guile-sdl" "guix" "gurgle" "gv" "gvpe" "gwl" "gxmessage"
                                        "gzip"
                                        "halifax" "health" "hello" "help2man" "hp2xx" "html-info" "httptunnel"
                                        "hurd" "hyperbole"
                                        "icecat" "idutils" "ignuit" "indent" "inetutils" "inklingreader" "intlfonts"
                                        "jacal" "jami" "java-getopt" "jel" "jitter" "jtw" "jwhois"
                                        "kawa" "kopi"
                                        "leg" "less" "libc" "libcdio" "libdbh" "liberty-eiffel" "libextractor"
                                        "libffcall" "libgcrypt" "libiconv" "libidn" "libjit" "libmatheval"
                                        "libmicrohttpd" "libredwg" "librejs" "libsigsegv" "libtasn1" "libtool"
                                        "libunistring" "libxmi" "lightning" "lilypond" "lims" "linux-libre" "liquidwar6"
                                        "lispintro" "lrzsz" "lsh"
                                        "m4" "macchanger" "mailman" "mailutils" "make" "marst" "maverik"
                                        "mc" "mcron" "mcsim" "mdk" "mediagoblin" "melting" "mempool"
                                        "mes" "metaexchange" "metahtml" "metalogic-inference" "mifluz" "mig" "miscfiles"
                                        "mit-scheme" "moe" "motti" "mpc" "mpfr" "mpria" "mtools"
                                        "nana" "nano" "nano-archimedes" "ncurses" "nettle" "network"
                                        "ocrad" "octave" "oleo" "oo-browser" "orgadoc" "osip"
                                        "panorama" "parallel" "parted" "pascal" "patch" "paxutils" "pcb"
                                        "pem" "pexec" "pies" "pipo" "plotutils" "poke" "polyxmass"
                                        "powerguru" "proxyknife" "pspp" "psychosynth" "pth" "pythonwebkit"
                                        "qexo" "quickthreads"
                                        "r" "radius" "rcs" "readline" "recutils" "reftex" "remotecontrol"
                                        "rottlog" "rpge" "rush"
                                        "sather" "scm" "screen" "sed" "serveez" "sharutils" "shepherd"
                                        "shishi" "shmm" "shtool" "sipwitch" "slib" "smalltalk" "social"
                                        "solfege" "spacechart" "spell" "sqltutor" "src-highlite" "ssw" "stalkerfs"
                                        "stow" "stump" "superopt" "swbis" "sysutils"
                                        "taler" "talkfilters" "tar" "termcap" "termutils" "teseq" "teximpatient"
                                        "texinfo" "texmacs" "time" "tramp" "trans-coord" "trueprint"
                                        "unifont" "units" "unrtf" "userv" "uucp"
                                        "vc-dwim" "vcdimager" "vera" "vmgen"
                                        "wb" "wdiff" "websocket4j" "webstump" "wget" "which" "womb"
                                        "xaos" "xboard" "xlogmaster" "xmlat" "xnee" "xorriso"
                                        "zile")
  "List of GNU software for Info manual links.
The software list is taken from https://www.gnu.org/software/."
  :group 'org-export-hugo
  :type '(repeat string))



;;; Define Back-End

(org-export-define-derived-backend 'hugo 'blackfriday ;hugo < blackfriday < md < html
  :menu-entry
  '(?H "Export to Hugo-compatible Markdown"
       ((?H "Subtree or File to Md file            "
            (lambda (a _s v _b)
              (org-hugo-export-wim-to-md nil a v)))
        (?h "File to Md file"
            (lambda (a s v _b)
              (org-hugo-export-to-md a s v)))
        (?O "Subtree or File to Md file and open   "
            (lambda (a _s v _b)
              (if a
                  (org-hugo-export-wim-to-md nil :async v)
                (org-open-file (org-hugo-export-wim-to-md nil nil v)))))
        (?o "File to Md file and open"
            (lambda (a s v _b)
              (if a
                  (org-hugo-export-to-md :async s v)
                (org-open-file (org-hugo-export-to-md nil s v)))))
        (?A "All subtrees (or File) to Md file(s)  "
            (lambda (a _s v _b)
              (org-hugo-export-wim-to-md :all-subtrees a v)))
        (?t "File to a temporary Md buffer"
            (lambda (a s v _b)
              (org-hugo-export-as-md a s v)))))
;;;; translate-alist
  :translate-alist '((code . org-hugo-kbd-tags-maybe)
                     (drawer . org-hugo-drawer)
                     (example-block . org-hugo-example-block)
                     (export-block . org-hugo-export-block)
                     (export-snippet . org-hugo-export-snippet)
                     (headline . org-hugo-heading)
                     (inner-template . org-hugo-inner-template)
                     (inline-src-block . org-hugo-inline-src-block)
                     (keyword . org-hugo-keyword)
                     (link . org-hugo-link)
                     (paragraph . org-hugo-paragraph)
                     (src-block . org-hugo-src-block)
                     (special-block . org-hugo-special-block))
  :filters-alist '((:filter-body . org-hugo-body-filter))
;;;; options-alist
  ;;                KEY                       KEYWORD                    OPTION  DEFAULT                     BEHAVIOR
  :options-alist '(;; Variables not setting the front-matter directly
                   (:with-toc nil "toc" org-hugo-export-with-toc)
                   (:section-numbers nil "num" org-hugo-export-with-section-numbers)
                   (:author "AUTHOR" nil user-full-name newline)
                   (:creator "CREATOR" nil org-hugo-export-creator-string)
                   (:with-smart-quotes nil "'" nil) ;Hugo/Goldmark does more correct conversion to smart quotes, especially for single quotes.
                   (:with-special-strings nil "-" nil) ;Hugo/Goldmark does the auto-conversion of "--" -> "–", "---" -> "—" and "..." -> "…"
                   (:with-sub-superscript nil "^" '{}) ;Require curly braces to be wrapped around text to sub/super-scripted
                   (:hugo-with-locale "HUGO_WITH_LOCALE" nil nil)
                   (:hugo-front-matter-format "HUGO_FRONT_MATTER_FORMAT" nil     org-hugo-front-matter-format)
                   (:hugo-level-offset "HUGO_LEVEL_OFFSET" nil "1")
                   (:hugo-preserve-filling "HUGO_PRESERVE_FILLING" nil org-hugo-preserve-filling) ;Preserve breaks so that text filling in Markdown matches that of Org
                   (:hugo-delete-trailing-ws "HUGO_DELETE_TRAILING_WS" nil org-hugo-delete-trailing-ws)
                   (:hugo-section "HUGO_SECTION" nil org-hugo-section)
                   (:hugo-bundle "HUGO_BUNDLE" nil nil)
                   (:hugo-base-dir "HUGO_BASE_DIR" nil org-hugo-base-dir)
                   (:hugo-base-dir "HUGO_BASE_CONTENT_FOLDER" nil org-hugo-content-folder)
                   (:hugo-goldmark "HUGO_GOLDMARK" nil org-hugo-goldmark)
                   (:hugo-code-fence "HUGO_CODE_FENCE" nil t) ;Prefer to generate triple-backquoted Markdown code blocks by default.
                   (:hugo-use-code-for-kbd "HUGO_USE_CODE_FOR_KBD" nil org-hugo-use-code-for-kbd)
                   (:hugo-prefer-hyphen-in-tags "HUGO_PREFER_HYPHEN_IN_TAGS" nil org-hugo-prefer-hyphen-in-tags)
                   (:hugo-allow-spaces-in-tags "HUGO_ALLOW_SPACES_IN_TAGS" nil org-hugo-allow-spaces-in-tags)
                   (:hugo-auto-set-lastmod "HUGO_AUTO_SET_LASTMOD" nil org-hugo-auto-set-lastmod)
                   (:hugo-custom-front-matter "HUGO_CUSTOM_FRONT_MATTER" nil nil space)
                   (:hugo-blackfriday "HUGO_BLACKFRIDAY" nil nil space) ;Deprecated. See https://github.com/kaushalmodi/ox-hugo/discussions/485.
                   (:hugo-front-matter-key-replace "HUGO_FRONT_MATTER_KEY_REPLACE" nil nil space)
                   (:hugo-date-format "HUGO_DATE_FORMAT" nil org-hugo-date-format)
                   (:hugo-paired-shortcodes "HUGO_PAIRED_SHORTCODES" nil org-hugo-paired-shortcodes space)
                   (:hugo-pandoc-citations "HUGO_PANDOC_CITATIONS" nil nil)
                   (:bibliography "BIBLIOGRAPHY" nil nil newline) ;Used in ox-hugo-pandoc-cite
                   (:html-container "HTML_CONTAINER" nil org-hugo-container-element)
                   (:html-container-class "HTML_CONTAINER_CLASS" nil "")
                   (:html-container-nested "HTML_CONTAINER_NESTED" nil nil)

                   ;; Front-matter variables
                   ;; https://gohugo.io/content-management/front-matter/#front-matter-variables
                   ;; aliases
                   (:hugo-aliases "HUGO_ALIASES" nil nil space)
                   ;; audio
                   (:hugo-audio "HUGO_AUDIO" nil nil)
                   ;; date
                   ;; "date" is parsed from the Org #+date or subtree property EXPORT_HUGO_DATE
                   (:date "DATE" nil nil)
                   ;; description
                   (:description "DESCRIPTION" nil nil)
                   ;; draft
                   ;; "draft" value interpreted by the TODO state of a
                   ;; post as Org subtree gets higher precedence.
                   (:hugo-draft "HUGO_DRAFT" nil nil)
                   ;; expiryDate
                   (:hugo-expirydate "HUGO_EXPIRYDATE" nil nil)
                   ;; headless (only for Page Bundles - Hugo v0.35+)
                   (:hugo-headless "HUGO_HEADLESS" nil nil)
                   ;; images
                   (:hugo-images "HUGO_IMAGES" nil nil newline)
                   ;; isCJKLanguage
                   (:hugo-iscjklanguage "HUGO_ISCJKLANGUAGE" nil nil)
                   ;; keywords
                   ;; "keywords" is parsed from the Org #+keywords or
                   ;; subtree property EXPORT_KEYWORDS.
                   (:keywords "KEYWORDS" nil nil newline)
                   ;; layout
                   (:hugo-layout "HUGO_LAYOUT" nil nil)
                   ;; lastmod
                   (:hugo-lastmod "HUGO_LASTMOD" nil nil)
                   ;; linkTitle
                   (:hugo-linktitle "HUGO_LINKTITLE" nil nil)
                   ;; locale (used in Hugo internal templates)
                   (:hugo-locale "HUGO_LOCALE" nil nil)
                   ;; markup
                   (:hugo-markup "HUGO_MARKUP" nil nil) ;default is "md"
                   ;; menu
                   (:hugo-menu "HUGO_MENU" nil nil space)
                   (:hugo-menu-override "HUGO_MENU_OVERRIDE" nil nil space)
                   ;; outputs
                   (:hugo-outputs "HUGO_OUTPUTS" nil nil space)
                   ;; publishDate
                   (:hugo-publishdate "HUGO_PUBLISHDATE" nil nil)
                   ;; series
                   (:hugo-series "HUGO_SERIES" nil nil newline)
                   ;; slug
                   (:hugo-slug "HUGO_SLUG" nil nil)
                   ;; taxomonomies - tags, categories
                   (:hugo-tags "HUGO_TAGS" nil nil newline)
                   ;; #+hugo_tags are used to set the post tags in Org
                   ;; files written for file-based exports.  But for
                   ;; subtree-based exports, the EXPORT_HUGO_TAGS
                   ;; property can be used to override inherited tags
                   ;; and Org-style tags.
                   (:hugo-categories "HUGO_CATEGORIES" nil nil newline)
                   ;; #+hugo_categories are used to set the post
                   ;; categories in Org files written for file-based
                   ;; exports.  But for subtree-based exports, the
                   ;; EXPORT_HUGO_CATEGORIES property can be used to
                   ;; override inherited categories and Org-style
                   ;; categories (Org-style tags with "@" prefix).
                   ;; resources
                   (:hugo-resources "HUGO_RESOURCES" nil nil space)
                   ;; title
                   ;; "title" is parsed from the Org #+title or the subtree heading.
                   ;; type
                   (:hugo-type "HUGO_TYPE" nil nil)
                   ;; url
                   (:hugo-url "HUGO_URL" nil nil)
                   ;; videos
                   (:hugo-videos "HUGO_VIDEOS" nil nil newline)
                   ;; weight
                   (:hugo-weight "HUGO_WEIGHT" nil nil space)))



;;; Miscellaneous Helper Functions

;;;; Check if a value is non-nil
(defun org-hugo--value-get-true-p (value)
  "Return non-nil if VALUE is non-nil.
Return nil if VALUE is nil, \"nil\" or \"\"."
  (cond
   ((or (equal t value)
        (equal nil value))
    value)
   ((and (stringp value)
         (string= value "nil"))
    nil)
   (t
    ;; "" -> nil
    ;; "t" -> "t"
    ;; "anything else" -> "anything else"
    ;; 123 -> nil
    (org-string-nw-p value))))

;;;; Check if a boolean plist value is non-nil
(defun org-hugo--plist-get-true-p (info key)
  "Return non-nil if KEY in INFO is non-nil.
Return nil if the value of KEY in INFO is nil, \"nil\" or \"\".

This is a special version of `plist-get' used only for keys that
are expected to hold a boolean value.

INFO is a plist used as a communication channel."
  (let ((value (plist-get info key)))
    ;; (message "dbg: org-hugo--plist-get-true-p:: key:%S value:%S" key value)
    (org-hugo--value-get-true-p value)))

;;;; Workaround to retain custom parameters in src-block headers post `org-babel-exp-code'
;; http://lists.gnu.org/archive/html/emacs-orgmode/2017-10/msg00300.html
(defun org-hugo--org-babel-exp-code (orig-fun &rest args)
  "Return the original code block formatted for export.
ORIG-FUN is the original function `org-babel-exp-code' that this
function is designed to advice using `:around'.  ARGS are the
arguments of the ORIG-FUN.

This advice retains the `:hl_lines', `linenos', `syntax-style' and
`:front_matter_extra' parameters, if added to any source block.
This parameter is used in `org-hugo-src-block'."
  (let* ((param-keys-to-be-retained '(:hl_lines :linenos :syntax-style :front_matter_extra))
         (info (car args))
         (parameters (nth 2 info))
         (ox-hugo-params-str (let ((str ""))
                               (dolist (param parameters)
                                 (dolist (retain-key param-keys-to-be-retained)
                                   (when (equal retain-key (car param))
                                     (let ((val (cdr param)))
                                       (setq str
                                             (concat str " "
                                                     (symbol-name retain-key) " "
                                                     (cond
                                                      ((stringp val)
                                                       val)
                                                      ((numberp val)
                                                       (number-to-string val))
                                                      (t
                                                       (user-error "Invalid value %S assigned to %S"
                                                                   val retain-key)))))))))
                               (org-string-nw-p (org-trim str))))
         ret)
    ;; (message "[ox-hugo ob-exp] info: %S" info)
    ;; (message "[ox-hugo ob-exp] parameters: %S" parameters)
    ;; (message "[ox-hugo ob-exp] ox-hugo-params-str: %S" ox-hugo-params-str)
    (setq ret (apply orig-fun args))
    (when ox-hugo-params-str
      (let ((case-fold-search t))
        (setq ret (replace-regexp-in-string "\\`#\\+begin_src .*"
                                            (format "\\& %s" ox-hugo-params-str) ret))))
    ;; (message "[ox-hugo ob-exp] ret: %S" ret)
    ret))


;;;; Workaround to fix the regression in the behavior of `org-babel--string-to-number'.
;; https://lists.gnu.org/r/emacs-orgmode/2020-02/msg00931.html
(defun org-hugo--org-babel--string-to-number (string)
  "If STRING represents a number return its value.
Otherwise return nil.

This function restores the behavior of
`org-babel--string-to-number' to that of before
https://git.savannah.gnu.org/cgit/emacs/org-mode.git/commit/?id=6b2a7cb20b357e730de151522fe4204c96615f98."
  (and (string-match-p "\\`-?\\([0-9]\\|\\([1-9]\\|[0-9]*\\.\\)[0-9]*\\)\\'" string)
       (string-to-number string)))

(defun org-hugo--org-info-export (path desc format)
  "Add support for exporting [[info:..]] links for `hugo' format.

See `org-link-parameters' for details about PATH, DESC and FORMAT."
  (let* ((parts (split-string path "#\\|::"))
         (manual (car parts))
         (node (or (nth 1 parts) "Top"))
         (title (format "Emacs Lisp: (info \\\"(%s) %s\\\")" manual node))
         (desc (or desc
                   (if (string= node "Top")
                       (format "%s Info" (capitalize manual))
                     (format "%s Info: %s" (capitalize manual) node))))
         ;; `link' below is mostly derived from the code in
         ;; `org-info-map-html-url'.
         (link (cond ((member manual org-info-emacs-documents)
                      (let ((manual-url (if (string= (downcase manual) "org")
                                            "https://orgmode.org/manual"
                                          (format "https://www.gnu.org/software/emacs/manual/html_node/%s" manual)))
                            (node-url (if (string= node "Top")
                                          "index.html"
                                        (concat (org-info--expand-node-name node) ".html"))))
                        (format "%s/%s" manual-url node-url)))
                     ((member manual org-hugo-info-gnu-software)
                      (let ((manual-url (format "https://www.gnu.org/software/%s/manual/html_node" manual))
                            (node-url (if (string= node "Top")
                                          "index.html"
                                        (concat (org-info--expand-node-name node) ".html"))))
                        (format "%s/%s" manual-url node-url)))
                     ((cdr (assoc manual org-info-other-documents)))
                     (t
                      (concat manual ".html")))))
    (when (member format '(md hugo))
      (format "[%s](%s \"%s\")" desc link title))))

(defun org-hugo--org-cite-export-bibliography (orig-fun &rest args)
  "Insert a heading before the exported bibliography.

ORIG-FUN is the original function `org-cite-export-bibliography'
that this function is designed to advice using `:around'.  ARGS
are the arguments of the ORIG-FUN."
  (let ((bib (apply orig-fun args)))
    (when (org-string-nw-p bib)
      ;; Auto-inject Bibliography heading.
      (let ((info (nth 2 args)) ;(org-cite-export-bibliography KEYWORD _ INFO)
            (bib-heading (org-string-nw-p (plist-get org-hugo-citations-plist :bibliography-section-heading))))
        (when bib-heading
          (let* ((bib-heading (org-blackfriday--translate nil info bib-heading))
                 (loffset (string-to-number
                           (or (org-entry-get nil "EXPORT_HUGO_LEVEL_OFFSET" :inherit)
                               (plist-get info :hugo-level-offset))))
                 (level-mark (make-string (+ loffset 1) ?#)))
            (format "%s %s\n\n%s" level-mark bib-heading bib)))))))

(defun org-hugo--before-export-function (subtreep)
  "Function to be run before an ox-hugo export.

This function is called in the very beginning of
`org-hugo-export-to-md' and `org-hugo-export-as-md'.

SUBTREEP is non-nil for subtree-based exports.

This function is used to advise few functions.  Those advices are
effective only while an ox-hugo export is in progress because
they get removed later in `org-hugo--after-1-export-function'.

This is an internal function."
  (unless subtreep
    ;; Reset the variables that are used only for subtree exports.
    (setq org-hugo--subtree-coord nil))
  (advice-add 'org-babel-exp-code :around #'org-hugo--org-babel-exp-code)
  (advice-add 'org-babel--string-to-number :override #'org-hugo--org-babel--string-to-number)
  (advice-add 'org-info-export :override #'org-hugo--org-info-export)
  (advice-add 'org-cite-export-bibliography :around #'org-hugo--org-cite-export-bibliography))

(defun org-hugo--after-1-export-function (info outfile)
  "Function to be run after exporting one post.

The post could be exported using the subtree-based or file-based
method.

This function is called in the end of `org-hugo-export-to-md',
and `org-hugo-export-as-md'.

INFO is a plist used as a communication channel.

OUTFILE is the Org exported file name.

This is an internal function."
  (advice-remove 'org-cite-export-bibliography #'org-hugo--org-cite-export-bibliography)
  (advice-remove 'org-info-export #'org-hugo--org-info-export)
  (advice-remove 'org-babel--string-to-number #'org-hugo--org-babel--string-to-number)
  (advice-remove 'org-babel-exp-code #'org-hugo--org-babel-exp-code)
  (when (and outfile
             (org-hugo--pandoc-citations-enabled-p info))
    (require 'ox-hugo-pandoc-cite)
    (plist-put info :outfile outfile)
    (plist-put info :front-matter org-hugo--fm)
    (org-hugo-pandoc-cite--parse-citations-maybe info))
  (setq org-hugo--fm nil)
  (setq org-hugo--fm-yaml nil))

(defun org-hugo--cleanup ()
  "Function to kill Ox-Hugo opened buffers and reset internal variables.

This is an internal function."
  (setq org-hugo--subtree-count 0) ;Reset the subtree count

  ;; Kill all the buffers opened by during an export.
  (dolist (buf org-hugo--opened-buffers)
    (kill-buffer buf))
  (setq org-hugo--opened-buffers nil)

  (setq org-hugo--preprocessed-buffer nil))

(defun org-hugo--after-all-exports-function ()
  "Function to be run after Ox-Hugo exports all the posts.

This function is called in the end of
`org-hugo-export-wim-to-md', `org-hugo-export-to-md' and
`org-hugo-export-as-md' (if its ALL-SUBTREES arg is non-nil).

This is an internal function."
  (org-hugo--cleanup)
  (dolist (fn org-hugo--all-subtrees-export--functions-to-silence)
    (advice-remove fn #'org-hugo--advice-silence-messages)))

;;;; HTMLized section number for heading
(defun org-hugo--get-heading-number (heading info &optional toc)
  "Return htmlized section number for the HEADING.
INFO is a plist used as a communication channel.

When the \"num\" export option is `onlytoc', heading number is
returned only if the optional argument TOC is non-nil.

Return nil if there is no heading number, or if it has been
disabled."
  (let ((onlytoc (equal 'onlytoc (plist-get info :section-numbers))))
    (when (and (if toc
                   t
                 (not onlytoc)) ;If `toc' is nil, but `onlytoc' is non-nil, return nil
               (org-export-numbered-headline-p heading info))
      (let ((number-str (mapconcat
                         'number-to-string
                         (org-export-get-headline-number heading info) ".")))
        (format "<span class=\"section-num\">%s</span> " number-str)))))

;;;; Build TOC
(defun org-hugo--build-toc (info &optional n scope local)
  "Return table of contents as a string.

INFO is a plist used as a communication channel.

Optional argument N, when non-nil, is a positive integer
specifying the depth of the table.

When optional argument SCOPE is non-nil, build a table of
contents according to the specified element.

When optional argument LOCAL is non-nil, build a table of
contents according to the current heading."
  (let* ((toc-heading
          (unless local
            (format "\n<div class=\"heading\">%s</div>\n"
                    (org-html--translate "Table of Contents" info))))
         (current-level nil)
         (toc-items
          (mapconcat
           (lambda (heading)
             (let* ((level-raw (org-export-get-relative-level heading info))
                    (level (if scope
                               (let* ((current-level-inner
                                       (progn
                                         (unless current-level
                                           (setq current-level level-raw))
                                         current-level))
                                      (relative-level
                                       (1+ (- level-raw current-level-inner))))
                                 ;; (message (concat "[ox-hugo build-toc DBG] "
                                 ;;                  "current-level-inner:%d relative-level:%d")
                                 ;;          current-level-inner relative-level)
                                 relative-level)
                             level-raw))
                    (indentation (make-string (* 4 (1- level)) ?\s))
                    (todo (and (org-hugo--plist-get-true-p info :with-todo-keywords)
                               (org-element-property :todo-keyword heading)))
                    (todo-str (if todo
                                  (concat (org-hugo--todo todo info) " ")
                                ""))
                    (heading-num-list (org-export-get-headline-number heading info))
                    (number (if heading-num-list
                                ;; (message "[ox-hugo TOC DBG] heading-num-list: %S" heading-num-list)
                                (org-hugo--get-heading-number heading info :toc)
                              ""))
                    (toc-entry
                     (format "[%s%s](#%s)"
                             todo-str
                             (org-export-data-with-backend
                              (org-export-get-alt-title heading info)
                              (org-export-toc-entry-backend 'hugo)
                              info)
                             (org-hugo--get-anchor heading info)))
                    (tags (and (plist-get info :with-tags)
                               (not (eq 'not-in-toc (plist-get info :with-tags)))
                               (let ((tags (org-export-get-tags heading info)))
                                 (and tags
                                      (format ":%s:"
                                              (mapconcat #'identity tags ":")))))))
               ;; (message "[ox-hugo build-toc DBG] level:%d, number:%s" level number)
               ;; (message "[ox-hugo build-toc DBG] indentation: %S" indentation)
               ;; (message "[ox-hugo build-toc DBG] todo: %s | %s" todo todo-str)
               (concat indentation "- " number toc-entry tags)))
           (org-export-collect-headlines info n scope)
           "\n"))                       ;Newline between TOC items
         ;; Remove blank lines from in-between TOC items, which can
         ;; get introduced when using the "UNNUMBERED: t" heading
         ;; property.
         (toc-items (org-string-nw-p
                     (replace-regexp-in-string "\n\\{2,\\}" "\n" toc-items))))
    ;; (message "[ox-hugo build-toc DBG] toc-items:%s" toc-items)
    (when toc-items
      (let ((toc-classes '("toc" "ox-hugo-toc"))
            ;; `has-section-numbers' is non-nil if section numbers are
            ;; present for even one heading.
            (has-section-numbers (string-match-p "^\\s-*\\-\\s-<span class=\"section\\-num\"" toc-items)))
        (when has-section-numbers
          (push "has-section-numbers" toc-classes))
        (when local
          (push "local" toc-classes))
        (concat (format "<div class=\"%s\">\n" (string-join (reverse toc-classes) " "))
                (unless (org-hugo--plist-get-true-p info :hugo-goldmark)
                  "<div></div>\n") ;This is a nasty workaround till Hugo/Blackfriday support
                toc-heading    ;wrapping Markdown in HTML div's.
                "\n"
                toc-items ;https://github.com/kaushalmodi/ox-hugo/issues/93
                "\n\n"
                "</div>\n"
                ;; Special comment that can be use to filter out the TOC
                ;; from .Summary in Hugo templates.
                ;;
                ;;     {{ $summary_splits := split .Summary "<!--endtoc-->" }}
                ;;     {{ if eq (len $summary_splits) 2 }}
                ;;         <!-- If that endtoc special comment is present, output only the part after that comment as Summary. -->
                ;;         {{ index $summary_splits 1 | safeHTML }}
                ;;     {{ else }}
                ;;         <!-- Print the whole Summary if endtoc special comment is not found. -->
                ;;         {{ .Summary }}
                ;;     {{ end }}
                "<!--endtoc-->\n")))))

;;;; Escape Hugo shortcode
(defun org-hugo--escape-hugo-shortcode (code lang)
  "Escape Hugo shortcodes if present in CODE string.

The escaping is enabled only if LANG is \"md\", \"org\",
\"go-html-template\" or \"emacs-lisp\".

 - Shortcode with Markdown    : {{% foo %}} -> {{%/* foo */%}}

 - Shortcode without Markdown : {{< foo >}} -> {{</* foo */>}}

Return the escaped/unescaped string."
  (if (member lang '("md" "org" "go-html-template" "emacs-lisp"))
      (replace-regexp-in-string
       "\\({{<\\)\\([^}][^}]*\\)\\(>}}\\)" "\\1/*\\2*/\\3"
       (replace-regexp-in-string
        "\\({{%\\)\\([^}][^}]*\\)\\(%}}\\)" "\\1/*\\2*/\\3" code))
    code))

;;;; Hugo Version
(defun org-hugo--hugo-version ()
  "Return hugo version.

If hugo is found in PATH, return (LONG . SHORT).

LONG is the exact string returned by \"hugo version\".

SHORT is the short version of above.
Examples: \"0.31.1\", \"0.31.99\" (for \"0.32-DEV\" version).

If hugo is not found, return nil."
  (when (executable-find "hugo")
    (let* ((long-ver (org-trim (shell-command-to-string "hugo version")))
           (short-ver (replace-regexp-in-string ".* v\\([^ ]+\\) .*" "\\1" long-ver)))
      (when (string-match "-DEV-.*" short-ver)
        ;; Replace "-DEV-*" in version string with "-BETA" because
        ;; `version-to-list' does not understand "-DEV".
        (setq short-ver (replace-match "-BETA" nil nil short-ver))
        ;; Below, convert "0.32-DEV" -> "0.31.99" (example) so that
        ;; version strings can be compared with functions like
        ;; `version<'.
        (let* ((short-ver-list (version-to-list short-ver))
               (major-ver (nth 0 short-ver-list))
               (minor-ver (nth 1 short-ver-list))
               (micro-ver (nth 2 short-ver-list)))
          ;; micro-ver will be -2 for "-beta" (DEV) versions.
          (setq micro-ver 99)  ;Assuming that the real micro-ver will never become 99
          (if (= 0 minor-ver)  ;Example: "1.0-DEV" -> (1 0 99) -> (0 99 99)
              (progn
                (setq minor-ver 99) ;Assuming that the max minor version is 99
                (setq major-ver (1- major-ver))) ;Assuming that major-ver is not 0 to begin with
            (setq minor-ver (1- minor-ver))) ;Example: "0.32-DEV" -> (0 32 99) -> (0 31 99)
          (setq short-ver-list (list major-ver minor-ver micro-ver))
          (setq short-ver (mapconcat #'number-to-string short-ver-list "."))))
      (cons long-ver short-ver))))

;;;; Resources Alist Merging
(defun org-hugo--get-resources-alist (resources)
  "Generate a merged RESOURCES alist.

All parameters for the same \"src\" are merged together in the
same Lisp form.  Parameters that are none of \"src\", \"title\"
or \"name\" are packed into an alist with `car' as \"params\"."
  ;; (message "[resources IN DBG]: %S" resources)
  (when resources
    (let (src1 all-src src-cons src-already-exists)
      (dolist (res resources)
        ;; (message "res: %S" res)
        (let ((key (car res)))
          (cond
           ((equal key 'src)
            (unless (null src1)
              (setq src1 (nreverse src1))
              (if src-already-exists
                  (setcdr src-already-exists (cdr src1))
                (push src1 all-src)))
            (setq src-cons res)
            (setq src-already-exists (assoc src-cons all-src))
            ;; (message "%S exists? %S" (cdr src-cons) src-already-exists)
            (setq src1 (or (nreverse src-already-exists) (list res)))
            ;; (message "src1 temp: %S" src1)
            )
           ((member key '(title name))
            (push res src1))
           (t                             ;Resource Params
            (let* ((params-cons (assoc 'params src1))
                   (params (cdr params-cons)))
              (if params
                  (progn
                    ;; (message "params 1: %S" params)
                    (push res params)
                    (setq params (nreverse params))
                    ;; (message "params 2: %S" params)
                    (setcdr params-cons params))
                (setq params (list res))
                (push `(params . ,params) src1))
              ;; (message "src1 temp 2: %S" src1)
              (setcdr (assoc 'params src1) params))))))
      (setq src1 (nreverse src1))
      ;; (message "src1: %S" src1)
      (if src-already-exists
          (setcdr src-already-exists (cdr src1))
        (push src1 all-src))
      ;; Retain the order of src
      (setq all-src (nreverse all-src))
      ;; (message "all-src: %S" all-src)
      all-src)))

;;;; Publication Directory
(defun org-hugo--get-pub-dir (info)
  "Return the post publication directory path.

The publication directory is created if it does not exist.

INFO is a plist used as a communication channel."
  (let* ((base-dir (if (plist-get info :hugo-base-dir)
                       (file-name-as-directory (plist-get info :hugo-base-dir))
                     (user-error "It is mandatory to set the HUGO_BASE_DIR property or the `org-hugo-base-dir' local variable")))
         (content-dir (concat org-hugo-content-folder "/"))
         (section-path (org-hugo--get-section-path info))
         (bundle-dir (let ((bundle-path (or ;Hugo bundle set in the post subtree gets higher precedence
                                         (org-hugo--entry-get-concat nil "EXPORT_HUGO_BUNDLE" "/")
                                         (plist-get info :hugo-bundle)))) ;This is mainly to support per-file flow
                       (if bundle-path
                           (file-name-as-directory bundle-path)
                         "")))
         (pub-dir (let ((dir (concat base-dir content-dir section-path bundle-dir)))
                    (make-directory dir :parents) ;Create the directory if it does not exist
                    dir)))
    (file-truename pub-dir)))

;;;; Get the publish date for the current post
(defun org-hugo--get-date (info fmt)
  "Return current post's publish date as a string.

The date is derived with this precedence:

1. `:logbook-date' property from INFO

2. `CLOSED' time stamp if the point is in an Org subtree with the
   `CLOSED' property set (usually generated automatically when
   switching a heading's TODO state to \"DONE\")

3. `EXPORT_DATE' property in current post subtree

4. Date if set in the Org file's \"#+date\" keyword. This date is
   formatted using the time format string FMT.

If none of the above apply, return nil.

INFO is a plist used as a communication channel."
  (or
   (plist-get info :logbook-date)
   (org-entry-get (point) "CLOSED")
   (org-string-nw-p
    (org-export-data (plist-get info :date) info)) ;`org-export-data' required
   (org-string-nw-p
    (org-export-get-date info fmt))))

;;;; Format Dates
(defun org-hugo--org-date-time-to-rfc3339 (date-time info)
  "Convert DATE-TIME to RFC 3339 format.

DATE-TIME can be either Emacs format time list (example: return
value of `current-time'), or an Org date/time string.

INFO is a plist used as a communication channel."
  (let* ((date-time (if (stringp date-time)
                        (apply #'encode-time (org-parse-time-string date-time))
                      date-time))
         (date-nocolon (format-time-string
                        (plist-get info :hugo-date-format)
                        date-time)))
    ;; Hugo expects the date stamp in this format (RFC3339 -- See
    ;; `org-hugo--date-time-regexp'.) i.e. if the date contains the
    ;; time-zone, a colon is required to separate the hours and
    ;; minutes in the time-zone section.  2017-07-06T14:59:45-04:00

    ;; But by default the "%z" placeholder for time-zone (see
    ;; `format-time-string') produces the zone time-string as "-0400"
    ;; (Note the missing colon).  Below simply adds a colon between
    ;; "04" and "00" in that example.
    (and (stringp date-nocolon)
         (replace-regexp-in-string
          "\\([0-9]\\{2\\}\\)\\([0-9]\\{2\\}\\)\\'" "\\1:\\2"
          date-nocolon))))

(defun org-hugo--format-date (date-key info)
  "Return a date string formatted in Hugo-compatible format.

DATE-KEY is the key in INFO from which the date is to be
retrieved.  INFO is a plist used as a communication channel.

Possible values of DATE-KEY are `:date', `:hugo-lastmod',
`:hugo-publishdate', and `:hugo-expirydate'.

Return nil if the retrieved date from INFO is nil or if the date
cannot be formatted in Hugo-compatible format."
  (let* ((date-fmt (plist-get info :hugo-date-format))
         (date-raw (cond
                    ((equal date-key :date)
                     ;; (message "[ox-hugo date DBG] 1 %s" (plist-get info date-key))
                     ;; (message "[ox-hugo date DBG] 2 %s" (org-export-data (plist-get info date-key) info))
                     (org-hugo--get-date info date-fmt))
                    ((equal date-key :hugo-lastmod)
                     (or (plist-get info :logbook-lastmod) ;lastmod derived from LOGBOOK gets higher precedence
                         (org-string-nw-p (plist-get info date-key))))
                    ((and (equal date-key :hugo-publishdate)
                          (org-entry-get (point) "SCHEDULED"))
                     ;; Get the date from the "SCHEDULED" property.
                     (org-entry-get (point) "SCHEDULED"))
                    (t            ;:hugo-publishdate, :hugo-expirydate
                     (org-string-nw-p (plist-get info date-key)))))
         (dt-rfc3339 (cond
                      ;; If the date set for the DATE-KEY parameter is
                      ;; already in Hugo-compatible format, use it.
                      ((and (stringp date-raw)
                            (string-match-p org-hugo--date-time-regexp date-raw))
                       date-raw)
                      ;; Else if it's any other string (like
                      ;; "<2018-01-23 Tue>"), try to parse that date.
                      ((stringp date-raw)
                       (condition-case err
                           (org-hugo--org-date-time-to-rfc3339 date-raw info)
                         (error
                          ;; Set dt-rfc3339 to nil if error happens.
                          ;; An example: If #+date is set to 2012-2017
                          ;; to set the copyright years, just set the
                          ;; date to nil instead of throwing an error
                          ;; like: org-parse-time-string: Not a
                          ;; standard Org time string: 2012-2017
                          (message
                           (format "[ox-hugo] Date will not be set in the front-matter: %s"
                                   (nth 1 err)))
                          nil)))
                      ;; Else (if nil) and user want to auto-set the
                      ;; lastmod field. If the lastmod value is
                      ;; derived from LOGBOOK, disable the
                      ;; auto-setting of lastmod.
                      ((and (equal date-key :hugo-lastmod)
                            (null (plist-get info :logbook-lastmod))
                            (org-hugo--plist-get-true-p info :hugo-auto-set-lastmod))
                       (let* ((curr-time (org-current-time))
                              (lastmod-str (org-hugo--org-date-time-to-rfc3339 curr-time info)))
                         ;; (message "[ox-hugo suppress-lastmod] current-time = %S (decoded = %S)"
                         ;;          curr-time (decode-time curr-time))
                         ;; (message "[ox-hugo suppress-lastmod] lastmod-str = %S"
                         ;;          lastmod-str )
                         (if (= 0.0 org-hugo-suppress-lastmod-period)
                             (progn
                               ;; (message "[ox-hugo suppress-lastmod] not suppressed")
                               lastmod-str)
                           (let ((date-str (org-string-nw-p (org-hugo--get-date info date-fmt))))
                             ;; (message "[ox-hugo suppress-lastmod] date-str = %S"
                             ;;          date-str)
                             (when date-str
                               (let* ((date-time (apply #'encode-time
                                                        (mapcar (lambda (el) (or el 0))
                                                                (parse-time-string date-str))))
                                      ;; It's safe to assume that
                                      ;; `current-time' will always
                                      ;; be >= the post date.
                                      (delta (float-time
                                              (time-subtract curr-time date-time)))
                                      (suppress-period (if (< 0.0 org-hugo-suppress-lastmod-period)
                                                           org-hugo-suppress-lastmod-period
                                                         (- org-hugo-suppress-lastmod-period))))
                                 ;; (message "[ox-hugo suppress-lastmod] date-time = %S (decoded = %S)"
                                 ;;          date-time (decode-time date-time))
                                 ;; (message "[ox-hugo suppress-lastmod] delta = %S" delta)
                                 ;; (message "[ox-hugo suppress-lastmod] suppress-period = %S"
                                 ;;          suppress-period)
                                 (when (>= delta suppress-period)
                                   lastmod-str)))))))
                      ;; Else.. do nothing.
                      (t
                       nil))))
    dt-rfc3339))

;;;; Replace Front-matter Keys
(defun org-hugo--replace-keys-maybe (data info)
  "Return DATA with its keys replaced, maybe.

The keys in DATA are replaced if HUGO_FRONT_MATTER_KEY_REPLACE is
set appropriately.

The replacement syntax is:

    #+hugo_front_matter_key_replace: oldkey>newkey

If newkey is a special string \"nil\", oldkey will be removed
from the front-matter.

You can also do multiple key replacements:

    #+hugo_front_matter_key_replace: oldkey1>newkey1 oldkey2>newkey2

Above examples are using the keyword
HUGO_FRONT_MATTER_KEY_REPLACE, but the same also applies when
using its subtree property form
:EXPORT_HUGO_FRONT_MATTER_KEY_REPLACE:.

Note that:

1. There are no spaces around the special character \">\".
2. Spaces are used to only separate multiple replacements are shown in
   the second example above.
3. The replacements are literal.. there are no regular expressions
   involved.

INFO is a plist used as a communication channel."
  (let* ((repl-str (plist-get info :hugo-front-matter-key-replace))
         (repl-str (when (org-string-nw-p repl-str)
                     (org-trim repl-str))))
    (when repl-str
      ;; (message "[ox-hugo replace-key str DBG] %S" repl-str)
      (let* ((repl-list (split-string repl-str)) ;`repl-str' is space-separated
             (repl-alist (let (alist)
                           (dolist (repl repl-list)
                             (when (and (stringp repl) ;`repl' would look like "oldkey>newkey"
                                        (string-match-p ">" repl))
                               (let* ((pair (split-string repl ">"))
                                      (key-orig-str (org-string-nw-p (nth 0 pair)))
                                      (key-repl-str (org-string-nw-p (nth 1 pair)))
                                      (repl-pair (when (and key-orig-str
                                                            key-repl-str)
                                                   (cons (intern key-orig-str)
                                                         (intern key-repl-str)))))
                                 (when repl-pair
                                   ;; (message "[ox-hugo pair DBG] %S" pair)
                                   ;; (message "[ox-hugo repl-pair DBG] %S" repl-pair)
                                   ;; (message "[ox-hugo repl-pair car DBG] %S" (car repl-pair))
                                   ;; (message "[ox-hugo repl-pair cdr DBG] %S" (cdr repl-pair))
                                   (push repl-pair alist)))))
                           alist)))
        ;; (message "[ox-hugo replace-key list DBG] %S" repl-list)
        ;; (message "[ox-hugo replace-key alist DBG] %S" repl-alist)
        (dolist (repl repl-alist)
          (let ((key-orig (car repl))
                (key-repl (cdr repl)))
            (let ((found-key-cell (assoc key-orig data)))
              (when found-key-cell
                ;; (message "[ox-hugo replace-key found-key-cell DBG] %S" found-key-cell)
                ;; (message "[ox-hugo replace-key key-orig DBG] %S" key-orig)
                ;; (message "[ox-hugo replace-key key-repl DBG] %S" key-repl)
                (if (string= "nil" key-repl)
                    ;; Setting value of a front-matter key to nil will
                    ;; cause that key to be removed during export.
                    ;; See `org-hugo--gen-front-matter'.
                    (setf (cdr found-key-cell) nil)
                  ;; https://emacs.stackexchange.com/a/3398/115
                  (setf (car found-key-cell) key-repl))))))))
    data))

;;;; TODO keywords
(defun org-hugo--todo (todo info)
  "Format TODO keywords into HTML.

This function is almost like `org-html--todo' except that:
- An \"org-todo\" class is always added to the span element.
- `org-hugo--replace-underscores-with-spaces' is used to replace
  double-underscores in TODO with spaces.

INFO is a plist used as a communication channel."
  (when todo
    ;; (message "[DBG todo] todo: %S" todo)
    ;; (message "[DBG todo] org-done-keywords: %S" org-done-keywords)
    ;; (message "[DBG todo] is a done keyword? %S" (member todo org-done-keywords))
    ;; (message "[DBG todo] html-todo-kwd-class-prefix: %S" (plist-get info :html-todo-kwd-class-prefix))
    (format "<span class=\"org-todo %s %s%s\">%s</span>"
            (if (member todo org-done-keywords) "done" "todo")
            (or (org-string-nw-p (plist-get info :html-todo-kwd-class-prefix)) "")
            (org-html-fix-class-name todo)
            (org-hugo--replace-underscores-with-spaces todo))))

;;;; Parse draft state
(defun org-hugo--parse-draft-state (info)
  "Parse the draft state of the post heading at point.

Return a \"true\" or \"false\" string.

For per-subtree export flow, the draft state parsed from the Org
TODO state has a higher precedence than the value of HUGO_DRAFT
keyword/property.

INFO is a plist used as a communication channel."
  (let* ((todo-keyword (org-entry-get (point) "TODO"))
         (draft (cond
                 ((stringp todo-keyword)
                  (if (member todo-keyword org-done-keywords)
                      nil
                    (progn
                      (when (string= "DRAFT" todo-keyword)
                        (let ((title (org-entry-get (point) "ITEM"))) ;Post title
                          (message "[ox-hugo] `%s' post is marked as a DRAFT" title)))
                      t)))
                 (;; If the HUGO_DRAFT keyword/property *is* set, but
                  ;; not to nil.
                  (plist-get info :hugo-draft)
                  (let* ((draft-1 (org-hugo--front-matter-value-booleanize (plist-get info :hugo-draft)))
                         (is-draft (if (string= "true" draft-1) t nil)))
                    (when is-draft
                      (let* ((entry (org-element-at-point))
                             (is-subtree (org-element-property :EXPORT_FILE_NAME entry))
                             (title (if is-subtree
                                        (org-entry-get (point) "ITEM")
                                      (or (car (plist-get info :title)) "<EMPTY TITLE>"))))
                        (message "[ox-hugo] `%s' post is marked as a DRAFT" title)))
                    is-draft))
                 (t ;Neither of Org TODO state and HUGO_DRAFT keyword/property are set
                  nil)))
         (draft-bool-str (org-hugo--front-matter-value-booleanize (symbol-name draft))))
    ;; (message "dbg: draft-state: todo keyword=%S HUGO_DRAFT=%S draft=%S"
    ;;          todo-keyword (plist-get info :hugo-draft) draft-bool-str)
    draft-bool-str))

;;;; Check if Pandoc Citations parsing is needed
(defun org-hugo--pandoc-citations-enabled-p (info)
  "Return non-nil if Pandoc Citation parsing is enabled.

INFO is a plist used as a communication channel."
  (let* ((pandoc-citations-enabled--prop-val
          (org-entry-get nil "EXPORT_HUGO_PANDOC_CITATIONS" :inherit :literal-nil))
         (pandoc-citations-enabled--plist-val
          (org-hugo--plist-get-true-p info :hugo-pandoc-citations))
         (pandoc-enabled (or pandoc-citations-enabled--prop-val
                             pandoc-citations-enabled--plist-val))
         (pandoc-enabled-bool (org-hugo--value-get-true-p pandoc-enabled)))
    ;; (message "[ox-hugo DBG pandoc-citations-enabled--prop-val] %S" pandoc-citations-enabled--prop-val)
    ;; (message "[ox-hugo DBG pandoc-citations-enabled--plist-val] %S" pandoc-citations-enabled--plist-val)
    ;; (message "[ox-hugo DBG pandoc-enabled-bool] %S" pandoc-enabled-bool)
    pandoc-enabled-bool))

;;;; Get a property value and concat it with its parent value
(defun org-hugo--entry-get-concat (pom property &optional sep)
  "Concatenate an Org Property value with its inherited value.

Get value of PROPERTY for entry or content at point-or-marker
POM.  If a parent subtree has the same PROPERTY set, append the
current property value to that, following the optional SEP.

SEP is the concatenation separator string.  If it is nil, it
defaults to \"\".

This function internally calls `org-entry-get' with its INHERIT
argument set to non-nil and the LITERAL-NIL argument set to nil.

If the property is present but empty, the return value is the
empty string.  If the property is not present at all, nil is
returned.  In any other case, return the value as a string.
Search is case-insensitive."
  (let ((sep (or sep ""))
        (value-no-concat (org-entry-get pom property :inherit)))
    ;; (message "[ox-hugo section concat DBG] value-no-concat: %S" value-no-concat)
    (if value-no-concat
        ;; Get the value of PROPERTY from the parent relative to
        ;; current point.
        (let ((value-here-no-inherit (org-entry-get pom property nil))
              (value-parent (org-with-wide-buffer
                             (when (org-up-heading-safe)
                               (org-hugo--entry-get-concat nil property sep)))))
          ;; (message "[ox-hugo section concat DBG] value-here-no-inherit: %S" value-here-no-inherit)
          ;; (message "[ox-hugo section concat DBG] value-parent: %S" value-parent)
          (if value-here-no-inherit
              (format "%s%s%s"
                      (or value-parent "")
                      (if value-parent
                          (if (and (org-string-nw-p sep)
                                   (string-suffix-p sep value-parent))
                              "" ;Don't add the `sep' if `value-parent' already ends with that `sep'
                            sep)
                        "")
                      value-no-concat)
            ;; Use the value from parent directly if the property is not
            ;; set in the current subtree.
            value-parent))
      nil)))

(defun org-hugo--get-section-path (info)
  "Return the Hugo section path.
This is the path relative to the Hugo \"content\" directory.

If the EXPORT_HUGO_SECTION_FRAG keyword is set in the current or a
parent subtree, return the concatenation of the \"HUGO_SECTION\"
and the concatenated \"EXPORT_HUGO_SECTION_FRAG\" values as a path.

Else, return the \"HUGO_SECTION\" path.

The function always returns a string.

INFO is a plist used as a communication channel."
  (let* ((hugo-section-prop (org-entry-get nil "EXPORT_HUGO_SECTION" :inherit))
         (hugo-section-kwd (plist-get info :hugo-section))
         (hugo-section-frag-prop (org-entry-get nil "EXPORT_HUGO_SECTION_FRAG" :inherit))
         (section-path-1 (or hugo-section-prop ;EXPORT_HUGO_SECTION gets higher precedence
                             hugo-section-kwd)) ;This is mainly to support per-file flow
         section-path)
    ;; (message "[ox-hugo section-path DBG] hugo-section-prop: %S" hugo-section-prop)
    ;; (message "[ox-hugo section-path DBG] hugo-section-kwd: %S" hugo-section-kwd)
    ;; (message "[ox-hugo section-path DBG] hugo-section-frag-prop: %S" hugo-section-frag-prop)
    ;; (message "[ox-hugo section-path DBG] section path-1: %S" section-path-1)
    (unless section-path-1
      (user-error "It is mandatory to set the HUGO_SECTION property"))
    (when (org-string-nw-p hugo-section-frag-prop)
      (setq section-path-1
            (concat (file-name-as-directory section-path-1) ;Add trailing slash if absent
                    (org-hugo--entry-get-concat nil "EXPORT_HUGO_SECTION_FRAG" "/"))))
    (setq section-path (file-name-as-directory section-path-1))
    ;; (message "[ox-hugo section-path DBG] section path: %S" section-path)
    section-path))

;;;; Get Language
(defun org-hugo--get-lang (info)
  "Return the language used for the content.

The returned value is a string that can consist of only English
alphabets and an underscore.

The first 2 characters of this string is a language codes as per
ISO 639-1 standard.  See
https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes.

INFO is a plist used as a communication channel."
  (let ((lang (plist-get info :lang-iso-code)))
    (unless lang
      (setq lang
            (or (plist-get info :hugo-locale)
                ;; https://www.gnu.org/software/gettext/manual/html_node/Locale-Environment-Variables.html
                (getenv "LANGUAGE")
                (getenv "LC_ALL")
                (getenv "LANG")))
      (when (stringp lang)
        (setq lang
              (replace-regexp-in-string "\\`\\([a-z]+_[A-Z]+\\).*\\'" "\\1" lang)))
      (setq lang (org-string-nw-p lang))
      (when lang
        ;; (message "[org-hugo--get-lang DBG] language: %s" lang)
        (plist-put info :lang-iso-code lang)))
    lang))

;;;; Check if lang is CJ(K)
(defun org-hugo--lang-cjk-p (info)
  "Return non-nil is the language is Chinese or Japanese.

\(Check for Korean language has not been added as no `ox-hugo'
user has requested for it.)

INFO is a plist used as a communication channel."
  (let* ((lang (org-hugo--get-lang info))
         (lang-2chars (when (and (stringp lang)
                                 (>= (length lang) 2))
                        (substring lang 0 2))))
    (and lang-2chars
         (member lang-2chars '("zh"      ;"zh", "zh_CH", ..
                               "ja"))))) ;"ja", ..

;;;; Format tags into HTML
(defun org-hugo--tags (tags info)
  "Format TAGS into HTML.
INFO is a plist containing export options.

This function is almost identical to `org-html--tags' from
`ox-html' except that the tag separator is an empty string."
  (when tags
    (format "<span class=\"tag\">%s</span>"
            (mapconcat
             (lambda (tag)
               (format "<span class=\"%s\">%s</span>"
                       (concat (plist-get info :html-tag-class-prefix)
                               (org-html-fix-class-name tag))
                       tag))
             tags ""))))

;;;; Check if the buffer has any valid post subtree
(defun org-hugo--buffer-has-valid-post-subtree-p ()
  "Return non-nil if the current Org buffer has at least one valid post subtree.

A valid Hugo post subtree has the `:EXPORT_FILE_NAME:' property
set to a non-empty string."
  (org-with-wide-buffer
   (catch 'found
     (org-map-entries
      (lambda () (throw 'found t)) ;Return quickly on finding the first match
      "EXPORT_FILE_NAME<>\"\""))))

;;;; Advice for silencing messages
(defun org-hugo--advice-silence-messages (orig-fun &rest args)
  "Advice function that silences all messages in ORIG-FUN.
ARGS are the ORIG-FUN function's arguments."
  (let ((inhibit-message t)      ;Don't show the messages in Echo area
        (message-log-max nil))   ;Don't show the messages in the *Messages* buffer
    (apply orig-fun args)))

;;;; Plainify (mimick the Hugo plainify function)
(defun org-hugo--plainify-string (str info)
  "Return STR string without any markup.

INFO is a plist used as a communication channel.

If STR is an empty string or nil, return nil.

This function aims to mimick the Hugo `plainify' function:
https://gohugo.io/functions/plainify/.  For example, if STR is
\"string *with* some /markup/\", the returned string is \"string
with some markup\"."
  (org-string-nw-p
   (replace-regexp-in-string
    "</?[^>]+>" ""
    (org-export-data-with-backend str 'html info))))



;;; Transcode Functions

;;;; Code (<kdb> tags)
(defun org-hugo-kbd-tags-maybe (verbatim _contents info)
  "Wrap text in VERBATIM object with HTML kbd tags.
The kdb wrapping is done if `org-hugo-use-code-for-kbd' is non-nil.

CONTENTS is nil.  INFO is a plist used as a communication
channel."
  (if (org-hugo--plist-get-true-p info :hugo-use-code-for-kbd)
      (format "<kbd>%s</kbd>" (org-html-encode-plain-text
                               (org-element-property :value verbatim)))
    (org-md-verbatim verbatim nil nil)))

;;;; Drawer
(defun org-hugo--parse-logbook-entry (para parent-heading-title info)
  "Parse a LOGBOOK `paragraph' element PARA and save data to INFO.

If the LOGBOOK drawer is under a sub-heading,
PARENT-HEADING-TITLE will be that heading's \"plainified\" title
string.  If LOGBOOK drawer is at the top level, this argument
will be nil.

INFO is a plist used as a communication channel.

This function updates these properties in INFO: `:logbook-date',
`:logbook-lastmod', `:logbook'."
  ;; (pp para)
  (let* ((logbook-entry ())
         (para-raw-str (org-export-data para info))
         ;; Parse the logbook entry's timestamp.
         (timestamp
          (org-element-map para 'timestamp
            (lambda (ts)
              ;; (pp ts)
              (let* ((ts-raw-str (org-element-property :raw-value ts))
                     (ts-str (org-hugo--org-date-time-to-rfc3339 ts-raw-str info)))
                ;; (message "[ox-hugo logbook DBG] ts: %s, ts fmtd: %s"
                ;;          ts-raw-str ts-str)
                (push `(timestamp . ,ts-str) logbook-entry)
                ts-str)) ;lambda return for (org-element-map para 'timestamp
            nil :first-match)))
    ;; (message "\n[ox-hugo logbook DBG] paragraph raw str : %s" para-raw-str)
    ;; (message "[ox-hugo logbook DBG] timestamp : %s" timestamp)
    (unless timestamp
      (user-error "No time stamp is recorded in the LOGBOOK drawer entry"))

    (cl-labels ((get-match-string-and-trim-quotes
                 (num str)
                 (org-string-nw-p
                  (replace-regexp-in-string
                   ;; Handle corner case: If a TODO state has "__" in them, the
                   ;; underscore will be escaped. Remove that "\".
                   "\\\\" ""
                   (save-match-data ;Required because `string-trim' changes match data
                     (string-trim
                      (or (match-string-no-properties num str) "")
                      "\"" "\"")))))

                ;; Parse (assq 'state org-log-note-headings)
                (parse-state-change-maybe
                 ()
                 (let ((state-change-re "^State\\s-+\\(?1:\".+?\"\\)*\\s-+from\\s-+\\(?2:\".+?\"\\)*"))
                   (when (string-match state-change-re para-raw-str)
                     (let ((to-state (get-match-string-and-trim-quotes 1 para-raw-str))
                           ;; (from-state (get-match-string-and-trim-quotes 2 para-raw-str)) ;For debug
                           )
                       ;; (message "[ox-hugo logbook DBG] state change : from %s to %s @ %s"
                       ;;          from-state to-state timestamp)
                       (when to-state
                         (push `(to_state . ,to-state) logbook-entry)
                         ;; (message "[ox-hugo logbook DBG] org-done-keywords: %S" org-done-keywords)
                         (when (and (null parent-heading-title) ;Parse dates from only the toplevel LOGBOOK drawer.
                                    (member to-state org-done-keywords))
                           ;; The first parsed TODO state change entry will be the
                           ;; latest one, and `:logbook-date' would already have
                           ;; been set to that.  So if `:logbook-lastmod' is not set,
                           ;; set that that to the value of `:logbook-date'.
                           ;; *This always works because the newest state change or note
                           ;; entry is always put to the top of the LOGBOOK.*
                           (unless (plist-get info :logbook-lastmod)
                             (when (plist-get info :logbook-date)
                               (plist-put info :logbook-lastmod (plist-get info :logbook-date))))
                           ;; `:logbook-date' will keep on getting updating until the last
                           ;; parsed (first entered) "state changed to DONE" entry.
                           (plist-put info :logbook-date timestamp)))
                       ;; (when from-state ;For debug
                       ;;   (push `(from_state . ,from-state) logbook-entry))
                       )
                     t)))

                ;; Parse (assq 'note org-log-note-headings)
                (parse-note-maybe
                 ()
                 (let ((note-re "^Note taken on .*?\n\\(?1:\\(.\\|\n\\)*\\)"))
                   (when (string-match note-re para-raw-str)
                     (let ((logbook-notes (plist-get info :logbook))
                           (note (string-trim
                                  (match-string-no-properties 1 para-raw-str))))
                       ;; (message "[ox-hugo logbook DBG] note : %s @ %s" note timestamp)
                       (push `(note . ,note) logbook-entry)
                       ;; Update the `lastmod' field using the
                       ;; note's timestamp.
                       ;; *This always works because the newest state change or note
                       ;; entry is always put to the top of the LOGBOOK.*
                       (unless parent-heading-title ;Parse dates from only the toplevel LOGBOOK drawer.
                         (unless (plist-get info :logbook-lastmod)
                           (plist-put info :logbook-lastmod timestamp)))

                       (let ((context-key (or parent-heading-title "_toplevel")))
                         (unless (assoc context-key logbook-notes)
                           (push (cons context-key (list (cons 'notes (list)))) logbook-notes))
                         (setcdr (assoc 'notes (assoc context-key logbook-notes))
                                 (append (cdr (assoc 'notes (assoc context-key logbook-notes)))
                                         (list (nreverse logbook-entry)))))
                       (plist-put info :logbook logbook-notes))
                     t))))

      (save-match-data
        (cond
         ((parse-state-change-maybe))
         ((parse-note-maybe))
         (t
          (user-error "LOGBOOK drawer entry is neither a state change, nor a note"))))
      ;; (message "[org-hugo--parse-logbook-entry DBG] logbook derived `date' : %S" (plist-get info :logbook-date))
      ;; (message "[org-hugo--parse-logbook-entry DBG] logbook derived `lastmod' : %S" (plist-get info :logbook-lastmod))
      ;; (message "[org-hugo--parse-logbook-entry DBG] logbook entry : %S" logbook-entry)
      nil)))

(defun org-hugo-drawer (drawer contents info)
  "Transcode a DRAWER element from Org to appropriate Hugo front-matter.
CONTENTS holds the contents of the block.  INFO is a plist
holding contextual information."
  (let* ((drawer-name (org-element-property :drawer-name drawer))
         (parent-heading (catch 'found
                           (let ((el drawer))
                             (while t
                               (let ((p-el (org-export-get-parent el)))
                                 (when (or (null p-el)
                                           (equal 'headline (org-element-type p-el)))
                                   ;; Return when there's no parent element
                                   ;; or if the parent element is a `headline'.
                                   (throw 'found p-el))
                                 (setq el p-el))))))
         (parent-heading-title (org-hugo--plainify-string
                                (org-element-property :title parent-heading)
                                info)))
    ;; (message "[org-hugo-drawer DBG] parent-heading : %S" parent-heading)
    ;; (message "[org-hugo-drawer DBG] parent-heading-title : %S" parent-heading-title))
    (cond
     ;; :LOGBOOK: Drawer
     ((equal drawer-name (org-log-into-drawer))
      ;; (message "[org-hugo-drawer DBG] elem type: %s" (org-element-type drawer))
      ;; (drawer
      ;;   ..
      ;;   (plain-list
      ;;     (item
      ;;       (paragraph
      ;;         <State change text or Note>
      ;;         (timestamp <timestamp> )))))
      (org-element-map drawer 'plain-list
        (lambda (lst)
          (org-element-map lst 'item
            (lambda (item)
              (org-element-map item 'paragraph
                (lambda (para)
                  (org-hugo--parse-logbook-entry para parent-heading-title info))
                nil :first-match)) ;Each 'item element will have only one 'paragraph element
            )) ;But a 'plain-list element can have multiple 'item elements, so loop through all
        nil :first-match) ;The 'logbook element will have only one 'plain-list element
      ;; Nothing from the LOGBOOK gets exported to the Markdown body
      "")
     ;; Other Org Drawers
     (t
      (org-html-drawer drawer contents info)))))

;;;; Example Block
(defun org-hugo-example-block (example-block _contents info)
  "Transcode an EXAMPLE-BLOCK element into Markdown format.

CONTENTS is nil.  INFO is a plist holding contextual
information."
  (let* ((switches-str (org-element-property :switches example-block))
         ;; Below is a hack for allowing ":linenos <value>" parameter
         ;; in example block header, because the example-block Org
         ;; element parses only "-switches", not ":parameters".
         (linenos-style (and (org-string-nw-p switches-str)
                             (string-match ":linenos\\s-+\\([^ ]+\\)\\b" switches-str)
                             (match-string-no-properties 1 switches-str))))
    (org-element-put-property example-block :language "text")
    (org-element-put-property example-block :linenos-style linenos-style)
    (org-hugo-src-block example-block nil info)))

;;;; Export Snippet
(defun org-hugo-export-snippet (export-snippet _contents _info)
  "Transcode a EXPORT-SNIPPET object from Org to Hugo-compatible Markdown.
CONTENTS is nil.  INFO is a plist holding contextual information.

Example:

  \"@@hugo:foo@@\"

exports verbatim to \"foo\" only when exported using `hugo'
backend.

Export snippets with backend tags \"markdown:\" and \"md:\" are
also handled.  Exporting of export snippets with backend tag
\"html:\" uses the HTML exporter."
  (cond
   ((member (org-export-snippet-backend export-snippet) '(hugo markdown md))
    ;; ox-md.el does not support export snippets, so let's handle
    ;; Markdown export snippets here as well.
    (org-element-property :value export-snippet))
   ;; Also include HTML export snippets.
   (t
    (org-export-with-backend 'html export-snippet nil nil))))

;;;; Export Block
(defun org-hugo-export-block (export-block _contents _info)
  "Transcode a EXPORT-BLOCK element from Org to Hugo-compatible Markdown.
CONTENTS is nil.  INFO is a plist holding contextual information.

Example:

  #+begin_export hugo
  foo
  #+end_export

exports verbatim to \"foo\" only when exported using `hugo'
backend.

If the backend tag is \"markdown\"/\"md\" or \"html\", exporting
of those blocks falls back to the respective exporters."
  (cond
   ((string= (org-element-property :type export-block) "HUGO")
    (org-remove-indentation (org-element-property :value export-block)))
   ;; Also include Markdown and HTML export blocks.
   ;; ox-md handles HTML export blocks too.
   (t
    (org-export-with-backend 'md export-block nil nil))))

;;;; Heading
(defun org-hugo-heading (heading contents info)
  "Transcode HEADING element into Markdown format.
CONTENTS is the heading contents.  INFO is a plist used as
a communication channel."
  (unless (org-element-property :footnote-section-p heading)
    (let* ((numbers (org-hugo--get-heading-number heading info nil))
           (loffset (string-to-number (plist-get info :hugo-level-offset))) ;"" -> 0, "0" -> 0, "1" -> 1, ..
           (level (org-export-get-relative-level heading info))
           (level-effective (+ loffset level))
           (title (org-export-data (org-element-property :title heading) info)) ;`org-export-data' required
           (todo (and (org-hugo--plist-get-true-p info :with-todo-keywords)
                      (org-element-property :todo-keyword heading)))
           (todo-fmtd (when todo
                        (concat (org-hugo--todo todo info) " ")))
           (tags-fmtd (and (org-hugo--plist-get-true-p info :with-tags)
                           (let* ((tags-list (org-export-get-tags heading info))
                                  (tags-list (dolist (fn org-hugo-tag-processing-functions tags-list)
                                               (setq tags-list (funcall fn tags-list info))))
                                  (tags-html (org-hugo--tags tags-list info)))
                             (when (org-string-nw-p tags-html)
                               (concat " " tags-html)))))
           (priority
            (and (org-hugo--plist-get-true-p info :with-priority)
                 (let ((char (org-element-property :priority heading)))
                   (and char (format "[#%c] " char)))))
           (style (plist-get info :md-headline-style)))
      ;; (message "[ox-hugo-heading DBG] num: %s" numbers)
      ;; (message "[ox-hugo-heading DBG] with-tags: %S" (org-hugo--plist-get-true-p info :with-tags))
      ;; (message "[ox-hugo-heading DBG] tags: %S" (org-export-get-tags heading info))
      (cond
       ;; Cannot create a heading.  Fall-back to a list.
       ((or (org-export-low-level-p heading info)
            (not (memq style '(atx setext)))
            (and (eq style 'atx) (> level-effective 6))
            (and (eq style 'setext) (> level-effective 2)))
        (let ((bullet
               (if (not (org-export-numbered-headline-p heading info)) "-"
                 (concat (number-to-string
                          (car (last (org-export-get-headline-number
                                      heading info))))
                         ".")))
              (heading (concat todo-fmtd " " priority title))) ;Heading text without tags
          (concat "<!--list-separator-->\n\n"
                  ;; Above is needed just in case the body of the
                  ;; section above is ending with a plain list. That
                  ;; HTML comment will force-end the <ul> or <ol> tag
                  ;; of that preceding list.
                  bullet " " heading tags-fmtd "\n\n"
                  (and contents (replace-regexp-in-string "^" "    " contents)))))
       (t
        (let* ((anchor (when org-hugo-headline-anchor
                         (format "{#%s}" (org-hugo--get-anchor heading info)))) ;https://gohugo.io/extras/crossreferences/
               (heading-title (org-hugo--heading-title style level loffset title
                                                       todo-fmtd tags-fmtd anchor numbers))
               (wrap-element (org-hugo--container heading info))
               (content-str (or (org-string-nw-p contents) "")))
          (if wrap-element
              (let* ((container-class (or (org-element-property :HTML_CONTAINER_CLASS heading)
                                          (org-element-property :EXPORT_HTML_CONTAINER_CLASS heading)
                                          (plist-get info :html-container-class)))
                     (container-class-str (if (org-string-nw-p container-class)
                                              (concat " " container-class)
                                            container-class)))
                (format (concat "<%s class=\"outline-%d%s\">\n"
                                "%s%s\n"
                                "</%s>")
                        wrap-element level container-class-str
                        heading-title content-str
                        wrap-element))
            (format "%s%s" heading-title content-str))))))))

;;;;; Heading Helpers
(defun org-hugo--container (heading info)
  "Get the HTML container element for HEADING.

INFO is a plist used as a communication channel.

If a heading has `:HTML_CONTAINER:' or `:EXPORT_HTML_CONTAINER:'
property, that is used for the container element.

Else if the `:html-container' property is a non-empty string:
  - For the top level headings, wrapping is done using that property.
  - For second and lower level headings, wrapping is done using
    the HTML <div> tags.

Else, no HTML element is wrapped around the HEADING."
  (or (org-element-property :HTML_CONTAINER heading) ;property of the immediate heading
      (org-element-property :EXPORT_HTML_CONTAINER heading) ;property of the immediate heading
      (and (org-string-nw-p (plist-get info :html-container)) ;inherited :html-container: property if any
           (if (or (plist-get info :html-container-nested)
                   (= 1 (org-export-get-relative-level heading info)))
               (plist-get info :html-container)
             "div"))))

;;;###autoload
(defun org-hugo-slug (str &optional allow-double-hyphens)
  "Convert string STR to a `slug' and return that string.

A `slug' is the part of a URL which identifies a particular page
on a website in an easy to read form.

Example: If STR is \"My First Post\", it will be converted to a
slug \"my-first-post\", which can become part of an easy to read
URL like \"https://example.com/posts/my-first-post/\".

In general, STR is a string.  But it can also be a string with
Markdown markup because STR is often a post's sub-heading (which
can contain bold, italics, link, etc markup).

The `slug' generated from that STR follows these rules:

- Contain only lower case alphabet, number and hyphen characters
  ([[:alnum:]-]).
- Not have *any* HTML tag like \"<code>..</code>\",
  \"<span class=..>..</span>\", etc.
- Not contain any URLs (if STR happens to be a Markdown link).
- Replace \".\" in STR with \"dot\", \"&\" with \"and\",
  \"+\" with \"plus\".
- Replace parentheses with double-hyphens.  So \"foo (bar) baz\"
  becomes \"foo--bar--baz\".
- Replace non [[:alnum:]-] chars with spaces, and then one or
  more consecutive spaces with a single hyphen.
- If ALLOW-DOUBLE-HYPHENS is non-nil, at most two consecutive
  hyphens are allowed in the returned string, otherwise consecutive
  hyphens are not returned.
- No hyphens allowed at the leading or trailing end of the slug."
  (let* (;; All lower-case
         (str (downcase str))
         ;; Remove "<FOO>..</FOO>" HTML tags if present.
         (str (replace-regexp-in-string "<\\(?1:[a-z]+\\)[^>]*>.*</\\1>" "" str))
         ;; Remove URLs if present in the string.  The ")" in the
         ;; below regexp is the closing parenthesis of a Markdown
         ;; link: [Desc](Link).
         (str (replace-regexp-in-string (concat "\\](" ffap-url-regexp "[^)]+)") "]" str))
         ;; Replace "&" with " and ", "." with " dot ", "+" with
         ;; " plus ".
         (str (replace-regexp-in-string
               "&" " and "
               (replace-regexp-in-string
                "\\." " dot "
                (replace-regexp-in-string
                 "\\+" " plus " str))))
         ;; Replace all characters except alphabets, numbers and
         ;; parentheses with spaces.
         (str (replace-regexp-in-string "[^[:alnum:]()]" " " str))
         ;; On emacs 24.5, multibyte punctuation characters like ":"
         ;; are considered as alphanumeric characters! Below evals to
         ;; non-nil on emacs 24.5:
         ;;   (string-match-p "[[:alnum:]]+" ":")
         ;; So replace them with space manually..
         (str (if (version< emacs-version "25.0")
                  (let ((multibyte-punctuations-str ":")) ;String of multibyte punctuation chars
                    (replace-regexp-in-string (format "[%s]" multibyte-punctuations-str) " " str))
                str))
         ;; Remove leading and trailing whitespace.
         (str (replace-regexp-in-string "\\(^[[:space:]]*\\|[[:space:]]*$\\)" "" str))
         ;; Replace 2 or more spaces with a single space.
         (str (replace-regexp-in-string "[[:space:]]\\{2,\\}" " " str))
         ;; Replace parentheses with double-hyphens.
         (str (replace-regexp-in-string "\\s-*([[:space:]]*\\([^)]+?\\)[[:space:]]*)\\s-*" " -\\1- " str))
         ;; Remove any remaining parentheses character.
         (str (replace-regexp-in-string "[()]" "" str))
         ;; Replace spaces with hyphens.
         (str (replace-regexp-in-string " " "-" str))
         ;; Remove leading and trailing hyphens.
         (str (replace-regexp-in-string "\\(^[-]*\\|[-]*$\\)" "" str)))
    (unless allow-double-hyphens
      (setq str (replace-regexp-in-string "--" "-" str)))
    str))

(defun org-hugo-get-page-or-bundle-name (element info)
  "Return ELEMENT's slug based on `:EXPORT_FILE_NAME' and `:EXPORT_HUGO_BUNDLE'.

If the \"slug\" of the element is \"section/post\", return
\"post\".

Return nil if ELEMENT doesn't have the EXPORT_FILE_NAME property
set.

INFO is a plist used as a communication channel."
  (let ((slug (org-hugo--heading-get-slug element info nil)))
    (when (org-string-nw-p slug)
      (file-name-base slug))))

(defun org-hugo-get-custom-id (element &optional _info)
  "Return ELEMENT's `:CUSTOM_ID' property.

Return nil if ELEMENT doesn't have the CUSTOM_ID property set."
  (org-string-nw-p (org-element-property :CUSTOM_ID element)))

(defun org-hugo-get-id (&optional element _info)
  "Return the value of `:ID' property for ELEMENT.

Return nil if id is not found."
  (let ((element-begin (org-element-property :begin element)))
    (save-excursion
      (goto-char element-begin)
      (org-id-get))))

(defun org-hugo-get-heading-slug (element info)
  "Return the slug string derived from an Org heading ELEMENT.

The slug string is parsed from the ELEMENT's `:title' property.

INFO is a plist used as a communication channel.

Return nil if ELEMENT's `:title' property is nil or an empty string."
  (let ((title (org-export-data-with-backend
                (org-element-property :title element) 'md info)))
    (org-string-nw-p (org-hugo-slug title :allow-double-hyphens))))

(defun org-hugo-get-md5 (element info)
  "Return md5 sum derived string using ELEMENT's title property.

INFO is a plist used as a communication channel.

This function will never return nil."
  (let ((hash-len 6)
        (title (or (org-string-nw-p (org-export-data-with-backend
                                     (org-element-property :title element) 'md info))
                   "")))
    (substring (md5 title) 0 hash-len)))

(defun org-hugo--get-elem-with-prop (prop &optional pom _info)
  "Find the first element with PROP property in the current tree.

PROP is a property symbol with a : prefix, example:
`:EXPORT_FILE_NAME'.

Optional argument POM is the position or marker from which the
upward search for PROP should begin.

Return a cons of type (ELEM . PVAL) where ELEM is the element
containing the property PROP and PVAL is the property's value.

Return nil if the PROP is not found or if the PVAL is nil.

This function is created as a workaround for Org 9.5 and older
versions for the issue that `org-element-at-point' does not
return an element with all the inherited properties.  That issue
is fixed in Org main branch at least as of 2022-03-17."
  (org-with-wide-buffer
   ;; (message (format "[search prop DBG] point 1 : %S" (point)))
   (when pom
     (goto-char pom))
   ;; (message (format "[search prop DBG] point 2 : %S" (point)))
   (org-back-to-heading-or-point-min :invisible-ok)
   (let ((elem (org-element-at-point))
         (level t)
         pval)
     (catch :found
       (while elem
         ;; (message (format "[search prop DBG] prop %S, elem : %S" prop elem))
         (setq pval (org-element-property prop elem))
         ;; (message "[search prop DBG] level %S, pval %S" level pval)
         (when (or pval (null level))
           (if (null pval)
               ;; There's probably no value to distinguish
               ;; between the case where a property is not
               ;; found, or the case where the property
               ;; value is nil. Revisit this if that
               ;; changes.
               (throw :found nil)
             (throw :found (cons elem pval))))
         (setq level (org-up-heading-safe))
         (setq elem (org-element-at-point)))))))

(defun org-hugo--heading-get-slug (heading info &optional inherit-export-file-name)
  "Return the slug string derived from an Org HEADING element.

1. If HEADING has only `:EXPORT_FILE_NAME' and it's not a Hugo
   page bundle, use that property as slug.

2. If HEADING has a `:EXPORT_FILE_NAME' property, and its value
   is either \"index\" or \"_index\", use `:EXPORT_HUGO_BUNDLE'
   to derive the slug.  \"index\" subtree is a Leaf Bundle, and
   \"_index\" subtree is a Branch Bundle.

3. If HEADING has a `:EXPORT_FILE_NAME' property, and its value
   is neither \"index\" nor \"_index\", use that to derive the
   slug.

If INHERIT-EXPORT-FILE-NAME is non-nil, allow inheriting the
`:EXPORT_FILE_NAME' property from a parent subtree.

The `:EXPORT_HUGO_SECTION' property or `#+hugo_section' keyword
value is prepended to all of the above options.

INFO is a plist used as a communication channel.

Return nil if none of the above are true."
  (org-with-wide-buffer
   (let ((heading-begin (org-element-property :begin heading)))
     (when heading-begin
       (goto-char heading-begin)))
   (let ((file (org-string-nw-p (org-export-get-node-property :EXPORT_FILE_NAME heading inherit-export-file-name)))
         bundle slug)
     ;; (message "[org-hugo--heading-get-slug DBG] EXPORT_FILE_NAME: %S" file)
     (when file
       (setq bundle (let* ((elem-pval (org-hugo--get-elem-with-prop :EXPORT_HUGO_BUNDLE))
                           (pval (when elem-pval
                                   (cdr elem-pval))))
                      pval))

       (cond
        ;; Leaf or branch bundle landing page.
        ((and bundle file (member file '("index" ;Leaf bundle
                                         "_index" ;Branch bundle
                                         )))
         (setq slug bundle)
         ;; (message "[org-hugo--heading-get-slug DBG] bundle slug: %S" slug)
         )
        ;; It's a Hugo page bundle, but the file is neither index nor
        ;; _index. So likely a page in a branch bundle.
        ((and bundle file)
         (setq slug (concat (file-name-as-directory bundle) file))
         ;; (message "[org-hugo--heading-get-slug DBG] branch bundle file slug: %S" slug)
         )
        ;; Not a Hugo page bundle.
        (t
         (setq slug file)))

       ;; Prefix with section and fragmented sections if any.
       (let ((pheading heading)
             section fragment fragments)
         (setq section (org-string-nw-p
                        (or (org-export-get-node-property :EXPORT_HUGO_SECTION heading :inherited)
                            (plist-get info :hugo-section))))

         ;; Iterate over all parents of heading, and collect section
         ;; path fragments.
         (while (and pheading
                     (not (org-export-get-node-property :EXPORT_HUGO_SECTION pheading nil)))
           ;; Add the :EXPORT_HUGO_SECTION_FRAG value to the fragment list.
           (when (setq fragment (org-export-get-node-property :EXPORT_HUGO_SECTION_FRAG pheading nil))
             (push fragment fragments))
           (setq pheading (org-element-property :parent pheading)))

         (when section
           (setq slug (concat (file-name-as-directory section)
                              (mapconcat #'file-name-as-directory fragments "")
                              slug)))
         ;; (message "[org-hugo--heading-get-slug DBG] section: %S" section)
         ;; (message "[org-hugo--heading-get-slug DBG] section + slug: %S" slug)
         ))
     ;; (message "[org-hugo--heading-get-slug DBG] FINAL slug: %S" slug)
     slug)))

(defun org-hugo--get-anchor(element info)
  "Return anchor string for Org heading ELEMENT.

The anchor is derived using the first function that returns a
non-nil value (a string) from the list
`org-hugo-anchor-functions'.

INFO is a plist used as a communication channel.

Return an empty string if all functions in
`org-hugo-anchor-functions' return nil."
  (or (seq-some
       (lambda (fn) (funcall fn element info))
       org-hugo-anchor-functions)
      ""))

(defun org-hugo--heading-title (style level loffset title &optional todo tags anchor numbers)
  "Generate a heading title in the preferred Markdown heading style.

STYLE is the preferred style (`atx' or `setext').
LEVEL is the header level.
LOFFSET is the offset (a non-negative number) that is added to the
Markdown heading level for `atx' style.
TITLE is the heading title.

Optional argument TODO is the Org TODO string.

Optional argument TAGS is a string containing the current
heading's tags.

Optional argument ANCHOR is the Hugo anchor tag for the section as a
string.

Optional argument NUMBERS, if non-nil, is an htmlized string
containing the TITLE's number."
  (let ((heading (concat todo numbers title tags " " anchor "\n")))
    ;; Use "Setext" style
    (if (and (eq style 'setext) (< level 3))
        (let* ((underline-char (if (= level 1) ?= ?-))
               (underline (concat (make-string (length heading) underline-char)
                                  "\n")))
          (concat "\n" heading underline "\n"))
      ;; Use "Atx" style
      ;; Always translate level N Org heading to level N+1 Markdown
      ;; heading because Markdown level 1 heading and HTML title both
      ;; get the HTML <h1> tag, and we do not want the top-most heading
      ;; of a post to look the exact same as the post's title.
      (let ((level-mark (make-string (+ loffset level) ?#)))
        (concat "\n" level-mark " " heading "\n")))))

;;;; Inner Template
(defun org-hugo-inner-template (contents info)
  "Return body of document after converting it to Hugo-compatible Markdown.
CONTENTS is the transcoded contents string.  INFO is a plist
holding export options."
  (let* ((toc-level (plist-get info :with-toc))
         (toc-level (if (and toc-level
                             (not (wholenump toc-level)))
                        (plist-get info :headline-levels)
                      toc-level))
         (toc (if (and toc-level
                       (wholenump toc-level)
                       (> toc-level 0)) ;TOC will be exported only if toc-level is positive
                  (concat (org-hugo--build-toc info toc-level) "\n")
                ""))
         ;; Handling the case of special blocks inside markdown quote
         ;; blocks.
         (contents (replace-regexp-in-string
                    (concat "\\(\n\\s-*> \\)*" (regexp-quote org-hugo--trim-pre-marker))
                    ;;          ^^^^^^^^ Markdown quote blocks have lines beginning with "> ".
                    org-hugo--trim-pre-marker ;Keep the trim marker; it will be removed next.
                    contents))
         (contents (replace-regexp-in-string
                    (concat "\\([[:space:]]\\|\n\\)*" (regexp-quote org-hugo--trim-pre-marker))
                    "\n"
                    contents))
         (contents (replace-regexp-in-string ;Trim stuff after selected exported elements
                    (concat (regexp-quote org-hugo--trim-post-marker)
                            ;; Pull up the contents from the next
                            ;; line, unless the next line is a list
                            ;; item (-), a heading (#) or a code block
                            ;; (`).
                            "\\([[:space:]>]\\|\n\\)+\\([^-#`]\\)")
                    " \\2" contents)))

    ;; (message "[org-hugo-inner-template DBG] toc-level: %s" toc-level)
    (string-trim-left ;Remove any extra blank lines between front-matter and the content #consistency
     (concat
      toc
      contents
      ;; Make sure CONTENTS is separated from table of contents
      ;; and footnotes with at least a blank line.
      "\n"
      (org-blackfriday-footnote-section info (org-hugo--lang-cjk-p info))))))

;;;; Inline Src Block
(defun org-hugo-inline-src-block (inline-src-block _contents _info)
  "Transcode INLINE-SRC-BLOCK object into HTML.

Escape Hugo shortcodes if present in this element's value."
  (let* ((lang (org-element-property :language inline-src-block))
         (code (org-hugo--escape-hugo-shortcode
                (org-element-property :value inline-src-block)
                lang)))
    (org-element-put-property inline-src-block :value code)
    (format "<span class=\"inline-src language-%s\" data-lang=\"%s\">%s</span>"
            lang lang
            (org-md-verbatim inline-src-block nil nil))))

;;;; Keyword
(defun org-hugo-keyword (keyword contents info)
  "Transcode a KEYWORD element into Hugo-compatible Markdown format.
CONTENTS is nil.  INFO is a plist used as a communication
channel."
  (let ((kwd (org-element-property :key keyword))
        (value (org-element-property :value keyword)))
    (cond
     ((and (equal "HUGO" kwd))
      (if (and (stringp value)          ;Hugo summary splitting
               (string-match-p "\\`\\s-*more\\s-*\\'" value))
          (progn
            ;; https://gohugo.io/content-management/summaries#user-defined-manual-summary-splitting
            "<!--more-->")
        (progn
          value)))
     ((and (equal "TOC" kwd)
           (string-match-p "\\<headlines\\>" value))
      (let* ((depth (and (string-match "\\<[0-9]+\\>" value)
                         (string-to-number (match-string 0 value))))
             (local? (string-match-p "\\<local\\>" value))
             (scope                     ;From `org-md-keyword'
              (cond
               ((string-match ":target +\\(\".+?\"\\|\\S-+\\)" value) ;link
                (org-export-resolve-link
                 (org-strip-quotes (match-string 1 value)) info))
               (local? keyword))))
        (when (and depth
                   (> depth 0))
          (let ((toc-str (org-hugo--build-toc info depth scope local?)))
            (when toc-str
              (org-remove-indentation toc-str))))))
     (t
      (org-md-keyword keyword contents info)))))

;;;; Links
(defun org-hugo--get-coderef-anchor-prefix (el)
  "Get anchor prefix string for code refs in element EL.

Return a cons (CODE-REFS . ANCHOR-PREFIX) where

- CODE-REFS is an alist of the type (LINENUM . LABEL) where

  LINENUM is the line number where the code referenced labeled
  LABEL was found.  LABEL is a string.

- ANCHOR-PREFIX is a string.

Return nil if EL has no code references."
  (let ((prefix "org-coderef")
        (hash-len 6)
        (code-refs (cdr (org-export-unravel-code el))))
    (when code-refs
      (let* ((unique-id (substring
                         (md5 (format "%s" code-refs)) 0 hash-len))
             (anchor-prefix (format "%s--%s" prefix unique-id)))
        (cons code-refs anchor-prefix)))))

(defun org-hugo-link--resolve-coderef (ref info)
  "Resolve a code reference REF.

This function is heavily derived from
`org-export-resolve-coderef'.

INFO is a plist used as a communication channel.

Return a plist with these elements:

- `:line-num' :: REF associated line number

- `:ref' :: REF associated line number in source code (if the Org
  element's `:use-labels' property is unset.  This happens when
  the `-r' switch is used) , or REF itself.

- `:anchor-prefix' :: String prefix for REF's anchor.

Throw an error if no block contains REF."
  (or (org-element-map (plist-get info :parse-tree) '(example-block src-block)
        (lambda (el)
          (with-temp-buffer
            (insert (org-trim (org-element-property :value el)))
            (let* ((ref-info ())
                   (label-fmt (or (org-element-property :label-fmt el)
                                  org-coderef-label-format))
                   (ref-re (org-src-coderef-regexp label-fmt ref)))
              ;; Element containing REF is found.  Resolve it to
              ;; either a label or a line number, as needed.
              (when (re-search-backward ref-re nil :noerror)
                (let* ((line-num (+ (or (org-export-get-loc el info) 0)
                                    (line-number-at-pos)))
                       (ref-str (format "%s" (if (org-element-property :use-labels el)
                                                 ref
                                               line-num))))
                  (setq ref-info (plist-put ref-info :line-num line-num))
                  (setq ref-info (plist-put ref-info :ref ref-str))
                  (let ((anchor-prefix (or (org-element-property :anchor-prefix el) ;set in `org-hugo-src-block'
                                           (cdr (org-hugo--get-coderef-anchor-prefix el)))))
                    (setq ref-info (plist-put ref-info :anchor-prefix anchor-prefix))))
                ref-info))))
        info 'first-match)
      (signal 'org-link-broken (list ref))))

(defun org-hugo--org-mode-light ()
  "Enable set current buffer's `major-mode' to `org-mode' quickly.

It is necessary for the `major-mode' to be `org-mode' for many
functions like `org-link-search'."
  (unless (derived-mode-p 'org-mode)
    (let ((inhibit-modification-hooks t)
          (org-mode-hook nil)   ;Don't run any Org mode hook functions
          (org-inhibit-startup t)) ;Don't run any Org buffer startup functions
      (org-mode))))

(defun org-hugo--get-anchor-at-point (info)
  "Return anchor string based on the current point.

If point is in a `headline' element, derive the anchor using
`org-hugo--get-anchor'.

Otherwise, if the current point has an Org target, get the target
anchor.

If current element has `:EXPORT_FILE_NAME' property, return the
anchor as-is, otherwise prefix the anchor string with \"#\".

Return an empty string if an anchor cannot be derived.

INFO is a plist used as a communication channel."
  (let ((elem (org-element-at-point))
        (anchor ""))
    (cond
     ((equal (org-element-type elem) 'headline)
      (setq anchor (org-hugo--get-anchor elem info)))
     (t
      ;; If current point has an Org Target, get the target anchor.
      (let ((target-elem (org-element-target-parser)))
        (when (equal (org-element-type target-elem) 'target)
          (setq anchor (org-blackfriday--get-target-anchor target-elem))))))
    (when (org-string-nw-p anchor)
      ;; If the element has the `:EXPORT_FILE_NAME' it's not a
      ;; sub-heading, but the subtree's main heading.  Don't prefix
      ;; the "#" in that case.
      (unless (org-export-get-node-property :EXPORT_FILE_NAME elem nil)
        (setq anchor (format "#%s" anchor))))
    ;; (message "[search and get anchor DBG] anchor: %S" anchor)
    anchor))

(defun org-hugo--search-and-get-anchor (org-file search-str info)
  "Return HTML anchor for the point where SEARCH-STR is found in ORG-FILE.

ORG-FILE is the file path in which the SEARCH-STR is to be searched.

SEARCH-STR needs to be a non-empty string.  Example values: \"*
Some heading\", \"#some_custom_id\".

If the search fails, return \"\".

INFO is a plist used as a communication channel."
  ;; (message "[search and get anchor DBG] org-file: %S" org-file)
  ;; (message "[search and get anchor DBG] search-str: %S" search-str)
  (let ((buffer (get-file-buffer org-file))) ;nil if `org-file' buffer is not already open
    (unless (file-exists-p org-file)
      (error "[org-hugo--search-and-get-anchor] Unable to open Org file `%s'" org-file))
    (with-current-buffer (find-file-noselect org-file)
      (unless buffer
        (add-to-list 'org-hugo--opened-buffers (current-buffer)))
      ;; `org-mode' needs to be loaded for `org-link-search' to work
      ;; correctly. Otherwise `org-link-search' returns starting
      ;; points for incorrect subtrees.
      (org-hugo--org-mode-light)
      (org-export-get-environment) ;Eval #+bind keywords, etc.
      (org-link-search search-str) ;This is extracted from the `org-open-file' function.
      (org-hugo--get-anchor-at-point info))))

(defun org-hugo-link (link desc info)
  "Convert LINK to Markdown format.

DESC is the link's description.
INFO is a plist used as a communication channel.

Unlike `org-md-link', this function will also copy local images
and rewrite link paths to make blogging more seamless."
  (let* ((raw-link (org-element-property :raw-link link))
         (raw-path (org-element-property :path link))
         (type (org-element-property :type link))
         (link-is-url (member type '("http" "https" "ftp" "mailto"))))
    ;; (message "[org-hugo-link DBG] raw-path 1: %s" raw-path)

    (when (and (stringp raw-path)
               link-is-url)
      (setq raw-path (org-blackfriday--url-sanitize-maybe
                      info (url-encode-url raw-path))))
    ;; (message "[org-hugo-link DBG] raw-link: %s" raw-link)
    ;; (message "[org-hugo-link DBG] raw-path 2: %s" raw-path)
    ;; (message "[org-hugo-link DBG] link: %S" link)
    ;; (message "[org-hugo-link DBG] link type: %s" type)
    (cond
     ;; Link type is handled by a special function.
     ((org-export-custom-protocol-maybe link desc 'md info))
     ((member type '("custom-id" "id"
                     "fuzzy")) ;<<target>>, #+name, heading links
      (let ((destination (if (string= type "fuzzy")
                             (org-export-resolve-fuzzy-link link info)
                           (org-export-resolve-id-link link info))))
        ;; (message "[org-hugo-link DBG] link type: %s" type)
        ;; (message "[org-hugo-link DBG] destination: %s" destination)
        ;; (message "[org-hugo-link DBG] link: %S" link)
        ;; (message "[org-hugo-link DBG] link destination elem type: %S" (org-element-type destination))
        (pcase (org-element-type destination)
          ;; External file.
          (`plain-text
           (let ((path (progn
                         ;; Treat links to `file.org' as links to `file.md'.
                         (if (string= ".org" (downcase (file-name-extension destination ".")))
                             (concat (file-name-sans-extension destination) ".md")
                           destination))))
             ;; (message "[org-hugo-link DBG] plain-text path: %s" path)
             (if (org-id-find-id-file raw-path)
                 (let* ((anchor (org-hugo-link--heading-anchor-maybe link info))
                        (ref (if (and (org-string-nw-p anchor)
                                      (not (string-prefix-p "#" anchor)))
                                 ;; If the "anchor" doesn't begin with
                                 ;; "#", it's a direct reference to a
                                 ;; post subtree.
                                 anchor
                               (concat path anchor))))
                   ;; (message "[org-hugo-link DBG] plain-text org-id anchor: %S" anchor)
                   (format "[%s]({{< relref \"%s\" >}})" (or desc path) ref))
               (if desc
                   (format "[%s](%s)" desc path)
                 (format "<%s>" path)))))
          ;; Links of type [[* Some heading]].
          (`headline
           (let ((title (org-export-data (org-element-property :title destination) info)))
             (format
              "[%s](#%s)"
              ;; Description
              (cond ((org-string-nw-p desc))
                    ((org-export-numbered-headline-p destination info)
                     (mapconcat #'number-to-string
                                (org-export-get-headline-number destination info)
                                "."))
                    (t
                     title))
              ;; Reference
              (org-hugo--get-anchor destination info))))
          ;; Links to other Org elements like source blocks, tables,
          ;; paragraphs, standalone figures, <<target>> links, etc.
          (_
           (let ((description
                  (or (org-string-nw-p desc)
                      (let ((number (org-export-get-ordinal
                                     destination info
                                     nil #'org-html--has-caption-p)))
                        (when number
                          (let ((num-str (if (atom number)
                                             (number-to-string number)
                                           (mapconcat #'number-to-string number "."))))
                            ;; (message "[org-hugo-link DBG] num-str: %s" num-str)
                            (if org-hugo-link-desc-insert-type
                                (let* ((type (org-element-type destination))
                                       ;; Org doesn't have a specific
                                       ;; element for figures. So if
                                       ;; the element is `paragraph',
                                       ;; and as this element has an
                                       ;; ordinal, we will assume that
                                       ;; to be a figure.
                                       (type (if (equal 'paragraph type)
                                                 'figure
                                               type))
                                       (type-str (org-blackfriday--translate type info)))
                                  (format "%s %s" type-str num-str))
                              num-str)))))))
             ;; (message "[org-hugo-link DBG] link description: %s" description)
             (when description
               (let ((dest-link (cond
                                 ;; Ref to a source block or table.
                                 ((memq (org-element-type destination) '(src-block table))
                                  (org-blackfriday--get-reference destination))
                                 ;; Ref to a standalone figure.
                                 ((and (org-html-standalone-image-p destination info)
                                       (eq (org-element-type destination) 'paragraph))
                                  (let ((figure-ref (org-blackfriday--get-reference destination)))
                                    (if (org-string-nw-p figure-ref)
                                        (replace-regexp-in-string
                                         "\\`org-paragraph--"
                                         (org-blackfriday--get-ref-prefix 'figure)
                                         figure-ref)
                                      (org-export-get-reference destination info))))
                                 ;; Ref to a <<target>>.
                                 ((eq (org-element-type destination) 'target)
                                  (org-blackfriday--get-target-anchor destination))
                                 ;; Ref to all other link destinations.
                                 (t
                                  (org-export-get-reference destination info)))))
                 (format "[%s](#%s)" description dest-link))))))))
     ((org-export-inline-image-p link org-html-inline-image-rules)
      ;; (message "[org-hugo-link DBG] processing an image: %s" desc)
      (let* ((parent (org-export-get-parent link))
             (parent-type (org-element-type parent))
             ;; If this is a hyper-linked image, it's parent type will
             ;; be a link too. Get the parent of *that* link in that
             ;; case.
             (grand-parent (when (eq parent-type 'link)
                             (org-export-get-parent parent)))
             (useful-parent (if grand-parent
                                grand-parent
                              parent))
             (attr (org-export-read-attribute :attr_html useful-parent))
             (caption (or
                       ;; Caption set using #+caption takes higher precedence.
                       (org-string-nw-p
                        (org-export-data  ;Look for caption set using #+caption
                         (org-export-get-caption (org-export-get-parent-element link))
                         info))
                       (plist-get attr :caption)))
             (caption (when (org-string-nw-p caption)
                        (format "%s%s%s%s"
                                "<span class=\"figure-number\">"
                                (format (org-html--translate
                                         (concat
                                          (cdr (assoc 'figure org-blackfriday--org-element-string))
                                          " %d:")
                                         info)
                                        (org-export-get-ordinal
                                         useful-parent info
                                         nil #'org-html--has-caption-p))
                                " </span>"
                                caption)))
             (extension (file-name-extension raw-path))
             (inlined-svg (and (stringp extension)
                               (string= "svg" (downcase extension))
                               (plist-get attr :inlined))))
        ;; (message "[org-hugo-link DBG] Inline image: %s, extension: %s" raw-path extension)
        ;; (message "[org-hugo-link DBG] inlined svg? %S" inlined-svg)
        ;; (message "[org-hugo-link DBG] caption: %s" caption)
        (if inlined-svg
            (let* ((svg-contents (with-temp-buffer
                                   (insert-file-contents raw-path)
                                   (fill-region (point-min) (point-max)) ;Make huge one-liner SVGs sane
                                   (buffer-substring-no-properties (point-min) (point-max))))
                   (svg-contents-sanitized (replace-regexp-in-string
                                            ;; Remove the HTML comments.
                                            "<!--\\(.\\|\n\\)*?-->" ""
                                            (replace-regexp-in-string
                                             ;; Remove the xml document tag as that cannot be inlined in-between
                                             ;; a Markdown (or even an HTML) file.
                                             "<\\?xml version=\"1\\.0\" encoding=\"UTF-8\" standalone=\"no\"\\?>" ""
                                             ;; Remove !DOCTYPE tag from the inlined SVG.
                                             (replace-regexp-in-string
                                              "<!DOCTYPE svg[^>]+>" ""
                                              svg-contents))))
                   (svg-html (if caption
                                 (format "<figure>\n%s\n<figcaption>\n\n  %s\n</figcaption>\n</figure>"
                                         svg-contents-sanitized caption)
                               svg-contents-sanitized)))
              ;; (message "[org-hugo-link DBG] svg contents: %s" svg-contents)
              ;; (message "[org-hugo-link DBG] svg contents sanitized: %s" svg-contents-sanitized)
              svg-html)
          (let* ((path (org-hugo--attachment-rewrite-maybe raw-path info))
                 (inline-image (not (org-html-standalone-image-p useful-parent info)))
                 (source (if link-is-url
                             (concat type ":" path)
                           path))
                 (num-attr (/ (length attr) 2)) ;(:alt foo) -> num-attr = 1
                 (alt-text (plist-get attr :alt)))
            ;; (message "[org-hugo-link DBG] path: %s" path)
            ;; (message "[org-hugo-link DBG] inline image? %s" inline-image)
            ;; (message "[org-hugo-link DBG] attr: %s num of attr: %d"
            ;;          attr (length attr))
            ;; (message "[org-hugo-link DBG] parent-type: %s" parent-type)
            ;; (message "[org-hugo-link DBG] useful-parent-type: %s"
            ;;          (org-element-type useful-parent))
            (cond
             (;; Use the Markdown image syntax if the image is inline and
              ;; there are no HTML attributes for the image, or just one
              ;; attribute, the `alt-text'.
              (and inline-image
                   (or (= 0 num-attr)
                       (and alt-text
                            (= 1 num-attr))))
              (let ((alt-text (if alt-text
                                  alt-text
                                "")))
                (format "![%s](%s)" alt-text source)))
             (;; Else if the image is inline (with non-alt-text
              ;; attributes), use HTML <img> tag syntax.
              inline-image
              ;; The "target" and "rel" attributes would be meant for <a>
              ;; tags. So do not pass them to the <img> tag.
              (plist-put attr :target nil)
              (plist-put attr :rel nil)
              (org-html--format-image source attr info))
             (t ;Else use the Hugo `figure' shortcode.
              ;; Hugo `figure' shortcode named parameters.
              ;; https://gohugo.io/content-management/shortcodes/#figure
              (let ((figure-params `((src . ,source)
                                     (alt . ,alt-text)
                                     (caption . ,(when (org-string-nw-p caption)
                                                   (replace-regexp-in-string "\"" "\\\\\\&" caption))) ;Escape the double-quotes, if any.
                                     (link . ,(plist-get attr :link))
                                     (title . ,(plist-get attr :title))
                                     (class . ,(plist-get attr :class))
                                     (attr . ,(plist-get attr :attr))
                                     (attrlink . ,(plist-get attr :attrlink))
                                     (width . ,(plist-get attr :width))
                                     (height . ,(plist-get attr :height))
                                     ;; While the `target' and `rel'
                                     ;; attributes are not supported by
                                     ;; the inbuilt Hugo `figure'
                                     ;; shortcode, they can be used as
                                     ;; intended if a user has a custom
                                     ;; `figure' shortcode with the
                                     ;; support added for those.
                                     (target . ,(plist-get attr :target))
                                     (rel . ,(plist-get attr :rel))))
                    (figure-param-str ""))
                (dolist (param figure-params)
                  (let ((name (car param))
                        (val (cdr param)))
                    (when val
                      (setq figure-param-str (concat figure-param-str
                                                     (format "%s=\"%s\" "
                                                             name val))))))
                ;; (message "[org-hugo-link DBG] figure params: %s" figure-param-str)
                (format "{{< figure %s >}}" (org-trim figure-param-str)))))))))
     ((string= type "coderef")
      (let* ((ref-label (org-element-property :path link))
             (ref-info (org-hugo-link--resolve-coderef ref-label info))
             (desc (format (org-export-get-coderef-format ref-label desc)
                           (plist-get ref-info :ref))))
        ;; (message "[org-hugo-link DBG] coderef ref label: %s" ref-label)
        ;; (message "[org-hugo-link DBG] coderef ref str: %s" (plist-get ref-info :ref))
        ;; (message "[org-hugo-link DBG] coderef anchor prefix: %s" (plist-get ref-info :anchor-prefix))
        ;; (message "[org-hugo-link DBG] coderef line num: %s" (plist-get ref-info :line-num))
        ;; (message "[org-hugo-link DBG] coderef desc: %s" desc)
        (format "[%s](#%s-%s)"
                desc
                (plist-get ref-info :anchor-prefix)
                (plist-get ref-info :line-num))))
     ((string= type "radio")
      (let ((destination (org-export-resolve-radio-link link info)))
        (format "[%s](#%s%s)"
                desc
                (org-blackfriday--get-ref-prefix 'radio)
                (org-blackfriday--valid-html-anchor-name
                 (org-element-property :value destination)))))
     (t ;[[file:foo.png]], [[file:foo.org::* Heading]], [[file:foo.org::#custom-id]], link type: file
      (let* ((link-param-str "")
             (path (cond
                    (link-is-url
                     ;; Taken from ox-html.el -- Extract attributes
                     ;; from parent's paragraph.  HACK: Only do this
                     ;; for the first link in parent (inner image link
                     ;; for inline images).  This is needed as long as
                     ;; attributes cannot be set on a per link basis.
                     (let* ((attr
                             (let ((parent (org-export-get-parent-element link)))
                               (and (eq (org-element-map parent 'link #'identity info :first-match) link)
                                    (org-export-read-attribute :attr_html parent))))
                            ;; https://www.w3schools.com/tags/tag_link.asp
                            (link-params `((title . ,(plist-get attr :title))
                                           (style . ,(plist-get attr :style))
                                           (referrerpolicy . ,(plist-get attr :referrerpolicy))
                                           (media . ,(plist-get attr :media))
                                           (target . ,(plist-get attr :target))
                                           (rel . ,(plist-get attr :rel))
                                           (sizes . ,(plist-get attr :sizes))
                                           (type . ,(plist-get attr :type)))))
                       (dolist (param link-params)
                         (let ((name (car param))
                               (val (cdr param)))
                           (when val
                             (setq link-param-str (concat link-param-str
                                                          (format "%s=\"%s\" "
                                                                  name val))))))
                       ;; (message "[org-hugo-link DBG] link params: %s" link-param-str)
                       )
                     (concat type ":" raw-path))
                    (;; Remove the "file://" prefix.
                     (string= type "file")
                     ;; (message "[org-hugo-link DBG] raw-path: %s" raw-path)
                     (let* ((path1 (replace-regexp-in-string "\\`file://" "" raw-path))
                            (path-lc (downcase path1)))
                       (cond
                        (;; foo.org, foo.org::* Heading, foo.org::#custom_id
                         (string= ".org" (file-name-extension path-lc "."))
                         (let ((ref "")
                               (anchor ""))
                           (if (string-suffix-p org-hugo--preprocessed-buffer-dummy-file-suffix path-lc)
                               (progn
                                 (setq ref (string-remove-suffix
                                            org-hugo--preprocessed-buffer-dummy-file-suffix
                                            (file-name-nondirectory path1)))
                                 ;; Dummy Org file paths created in
                                 ;; `org-hugo--get-pre-processed-buffer'
                                 ;; For dummy Org file paths, we are
                                 ;; limiting to only "#" style search
                                 ;; strings.
                                 (when (string-match ".*\\.org::\\(#.*\\)" raw-link)
                                   (setq anchor (match-string-no-properties 1 raw-link))))
                             ;; Regular Org file paths.
                             (setq ref (file-name-sans-extension (file-name-nondirectory path1)))
                             (let ((link-search-str
                                    ;; If raw-link is "./foo.org::#bar",
                                    ;; set `link-search-str' to
                                    ;; "#bar".
                                    (when (string-match ".*\\.org::\\(.*\\)" raw-link)
                                      (match-string-no-properties 1 raw-link))))
                               ;; (message "[org-hugo-link DBG] link-search-str: %s" link-search-str)
                               (when link-search-str
                                 (setq anchor (org-hugo--search-and-get-anchor raw-path link-search-str info)))))
                           ;; (message "[org-hugo-link file.org::*Heading DBG] ref    = %s" ref)
                           ;; (message "[org-hugo-link file.org::*Heading DBG] anchor = %s" anchor)
                           (cond
                            ;; Link to a post subtree.  In this case,
                            ;; the "anchor" is actually the post's
                            ;; slug.
                            ((and (org-string-nw-p anchor) (not (string-prefix-p "#" anchor)))
                             (format "{{< relref \"%s\" >}}" anchor))
                            ;; Link to a non-post subtree, like a subheading in a post.
                            ((or (org-string-nw-p ref) (org-string-nw-p anchor))
                             (format "{{< relref \"%s%s\" >}}" ref anchor))
                            (t
                             ""))))
                        (t ;; attachments like foo.png
                         (org-hugo--attachment-rewrite-maybe path1 info)))))
                    (t
                     raw-path)))
             (link-param-str (org-string-nw-p (org-trim link-param-str))))
        ;; (message "[org-hugo-link DBG] desc=%s path=%s" desc path)
        ;; (message "[org-hugo-link DBG] link-param-str=%s" link-param-str)
        (cond
         ;; Link description is a `figure' shortcode but does not
         ;; already have the `link' parameter set.
         ((and desc
               (string-match-p "\\`{{<\\s-*figure\\s-+" desc)
               (not (string-match-p "\\`{{<\\s-*figure\\s-+.*link=" desc)))
          (replace-regexp-in-string "\\s-*>}}\\'"
                                    (format " link=\"%s\"\\&" path)
                                    desc))
         ;; Both link description and link attributes are present.
         ((and desc
               link-param-str)
          (format "<a href=\"%s\" %s>%s</a>"
                  (org-html-encode-plain-text path)
                  link-param-str
                  (org-link-unescape desc)))
         ;; Only link description, but no link attributes.
         (desc
          (let* ((path-has-space (and
                                  (not (string-prefix-p "{{< relref " path))
                                  (string-match-p "\\s-" path)))
                 (path (if path-has-space
                           ;; https://github.com/kaushalmodi/ox-hugo/issues/376
                           ;; https://github.com/gohugoio/hugo/issues/6742#issuecomment-573924706
                           (format "<%s>" path)
                         path)))
            (format "[%s](%s)" desc path)))
         ;; Only link attributes, but no link description.
         (link-param-str
          (let ((path (org-html-encode-plain-text path)))
            (format "<a href=\"%s\" %s>%s</a>"
                    path
                    link-param-str
                    ;; Below trick is to prevent Hugo from
                    ;; auto-hyperlinking the link in the
                    ;; description. Idea from
                    ;; https://stackoverflow.com/q/25706012/1219634.
                    (replace-regexp-in-string ":" "&colon;" (org-link-unescape path)))))
         ;; Neither link description, nor link attributes.
         ((string-prefix-p "{{< relref " path)
          (format "[%s](%s)" path path))
         ((org-string-nw-p path)
          (format "<%s>" path))
         (t
          "")))))))

(defun org-hugo-link--heading-anchor-maybe (link info)
  "Return anchor of the heading pointed to by LINK.

INFO is a plist used as a communication channel."
  ;; (message "dbg link id: %S" (org-element-property :path link))
  (let* ((id-loc (org-id-find (org-element-property :path link)))
         (id-file (car id-loc))
         (id-pos (cdr id-loc))
         (id-buffer (get-file-buffer id-file))) ;nil if `id-file' buffer is not already open
    ;; (message "[org-hugo-link--heading-anchor-maybe DBG] id-loc: %S" id-loc)
    (with-current-buffer (or id-buffer (find-file-noselect id-file :nowarn))
      (unless id-buffer
        (add-to-list 'org-hugo--opened-buffers (current-buffer)))
      (org-export-get-environment)        ;Eval #+bind keywords, etc.
      (goto-char id-pos)
      (org-hugo--get-anchor-at-point info))))

;;;;; Helpers
(defun org-hugo--copy-resources-maybe (info)
  "Copy resources to the bundle directory if needed.

INFO is a plist used as a communication channel."
  (let* ((exportables org-hugo-external-file-extensions-allowed-for-copying)
         (bundle-dir (and (plist-get info :hugo-bundle)
                          (org-hugo--get-pub-dir info)))
         (resources (org-hugo--parse-property-arguments (plist-get info :hugo-resources))))
    (when (and bundle-dir resources)
      (dolist (resource resources)
        (let ((key (car resource)))
          (when (equal key 'src)
            (let* ((val (cdr resource))
                   (sources (file-expand-wildcards val)))
              (dolist (source sources)
                (let ((src-path (file-truename source)))
                  (when (and (file-exists-p src-path)
                             (member (file-name-extension src-path) exportables))
                    (let* ((dest-path (concat bundle-dir source))
                           (dest-path-dir (file-name-directory dest-path)))
                      (unless (file-exists-p dest-path-dir)
                        (mkdir dest-path-dir :parents))
                      (when (file-newer-than-file-p src-path dest-path)
                        (message "[ox-hugo] Copied resource %S to %S" src-path dest-path)
                        (copy-file src-path dest-path :ok-if-already-exists)))))))))))))

(defun org-hugo--copy-ltximg-maybe (info)
  "Copy `org-preview-latex-image-directory' contents into site's ltximg directory.

INFO is a plist used as a communication channel."
  (when (file-exists-p org-preview-latex-image-directory)
    (let* ((hugo-base-dir (file-name-as-directory (plist-get info :hugo-base-dir)))
           (static-ltximg-dir (file-truename
                               (file-name-as-directory
                                (expand-file-name
                                 org-blackfriday--ltximg-directory
                                 (expand-file-name "static" hugo-base-dir))))))
      (when (file-newer-than-file-p
             org-preview-latex-image-directory static-ltximg-dir)
        (copy-directory org-preview-latex-image-directory static-ltximg-dir
                        nil :parents :copy-contents)
        (message "[ox-hugo] Copied contents of %S into %S"
                 org-preview-latex-image-directory static-ltximg-dir)))))

(defun org-hugo--attachment-rewrite-maybe (path info)
  "Copy local images and pdfs to the static/bundle directory if needed.
Also update the link paths to match those.

PATH is the path to the image or any other attachment.

INFO is a plist used as a communication channel."
  ;; (message "[ox-hugo attachment DBG] The Hugo section is: %s" (plist-get info :hugo-section))
  ;; (message "[ox-hugo attachment DBG] The Hugo base dir is: %s" (plist-get info :hugo-base-dir))
  (let* ((pub-dir (org-hugo--get-pub-dir info)) ;This needs to happen first so that the check for HUGO_BASE_DIR happens.
         (hugo-base-dir (file-name-as-directory (plist-get info :hugo-base-dir)))
         (path-unhexified (url-unhex-string path))
         (path-true (file-truename path-unhexified))
         (exportables org-hugo-external-file-extensions-allowed-for-copying)
         (bundle-dir (and (plist-get info :hugo-bundle) pub-dir))
         (bundle-name (when bundle-dir
                        (let* ((content-dir (file-truename
                                             (file-name-as-directory
                                              (expand-file-name org-hugo-content-folder hugo-base-dir))))
                               (is-home-branch-bundle (string= bundle-dir content-dir)))
                          (cond
                           (is-home-branch-bundle
                            "_home")
                           (t ;`bundle-dir'="/foo/bar/" -> `bundle-name'="bar"
                            (file-name-base (directory-file-name bundle-dir)))))))
         (static-dir (file-truename
                      (file-name-as-directory
                       (expand-file-name "static" hugo-base-dir))))
         (dest-dir (or bundle-dir static-dir))
         ret)
    (unless (file-directory-p static-dir)
      (user-error "Please create the %s directory" static-dir))
    ;; (message "[ox-hugo DBG attch rewrite] Image export dir is: %s" static-dir)
    ;; (message "[ox-hugo DBG attch rewrite] path: %s" path)
    ;; (message "[ox-hugo DBG attch rewrite] path-true: %s" path-true)
    ;; (message "[ox-hugo DBG attch rewrite] bundle-dir: %s" bundle-dir)
    ;; (message "[ox-hugo DBG attch rewrite] bundle-name: %s" bundle-name)
    ;; (message "[ox-hugo DBG attch rewrite] default-dir: %s" default-directory)
    ;; (message "[ox-hugo DBG attch rewrite] dest-dir: %s" dest-dir)
    (if (and (file-exists-p path-true)
             (member (file-name-extension path-unhexified) exportables)
             (file-directory-p dest-dir))
        (progn
          ;; Check if `path-true' is already inside `dest-dir'.
          (if (string-match (regexp-quote dest-dir) path-true)
              (progn
                ;; If so, return *only* the path considering the
                ;; destination directory as root.
                (setq ret (concat "/" (substring path-true (match-end 0)))))
            (let* ((file-name-relative-path
                    (cond
                     ((string-match "/static/" path-true)
                      ;; `path-true' is "/foo/static/bar/baz.png",
                      ;; return "bar/baz.png".
                      ;; (message "[ox-hugo DBG attch rewrite] path contains static")
                      ;; If path-true contains "/static/", set the
                      ;; `dest-dir' to `static-dir' (even if this is a
                      ;; page bundle).
                      (setq dest-dir static-dir)
                      (substring path-true (match-end 0)))
                     (bundle-dir
                      (cond
                       ((string-match (concat "/" (regexp-quote bundle-name) "/") path-true)
                        ;; This is a page bundle.  `bundle-name' is
                        ;; "<BUNDLE_NAME>", `path-true' is
                        ;; "<ORG_FILE_DIR>/bar/<BUNDLE_NAME>/zoo/baz.png",
                        ;; return "zoo/baz.png".
                        ;; (message "[ox-hugo DBG attch rewrite BUNDLE 1] bundle-name: %s" bundle-name)
                        ;; (message "[ox-hugo DBG attch rewrite BUNDLE 1] attch along with Org content: %s"
                        ;;          (substring path-true (match-end 0)))
                        (substring path-true (match-end 0)))
                       ((string-match (regexp-quote default-directory) path-true)
                        ;; This is a page bundle.  `default-path' is
                        ;; "<ORG_FILE_DIR>/", `path-true' is
                        ;; "<ORG_FILE_DIR>/bar/baz.png", return
                        ;; "bar/baz.png".
                        ;; (message "[ox-hugo DBG attch rewrite BUNDLE 2] attch along with Org content: %s"
                        ;;          (substring path-true (match-end 0)))
                        (substring path-true (match-end 0)))
                       (t
                        ;; This is a page bundle.  `default-path' is
                        ;; "<ORG_FILE_DIR>/", `path-true' is
                        ;; "/foo/bar/baz.png", return "baz.png".
                        ;; (message "[ox-hugo DBG attch rewrite BUNDLE 3] attch neither in static nor in Org file dir")
                        (file-name-nondirectory path-unhexified))))
                     (t
                      ;; Else, `path-true' is "/foo/bar/baz.png",
                      ;; return "ox-hugo/baz.png".  "ox-hugo" is the
                      ;; default value of
                      ;; `org-hugo-default-static-subdirectory-for-externals'.
                      ;; (message "[ox-hugo DBG attch rewrite] neither BUNDLE nor contains static")
                      (concat
                       (file-name-as-directory org-hugo-default-static-subdirectory-for-externals)
                       (file-name-nondirectory path-unhexified)))))
                   (dest-path (concat dest-dir file-name-relative-path))
                   (dest-path-dir (file-name-directory dest-path)))
              ;; The `dest-dir' would already exist.  But if
              ;; `file-name-relative-path' is "images/image.png" or
              ;; "foo/bar.txt", it's likely that "`dest-dir'/images"
              ;; or "`dest-dir'/foo" might not exist.  So create those
              ;; if needed below.
              (unless (file-exists-p dest-path-dir)
                (mkdir dest-path-dir :parents))
              ;; (message "[ox-hugo DBG attch rewrite] file-name-relative-path: %s" file-name-relative-path)
              ;; (message "[ox-hugo DBG attch rewrite] dest-path: %s" dest-path)
              ;; (message "[ox-hugo DBG attch rewrite] dest-path-dir: %s" dest-path-dir)

              ;; Do the copy only if the file to be copied is newer or
              ;; doesn't exist in the static dir.
              (when (file-newer-than-file-p path-true dest-path)
                (message "[ox-hugo] Copied %S to %S" path-true dest-path)
                (copy-file path-true dest-path :ok-if-already-exists))
              (setq ret (if (and bundle-dir
                                 (string= bundle-dir dest-dir))
                            ;; If attachments are copied to the bundle
                            ;; directory, don't prefix the path as "/"
                            ;; as those paths won't exist at the site
                            ;; base URL.
                            file-name-relative-path
                          (concat "/" file-name-relative-path))))))
      (setq ret path))
    ;; (message "[ox-hugo DBG attch rewrite] returned path: %s" ret)
    ret))

;;;; Paragraph
(defun org-hugo-paragraph--process-content (paragraph contents info)
  "Process the content of paragraphs.

- Prevent unwanted spaces when joining Chinese/Japanese lines.
- Join all lines in a paragraph into a single line if
  `:hugo-preserve-filling' plist property is nil.
- Add \"&nbsp;\" HTML entity before footnote anchors so that the
  anchors won't be on a separate line by themselves.

Return the processed CONTENTS string from the PARAGRAPH element.
INFO is a plist used as a communication channel."
  (let ((ret contents))
    ;; Join consecutive Chinese, Japanese lines into a single long
    ;; line without unwanted space inbetween.
    (when (org-hugo--lang-cjk-p info)
      ;; https://emacs-china.org/t/ox-hugo-auto-fill-mode-markdown/9547/5
      ;; Example: 这是一个测试     -> 这是一个测试文本 ("This is a test text")
      ;;          文本
      (setq ret (replace-regexp-in-string
                 "\\([[:multibyte:]]\\)[[:blank:]]*\n[[:blank:]]*\\([[:multibyte:]]\\)" "\\1\\2"
                 ret))
      ;; (message "[org-hugo-paragraph--process-content DBG] contents 1: %s" contents)
      )

    ;; Join all content into a single line (followed by a newline)
    ;; if :hugo-preserve-filling is nil.
    (unless (org-hugo--plist-get-true-p info :hugo-preserve-filling)
      (setq ret (concat (mapconcat 'identity (split-string ret) " ") "\n")))

    ;; Special processing for footnotes.
    (setq ret (replace-regexp-in-string
               ;; Glue footnotes to the words before them using &nbsp;
               ;; so that the footnote reference does not end up on a
               ;; new line by itself.
               ;; "something FN" -> "something&nbsp;FN"
               "[[:blank:]]+\\(\\[\\^[^]]+\\]\\)" "&nbsp;\\1"
               (replace-regexp-in-string
                ;; "FN ." -> "FN."
                "\\(\\[\\^[^]]+\\]\\)[[:blank:]]*\\([.]+\\)" "\\1\\2"
                ret)))

    ;; Escape any lines starting with `#' which is the markup for
    ;; headings in Markdown.
    (setq ret (org-md-paragraph paragraph ret info))

    ;; (message "[org-hugo-paragraph--process-content DBG] contents 2: %s" contents)
    ret))

(defun org-hugo-paragraph (paragraph contents info)
  "Transcode PARAGRAPH element into Hugo Markdown format.
CONTENTS is the paragraph contents.  INFO is a plist used as a
communication channel."
  (let* ((parent (org-export-get-parent paragraph))
         (parent-type (org-element-type parent)))

    ;; (message "[ox-hugo-para DBG] standalone image? %s\ncontents: %s"
    ;;          (org-html-standalone-image-p paragraph info)
    ;;          contents)

    (cond
     ;; First paragraph in an item has no tag if it is alone or
     ;; followed, at most, by a sub-list. (Below condition is taken
     ;; as-is from `org-html-paragraph').
     ((and (eq parent-type 'item)
           (not (org-export-get-previous-element paragraph info))
           (let ((followers (org-export-get-next-element paragraph info 2)))
             (and (not (cdr followers))
                  (memq (org-element-type (car followers)) '(nil plain-list)))))
      (org-hugo-paragraph--process-content paragraph contents info))

     ;; Standalone image.
     ((org-html-standalone-image-p paragraph info)
      (let ((figure-ref (org-blackfriday--get-reference paragraph))
            label)
        (when (org-string-nw-p figure-ref)
          (setq figure-ref (replace-regexp-in-string
                            "\\`org-paragraph--"
                            (org-blackfriday--get-ref-prefix 'figure)
                            figure-ref)))
        (setq label (if figure-ref
                        (format "<a id=\"%s\"></a>\n\n" figure-ref)
                      ""))
        (concat label contents)))

     ;; Normal paragraph.
     (t
      (let ((label (let ((paragraph-ref (and (org-element-property :name paragraph)
                                             (org-export-get-reference paragraph info))))
                     (if paragraph-ref
                         (format "<a id=\"%s\"></a>\n\n" paragraph-ref)
                       ""))))

        ;; Wrap the paragraph with HTML div tag with user-specified
        ;; attributes.
        (org-blackfriday--div-wrap-maybe
         paragraph
         (concat label
                 (org-hugo-paragraph--process-content paragraph contents info))
         info))))))

;;;; Source Blocks
(defun org-hugo-src-block (src-block _contents info)
  "Convert SRC-BLOCK element to Hugo-compatible Markdown.

The Markdown style triple-backquoted code blocks are created if:
  - The HUGO_CODE_FENCE property is set to a non-nil value
    (default),
  - *AND* the Hugo \"highlight\" shortcode is not needed (see
    below).

Hugo v0.60.0 onwards, the `markup.highlight.codeFences' (new name
for the old `pygmentsCodeFences') config variable defaults to
true.  See the \"Highlighting in Code Fences\" section on
https://gohugo.io/content-management/syntax-highlighting.
Attributes like highlighting code, \"linenos\", etc. are now
supported with code fences too.

CONTENTS is nil.  INFO is a plist used as a communication
channel.

--- When is the \"highlight\" shortcode needed? ---

It's needed only in Blackfriday mode (`org-hugo-goldmark' is
nil), and if any of these is true:
  - Code blocks with line numbers (if the -n or +n switch is used).
  - Highlight certains lines in the code block (if the :hl_lines
    parameter is used).
  - Set the `linenos' argument to the value passed by :linenos
    (defaults to `true').
  - Coderefs are used.

Note: If using a Hugo version older than v0.60.0, the user
*needs* to set the `pygmentsCodeFences' variable to `true' in
their Hugo site's config."
  (let* ((lang (org-element-property :language src-block))
         (parameters-str (org-element-property :parameters src-block))
         (parameters (org-babel-parse-header-arguments parameters-str))
         (is-fm-extra (cdr (assoc :front_matter_extra parameters))))
    ;; (message "ox-hugo src [dbg] lang: %S" lang)
    ;; (message "ox-hugo src [dbg] parameters: %S" parameters)
    ;; (message "ox-hugo src [dbg] is-fm-extra: %S" is-fm-extra)

    ;; Extra front matter.
    (cond
     ((and is-fm-extra
           (member lang '("toml" "yaml")))
      (let ((fm-format (plist-get info :hugo-front-matter-format)))
        ;; The fm-extra src block lang and user-set fm-format have to
        ;; be the same.  Else. that src block is completely discarded.
        (when (string= lang fm-format)
          (let ((fm-extra (org-export-format-code-default src-block info)))
            ;; (message "ox-hugo src [dbg] fm-extra: %S" fm-extra)
            (plist-put info :fm-extra fm-extra)))
        ;; Do not export the `:front_matter_extra' TOML/YAML source
        ;; blocks in Markdown body.
        nil))

     ;; Regular src block.
     (t
      (let* (;; See `org-element-src-block-parser' for all SRC-BLOCK properties.
             (line-num-p (org-element-property :number-lines src-block)) ;Non-nil if -n or +n switch is used
             (linenos-style (or (cdr (assoc :linenos parameters))
                                ;; If `org-hugo-src-block' is called from
                                ;; `org-hugo-example-block'.
                                (org-element-property :linenos-style src-block)))
             ;; Convert `hl-lines' to string.  If it's not a number,
             ;; it's already a string, or nil.
             (hl-lines (let* ((hl-lines-param (cdr (assoc :hl_lines parameters))))
                         ;; (message "ox-hugo src [dbg] hl-lines-param: %S" hl-lines-param)
                         (if (numberp hl-lines-param)
                             (number-to-string hl-lines-param)
                           hl-lines-param)))
             (syntax-style (cdr (assoc :syntax-style parameters)))
             (code-refs-and-anchor (org-hugo--get-coderef-anchor-prefix src-block))
             (code-refs (let ((code-refs1 (car code-refs-and-anchor)))
                          (when code-refs1
                            (setq line-num-p t))
                          code-refs1))
             (goldmarkp (org-hugo--plist-get-true-p info :hugo-goldmark))
             ;; Use the `highlight' shortcode only if ..
             (use-highlight-sc (or ;; HUGO_CODE_FENCE is nil, or ..
                                (null (org-hugo--plist-get-true-p info :hugo-code-fence))
                                ;; "Blackfriday mode" is enabled and line numbering
                                ;; , highlighting or code refs are needed.
                                (and (or line-num-p hl-lines linenos-style code-refs)
                                     (not goldmarkp))))
             (hl-lines (when (stringp hl-lines)
                         (if use-highlight-sc
                             (progn
                               ;; Syntax of hl_lines in `highlight' shortcode:
                               ;;   {{< highlight emacs-lisp "hl_lines=1 3-5" >}} ..
                               (replace-regexp-in-string "," " " hl-lines)) ;"1,3-5" -> "1 3-5"
                           ;; Fenced code blocks
                           ;; Syntax of hl_lines in fenced code attributes:
                           ;;   ```emacs-lisp { hl_lines=["1","3-5"] } ..
                           (format "[%s]"
                                   (mapconcat
                                    (lambda(el) (format "%S" el))
                                    (split-string hl-lines ",") ","))))) ;"1,3-5" -> "[\"1\",\"3-5\"]"
             (src-ref (org-blackfriday--get-reference src-block))
             (src-anchor (if src-ref
                             (format "<a id=\"%s\"></a>\n" src-ref)
                           ""))
             (caption (org-export-get-caption src-block))
             (caption-html (if (not caption)
                               ""
                             (let* ((src-block-num (org-export-get-ordinal
                                                    src-block info
                                                    nil #'org-html--has-caption-p))
                                    (caption-prefix (org-blackfriday--translate 'src-block info))
                                    (caption-str
                                     (org-html-convert-special-strings ;Interpret em-dash, en-dash, etc.
                                      (org-export-data-with-backend caption 'html info))))
                               (format (concat "\n<div class=\"src-block-caption\">\n"
                                               "  <span class=\"src-block-number\">%s:</span>\n"
                                               "  %s\n"
                                               "</div>")
                                       (if src-ref ;Hyperlink the code snippet prefix + number
                                           (format "<a href=\"#%s\">%s %s</a>"
                                                   src-ref caption-prefix src-block-num)
                                         (format "%s %s"
                                                 caption-prefix src-block-num))
                                       caption-str))))
             (src-code (org-hugo--escape-hugo-shortcode
                        (org-export-format-code-default src-block info)
                        lang))
             (code-attr-str "")
             src-code-wrap
             ret)
        ;; (message "ox-hugo src [dbg] line-num-p: %S" line-num-p)
        ;; (message "ox-hugo src [dbg] parameters: %S" parameters)
        ;; (message "ox-hugo src [dbg] code refs: %S" code-refs)
        ;; (message "ox-hugo src [dbg] code-attr-str: %S" code-attr-str)

        (when (and goldmarkp (not use-highlight-sc))
          (let ((html-attr (org-export-read-attribute :attr_html src-block)))
            (setq code-attr-str (org-html--make-attribute-string html-attr))))

        (when (or linenos-style line-num-p)
          ;; Set "linenos" to "true" if linenos-style is nil.
          (setq linenos-style (or linenos-style "true"))
          (if (org-string-nw-p code-attr-str)
              (setq code-attr-str (format "%s, linenos=%s" code-attr-str linenos-style))
            (setq code-attr-str (format "linenos=%s" linenos-style)))
          (let ((linenostart-str (and ;Extract the start line number of the src block
                                  (string-match "\\`\\s-*\\([0-9]+\\)\\s-\\{2\\}" src-code)
                                  (match-string-no-properties 1 src-code))))
            (when linenostart-str
              (setq code-attr-str (format "%s, linenostart=%s" code-attr-str linenostart-str))))

          (when line-num-p
            ;; Remove Org-inserted numbers from the beginning of each line
            ;; as the Hugo highlight shortcode will be used instead of
            ;; literally inserting the line numbers.
            (setq src-code (replace-regexp-in-string "^\\s-*[0-9]+\\s-\\{2\\}" "" src-code))))

        ;; (message "ox-hugo src [dbg] hl-lines: %S" hl-lines)
        (when hl-lines
          (if (org-string-nw-p code-attr-str)
              (setq code-attr-str (format "%s, hl_lines=%s" code-attr-str hl-lines))
            (setq code-attr-str (format "hl_lines=%s" hl-lines))))

        (when syntax-style
          (if (org-string-nw-p code-attr-str)
              (setq code-attr-str (format "%s, style=%s" code-attr-str syntax-style))
            (setq code-attr-str (format "style=%s" syntax-style))))

        (when code-refs
          (let* ((anchor-prefix (cdr code-refs-and-anchor))
                 (anchor-str (format "anchorlinenos=true, lineanchors=%s" anchor-prefix)))
            (org-element-put-property src-block :anchor-prefix anchor-prefix)
            (setq code-attr-str (format "%s, %s" code-attr-str anchor-str))))

        (unless use-highlight-sc
          (plist-put info :md-code src-code)
          (plist-put info :md-code-attr (org-string-nw-p code-attr-str)))

        (setq src-code-wrap
              (if use-highlight-sc
                  (let ((hl-attr (if (org-string-nw-p code-attr-str)
                                     (format " \"%s\"" code-attr-str)
                                   "")))
                    (format "{{< highlight %s%s >}}\n%s{{< /highlight >}}\n"
                            lang hl-attr src-code))
                (org-blackfriday-src-block src-block nil info)))

        (if (and goldmarkp (not use-highlight-sc))
            (setq ret (concat (org-blackfriday--get-style-str src-block)
                              src-anchor src-code-wrap caption-html))
          (setq ret (org-blackfriday--div-wrap-maybe
                     src-block
                     (concat src-anchor src-code-wrap caption-html)
                     info)))
        ret)))))

;;;; Special Block
(defun org-hugo-special-block (special-block contents info)
  "Transcode a SPECIAL-BLOCK element from Org to Hugo-compatible Markdown.
CONTENTS holds the contents of the block.

If the special block is of type \"description\", the value of
`:description' key of the INFO plist gets overwritten by the
contents of that block.

Else if the special block is of type \"details\", an HTML
`<details>' element with an optional `<summary>' element is
created.  The \"summary\" portion if present comes first, and is
separated from the following \"details\" portion using a solo
\"---\" string on a newline.  See
https://ox-hugo.scripter.co/doc/details-and-summary/ for more.

Else if the SPECIAL-BLOCK type matches one of the shortcodes set
in HUGO_PAIRED_SHORTCODES property, export them as Markdown or
non-Markdown shortcodes.  See `org-hugo-paired-shortcodes' for
more information.

For all other special blocks, processing is passed on to
`org-blackfriday-special-block'.

If a block type has the `:trim-pre' property set to t in
`org-hugo-special-block-type-properties' or in the `#+header'
keyword above the special block, whitespace exported before that
block is trimmed.  Similarly, if `:trim-post' property is set to
t, whitespace after that block is trimmed.

INFO is a plist holding export options."
  (let* ((block-type (org-element-property :type special-block))
         (block-type-plist (cdr (assoc block-type org-hugo-special-block-type-properties)))
         (header (org-babel-parse-header-arguments
                  (car (org-element-property :header special-block))))
         (trim-pre (or (alist-get :trim-pre header) ;`:trim-pre' in #+header has higher precedence.
                       (plist-get block-type-plist :trim-pre)))
         (trim-pre (org-hugo--value-get-true-p trim-pre)) ;If "nil", converts to nil
         (trim-pre-tag (if trim-pre org-hugo--trim-pre-marker ""))
         (last-element-p (null (org-export-get-next-element special-block info)))
         (trim-post (unless last-element-p ;No need to add trim-post markers if this is the last element.
                      (or (alist-get :trim-post header) ;`:trim-post' in #+header has higher precedence.
                          (plist-get block-type-plist :trim-pre))))
         (trim-post (org-hugo--value-get-true-p trim-post)) ;If "nil", converts to nil
         (trim-post-tag (if trim-post org-hugo--trim-post-marker ""))
         (paired-shortcodes (let* ((str (plist-get info :hugo-paired-shortcodes))
                                   (str-list (when (org-string-nw-p str)
                                               (split-string str " "))))
                              str-list))
         (sc-regexp "\\`%%?%s\\'") ;Regexp to match an element from `paired-shortcodes'
         (html-attr (org-export-read-attribute :attr_html special-block))
         (caption (plist-get html-attr :caption))
         (contents (when (stringp contents)
                     (org-trim
                      (if (plist-get block-type-plist :raw)
                          ;; https://lists.gnu.org/r/emacs-orgmode/2022-01/msg00132.html
                          (org-element-interpret-data (org-element-contents special-block))
                        contents)))))
    ;; (message "[ox-hugo-spl-blk DBG] block-type: %s" block-type)
    ;; (message "[ox-hugo-spl-blk DBG] last element?: %s" (null (org-export-get-next-element special-block info)))
    ;; (message "[ox-hugo-spl-blk DBG] %s: header: %s" block-type header)
    ;; (message "[ox-hugo-spl-blk DBG] %s: trim-pre (type = %S): %S" block-type (type-of trim-pre) trim-pre)
    ;; (message "[ox-hugo-spl-blk DBG] %s: trim-post (type = %S): %S" block-type (type-of trim-post) trim-post)
    (plist-put info :type-plist block-type-plist)
    (plist-put info :trim-pre-tag trim-pre-tag)
    (plist-put info :trim-post-tag trim-post-tag)
    (when contents
      (cond
       ((string= block-type "tikzjax")
        (setq contents (format "%s%s%s"
                               "<script type=\"text/tikz\">\n  \\begin{tikzpicture}\n"
                               contents
                               "\n\\end{tikzpicture}\n</script>"))
        (when (org-string-nw-p caption)
          (setq contents (format "%s%s%s"
                                 "<figure>\n"
                                 contents
                                 (format "\n<figcaption>%s</figcaption>\n</figure>"
                                         caption))))
        contents)
       ((string= block-type "description")
        ;; Overwrite the value of the `:description' key in `info'.
        (plist-put info :description (org-hugo--escape-hugo-shortcode contents "md"))
        nil)
       ;; https://emacs.stackexchange.com/a/28685/115
       ((cl-member block-type paired-shortcodes
                   ;; If `block-type' is "foo", check if any of the
                   ;; elements in `paired-shortcodes' is "foo" or
                   ;; "%foo".
                   :test (lambda (b sc) ;`sc' would be an element from `paired-shortcodes'
                           (string-match-p (format sc-regexp b) sc)))
        (let* ((attr-sc (org-export-read-attribute :attr_shortcode special-block))
               ;; Positional arguments.
               (pos-args (and (null attr-sc)
                              ;; If the shortcode attributes are not of
                              ;; the type ":foo bar" but are something
                              ;; like "foo bar".
                              (let* ((raw-list (org-element-property :attr_shortcode special-block))
                                     (raw-str (mapconcat #'identity raw-list " ")))
                                (org-string-nw-p raw-str))))
               ;; Named arguments.
               (named-args (unless pos-args
                             (org-string-nw-p (org-html--make-attribute-string attr-sc))))
               (sc-args (or pos-args named-args))
               (sc-args (if sc-args
                            (concat " " sc-args " ")
                          " "))
               (matched-sc-str (car
                                (cl-member block-type paired-shortcodes
                                           :test (lambda (b sc) ;`sc' would be an element from `paired-shortcodes'
                                                   (string-match-p (format sc-regexp b) sc)))))
               (sc-open-char (if (string-prefix-p "%" matched-sc-str)
                                 "%"
                               "<"))
               (sc-close-char (if (string-prefix-p "%" matched-sc-str)
                                  "%"
                                ">"))
               (sc-begin (format "%s{{%s %s%s%s}}"
                                 trim-pre-tag sc-open-char block-type sc-args sc-close-char))
               (sc-end (format "{{%s /%s %s}}%s"
                               sc-open-char block-type sc-close-char trim-post-tag)))
          ;; (message "[ox-hugo-spl-blk DBG] attr-sc1: %s"
          ;;          (org-element-property :attr_shortcode special-block))
          ;; (message "[ox-hugo-spl-blk DBG] attr-sc: %s" attr-sc)
          ;; (message "[ox-hugo-spl-blk DBG] pos-args: %s" pos-args)
          ;; (message "[ox-hugo-spl-blk DBG] named-args: %s" named-args)
          (format "%s\n%s\n%s"
                  sc-begin contents sc-end)))
       (t
        (org-blackfriday-special-block special-block contents info))))))



;;; Filter Functions

;;;; Body Filter
(defun org-hugo-body-filter (body _backend info)
  "Add front-matter to the BODY of the document.

BODY is the result of the export.
INFO is a plist holding export options."
  ;; Copy the page resources to the bundle directory.
  (org-hugo--copy-resources-maybe info)
  (org-hugo--copy-ltximg-maybe info)
  ;; (message "[ox-hugo body filter] ITEM %S" (org-entry-get (point) "ITEM"))
  ;; (message "[ox-hugo body filter] TAGS: %S" (org-entry-get (point) "TAGS"))
  ;; (message "[ox-hugo body filter] ALLTAGS: %S" (org-entry-get (point) "ALLTAGS"))

  (when (and (org-hugo--plist-get-true-p info :hugo-delete-trailing-ws)
             (not (org-hugo--plist-get-true-p info :preserve-breaks)))
    (setq body (with-temp-buffer
                 (insert body)
                 (delete-trailing-whitespace (point-min) nil)
                 (buffer-substring-no-properties (point-min) (point-max)))))
  (let ((fm (save-excursion
              (save-restriction
                ;; The point is at the beginning of the heading body
                ;; in this function! So move the point back by 1 char
                ;; to bring it into the Org heading before calling
                ;; `org-hugo--get-front-matter', because in there we
                ;; use `org-entry-get' at (point) to retrieve certain
                ;; property values.
                (widen)
                (ignore-errors ;If the point is at beginning of buffer even after widening
                  (backward-char))
                ;; (message "[body filter DBG] line at pt: %s" (thing-at-point 'line))
                (org-hugo--get-front-matter info))))
        (fm-extra (plist-get info :fm-extra))
        (body (if (org-string-nw-p body) ;Insert extra newline if body is non-empty
                  (format "\n%s" body)
                "")))
    ;; (message "[body filter DBG fm] %S" fm)
    ;; (message "[body filter DBG fm-extra] %S" fm-extra)
    (when fm-extra
      ;; If fm-extra is present, append it to the end of the
      ;; front-matter, before the closing "+++" or "---" marker.
      (setq fm (replace-regexp-in-string "\\(\\+\\+\\+\\|---\\)\n*\\'"
                                         (concat fm-extra "\\&")
                                         fm)))
    (setq org-hugo--fm fm)
    (if (org-hugo--pandoc-citations-enabled-p info)
        (format "%s%s%s" org-hugo--fm-yaml body org-hugo-footer)
      (format "%s%s%s" fm body org-hugo-footer))))

;;;;; Hugo Front-Matter
(defun org-hugo--parse-property-arguments (str)
  "Return an alist converted from a string STR of Hugo property value.

STR is of type \":KEY1 VALUE1 :KEY2 VALUE2 ..\".  Given that, the
returned value is ((KEY1 . VALUE1) (KEY2 . VALUE2) ..).

Example: Input STR \":foo bar :baz 1 :zoo \\\"two words\\\"\" would
convert to ((foo . \"bar\") (baz . 1) (zoo . \"two words\"))."
  (let ((alist (org-babel-parse-header-arguments str)))
    (dolist (pair alist)
      ;; :KEY -> KEY
      (let ((key (intern (replace-regexp-in-string "\\`:" "" (symbol-name (car pair))))))
        (setcar pair key)))
    alist))

(defun org-hugo--front-matter-value-booleanize (str)
  "Return a \"true\" or \"false\" string for input STR."
  (let ((str-lower (and (stringp str)
                        (downcase str))))
    (cond
     ((or (null str)
          (string= "nil" str-lower)
          (string= "false" str-lower)
          (string= "no" str-lower))
      "false")
     ((or (string= "t" str)
          (string= "true" str)
          (string= "yes" str))
      "true")
     (t
      (user-error "%S needs to represent a boolean value" str)))))

(defun org-hugo--parse-menu-prop-to-alist (info)
  "Return an alist of valid Hugo menu properties derived from INFO.

INFO is a plist used as a communication channel."
  (let* ((fm-format (plist-get info :hugo-front-matter-format))
         (menu-alist (org-hugo--parse-property-arguments (plist-get info :hugo-menu)))
         (menu-ov-alist (org-hugo--parse-property-arguments (plist-get info :hugo-menu-override)))
         (menu-props '(name url identifier pre post weight parent title))
         valid-menu-alist)
    ;; (message "[org-hugo--parse-menu-prop-to-alist DBG] menu str: %S, alist: %S" str menu-alist)
    ;; Hugo menu properties: https://gohugo.io/content-management/menus/
    ;; "title" property for menus was introduced in Hugo v0.32.
    ;; https://github.com/gohugoio/hugo/commit/9df3736fec164c51d819797416dc263f2869be77
    (cond
     ((string= fm-format "toml")
      (when (assoc 'menu menu-alist)
        (setq valid-menu-alist (list (cdr (assoc 'menu menu-alist))))
        (let (menu-params)
          (dolist (prop menu-props)
            (let ((cell (or (assoc prop menu-ov-alist)
                            (assoc prop menu-alist))))
              (when cell
                (push cell menu-params))))
          ;; Auto-set menu identifier if not already set by user.
          (unless (assoc 'identifier menu-params)
            (let ((id (org-hugo-slug (org-hugo--get-sanitized-title info))))
              (push `(identifier . ,id) menu-params)))
          ;; Auto-set menu weight if not already set by user.
          (unless (assoc 'weight menu-params)
            (when org-hugo--subtree-coord
              (push `(weight . ,(org-hugo--calc-weight)) menu-params)))
          (setcdr valid-menu-alist menu-params))
        (setq valid-menu-alist (list valid-menu-alist))))
     ((string= fm-format "yaml")
      (push 'menu menu-props)
      (dolist (prop menu-props)
        (let ((cell (or (assoc prop menu-ov-alist)
                        (assoc prop menu-alist))))
          (when cell
            (push cell valid-menu-alist))))))
    valid-menu-alist))

(defun org-hugo--get-sanitized-title (info)
  "Return sanitized version of an Org heading TITLE as a string.

INFO is a plist used as a communication channel.

Extract the document title from INFO (unless exporting title is
disabled by setting `org-export-with-title' to nil or using the
OPTIONS keyword e.g. \"title:nil\").

If the extracted document title is nil, and exporting the title
is disabled, return nil.

If the extracted document title is non-nil, return it after
removing all markup characters.

Also double-quote the title if it doesn't already contain any
double-quotes."
  (let ((title (when (plist-get info :with-title)
                 (plist-get info :title))))
    (when title
      ;; "Raw" backend that returns emphasis elements without any
      ;; markup characters --
      ;; http://lists.gnu.org/r/emacs-orgmode/2017-12/msg00490.html
      (let* ((raw-backend

              (let ((get-raw (lambda (object contents _)
                               (or contents
                                   (org-element-property :value object)))))
                (org-export-create-backend
                 :parent 'ascii
                 :transcoders (mapcar (lambda (type)
                                        (cons type get-raw))
                                      '(bold code italic strike-through
                                             underline verbatim))))))
        (setq title (org-export-data-with-backend title raw-backend info))
        ;; Hugo does not render Markdown in the titles.  So do that
        ;; here instead.  Convert "---" to EM DASH, "--" to EN DASH,
        ;; and "..." to HORIZONTAL ELLIPSIS.

        ;; Below two replacements are order sensitive!
        (setq title (replace-regexp-in-string "---\\([^-]\\)" "—\\1" title)) ;EM DASH
        (setq title (replace-regexp-in-string "--\\([^-]\\)" "–\\1" title)) ;EN DASH

        (setq title (replace-regexp-in-string "\\.\\.\\." "…" title)))) ;HORIZONTAL ELLIPSIS
    title))

(defun org-hugo--replace-underscores-with-spaces (str)
  "Replace double underscores in STR with single spaces.

For example, \"some__thing\" would get converted to \"some
thing\"."
  ;; It is safe to assume that no one would want leading/trailing
  ;; spaces in `str'.. so not checking for "__a" or "a__" cases.
  (let ((ret str)
        (rgx "\\([^_]\\)__\\([^_]\\)"))
    (while (string-match-p rgx ret)
      (setq ret (replace-regexp-in-string rgx "\\1 \\2" ret))) ;"a__b"  -> "a b"
    ret))

(defun org-hugo--tag-processing-fn-replace-with-spaces-maybe (tag-list info)
  "Replace double underscores in TAG-LIST elements with single spaces.

For example, an element \"some__tag\" would get converted to
\"some tag\".

This replacement is enabled if `org-hugo-allow-spaces-in-tags' or
HUGO_ALLOW_SPACES_IN_TAGS property is set to a non-nil value.

TAG-LIST which is a list of Org tags of the type \(\"TAG1\"
\"TAG2\" ..).  INFO is a plist used as a communication channel.

This is one of the processing functions in
`org-hugo-tag-processing-functions'."
  (let ((allow-spaces (org-hugo--plist-get-true-p info :hugo-allow-spaces-in-tags)))
    (if allow-spaces
        (mapcar #'org-hugo--replace-underscores-with-spaces tag-list)
      tag-list)))

(defun org-hugo--tag-processing-fn-replace-with-hyphens-maybe (tag-list info)
  "Replace single underscores in TAG-LIST elements with single hyphens.
And triple underscores will be replaced with single underscores.

For example, an element \"some_tag\" would get converted to
\"some-tag\", and \"some___tag\" to \"some_tag\".

This replacement is enabled if `org-hugo-prefer-hyphen-in-tags'
or HUGO_PREFER_HYPHEN_IN_TAGS property is set to a non-nil value.

TAG-LIST which is a list of Org tags of the type \(\"TAG1\"
\"TAG2\" ..).  INFO is a plist used as a communication channel.

This is one of the processing functions in
`org-hugo-tag-processing-functions'."
  (let ((prefer-hyphens (org-hugo--plist-get-true-p info :hugo-prefer-hyphen-in-tags)))
    (if prefer-hyphens
        (mapcar
         (lambda (tag)
           (setq tag (replace-regexp-in-string "\\`_\\([^_]\\)" "-\\1" tag))         ;"_a"    -> "-a"
           (setq tag (replace-regexp-in-string "\\`___\\([^_]\\)" "_\\1" tag))       ;"___a"  -> "_a"
           (setq tag (replace-regexp-in-string "\\([^_]\\)_\\'" "\\1-" tag))         ;"a_"    -> "a-"
           (setq tag (replace-regexp-in-string "\\([^_]\\)___\\'" "\\1_" tag))       ;"a___"  -> "a_"
           (setq tag (replace-regexp-in-string "\\([^_]\\)_\\([^_]\\)" "\\1-\\2" tag))   ;"a_b"   -> "a-b"
           (setq tag (replace-regexp-in-string "\\([^_]\\)___\\([^_]\\)" "\\1_\\2" tag)) ;"a___b" -> "a_b"
           tag)
         tag-list)
      tag-list)))

(defun org-hugo--delim-str-to-list (str)
  "Function to transform string STR to a list of strings.

The function assumes STR to use
`org-hugo--internal-list-separator' as delimiter.

The function does the following in order:

1. Trim leading/trailing spaces from STR.
2. Convert that string to a list using
   `org-hugo--internal-list-separator' as the separator.
3. Break up each element of that list into further string elements,
   delimited by spaces.  Though, spaces within quoted string are
   retained.  This is done using `org-babel-parse-header-arguments'.
4. Return the transformed list of strings.

Example: \"one\\n\\\"two words\\\" three\\nfour\"
         -> (\"one\" \"two words\" \"three\" \"four\").

Return nil if STR is not a string."
  (when (stringp str)
    (let* ((str (org-trim str))
           (str-list (split-string str org-hugo--internal-list-separator))
           ret)
      (dolist (str-elem str-list)
        (let* ((format-str ":dummy '(%s)") ;The :dummy key is discarded in the `lst' var below.
               (alist (org-babel-parse-header-arguments (format format-str str-elem)))
               (lst (cdr (car alist)))
               (str-list2 (mapcar (lambda (elem)
                                    (cond
                                     ((symbolp elem)
                                      (symbol-name elem))
                                     (t
                                      elem)))
                                  lst)))
          (setq ret (append ret str-list2))))
      ret)))

(defun org-hugo--category-p (tag)
  "Return non-nil if TAG begins with \"@\".

Org tags that begin with \"@\" are set as the categories field in
the Hugo front-matter."
  (and (stringp tag)
       (string-match-p "\\`@" tag)))

(defun org-hugo--subtree-export-p (info)
  "Return non-nil if the current export is subtree based.

INFO is a plist used as a communication channel."
  (memq 'subtree (plist-get info :export-options)))

(defun org-hugo--string-unquote (str)
  "Return STR after removing beginning and ending quotes if any.

Return nil if STR is an empty string, or not a string."
  (let ((unquoted-str (org-string-nw-p str))) ;Ensure that `str' is a non-empty string
    (when (and unquoted-str
               (string= (substring unquoted-str 0 1) "\"") ;First char is literally a "
               (string= (substring unquoted-str -1) "\"")) ;Last char is literally a "
      (setq unquoted-str (substring unquoted-str 1 -1)))
    unquoted-str))

(defun org-hugo--get-front-matter (info)
  "Return the Hugo front-matter string.

INFO is a plist used as a communication channel."
  ;; (message "[hugo front-matter DBG] info: %S" (pp info))
  (let* ((fm-format (plist-get info :hugo-front-matter-format))
         (author-list (and (plist-get info :with-author)
                           (let ((author-raw
                                  (org-string-nw-p
                                   (org-export-data (plist-get info :author) info)))) ;`org-export-data' required
                             (when author-raw
                               ;; Multiple authors can be comma or
                               ;; newline separated. The newline
                               ;; separated authors work only for the
                               ;; #+author keyword; example:
                               ;;   #+author: Author1
                               ;;   #+author: Author2
                               ;;
                               ;; If using the subtree properties they
                               ;; need to be comma-separated:
                               ;;   :EXPORT_AUTHOR: Author1, Author2
                               (let ((author-list-1 (org-split-string author-raw "[,\n]")))
                                 ;; Don't allow spaces around author names.
                                 ;; Also remove duplicate authors.
                                 (delete-dups (mapcar #'org-trim author-list-1)))))))
         (creator (and (plist-get info :with-creator)
                       (plist-get info :creator)))
         (locale (and (plist-get info :hugo-with-locale)
                      (org-hugo--get-lang info)))
         (description (org-string-nw-p (plist-get info :description)))
         (aliases-raw (let ((aliases-raw-1 (org-string-nw-p (plist-get info :hugo-aliases))))
                        (when aliases-raw-1
                          (org-split-string aliases-raw-1 " "))))
         (aliases (let (alias-list)
                    (dolist (alias aliases-raw)
                      (unless (string-match-p "/" alias)
                        (let ((section (file-name-as-directory ;Suffix section with "/" if it isn't already
                                        (org-export-data (plist-get info :hugo-section) info))))
                          (setq alias (concat "/" section alias))))
                      (setq alias-list (append alias-list `(,alias))))
                    alias-list))
         (outputs-raw (org-string-nw-p (plist-get info :hugo-outputs)))
         (outputs (when outputs-raw
                    (org-split-string outputs-raw " ")))
         (draft (org-hugo--parse-draft-state info))
         (headless (when (org-hugo--plist-get-true-p info :hugo-headless)
                     (org-hugo--front-matter-value-booleanize (org-hugo--plist-get-true-p info :hugo-headless))))
         (all-t-and-c-str (org-entry-get (point) "ALLTAGS")) ;Includes tags inherited from #+filetags: too.
         (all-t-and-c (or (when (stringp all-t-and-c-str)    ;tags/categories from `all-t-and-c' are used
                            (org-split-string all-t-and-c-str ":")) ;only if HUGO_TAGS or HUGO_CATEGORIES are not set.
                          (and (null (org-hugo--subtree-export-p info)) ;Use #+filetags: for file-based exports if #+hugo_tags are not set.
                               org-file-tags)))
         (tags (or
                ;; Look for tags set using HUGO_TAGS keyword, or
                ;; EXPORT_HUGO_TAGS property if available.
                (org-hugo--delim-str-to-list (plist-get info :hugo-tags))
                ;; Else use Org tags (the ones set in headings
                ;; and/or inherited) if any.
                (let* ((tags-list (cl-remove-if #'org-hugo--category-p all-t-and-c))
                       (tags-list (dolist (fn org-hugo-tag-processing-functions tags-list)
                                    (setq tags-list (funcall fn tags-list info)))))
                  ;; (message "[get fm DBG] tags: tags-list = %S" tags-list)
                  tags-list)))
         (categories (or
                      ;; Look for categories set using HUGO_CATEGORIES
                      ;; keyword, or EXPORT_HUGO_CATEGORIES property
                      ;; if available.
                      (org-hugo--delim-str-to-list (plist-get info :hugo-categories))
                      ;; Else use categories set using Org tags with
                      ;; "@" prefix (the ones set in headings and/or
                      ;; inherited) if any.
                      (let* ((categories-list (cl-remove-if-not #'org-hugo--category-p all-t-and-c))
                             (categories-list (dolist (fn org-hugo-tag-processing-functions categories-list)
                                                (setq categories-list (funcall fn categories-list info))))
                             (categories-list (mapcar (lambda (str)
                                                        ;; Remove "@" from beg of categories.
                                                        (replace-regexp-in-string "\\`@" "" str))
                                                      categories-list)))
                        ;; (message "dbg: categories: categories-list = %s" categories-list)
                        categories-list)))
         (keywords (org-hugo--delim-str-to-list (plist-get info :keywords)))
         (weight-data (let ((wt-raw-list (org-hugo--parse-property-arguments (plist-get info :hugo-weight)))
                            weight-data-1)
                        (dolist (wt-raw wt-raw-list)
                          (let (key value)
                            ;; (message "weight DBG wt-raw: %S" wt-raw)
                            ;; (message "weight DBG cdr wt-raw: %S" (cdr wt-raw))
                            ;; (message "weight DBG org-hugo--subtree-coord: %S" org-hugo--subtree-coord)
                            (cond
                             ((null (cdr wt-raw)) ;`wt-raw' is not of the type (TAXONOMY . WEIGHT)
                              (setq key 'weight)
                              (setq value (cond
                                           ((and org-hugo--subtree-coord
                                                 (equal (car wt-raw) 'auto)) ;(auto)
                                            (org-hugo--calc-weight))
                                           ((and (equal (car wt-raw) 'auto) ;Auto weight ineffective for file-based exports
                                                 (null org-hugo--subtree-coord))
                                            nil)
                                           (t
                                            (string-to-number (symbol-name (car wt-raw)))))))
                             (t
                              (setq key (if (equal (car wt-raw) 'page) ;`wt-raw' is of the type (page . WEIGHT)
                                            'weight
                                          (intern (format "%s_weight" (symbol-name (car wt-raw))))))
                              (setq value (cond
                                           ((and org-hugo--subtree-coord
                                                 (equal (cdr wt-raw) "auto")) ;(TAXONOMY . "auto") or (page . "auto")
                                            (org-hugo--calc-weight))
                                           ((numberp (cdr wt-raw))
                                            (cdr wt-raw))
                                           ((and (equal (cdr wt-raw) "auto") ;Auto weight ineffective for file-based exports
                                                 (null org-hugo--subtree-coord))
                                            nil)
                                           (t
                                            (user-error "Ox-hugo: Invalid weight %S" (cdr wt-raw)))))))
                            ;; (message "weight DBG key: %S" key)
                            ;; (message "weight DBG value: %S" value)
                            (push (cons key value) weight-data-1)))
                        ;; (message "weight DBG weight-data: %S" weight-data-1)
                        weight-data-1))
         (menu-alist (org-hugo--parse-menu-prop-to-alist info))
         (custom-fm-data (org-hugo--parse-property-arguments (plist-get info :hugo-custom-front-matter)))
         (resources (org-hugo--get-resources-alist
                     (org-hugo--parse-property-arguments (plist-get info :hugo-resources))))
         (blackfriday (unless (org-hugo--plist-get-true-p info :hugo-goldmark)
                        (require 'ox-hugo-deprecated)
                        (org-hugo--parse-blackfriday-prop-to-alist (plist-get info :hugo-blackfriday))))
         (data `(;; The order of the elements below will be the order in which the front-matter
                 ;; variables will be ordered.
                 (title . ,(org-hugo--get-sanitized-title info))
                 (audio . ,(org-hugo--string-unquote (plist-get info :hugo-audio)))
                 (author . ,author-list)
                 (description . ,description)
                 (date . ,(org-hugo--format-date :date info))
                 (publishDate . ,(org-hugo--format-date :hugo-publishdate info))
                 (expiryDate . ,(org-hugo--format-date :hugo-expirydate info))
                 (aliases . ,aliases)
                 (images . ,(org-hugo--delim-str-to-list (plist-get info :hugo-images)))
                 (isCJKLanguage . ,(org-hugo--plist-get-true-p info :hugo-iscjklanguage))
                 (keywords . ,keywords)
                 (layout . ,(plist-get info :hugo-layout))
                 (lastmod . ,(org-hugo--format-date :hugo-lastmod info))
                 (linkTitle . ,(plist-get info :hugo-linktitle))
                 (markup . ,(plist-get info :hugo-markup))
                 (outputs . ,outputs)
                 (series . ,(org-hugo--delim-str-to-list (plist-get info :hugo-series)))
                 (slug . ,(plist-get info :hugo-slug))
                 (tags . ,tags)
                 (categories . ,categories)
                 (type . ,(plist-get info :hugo-type))
                 (url . ,(plist-get info :hugo-url))
                 (videos . ,(org-hugo--delim-str-to-list (plist-get info :hugo-videos)))
                 (draft . ,draft)
                 (headless . ,headless)
                 (creator . ,creator)
                 (locale . ,locale)
                 (blackfriday . ,blackfriday)))
         (data `,(append data weight-data custom-fm-data
                         (list
                          (cons 'menu menu-alist)
                          (cons 'resources resources)
                          (cons 'logbook (plist-get info :logbook)))))
         ret)

    ;; (message "[get fm DBG] tags: %s" tags)
    ;; (message "dbg: hugo tags: %S" (plist-get info :hugo-tags))
    ;; (message "[get fm info DBG] %S" info)
    ;; (message "[get fm menu DBG] %S" menu-alist)
    ;; (message "[get fm menu override DBG] %S" menu-alist-override)
    ;; (message "[custom fm data DBG] %S" custom-fm-data)
    ;; (message "[fm resources OUT DBG] %S" resources)
    ;; (message "[fm data DBG] data: %S" data)
    ;; (progn (message "[fm data DBG] ") (pp data))
    ;; (message "[fm tags DBG] %S" tags)
    ;; (message "[fm categories DBG] %S" categories)
    ;; (message "[fm keywords DBG] %S" keywords)

    ;; Append group tags to user-set tags if tag groups are defined in
    ;; the buffer.
    (when (and org-group-tags org-tag-groups-alist)
      (let (tag-groups-alist-mod)

        ;; Copy `org-tag-groups-alist' to `tag-groups-alist-mod' while
        ;; modifying the tags and categories as defined by
        ;; `org-hugo-tag-processing-functions'.
        (dolist (group org-tag-groups-alist)
          (let ((group-mod group))
            (dolist (fn org-hugo-tag-processing-functions group-mod)
              (setq group-mod (funcall fn group-mod info)))
            (push group-mod tag-groups-alist-mod)))

        (dolist (t-or-c (append tags categories))
          (let ((to-be-searched `(,t-or-c)))
            (while (> (length to-be-searched) 0)
              ;; (message "[tag group DBG] t and c to search: %S" to-be-searched)
              (let ((tc (pop to-be-searched)))
                (dolist (group tag-groups-alist-mod)
                  ;; (message "[tag group DBG]   Searching %s in %S" tc group)
                  (when (member tc group)
                    (let ((head-tag (car group)))
                      (if (org-hugo--category-p head-tag)
                          (let ((head-cat (replace-regexp-in-string "\\`@" "" head-tag)))
                            (unless (member head-cat categories)
                              (push head-cat categories)
                              ;; (message "[tag group DBG] .... Adding cat %s" head-cat)
                              ))
                        (unless (member head-tag tags)
                          (push head-tag tags)
                          ;; (message "[tag group DBG] .... Adding tag %s" head-tag)
                          ))
                      ;; Add the current `head-tag' as the new tag to
                      ;; search if current tag or category (`tc') is not
                      ;; the `head-tag', and if it's not already in the
                      ;; search list.
                      (unless (or (string= tc head-tag)
                                  (member head-tag to-be-searched))
                        (push head-tag to-be-searched))))))))))
      ;; (message "[tag group DBG] updated tags: %S" tags)
      ;; (message "[tag group DBG] updated categories: %S" categories)

      ;; Overwrite the 'tags and 'categories key values in `data' with
      ;; the updated values.
      ;; https://stackoverflow.com/a/40815365/1219634
      (setf (alist-get 'tags data) tags)
      (setf (alist-get 'categories data) categories))

    (setq data (org-hugo--replace-keys-maybe data info))
    (setq ret (org-hugo--gen-front-matter data fm-format))
    (if (and (string= "toml" fm-format)
             (org-hugo--pandoc-citations-enabled-p info))
        (progn
          ;; Pandoc parses fields like csl and nocite from YAML
          ;; front-matter.  So create the `org-hugo--fm-yaml'
          ;; front-matter in YAML format just for Pandoc.
          (require 'ox-hugo-pandoc-cite)
          (setq org-hugo--fm-yaml
                (org-hugo-pandoc-cite--meta-data-generator data)))
      (setq org-hugo--fm-yaml ret))
    ;; (message "org-hugo--fm-yaml: `%s'" org-hugo--fm-yaml)
    ret))

(defun org-hugo--calc-weight ()
  "Calculate the weight for a Hugo post or menu item.

The returned weight = INDEX + 1000*LEVEL.  See
`org-hugo--get-post-subtree-coordinates' learn about INDEX and
LEVEL."
  (let* ((level (car org-hugo--subtree-coord))
         (index (cdr org-hugo--subtree-coord)))
    ;; (message "[org-hugo--calc-weight dbg] level = %S" level)
    ;; (message "[org-hugo--calc-weight dbg] index = %S" index)
    (+ (* 1000 level) index)))

(defun org-hugo--gen-front-matter (data format)
  "Generate the Hugo post front-matter, and return that string.

DATA is an alist of the form \((KEY1 . VAL1) (KEY2 . VAL2) .. \),
where KEY is a symbol and VAL is a string.

Generate the front-matter in the specified FORMAT.  Valid values
are \"toml\" and \"yaml\"."
  (if (string= format "yaml")
      (org-hugo--gen-yaml-front-matter data)
    (let ((tomelr-indent-multi-line-strings t))
      (format "+++\n%s\n+++\n" (tomelr-encode data)))))

(defun org-hugo--selective-property-inheritance ()
  "Return a list of properties that should be inherited."
  (let ((prop-list '("HUGO_FRONT_MATTER_FORMAT"
                     "HUGO_PREFER_HYPHEN_IN_TAGS"
                     "HUGO_PRESERVE_FILLING"
                     "HUGO_DELETE_TRAILING_WS"
                     "HUGO_ALLOW_SPACES_IN_TAGS"
                     "HUGO_BLACKFRIDAY"
                     "HUGO_SECTION"
                     "HUGO_SECTION_FRAG"
                     "HUGO_BUNDLE"
                     "HUGO_BASE_DIR"
                     "HUGO_BASE_CONTENT_FOLDER"
                     "HUGO_GOLDMARK"
                     "HUGO_CODE_FENCE"
                     "HTML_CONTAINER"
                     "HTML_CONTAINER_CLASS"
                     "HUGO_MENU"
                     "HUGO_CUSTOM_FRONT_MATTER"
                     "HUGO_DRAFT"
                     "HUGO_ISCJKLANGUAGE"
                     "KEYWORDS"
                     "HUGO_MARKUP"
                     "HUGO_OUTPUTS"
                     "HUGO_TAGS"
                     "HUGO_CATEGORIES"
                     "HUGO_SERIES"
                     "HUGO_TYPE"
                     "HUGO_LAYOUT"
                     "HUGO_WEIGHT"
                     "HUGO_RESOURCES"
                     "HUGO_FRONT_MATTER_KEY_REPLACE"
                     "HUGO_DATE_FORMAT"
                     "HUGO_WITH_LOCALE"
                     "HUGO_LOCALE"
                     "HUGO_PAIRED_SHORTCODES"
                     "DATE" ;Useful for inheriting same date to same posts in different languages
                     "HUGO_PUBLISHDATE"
                     "HUGO_EXPIRYDATE"
                     "HUGO_LASTMOD"
                     "HUGO_SLUG" ;Useful for inheriting same slug to same posts in different languages
                     "HUGO_PANDOC_CITATIONS"
                     "BIBLIOGRAPHY"
                     "HUGO_AUTO_SET_LASTMOD"
                     "LANGUAGE"
                     "AUTHOR"
                     "OPTIONS")))
    (mapcar (lambda (str)
              (concat "EXPORT_" str))
            prop-list)))

(defun org-hugo--get-valid-subtree ()
  "Return the Org element for a valid Hugo post subtree.
The condition to check validity is that the EXPORT_FILE_NAME
property is defined for the subtree element.

As this function is intended to be called inside a valid Hugo
post subtree, doing so also moves the point to the beginning of
the heading of that subtree.

Return nil if a valid Hugo post subtree is not found.  The point
will be moved in this case too."
  (let* ((subtree (car (org-hugo--get-elem-with-prop :EXPORT_FILE_NAME)))
         (point (org-element-property :begin subtree))) ;`point' will be nil if `subtree' is nil
    (when point
      (goto-char point))
    subtree))

(defun org-hugo--get-post-subtree-coordinates (subtree)
  "Return the coordinates for the current valid Hugo post SUBTREE.

The Org element returned by `org-hugo--get-valid-subtree' is a
valid Hugo post subtree.

The returned value is of type (LEVEL . INDEX) where LEVEL is the
level number of the subtree and INDEX is as explained in the
below example.

If we have

  * Level 1
  ** Level A
  ** Level B
  ** Level C
  * Level 2

the INDEX will be 1 for Level 1 and Level A, 2 for Level
B and Level 2, and 3 for Level C.

So the value returned for Level C will be (2 . 3)."
  (save-excursion
    (let ((level (org-element-property :level subtree))
          (index 1)
          (current-pos (point))
          (scope (if (org-up-heading-safe)
                     'tree ;Map entries only in parent subtree scope if parent exists
                   nil))) ;Else map in the whole buffer (provided the MATCH conditions below)
      ;; (message "[org-hugo--get-post-subtree-coordinates dbg] current-pos: %S, scope: %S"
      ;;          current-pos scope)
      (when level
        (org-map-entries (lambda ()
                           (when (< (point) current-pos)
                             (setq index (1+ index))))
                         ;; Loop through only headings that are at the
                         ;; same level as SUBTREE, and those which have
                         ;; the EXPORT_FILE_NAME property defined.
                         (concat "+LEVEL=" (number-to-string level)
                                 "+EXPORT_FILE_NAME<>\"\"")
                         scope)
        (cons level index)))))

(defun org-hugo--export-file-to-md (f-or-b-name &optional async visible-only noerror)
  "Export the Org file as a whole.

Note: This is an internal function, use
`org-hugo-export-wim-to-md' instead.

F-OR-B-NAME is the name of the file or buffer (if not a file
buffer) to be exported.

A non-nil optional argument ASYNC means the process should happen
asynchronously.  The resulting file should be accessible through the
`org-export-stack' interface.

When optional argument VISIBLE-ONLY is non-nil, don't export
contents of hidden elements.

Return the exported file name if the file has the #+title
keyword.

Else return nil and throw a user error.  If NOERROR is non-nil,
use `message' to display the error message instead of signaling a
user error."
  (let* ((info (org-combine-plists
                (org-export--get-export-attributes
                 'hugo nil visible-only)
                (org-export--get-buffer-attributes)
                (org-export-get-environment 'hugo)))
         (title (car (plist-get info :title)))
         ret)
    (if title
        (let* ((all-tags-1 (plist-get info :hugo-tags))
               (all-tags (when all-tags-1
                           (split-string
                            (replace-regexp-in-string "\"" "" all-tags-1))))
               (exclude-tags (plist-get info :exclude-tags))
               is-excluded matched-exclude-tag)
          (when all-tags
            ;; (message "[org-hugo--export-file-to-md DBG] exclude-tags = %s" exclude-tags)
            (dolist (exclude-tag exclude-tags)
              (when (member exclude-tag all-tags)
                (setq matched-exclude-tag exclude-tag)
                (setq is-excluded t))))
          (cond
           (is-excluded
            (message "[ox-hugo] %s was not exported as it is tagged with an exclude tag `%s'"
                     f-or-b-name matched-exclude-tag))

           (t
            (message "[ox-hugo] Exporting `%s' (%s)" title f-or-b-name)
            (setq ret (org-hugo-export-to-md async nil visible-only)))))

      (let ((msg "The entire file is attempted to be exported, but it is missing the #+title keyword")
            (error-fn (if noerror
                          #'message
                        #'user-error)))
        (apply error-fn
               (list (format "[ox-hugo] %s: %s" f-or-b-name msg)))))
    ret))

(defun org-hugo--export-subtree-to-md (&optional async visible-only all-subtrees)
  "Export the current subtree to a Hugo post.

Note: This is an internal function, use
`org-hugo-export-wim-to-md' instead.

A non-nil optional argument ASYNC means the process should happen
asynchronously.  The resulting file should be accessible through the
`org-export-stack' interface.

When optional argument VISIBLE-ONLY is non-nil, don't export
contents of hidden elements.

When optional argument ALL-SUBTREES is non-nil, print the
subtree-number being exported.

- If point is under a valid Hugo post subtree, export it, and
  also return the exported file name.

- If point is not under a valid Hugo post subtree, but one exists
  elsewhere in the Org file, do not export anything, but still
  return t.

- Else, return nil."
  (let ((subtree (org-hugo--get-valid-subtree)))
    (if subtree
        ;; If subtree is a valid Hugo post subtree, proceed ..
        (let* ((info (org-combine-plists
                      (org-export--get-export-attributes
                       'hugo subtree visible-only)
                      (org-export--get-buffer-attributes)
                      (org-export-get-environment 'hugo subtree)))
               (exclude-tags (plist-get info :exclude-tags))
               (is-commented (cdr (org-hugo--get-elem-with-prop :commentedp)))
               (commented-heading (when is-commented
                                    (org-element-property :title
                                      (car (org-hugo--get-elem-with-prop :commentedp)))))
               is-excluded matched-exclude-tag ret)
          ;; (message "[org-hugo--export-subtree-to-md DBG] exclude-tags =
          ;; %s" exclude-tags)
          (let ((all-tags (let ((org-use-tag-inheritance t))
                            (org-hugo--get-tags))))
            (when all-tags
              (dolist (exclude-tag exclude-tags)
                (when (member exclude-tag all-tags)
                  (setq matched-exclude-tag exclude-tag)
                  (setq is-excluded t)))))

          ;; (message "[current subtree DBG] subtree: %S" subtree)
          ;; (message "[current subtree DBG] is-commented:%S, tags:%S,
          ;; is-excluded:%S" is-commented tags is-excluded)
          (let ((title (org-element-property :title subtree))
                ;; FIXME: Sometimes `org-get-outline-path' returns the
                ;; list with empty string elements. It's not clear
                ;; why, but the below `cl-delete-if' workarounds works
                ;; (for now).
                (current-outline-path (cl-delete-if
                                       (lambda (el)
                                         (string= el ""))
                                       (org-get-outline-path :with-self)))
                ;; When batch-exporting subtrees, do not call
                ;; `org-hugo--after-all-exports-function' after each
                ;; subtree export.  In that case, that function is
                ;; called *after* looping through all the post
                ;; subtrees.
                (org-hugo--disable-after-all-exports-hook all-subtrees))
            ;; (message "[org-hugo--export-subtree-to-md dbg] @ point %S, current-outline-path: %S"
            ;;          (point) current-outline-path)
            (cond
             (is-commented
              (if (string= title commented-heading)
                  (message "[ox-hugo] `%s' was not exported as it is commented out" title)
                (message "[ox-hugo] `%s' was not exported as one of its parent subtrees `%s' is commented out"
                         title commented-heading)))
             (is-excluded
              (message "[ox-hugo] `%s' was not exported as it is tagged with an exclude tag `%s'"
                       title matched-exclude-tag))
             (t
              (if all-subtrees
                  (progn
                    (setq org-hugo--subtree-count (1+ org-hugo--subtree-count))
                    (message "[ox-hugo] %d/ Exporting `%s' .." org-hugo--subtree-count title))
                (message "[ox-hugo] Exporting `%s' .." title))

              ;; (message "[org-hugo--export-subtree-to-md dbg] EXPORT_HUGO_MENU value: %S"
              ;;          (org-entry-get nil "EXPORT_HUGO_MENU" :inherit))
              ;; Get the current subtree coordinates for
              ;; auto-calculation of menu item weight, page or
              ;; taxonomy weights ..
              (when (or
                     ;; .. if the menu front-matter is specified.
                     (or
                      (org-entry-get nil "EXPORT_HUGO_MENU" :inherit)
                      (save-excursion
                        (goto-char (point-min))
                        (let ((case-fold-search t))
                          (re-search-forward "^#\\+hugo_menu:.*:menu" nil :noerror))))
                     ;; .. or if auto-calculation is needed for page
                     ;; or taxonomy weights.
                     (or
                      (let ((page-or-taxonomy-weight (org-entry-get nil "EXPORT_HUGO_WEIGHT" :inherit)))
                        (and (stringp page-or-taxonomy-weight)
                             (string-match-p "auto" page-or-taxonomy-weight)))
                      (save-excursion
                        (goto-char (point-min))
                        (let ((case-fold-search t))
                          (re-search-forward "^#\\+hugo_weight:.*auto" nil :noerror)))))
                (setq org-hugo--subtree-coord
                      (org-hugo--get-post-subtree-coordinates subtree)))

              (let ((buffer (if org-hugo--preprocess-buffer
                                (let ((pre-proc-buf (or org-hugo--preprocessed-buffer
                                                        (org-hugo--get-pre-processed-buffer))))
                                  (unless org-hugo--preprocessed-buffer
                                    (setq org-hugo--preprocessed-buffer pre-proc-buf)
                                    (add-to-list 'org-hugo--opened-buffers pre-proc-buf))
                                  pre-proc-buf)
                              (current-buffer))))
                (with-current-buffer buffer
                  (goto-char (org-find-olp current-outline-path :this-buffer))
                  (setq ret (org-hugo-export-to-md async :subtreep visible-only)))))))
          ret)

      ;; If the point is not in a valid subtree, check if there's a
      ;; valid subtree elsewhere in the same Org file.
      (let ((valid-subtree-found (org-hugo--buffer-has-valid-post-subtree-p)))
        (when valid-subtree-found
          (message "Point is not in a valid Hugo post subtree; move to one and try again"))
        valid-subtree-found))))

(defun org-hugo--get-pre-processed-buffer ()
  "Return a pre-processed copy of the current buffer.

Internal links to other subtrees are converted to external
links."
  (let ((pre-processed-buffer-prefix "*Ox-hugo Pre-processed "))
    (let* (;; Create an abstract syntax tree (AST) of the Org document
           ;; in the current buffer.
           (ast (org-element-parse-buffer))
           (org-use-property-inheritance (org-hugo--selective-property-inheritance))
           (info (org-combine-plists
                  (list :parse-tree ast)
                  (org-export--get-export-attributes 'hugo)
                  (org-export--get-buffer-attributes)
                  (org-export-get-environment 'hugo))))

      ;; Process all link elements in the AST.
      (org-element-map ast '(link special-block)
        (lambda (el)
          (let ((el-type (org-element-type el)))
            (cond
             ((equal 'link el-type)
              (let ((type (org-element-property :type el)))
                (when (member type '("custom-id" "id" "fuzzy"))
                  (let* ((raw-link (org-element-property :raw-link el))
                         (destination
                          ;; Derived from ox.el -> `org-export-data'.  If a broken link is seen
                          ;; and if `broken-links' option is not nil, ignore the error.
                          (condition-case err
                              (if (string= type "fuzzy")
                                  (org-export-resolve-fuzzy-link el info)
                                (org-export-resolve-id-link el (org-export--collect-tree-properties ast info)))
                            (org-link-broken
                             (unless (or (plist-get info :with-broken-links)
                                         ;; Parse the `:EXPORT_OPTIONS' property if set
                                         ;; in a parent heading.
                                         (plist-get
                                          (org-export--parse-option-keyword
                                           (or (cdr (org-hugo--get-elem-with-prop
                                                     :EXPORT_OPTIONS
                                                     (org-element-property :begin el)))
                                               ""))
                                          :with-broken-links))
                               (user-error "Unable to resolve link: %S" (nth 1 err))))))
                         (source-path (org-hugo--heading-get-slug el info :inherit-export-file-name))
                         (destination-path (org-hugo--heading-get-slug destination info :inherit-export-file-name))
                         (destination-type (org-element-type destination)))
                    ;; (message "[ox-hugo pre process DBG] destination-type : %s" destination-type)

                    ;; Change the link if it points to a valid
                    ;; destination outside the subtree.
                    (unless (or  (equal source-path destination-path)
                                 (and (string= type "id")
                                      (org-id-find-id-file
                                       (org-element-property :path el))))
                      (let ((link-desc (org-element-contents el)))
                        ;; (message "[ox-hugo pre process DBG] link desc: %s" link-desc)

                        ;; Override the link types to be files.  We
                        ;; will be using out-of-subtree links as links
                        ;; to dummy files with
                        ;; `org-hugo--preprocessed-buffer-dummy-file-suffix'
                        ;; suffix.
                        (org-element-put-property el :type "file")
                        (org-element-put-property
                         el :path
                         (cond
                          ;; If the destination is a heading with the
                          ;; :EXPORT_FILE_NAME property defined, the
                          ;; link should point to the file (without
                          ;; anchor).
                          ((org-element-property :EXPORT_FILE_NAME destination)
                           (concat destination-path org-hugo--preprocessed-buffer-dummy-file-suffix))
                          ;; Hugo only supports anchors to headings,
                          ;; so if a "fuzzy" type link points to
                          ;; anything else than a heading, it should
                          ;; point to the file.
                          ((and (string= type "fuzzy")
                                (not (string-prefix-p "*" raw-link)))
                           (concat destination-path org-hugo--preprocessed-buffer-dummy-file-suffix))
                          ;; In "custom-id" type links, the raw-link
                          ;; matches the anchor of the destination.
                          ((string= type "custom-id")
                           (concat destination-path org-hugo--preprocessed-buffer-dummy-file-suffix "::" raw-link))
                          ;; In "id" and "fuzzy" type links, the anchor
                          ;; of the destination is derived from the
                          ;; :CUSTOM_ID property or the title.
                          (t
                           (let ((anchor (org-hugo--get-anchor destination info)))
                             (concat destination-path org-hugo--preprocessed-buffer-dummy-file-suffix "::#" anchor)))))
                        ;; If the link destination is a heading and if
                        ;; user hasn't set the link description, set the
                        ;; description to the destination heading title.
                        (when (and (null link-desc)
                                   (equal 'headline destination-type))
                          (let ((heading-title
                                 (org-export-data-with-backend
                                  (org-element-property :title destination) 'ascii info)))
                            ;; (message "[ox-hugo pre process DBG] destination heading: %s" heading-title)
                            (org-element-set-contents el heading-title)))))))))
             ((equal 'special-block el-type)
              ;; Handle empty Org special blocks.  When empty
              ;; blocks are found, set that elements content as ""
              ;; instead of nil.
              (unless (org-element-contents el)
                (org-element-adopt-elements el "")))))
          nil)) ;Minor performance optimization: Make `org-element-map' lambda return a nil.

      (when (version< "25.99" emacs-version) ;`kill-matching-buffers' got `:no-ask' arg in emacs 26.1
        ;; https://git.savannah.gnu.org/cgit/emacs.git/commit/?id=70d01daceddeb4e4c49c79473c81420f65ffd290
        ;; First kill all the old pre-processed buffers if still left open
        ;; for any reason.
        (kill-matching-buffers (regexp-quote pre-processed-buffer-prefix) :internal-too :no-ask))

      ;; Turn the AST with updated links into an Org buffer.
      (let ((local-variables (buffer-local-variables))
            (bound-variables (org-export--list-bound-variables))
            (buffer (generate-new-buffer (concat pre-processed-buffer-prefix (buffer-name) " *"))))
        (with-current-buffer buffer
          (let (vars)
            (org-hugo--org-mode-light)
            ;; Copy specific buffer local variables and variables set
            ;; through BIND keywords.  Below snippet is copied from
            ;; ox.el -> `org-export--generate-copy-script'.
            (dolist (entry local-variables vars)
              (when (consp entry)
                (let ((var (car entry))
                      (val (cdr entry)))
                  (and (not (memq var org-export-ignored-local-variables))
                       (or (memq var
                                 '(default-directory
                                    buffer-file-name
                                    buffer-file-coding-system))
                           (assq var bound-variables)
                           (string-match "^\\(org-\\|orgtbl-\\)"
                                         (symbol-name var)))
                       ;; Skip unreadable values, as they cannot be
                       ;; sent to external process.
                       (or (not val) (ignore-errors (read (format "%S" val))))
                       (push (set (make-local-variable var) val) vars)))))

            (insert (org-element-interpret-data ast))
            (set-buffer-modified-p nil)))
        buffer))))



;;; Interactive functions

;;;###autoload
(defun org-hugo-export-as-md (&optional async subtreep visible-only)
  "Export current buffer to a Hugo-compatible Markdown buffer.

If narrowing is active in the current buffer, only export its
narrowed part.

If a region is active, export that region.

A non-nil optional argument ASYNC means the process should happen
asynchronously.  The resulting buffer should be accessible
through the `org-export-stack' interface.

When optional argument SUBTREEP is non-nil, export the sub-tree
at point, extracting information from the heading properties
first.

When optional argument VISIBLE-ONLY is non-nil, don't export
contents of hidden elements.

Export is done in a buffer named \"*Org Hugo Export*\", which
will be displayed when `org-export-show-temporary-export-buffer'
is non-nil.

Return the buffer the export happened to."
  (interactive)
  (org-hugo--before-export-function subtreep)
  ;; Allow certain `ox-hugo' properties to be inherited.
  (let ((org-use-property-inheritance (org-hugo--selective-property-inheritance))
        (info (org-combine-plists
               (org-export--get-export-attributes
                'hugo subtreep visible-only)
               (org-export--get-buffer-attributes)
               (org-export-get-environment 'hugo subtreep))))
    (prog1
        (org-export-to-buffer 'hugo "*Org Hugo Export*"
          async subtreep visible-only nil nil (lambda () (text-mode)))
      (org-hugo--after-1-export-function info nil)
      (org-hugo--after-all-exports-function))))

;;;###autoload
(defun org-hugo-export-to-md (&optional async subtreep visible-only)
  "Export current buffer to a Hugo-compatible Markdown file.

This is the main exporting function which is called by both
`org-hugo--export-file-to-md' and
`org-hugo--export-subtree-to-md', and thus
`org-hugo-export-wim-to-md' too.

If narrowing is active in the current buffer, only export its
narrowed part.

If a region is active, export that region.

A non-nil optional argument ASYNC means the process should happen
asynchronously.  The resulting file should be accessible through
the `org-export-stack' interface.

When optional argument SUBTREEP is non-nil, export the sub-tree
at point, extracting information from the heading properties
first.

When optional argument VISIBLE-ONLY is non-nil, don't export
contents of hidden elements.

Return output file's name."
  (interactive)
  (org-hugo--before-export-function subtreep)
  ;; Allow certain `ox-hugo' properties to be inherited.  It is
  ;; important to set the `org-use-property-inheritance' before
  ;; setting the `info' var so that properties like
  ;; EXPORT_HUGO_SECTION get inherited.
  (let* ((org-use-property-inheritance (org-hugo--selective-property-inheritance))
         (info (org-combine-plists
                (org-export--get-export-attributes
                 'hugo subtreep visible-only)
                (org-export--get-buffer-attributes)
                (org-export-get-environment 'hugo subtreep)))
         (pub-dir (org-hugo--get-pub-dir info))
         ;; Don't print "Saving file .." for each exported file. This
         ;; works in interactive mode i.e. when exporting posts from
         ;; within emacs.  But in --batch mode, setting
         ;; `save-silently' to t, ironically prints a blank line
         ;; instead of the "Saving file .." message.  So leave this
         ;; variable value at nil for --batch runs.
         (save-silently (unless noninteractive
                          t))
         (outfile (org-export-output-file-name ".md" subtreep pub-dir)))
    ;; (message "[org-hugo-export-to-md DBG] section-dir = %s" section-dir)
    (prog1
        (org-export-to-file 'hugo outfile async subtreep visible-only)
      (org-hugo--after-1-export-function info outfile)
      (unless org-hugo--disable-after-all-exports-hook
        (org-hugo--after-all-exports-function)))))

;;;###autoload
(defun org-hugo-export-wim-to-md (&optional all-subtrees async visible-only noerror)
  "Export the current subtree/all subtrees/current file to a Hugo post.

This is an Export \"What I Mean\" function:

- If the current subtree has the \"EXPORT_FILE_NAME\" property,
  export only that subtree.  Return the return value of
  `org-hugo--export-subtree-to-md'.

- If the current subtree doesn't have that property, but one of
  its parent subtrees has, export from that subtree's scope.
  Return the return value of `org-hugo--export-subtree-to-md'.

- If there are no valid Hugo post subtrees (that have the
  \"EXPORT_FILE_NAME\" property) in the Org buffer the subtrees
  have that property, do file-based
  export (`org-hugo--export-file-to-md'), regardless of the value
  of ALL-SUBTREES.  Return the return value of
  `org-hugo--export-file-to-md'.

- If ALL-SUBTREES is non-nil and the Org buffer has at least 1
  valid Hugo post subtree, export all those valid post subtrees.
  Return a list of output files.

A non-nil optional argument ASYNC means the process should happen
asynchronously.  The resulting file should be accessible through
the `org-export-stack' interface.

When optional argument VISIBLE-ONLY is non-nil, don't export
contents of hidden elements.

The optional argument NOERROR is passed to
`org-hugo--export-file-to-md'."
  (interactive "P")
  (let ((f-or-b-name (if (buffer-file-name)
                         (file-name-nondirectory (buffer-file-name))
                       (buffer-name)))
        (buf-has-subtree (org-hugo--buffer-has-valid-post-subtree-p))
        ret)

    ;; Auto-update `org-id-locations' if it's nil or empty hash table
    ;; to avoid broken [[id:..]] type links.
    (unless org-id-locations (org-id-locations-load))
    (when (or (null org-id-locations) (zerop (hash-table-count org-id-locations)))
      (org-id-update-id-locations (directory-files "." :full "\\.org$" :nosort) :silent))

    (org-hugo--cleanup)

    (save-window-excursion
      (org-with-wide-buffer
       (cond
        ;; Publish all subtrees in the current Org buffer.
        ((and buf-has-subtree all-subtrees)
         (let ((start-time (current-time)))
           ;; Make the *Messages* buffer less noisy when exporting all
           ;; subtrees.
           (dolist (fn org-hugo--all-subtrees-export--functions-to-silence)
             (advice-add fn :around #'org-hugo--advice-silence-messages))

           (setq ret (org-map-entries
                      (lambda ()
                        (org-hugo--export-subtree-to-md async visible-only :all-subtrees))
                      ;; Export only the subtrees where
                      ;; EXPORT_FILE_NAME property is not empty.
                      "EXPORT_FILE_NAME<>\"\""))

           (let* ((elapsed-time (float-time (time-since start-time)))
                  (avg-time (/ elapsed-time org-hugo--subtree-count)))
             (message "[ox-hugo] Exported %d subtree%s from %s in %0.3fs (%0.3fs avg)"
                      org-hugo--subtree-count
                      (if (= 1 org-hugo--subtree-count) "" "s")
                      f-or-b-name
                      elapsed-time
                      avg-time))
           (org-hugo--after-all-exports-function)))

        ;; Publish only the current valid Hugo post subtree.  When
        ;; exporting only one subtree, buffer pre-processing is done
        ;; inside `org-hugo--export-subtree-to-md'.
        ((and buf-has-subtree (not all-subtrees))
         (setq ret (org-hugo--export-subtree-to-md async visible-only)))

        ;; Attempt file-based export.
        (t
         (setq ret (org-hugo--export-file-to-md f-or-b-name async visible-only noerror))))))
    ret))

;;;###autoload
(defun org-hugo-debug-info ()
  "Get Emacs, Org and Hugo version and ox-hugo customization info.
The information is converted to Markdown format and copied to the
kill ring.  The same information is displayed in the Messages
buffer and returned as a string in Org format."
  (interactive)
  (let* ((emacs-version (emacs-version))
         (org-version (org-version nil :full))
         (hugo-version (org-hugo--hugo-version))
         (hugo-version (if hugo-version
                           (car hugo-version) ;Long version
                         "=hugo= binary not found in PATH"))
         (info-org (mapconcat #'identity
                              `("* Debug information for =ox-hugo="
                                "** Emacs Version"
                                "#+begin_example" ;Prevent underscores from being interpreted as subscript markup
                                ,(format "%s%s"
                                         emacs-version
                                         (if emacs-repository-version
                                             (format " (commit %s)" emacs-repository-version)
                                           ""))
                                "#+end_example"
                                "** Org Version"
                                "#+begin_example" ;Prevent the forward slashes in paths being interpreted as Org markup
                                ,org-version
                                "#+end_example"
                                "** Hugo Version"
                                "#+begin_example" ;Prevent the forward slashes in paths being interpreted as Org markup
                                ,hugo-version
                                "#+end_example"
                                "*** Org =load-path= shadows"
                                ,(let* ((str (list-load-path-shadows :stringp))
                                        (str-list (split-string str "\n" :omit-nulls))
                                        (org-shadow-str ""))
                                   (dolist (shadow str-list)
                                     (when (string-match-p ".*org.+hides.+org.*" shadow)
                                       (setq org-shadow-str (concat org-shadow-str shadow "\n"))))
                                   (if (org-string-nw-p org-shadow-str)
                                       (mapconcat #'identity
                                                  `("*Warning*: Possible mixed installation of Org"
                                                    "#+begin_example" ;Prevent the forward slashes in paths being interpreted as Org markup
                                                    ,(org-trim org-shadow-str)
                                                    "#+end_example"
                                                    "Study the output of =M-x list-load-path-shadows=.")
                                                  "\n")
                                     "No Org mode shadows found in =load-path="))
                                "** =ox-hugo= defcustoms"
                                ,(format "|org-hugo-section                                      |%S|" org-hugo-section)
                                ,(format "|org-hugo-use-code-for-kbd                             |%S|" org-hugo-use-code-for-kbd)
                                ,(format "|org-hugo-preserve-filling                             |%S|" org-hugo-preserve-filling)
                                ,(format "|org-hugo-delete-trailing-ws                           |%S|" org-hugo-delete-trailing-ws)
                                ,(format "|org-hugo-prefer-hyphen-in-tags                        |%S|" org-hugo-prefer-hyphen-in-tags)
                                ,(format "|org-hugo-allow-spaces-in-tags                         |%S|" org-hugo-allow-spaces-in-tags)
                                ,(format "|org-hugo-tag-processing-functions                     |%S|" org-hugo-tag-processing-functions)
                                ,(format "|org-hugo-auto-set-lastmod                             |%S|" org-hugo-auto-set-lastmod)
                                ,(format "|org-hugo-export-with-toc                              |%S|" org-hugo-export-with-toc)
                                ,(format "|org-hugo-export-with-section-numbers                  |%S|" org-hugo-export-with-section-numbers)
                                ,(format "|org-hugo-front-matter-format                          |%S|" org-hugo-front-matter-format)
                                ,(format "|org-hugo-default-static-subdirectory-for-externals    |%S|" org-hugo-default-static-subdirectory-for-externals)
                                ,(format "|org-hugo-external-file-extensions-allowed-for-copying |%S|" org-hugo-external-file-extensions-allowed-for-copying)
                                ,(format "|org-hugo-date-format                                  |%S|" org-hugo-date-format)
                                ,(format "|org-hugo-paired-shortcodes                            |%S|" org-hugo-paired-shortcodes)
                                ,(format "|org-hugo-suppress-lastmod-period                      |%S|" org-hugo-suppress-lastmod-period)
                                ,(format "|org-hugo-front-matter-format                          |%S|" org-hugo-front-matter-format))
                              "\n"))
         (org-export-with-toc nil)
         (info-md (progn
                    (require 'ox-md)
                    (org-export-string-as info-org 'md :body-only))))
    (kill-new info-md)
    (message "%s" info-org)
    info-org))


(provide 'ox-hugo)

;;; ox-hugo.el ends here