build.scm 62 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420
  1. ;;; GNU Guix --- Functional package management for GNU
  2. ;;; Copyright © 2019-2022 Ludovic Courtès <ludo@gnu.org>
  3. ;;; Copyright © 2020 Björn Höfling <bjoern.hoefling@bjoernhoefling.de>
  4. ;;;
  5. ;;; This file is part of GNU Guix.
  6. ;;;
  7. ;;; GNU Guix is free software; you can redistribute it and/or modify it
  8. ;;; under the terms of the GNU General Public License as published by
  9. ;;; the Free Software Foundation; either version 3 of the License, or (at
  10. ;;; your option) any later version.
  11. ;;;
  12. ;;; GNU Guix is distributed in the hope that it will be useful, but
  13. ;;; WITHOUT ANY WARRANTY; without even the implied warranty of
  14. ;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  15. ;;; GNU General Public License for more details.
  16. ;;;
  17. ;;; You should have received a copy of the GNU General Public License
  18. ;;; along with GNU Guix. If not, see <http://www.gnu.org/licenses/>.
  19. ;; This file contains machinery to build HTML and PDF copies of the manual
  20. ;; that can be readily published on the web site. To do that, run:
  21. ;;
  22. ;; guix build -f build.scm
  23. ;;
  24. ;; The result is a directory hierarchy that can be used as the manual/
  25. ;; sub-directory of the web site.
  26. (use-modules (guix)
  27. (guix gexp)
  28. (guix git)
  29. (guix git-download)
  30. (guix utils)
  31. (git)
  32. (gnu packages base)
  33. (gnu packages compression)
  34. (gnu packages gawk)
  35. (gnu packages gettext)
  36. (gnu packages guile)
  37. (gnu packages guile-xyz)
  38. (gnu packages iso-codes)
  39. (gnu packages texinfo)
  40. (gnu packages tex)
  41. (ice-9 match)
  42. (srfi srfi-1)
  43. (srfi srfi-19)
  44. (srfi srfi-26)
  45. (srfi srfi-71))
  46. (define file-append*
  47. (@@ (guix self) file-append*))
  48. (define translated-texi-manuals
  49. (@@ (guix self) translate-texi-manuals))
  50. (define info-manual
  51. (@@ (guix self) info-manual))
  52. (define %manual
  53. ;; The manual to build--i.e., the base name of a .texi file, such as "guix"
  54. ;; or "guix-cookbook".
  55. (or (getenv "GUIX_MANUAL")
  56. "guix"))
  57. (define %manual-languages
  58. ;; Available translations for the 'guix-manual' text domain.
  59. '("de" "en" "es" "fr" "ru" "zh_CN"))
  60. (define %cookbook-languages
  61. ;; Available translations for the 'guix-cookbook' text domain.
  62. '("de" "en" "fr" "sk"))
  63. (define %languages
  64. ;; Available translations for the document being built.
  65. (if (string=? %manual "guix-cookbook")
  66. %cookbook-languages
  67. %manual-languages))
  68. (define (texinfo-manual-images source)
  69. "Return a directory containing all the images used by the user manual, taken
  70. from SOURCE, the root of the source tree."
  71. (define graphviz
  72. (module-ref (resolve-interface '(gnu packages graphviz))
  73. 'graphviz))
  74. (define images
  75. (file-append* source "doc/images"))
  76. (define build
  77. (with-imported-modules '((guix build utils))
  78. #~(begin
  79. (use-modules (guix build utils)
  80. (srfi srfi-26))
  81. (define (dot->image dot-file format)
  82. (invoke #+(file-append graphviz "/bin/dot")
  83. "-T" format "-Gratio=.9" "-Gnodesep=.005"
  84. "-Granksep=.00005" "-Nfontsize=9"
  85. "-Nheight=.1" "-Nwidth=.1"
  86. "-o" (string-append #$output "/"
  87. (basename dot-file ".dot")
  88. "." format)
  89. dot-file))
  90. ;; Build graphs.
  91. (mkdir-p #$output)
  92. (for-each (lambda (dot-file)
  93. (for-each (cut dot->image dot-file <>)
  94. '("png" "pdf")))
  95. (find-files #$images "\\.dot$"))
  96. ;; Copy other PNGs.
  97. (for-each (lambda (png-file)
  98. (install-file png-file #$output))
  99. (find-files #$images "\\.png$")))))
  100. (computed-file "texinfo-manual-images" build))
  101. (define* (texinfo-manual-source source #:key
  102. (version "0.0")
  103. (languages %languages)
  104. (date 1))
  105. "Gather all the source files of the Texinfo manuals from SOURCE--.texi file
  106. as well as images, OS examples, and translations."
  107. (define documentation
  108. (file-append* source "doc"))
  109. (define examples
  110. (file-append* source "gnu/system/examples"))
  111. (define build
  112. (with-imported-modules '((guix build utils))
  113. #~(begin
  114. (use-modules (guix build utils)
  115. (srfi srfi-19))
  116. (define (make-version-texi language)
  117. ;; Create the 'version.texi' file for LANGUAGE.
  118. (let ((file (if (string=? language "en")
  119. "version.texi"
  120. (string-append "version-" language ".texi"))))
  121. (call-with-output-file (string-append #$output "/" file)
  122. (lambda (port)
  123. (let* ((version #$version)
  124. (time (make-time time-utc 0 #$date))
  125. (date (time-utc->date time)))
  126. (format port "
  127. @set UPDATED ~a
  128. @set UPDATED-MONTH ~a
  129. @set EDITION ~a
  130. @set VERSION ~a\n"
  131. (date->string date "~e ~B ~Y")
  132. (date->string date "~B ~Y")
  133. version version))))))
  134. (install-file #$(file-append documentation "/htmlxref.cnf")
  135. #$output)
  136. (for-each (lambda (texi)
  137. (install-file texi #$output))
  138. (append (find-files #$documentation "\\.(texi|scm|json)$")
  139. (find-files #$(translated-texi-manuals source)
  140. "\\.texi$")))
  141. ;; Create 'version.texi'.
  142. (for-each make-version-texi '#$languages)
  143. ;; Copy configuration templates that the manual includes.
  144. (for-each (lambda (template)
  145. (copy-file template
  146. (string-append
  147. #$output "/os-config-"
  148. (basename template ".tmpl")
  149. ".texi")))
  150. (find-files #$examples "\\.tmpl$"))
  151. (symlink #$(texinfo-manual-images source)
  152. (string-append #$output "/images")))))
  153. (computed-file "texinfo-manual-source" build))
  154. (define %web-site-url
  155. ;; URL of the web site home page.
  156. (or (getenv "GUIX_WEB_SITE_URL")
  157. "/software/guix/"))
  158. (define %makeinfo-html-options
  159. ;; Options passed to 'makeinfo --html'.
  160. '("--css-ref=https://www.gnu.org/software/gnulib/manual.css"
  161. "-c" "EXTRA_HEAD=<meta name=\"viewport\" \
  162. content=\"width=device-width, initial-scale=1\" />"))
  163. (define (normalize-language-code language) ;XXX: deduplicate
  164. ;; Normalize LANGUAGE. For instance, "zh_CN" becomes "zh-cn".
  165. (string-map (match-lambda
  166. (#\_ #\-)
  167. (chr chr))
  168. (string-downcase language)))
  169. (define* (html-manual-identifier-index manual base-url
  170. #:key
  171. (name "html-manual-identifier-index"))
  172. "Return an index of all the identifiers that appear in MANUAL, a
  173. makeinfo-generated manual. The index is a file that contains an alist; each
  174. key is an identifier and the associated value is the URL reference pointing to
  175. that identifier. The URL is constructed by concatenating BASE-URL to the
  176. actual file name."
  177. (define build
  178. (with-extensions (list guile-lib)
  179. (with-imported-modules '((guix build utils))
  180. #~(begin
  181. (use-modules (guix build utils)
  182. (htmlprag)
  183. (srfi srfi-1)
  184. (srfi srfi-26)
  185. (ice-9 ftw)
  186. (ice-9 match)
  187. (ice-9 threads)
  188. (ice-9 pretty-print))
  189. (%strict-tokenizer? #t)
  190. (define file-url
  191. (let ((prefix (string-append #$manual "/")))
  192. (lambda (file)
  193. ;; Return the URL for FILE.
  194. (let ((file (string-drop file (string-length prefix)))
  195. (base #$base-url))
  196. (if (string-null? base)
  197. file
  198. (string-append base "/" file))))))
  199. (define (underscore-decode str)
  200. ;; Decode STR, an "underscore-encoded" string as produced by
  201. ;; makeinfo for indexes, such as "_0025base_002dservices" for
  202. ;; "%base-services".
  203. (let loop ((str str)
  204. (result '()))
  205. (match (string-index str #\_)
  206. (#f
  207. (string-concatenate-reverse (cons str result)))
  208. (index
  209. (let ((char (string->number
  210. (substring str (+ index 1) (+ index 5))
  211. 16)))
  212. (loop (string-drop str (+ index 5))
  213. (append (list (string (integer->char char))
  214. (string-take str index))
  215. result)))))))
  216. (define (anchor-id->key id)
  217. ;; Convert ID, an anchor ID such as
  218. ;; "index-pam_002dlimits_002dservice" to the corresponding key,
  219. ;; "pam-limits-service" in this example. Drop the suffix of
  220. ;; duplicate anchor IDs like "operating_002dsystem-1".
  221. (let ((id (if (any (cut string-suffix? <> id)
  222. '("-1" "-2" "-3" "-4" "-5"))
  223. (string-drop-right id 2)
  224. id)))
  225. (underscore-decode
  226. (string-drop id (string-length "index-")))))
  227. (define* (collect-anchors file #:optional (anchors '()))
  228. ;; Collect the anchors that appear in FILE, a makeinfo-generated
  229. ;; file. Grab those from <dt> tags, which corresponds to
  230. ;; Texinfo @deftp, @defvr, etc. Return ANCHORS augmented with
  231. ;; more name/reference pairs.
  232. (define string-or-entity?
  233. (match-lambda
  234. ((? string?) #t)
  235. (('*ENTITY* _ ...) #t)
  236. (_ #f)))
  237. (define (worthy-entry? lst)
  238. ;; Attempt to match:
  239. ;; Scheme Variable: <strong>x</strong>
  240. ;; but not:
  241. ;; <code>cups-configuration</code> parameter: …
  242. (let loop ((lst lst))
  243. (match lst
  244. (((? string-or-entity?) rest ...)
  245. (loop rest))
  246. ((('strong _ ...) _ ...)
  247. #t)
  248. ((('span ('@ ('class "symbol-definition-category"))
  249. (? string-or-entity?) ...) rest ...)
  250. #t)
  251. (x
  252. #f))))
  253. (let ((shtml (call-with-input-file file html->shtml)))
  254. (let loop ((shtml shtml)
  255. (anchors anchors))
  256. (match shtml
  257. (('dt ('@ ('id id) _ ...) rest ...)
  258. (if (and (string-prefix? "index-" id)
  259. (worthy-entry? rest))
  260. (alist-cons (anchor-id->key id)
  261. (string-append (file-url file)
  262. "#" id)
  263. anchors)
  264. anchors))
  265. ((tag ('@ _ ...) body ...)
  266. (fold loop anchors body))
  267. ((tag body ...)
  268. (fold loop anchors body))
  269. (_ anchors)))))
  270. (define (html-files directory)
  271. ;; Return the list of HTML files under DIRECTORY.
  272. (map (cut string-append directory "/" <>)
  273. (or (scandir #$manual (lambda (file)
  274. (string-suffix? ".html" file)))
  275. '())))
  276. (define anchors
  277. (sort (concatenate
  278. (n-par-map (parallel-job-count)
  279. (cut collect-anchors <>)
  280. (html-files #$manual)))
  281. (match-lambda*
  282. (((key1 . url1) (key2 . url2))
  283. (if (string=? key1 key2)
  284. (string<? url1 url2)
  285. (string<? key1 key2))))))
  286. (call-with-output-file #$output
  287. (lambda (port)
  288. (display ";; Identifier index for the manual.\n\n"
  289. port)
  290. (pretty-print anchors port)))))))
  291. (computed-file name build))
  292. (define* (html-identifier-indexes manual directory-suffix
  293. #:key (languages %languages)
  294. (manual-name %manual)
  295. (base-url (const "")))
  296. (map (lambda (language)
  297. (let ((language (normalize-language-code language)))
  298. (list language
  299. (html-manual-identifier-index
  300. (file-append manual "/" language directory-suffix)
  301. (base-url language)
  302. #:name (string-append manual-name "-html-index-"
  303. language)))))
  304. languages))
  305. (define* (syntax-highlighted-html input
  306. #:key
  307. (name "highlighted-syntax")
  308. (languages %languages)
  309. (mono-node-indexes
  310. (html-identifier-indexes input ""
  311. #:languages
  312. languages))
  313. (split-node-indexes
  314. (html-identifier-indexes input
  315. "/html_node"
  316. #:languages
  317. languages))
  318. (syntax-css-url
  319. "/static/base/css/code.css"))
  320. "Return a derivation called NAME that processes all the HTML files in INPUT
  321. to (1) add them a link to SYNTAX-CSS-URL, and (2) highlight the syntax of all
  322. its <pre class=\"lisp\"> blocks (as produced by 'makeinfo --html')."
  323. (define build
  324. (with-extensions (list guile-lib guile-syntax-highlight)
  325. (with-imported-modules '((guix build utils))
  326. #~(begin
  327. (use-modules (htmlprag)
  328. (syntax-highlight)
  329. (syntax-highlight scheme)
  330. (syntax-highlight lexers)
  331. (guix build utils)
  332. (srfi srfi-1)
  333. (srfi srfi-26)
  334. (ice-9 match)
  335. (ice-9 threads)
  336. (ice-9 vlist))
  337. (%strict-tokenizer? #t)
  338. (define (pair-open/close lst)
  339. ;; Pair 'open' and 'close' tags produced by 'highlights' and
  340. ;; produce nested 'paren' tags instead.
  341. (let loop ((lst lst)
  342. (level 0)
  343. (result '()))
  344. (match lst
  345. ((('open open) rest ...)
  346. (call-with-values
  347. (lambda ()
  348. (loop rest (+ 1 level) '()))
  349. (lambda (inner close rest)
  350. (loop rest level
  351. (cons `(paren ,level ,open ,inner ,close)
  352. result)))))
  353. ((('close str) rest ...)
  354. (if (> level 0)
  355. (values (reverse result) str rest)
  356. (begin
  357. (format (current-error-port)
  358. "warning: extra closing paren; context:~% ~y~%"
  359. (reverse result))
  360. (loop rest 0 (cons `(close ,str) result)))))
  361. ((item rest ...)
  362. (loop rest level (cons item result)))
  363. (()
  364. (when (> level 0)
  365. (format (current-error-port)
  366. "warning: missing ~a closing parens; context:~% ~y%"
  367. level (reverse result)))
  368. (values (reverse result) "" '())))))
  369. (define (highlights->sxml* highlights anchors)
  370. ;; Like 'highlights->sxml', but handle nested 'paren tags. This
  371. ;; allows for paren matching highlights via appropriate CSS
  372. ;; "hover" properties. When a symbol is encountered, look it up
  373. ;; in ANCHORS, a vhash, and emit the corresponding href, if any.
  374. (define (tag->class tag)
  375. (string-append "syntax-" (symbol->string tag)))
  376. (map (match-lambda
  377. ((? string? str) str)
  378. (('paren level open (body ...) close)
  379. `(span (@ (class ,(string-append "syntax-paren"
  380. (number->string level))))
  381. ,open
  382. (span (@ (class "syntax-symbol"))
  383. ,@(highlights->sxml* body anchors))
  384. ,close))
  385. (('symbol text)
  386. ;; Check whether we can emit a hyperlink for TEXT.
  387. (match (vhash-assoc text anchors)
  388. (#f
  389. `(span (@ (class ,(tag->class 'symbol))) ,text))
  390. ((_ . target)
  391. `(a (@ (class ,(tag->class 'symbol)) (href ,target))
  392. ,text))))
  393. ((tag text)
  394. `(span (@ (class ,(tag->class tag))) ,text)))
  395. highlights))
  396. (define entity->string
  397. (match-lambda
  398. ("rArr" "⇒")
  399. ("rarr" "→")
  400. ("hellip" "…")
  401. ("rsquo" "’")
  402. ("nbsp" " ")
  403. (e (pk 'unknown-entity e) (primitive-exit 2))))
  404. (define (concatenate-snippets pieces)
  405. ;; Concatenate PIECES, which contains strings and entities,
  406. ;; replacing entities with their corresponding string.
  407. (let loop ((pieces pieces)
  408. (strings '()))
  409. (match pieces
  410. (()
  411. (string-concatenate-reverse strings))
  412. (((? string? str) . rest)
  413. (loop rest (cons str strings)))
  414. ((('*ENTITY* "additional" entity) . rest)
  415. (loop rest (cons (entity->string entity) strings)))
  416. ((('span _ lst ...) . rest) ;for <span class="roman">
  417. (loop (append lst rest) strings))
  418. ((('var name) . rest) ;for @var{name} within @lisp
  419. (loop rest (cons name strings))) ;XXX: losing formatting
  420. (something
  421. (pk 'unsupported-code-snippet something)
  422. (primitive-exit 1)))))
  423. (define (highlight-definition id category symbol args)
  424. ;; Produce stylable HTML for the given definition (an @deftp,
  425. ;; @deffn, or similar).
  426. `(dt (@ (id ,id) (class "symbol-definition"))
  427. (span (@ (class "symbol-definition-category"))
  428. ,@category)
  429. (span (@ (class "symbol-definition-prototype"))
  430. ,symbol " " ,@args)))
  431. (define (space? obj)
  432. (and (string? obj)
  433. (string-every char-set:whitespace obj)))
  434. (define (syntax-highlight sxml anchors)
  435. ;; Recurse over SXML and syntax-highlight code snippets.
  436. (let loop ((sxml sxml))
  437. (match sxml
  438. (('*TOP* decl body ...)
  439. `(*TOP* ,decl ,@(map loop body)))
  440. (('head things ...)
  441. `(head ,@things
  442. (link (@ (rel "stylesheet")
  443. (type "text/css")
  444. (href #$syntax-css-url)))))
  445. (('pre ('@ ('class "lisp")) code-snippet ...)
  446. `(pre (@ (class "lisp"))
  447. ,@(highlights->sxml*
  448. (pair-open/close
  449. (highlight lex-scheme
  450. (concatenate-snippets code-snippet)))
  451. anchors)))
  452. ;; Replace the ugly <strong> used for @deffn etc., which
  453. ;; translate to <dt>, with more stylable markup.
  454. (('dt (@ ('id id)) category ... ('strong thing))
  455. (highlight-definition id category thing '()))
  456. (('dt (@ ('id id)) category ... ('strong thing)
  457. (? space?) ('em args ...))
  458. (highlight-definition id category thing args))
  459. ((tag ('@ attributes ...) body ...)
  460. `(,tag (@ ,@attributes) ,@(map loop body)))
  461. ((tag body ...)
  462. `(,tag ,@(map loop body)))
  463. ((? string? str)
  464. str))))
  465. (define (process-html file anchors)
  466. ;; Parse FILE and perform syntax highlighting for its Scheme
  467. ;; snippets. Install the result to #$output.
  468. (format (current-error-port) "processing ~a...~%" file)
  469. (let* ((shtml (call-with-input-file file html->shtml))
  470. (highlighted (syntax-highlight shtml anchors))
  471. (base (string-drop file (string-length #$input)))
  472. (target (string-append #$output base)))
  473. (mkdir-p (dirname target))
  474. (call-with-output-file target
  475. (lambda (port)
  476. (write-shtml-as-html highlighted port)))))
  477. (define (copy-as-is file)
  478. ;; Copy FILE as is to #$output.
  479. (let* ((base (string-drop file (string-length #$input)))
  480. (target (string-append #$output base)))
  481. (mkdir-p (dirname target))
  482. (catch 'system-error
  483. (lambda ()
  484. (if (eq? 'symlink (stat:type (lstat file)))
  485. (symlink (readlink file) target)
  486. (link file target)))
  487. (lambda args
  488. (let ((errno (system-error-errno args)))
  489. (pk 'error-link file target (strerror errno))
  490. (primitive-exit 3))))))
  491. (define (html? file stat)
  492. (string-suffix? ".html" file))
  493. (define language+node-anchors
  494. (match-lambda
  495. ((language files ...)
  496. (cons language
  497. (fold (lambda (file vhash)
  498. (let ((alist (call-with-input-file file read)))
  499. ;; Use 'fold-right' so that the first entry
  500. ;; wins (e.g., "car" from "Pairs" rather than
  501. ;; from "rnrs base" in the Guile manual).
  502. (fold-right (match-lambda*
  503. (((key . value) vhash)
  504. (vhash-cons key value vhash)))
  505. vhash
  506. alist)))
  507. vlist-null
  508. files)))))
  509. (define mono-node-anchors
  510. ;; List of language/vhash pairs, where each vhash maps an
  511. ;; identifier to the corresponding URL in a single-page manual.
  512. (map language+node-anchors '#$mono-node-indexes))
  513. (define multi-node-anchors
  514. ;; Likewise for split-node manuals.
  515. (map language+node-anchors '#$split-node-indexes))
  516. ;; Install a UTF-8 locale so we can process UTF-8 files.
  517. (setenv "GUIX_LOCPATH"
  518. #+(file-append glibc-utf8-locales "/lib/locale"))
  519. (setlocale LC_ALL "en_US.utf8")
  520. ;; First process the mono-node 'guix.html' files.
  521. (for-each (match-lambda
  522. ((language . anchors)
  523. (let ((files (find-files
  524. (string-append #$input "/" language)
  525. "^guix(-cookbook|)(\\.[a-zA-Z_-]+)?\\.html$")))
  526. (n-par-for-each (parallel-job-count)
  527. (cut process-html <> anchors)
  528. files))))
  529. mono-node-anchors)
  530. ;; Process the multi-node HTML files.
  531. (for-each (match-lambda
  532. ((language . anchors)
  533. (let ((files (find-files
  534. (string-append #$input "/" language
  535. "/html_node")
  536. "\\.html$")))
  537. (n-par-for-each (parallel-job-count)
  538. (cut process-html <> anchors)
  539. files))))
  540. multi-node-anchors)
  541. ;; Last, copy non-HTML files as is.
  542. (for-each copy-as-is
  543. (find-files #$input (negate html?)))))))
  544. (computed-file name build))
  545. (define* (stylized-html source input
  546. #:key
  547. (languages %languages)
  548. (manual %manual)
  549. (manual-css-url "/static/base/css/manual.css"))
  550. "Process all the HTML files in INPUT; add them MANUAL-CSS-URL as a <style>
  551. link, and add a menu to choose among LANGUAGES. Use the Guix PO files found
  552. in SOURCE."
  553. (define build
  554. (with-extensions (list guile-lib)
  555. (with-imported-modules `((guix build utils)
  556. ((localization)
  557. => ,(localization-helper-module
  558. source languages)))
  559. #~(begin
  560. (use-modules (htmlprag)
  561. (localization)
  562. (guix build utils)
  563. (srfi srfi-1)
  564. (ice-9 match)
  565. (ice-9 threads))
  566. (define* (menu-dropdown #:key (label "Item") (url "#") (items '()))
  567. ;; Return an SHTML <li> element representing a dropdown for the
  568. ;; navbar. LABEL is the text of the dropdown menu, and ITEMS is
  569. ;; the list of items in this menu.
  570. (define id "visible-dropdown")
  571. `(li
  572. (@ (class "navbar-menu-item dropdown dropdown-btn"))
  573. (input (@ (class "navbar-menu-hidden-input")
  574. (type "radio")
  575. (name "dropdown")
  576. (id ,id)))
  577. (label (@ (for ,id)) ,label)
  578. (label (@ (for "all-dropdowns-hidden")) ,label)
  579. (div
  580. (@ (class "navbar-submenu")
  581. (id "navbar-submenu"))
  582. (div (@ (class "navbar-submenu-triangle"))
  583. " ")
  584. (ul ,@items))))
  585. (define (menu-item label url)
  586. ;; Return an SHTML <li> element for a menu item with the given
  587. ;; LABEL and URL.
  588. `(li (a (@ (class "navbar-menu-item")
  589. (href ,url))
  590. ,label)))
  591. (define* (navigation-bar menus #:key split-node?)
  592. ;; Return the navigation bar showing all of MENUS.
  593. `(header (@ (class "navbar"))
  594. (h1 (a (@ (class "branding")
  595. (href ,(if split-node? ".." "#")))))
  596. (nav (@ (class "navbar-menu"))
  597. (input (@ (class "navbar-menu-hidden-input")
  598. (type "radio")
  599. (name "dropdown")
  600. (id "all-dropdowns-hidden")))
  601. (ul ,@menus))
  602. ;; This is the button that shows up on small screen in
  603. ;; lieu of the drop-down button.
  604. (a (@ (class "navbar-menu-btn")
  605. (href ,(if split-node? "../.." ".."))))))
  606. (define* (base-language-url code manual
  607. #:key split-node?)
  608. ;; Return the base URL of MANUAL for language CODE.
  609. (if split-node?
  610. (string-append "../../" (normalize code) "/html_node")
  611. (string-append "../" (normalize code) "/" manual
  612. (if (string=? code "en")
  613. ""
  614. (string-append "." code))
  615. ".html")))
  616. (define (language-menu-items file)
  617. ;; Return the language menu items to be inserted in FILE.
  618. (define split-node?
  619. (string-contains file "/html_node/"))
  620. (append
  621. (map (lambda (code)
  622. (menu-item (language-code->native-name code)
  623. (base-language-url code #$manual
  624. #:split-node?
  625. split-node?)))
  626. '#$%languages)
  627. (list
  628. (menu-item "⊕"
  629. (if (string=? #$manual "guix-cookbook")
  630. "https://translate.fedoraproject.org/projects/guix/documentation-cookbook/"
  631. "https://translate.fedoraproject.org/projects/guix/documentation-manual/")))))
  632. (define (stylized-html sxml file)
  633. ;; Return SXML, which was read from FILE, with additional
  634. ;; styling.
  635. (define split-node?
  636. (string-contains file "/html_node/"))
  637. (let loop ((sxml sxml))
  638. (match sxml
  639. (('*TOP* decl body ...)
  640. `(*TOP* ,decl ,@(map loop body)))
  641. (('head elements ...)
  642. ;; Add reference to our own manual CSS, which provides
  643. ;; support for the language menu.
  644. `(head ,@elements
  645. (link (@ (rel "stylesheet")
  646. (type "text/css")
  647. (href #$manual-css-url)))))
  648. (('body ('@ attributes ...) elements ...)
  649. `(body (@ ,@attributes)
  650. ,(navigation-bar
  651. ;; TODO: Add "Contribute" menu, to report
  652. ;; errors, etc.
  653. (list (menu-dropdown
  654. #:label
  655. `(img (@ (alt "Language")
  656. (src "/static/base/img/language-picker.svg")))
  657. #:items
  658. (language-menu-items file)))
  659. #:split-node? split-node?)
  660. ,@elements))
  661. ((tag ('@ attributes ...) body ...)
  662. `(,tag (@ ,@attributes) ,@(map loop body)))
  663. ((tag body ...)
  664. `(,tag ,@(map loop body)))
  665. ((? string? str)
  666. str))))
  667. (define (process-html file)
  668. ;; Parse FILE and add links to translations. Install the result
  669. ;; to #$output.
  670. (format (current-error-port) "processing ~a...~%" file)
  671. (let* ((shtml (parameterize ((%strict-tokenizer? #t))
  672. (call-with-input-file file html->shtml)))
  673. (processed (stylized-html shtml file))
  674. (base (string-drop file (string-length #$input)))
  675. (target (string-append #$output base)))
  676. (mkdir-p (dirname target))
  677. (call-with-output-file target
  678. (lambda (port)
  679. (write-shtml-as-html processed port)))))
  680. ;; Install a UTF-8 locale so we can process UTF-8 files.
  681. (setenv "GUIX_LOCPATH"
  682. #+(file-append glibc-utf8-locales "/lib/locale"))
  683. (setlocale LC_ALL "en_US.utf8")
  684. (setenv "LC_ALL" "en_US.utf8")
  685. (setvbuf (current-error-port) 'line)
  686. (n-par-for-each (parallel-job-count)
  687. (lambda (file)
  688. (if (string-suffix? ".html" file)
  689. (process-html file)
  690. ;; Copy FILE as is to #$output.
  691. (let* ((base (string-drop file (string-length #$input)))
  692. (target (string-append #$output base)))
  693. (mkdir-p (dirname target))
  694. (if (eq? 'symlink (stat:type (lstat file)))
  695. (symlink (readlink file) target)
  696. (copy-file file target)))))
  697. (find-files #$input))))))
  698. (computed-file "stylized-html-manual" build))
  699. (define* (html-manual source #:key (languages %languages)
  700. (version "0.0")
  701. (manual %manual)
  702. (mono-node-indexes (map list languages))
  703. (split-node-indexes (map list languages))
  704. (date 1)
  705. (options %makeinfo-html-options))
  706. "Return the HTML manuals built from SOURCE for all LANGUAGES, with the given
  707. makeinfo OPTIONS."
  708. (define manual-source
  709. (texinfo-manual-source source
  710. #:version version
  711. #:languages languages
  712. #:date date))
  713. (define images
  714. (texinfo-manual-images source))
  715. (define build
  716. (with-imported-modules '((guix build utils))
  717. #~(begin
  718. (use-modules (guix build utils)
  719. (ice-9 match))
  720. (define (normalize language)
  721. ;; Normalize LANGUAGE. For instance, "zh_CN" becomes "zh-cn".
  722. (string-map (match-lambda
  723. (#\_ #\-)
  724. (chr chr))
  725. (string-downcase language)))
  726. (define (language->texi-file-name language)
  727. (if (string=? language "en")
  728. (string-append #$manual-source "/"
  729. #$manual ".texi")
  730. (string-append #$manual-source "/"
  731. #$manual "." language ".texi")))
  732. ;; Install a UTF-8 locale so that 'makeinfo' is at ease.
  733. (setenv "GUIX_LOCPATH"
  734. #+(file-append glibc-utf8-locales "/lib/locale"))
  735. (setenv "LC_ALL" "en_US.utf8")
  736. (setvbuf (current-output-port) 'line)
  737. (setvbuf (current-error-port) 'line)
  738. ;; 'makeinfo' looks for "htmlxref.cnf" in the current directory, so
  739. ;; copy it right here.
  740. (copy-file (string-append #$manual-source "/htmlxref.cnf")
  741. "htmlxref.cnf")
  742. (for-each (lambda (language)
  743. (let* ((texi (language->texi-file-name language))
  744. (opts `("--html"
  745. "-c" ,(string-append "TOP_NODE_UP_URL=/manual/"
  746. language)
  747. #$@options
  748. ,texi)))
  749. (format #t "building HTML manual for language '~a'...~%"
  750. language)
  751. (mkdir-p (string-append #$output "/"
  752. (normalize language)))
  753. (setenv "LANGUAGE" language)
  754. (apply invoke #$(file-append texinfo "/bin/makeinfo")
  755. "-o" (string-append #$output "/"
  756. (normalize language)
  757. "/html_node")
  758. opts)
  759. (apply invoke #$(file-append texinfo "/bin/makeinfo")
  760. "--no-split"
  761. "-o"
  762. (string-append #$output "/"
  763. (normalize language)
  764. "/" #$manual
  765. (if (string=? language "en")
  766. ""
  767. (string-append "." language))
  768. ".html")
  769. opts)
  770. ;; Make sure images are available.
  771. (symlink #$images
  772. (string-append #$output "/" (normalize language)
  773. "/images"))
  774. (symlink #$images
  775. (string-append #$output "/" (normalize language)
  776. "/html_node/images"))))
  777. (filter (compose file-exists? language->texi-file-name)
  778. '#$languages)))))
  779. (let* ((name (string-append manual "-html-manual"))
  780. (manual* (computed-file name build #:local-build? #f)))
  781. (syntax-highlighted-html (stylized-html source manual*
  782. #:languages languages
  783. #:manual manual)
  784. #:mono-node-indexes mono-node-indexes
  785. #:split-node-indexes split-node-indexes
  786. #:name (string-append name "-highlighted"))))
  787. (define* (pdf-manual source #:key (languages %languages)
  788. (version "0.0")
  789. (manual %manual)
  790. (date 1)
  791. (options '()))
  792. "Return the HTML manuals built from SOURCE for all LANGUAGES, with the given
  793. makeinfo OPTIONS."
  794. (define manual-source
  795. (texinfo-manual-source source
  796. #:version version
  797. #:languages languages
  798. #:date date))
  799. ;; FIXME: This union works, except for the table of contents of non-English
  800. ;; manuals, which contains escape sequences like "^^ca^^fe" instead of
  801. ;; accented letters.
  802. ;;
  803. ;; (define texlive
  804. ;; (texlive-updmap.cfg (list texlive-tex-texinfo
  805. ;; texlive-generic-epsf
  806. ;; texlive-fonts-ec)))
  807. (define build
  808. (with-imported-modules '((guix build utils))
  809. #~(begin
  810. (use-modules (guix build utils)
  811. (srfi srfi-34)
  812. (ice-9 match))
  813. (define (normalize language) ;XXX: deduplicate
  814. ;; Normalize LANGUAGE. For instance, "zh_CN" becomes "zh-cn".
  815. (string-map (match-lambda
  816. (#\_ #\-)
  817. (chr chr))
  818. (string-downcase language)))
  819. ;; Install a UTF-8 locale so that 'makeinfo' is at ease.
  820. (setenv "GUIX_LOCPATH"
  821. #+(file-append glibc-utf8-locales "/lib/locale"))
  822. (setenv "LC_ALL" "en_US.utf8")
  823. (setenv "PATH"
  824. (string-append #+(file-append texlive "/bin") ":"
  825. #+(file-append texinfo "/bin") ":"
  826. ;; Below are command-line tools needed by
  827. ;; 'texi2dvi' and friends.
  828. #+(file-append sed "/bin") ":"
  829. #+(file-append grep "/bin") ":"
  830. #+(file-append coreutils "/bin") ":"
  831. #+(file-append gawk "/bin") ":"
  832. #+(file-append tar "/bin") ":"
  833. #+(file-append diffutils "/bin")))
  834. (setvbuf (current-output-port) 'line)
  835. (setvbuf (current-error-port) 'line)
  836. (setenv "HOME" (getcwd)) ;for kpathsea/mktextfm
  837. ;; 'SOURCE_DATE_EPOCH' is honored by pdftex.
  838. (setenv "SOURCE_DATE_EPOCH" "1")
  839. (for-each (lambda (language)
  840. (let ((opts `("--pdf"
  841. "-I" "."
  842. #$@options
  843. ,(if (string=? language "en")
  844. (string-append #$manual-source "/"
  845. #$manual ".texi")
  846. (string-append #$manual-source "/"
  847. #$manual "." language ".texi")))))
  848. (format #t "building PDF manual for language '~a'...~%"
  849. language)
  850. (mkdir-p (string-append #$output "/"
  851. (normalize language)))
  852. (setenv "LANGUAGE" language)
  853. ;; FIXME: Unfortunately building PDFs for non-Latin
  854. ;; alphabets doesn't work:
  855. ;; <https://lists.gnu.org/archive/html/help-texinfo/2012-01/msg00014.html>.
  856. (guard (c ((invoke-error? c)
  857. (format (current-error-port)
  858. "~%~%Failed to produce \
  859. PDF for language '~a'!~%~%"
  860. language)))
  861. (apply invoke #$(file-append texinfo "/bin/makeinfo")
  862. "--pdf" "-o"
  863. (string-append #$output "/"
  864. (normalize language)
  865. "/" #$manual
  866. (if (string=? language "en")
  867. ""
  868. (string-append "."
  869. language))
  870. ".pdf")
  871. opts))))
  872. '#$languages))))
  873. (computed-file (string-append manual "-pdf-manual") build
  874. #:local-build? #f))
  875. (define* (guix-manual-text-domain source
  876. #:optional (languages %manual-languages))
  877. "Return the PO files for LANGUAGES of the 'guix-manual' text domain taken
  878. from SOURCE."
  879. (define po-directory
  880. (file-append* source "/po/doc"))
  881. (define build
  882. (with-imported-modules '((guix build utils))
  883. #~(begin
  884. (use-modules (guix build utils))
  885. (mkdir-p #$output)
  886. (for-each (lambda (language)
  887. (define directory
  888. (string-append #$output "/" language
  889. "/LC_MESSAGES"))
  890. (mkdir-p directory)
  891. (invoke #+(file-append gnu-gettext "/bin/msgfmt")
  892. "-c" "-o"
  893. (string-append directory "/guix-manual.mo")
  894. (string-append #$po-directory "/guix-manual."
  895. language ".po")))
  896. '#$(delete "en" languages)))))
  897. (computed-file "guix-manual-po" build))
  898. (define* (localization-helper-module source
  899. #:optional (languages %languages))
  900. "Return a file-like object for use as the (localization) module. SOURCE
  901. must be the Guix top-level source directory, from which PO files are taken."
  902. (define content
  903. (with-extensions (list guile-json-3)
  904. #~(begin
  905. (define-module (localization)
  906. #:use-module (json)
  907. #:use-module (srfi srfi-1)
  908. #:use-module (srfi srfi-19)
  909. #:use-module (ice-9 match)
  910. #:use-module (ice-9 popen)
  911. #:export (normalize
  912. with-language
  913. translate
  914. language-code->name
  915. language-code->native-name
  916. seconds->string))
  917. (define (normalize language) ;XXX: deduplicate
  918. ;; Normalize LANGUAGE. For instance, "zh_CN" becomes "zh-cn".
  919. (string-map (match-lambda
  920. (#\_ #\-)
  921. (chr chr))
  922. (string-downcase language)))
  923. (define-syntax-rule (with-language language exp ...)
  924. (let ((lang (getenv "LANGUAGE")))
  925. (dynamic-wind
  926. (lambda ()
  927. (setenv "LANGUAGE" language)
  928. (setlocale LC_MESSAGES))
  929. (lambda () exp ...)
  930. (lambda ()
  931. (if lang
  932. (setenv "LANGUAGE" lang)
  933. (unsetenv "LANGUAGE"))
  934. (setlocale LC_MESSAGES)))))
  935. ;; (put 'with-language 'scheme-indent-function 1)
  936. (define* (translate str language
  937. #:key (domain "guix-manual"))
  938. (define exp
  939. `(begin
  940. (bindtextdomain "guix-manual"
  941. #+(guix-manual-text-domain source))
  942. (bindtextdomain "iso_639-3" ;language names
  943. #+(file-append iso-codes
  944. "/share/locale"))
  945. (setenv "LANGUAGE" ,language)
  946. (write (gettext ,str ,domain))))
  947. ;; Since the 'gettext' function caches msgid translations,
  948. ;; regardless of $LANGUAGE, we have to spawn a new process each
  949. ;; time we want to translate to a different language. Bah!
  950. (let* ((pipe (open-pipe* OPEN_READ
  951. #+(file-append guile-3.0
  952. "/bin/guile")
  953. "-c" (object->string exp)))
  954. (str (read pipe)))
  955. (close-pipe pipe)
  956. str))
  957. (define %iso639-languages
  958. (vector->list
  959. (assoc-ref (call-with-input-file
  960. #+(file-append iso-codes
  961. "/share/iso-codes/json/iso_639-3.json")
  962. json->scm)
  963. "639-3")))
  964. (define (language-code->name code)
  965. "Return the full name of a language from its ISO-639-3 code."
  966. (let ((code (match (string-index code #\_)
  967. (#f code)
  968. (index (string-take code index)))))
  969. (any (lambda (language)
  970. (and (string=? (or (assoc-ref language "alpha_2")
  971. (assoc-ref language "alpha_3"))
  972. code)
  973. (assoc-ref language "name")))
  974. %iso639-languages)))
  975. (define (language-code->native-name code)
  976. "Return the name of language CODE in that language."
  977. (translate (language-code->name code) code
  978. #:domain "iso_639-3"))
  979. (define (seconds->string seconds language)
  980. (let* ((time (make-time time-utc 0 seconds))
  981. (date (time-utc->date time)))
  982. (with-language language (date->string date "~e ~B ~Y")))))))
  983. (scheme-file "localization.scm" content))
  984. (define* (html-manual-indexes source
  985. #:key (languages %languages)
  986. (version "0.0")
  987. (manual %manual)
  988. (title (if (string=? "guix" manual)
  989. "GNU Guix Reference Manual"
  990. "GNU Guix Cookbook"))
  991. (date 1))
  992. (define build
  993. (with-imported-modules `((guix build utils)
  994. ((localization)
  995. => ,(localization-helper-module
  996. source languages)))
  997. #~(begin
  998. (use-modules (guix build utils)
  999. (localization)
  1000. (sxml simple)
  1001. (srfi srfi-1))
  1002. (define (guix-url path)
  1003. (string-append #$%web-site-url path))
  1004. (define (sxml-index language title body)
  1005. ;; FIXME: Avoid duplicating styling info from guix-artwork.git.
  1006. `(html (@ (lang ,language))
  1007. (head
  1008. (title ,(string-append title " — GNU Guix"))
  1009. (meta (@ (charset "UTF-8")))
  1010. (meta (@ (name "viewport") (content "width=device-width, initial-scale=1.0")))
  1011. ;; Menu prefetch.
  1012. (link (@ (rel "prefetch") (href ,(guix-url "menu/index.html"))))
  1013. ;; Base CSS.
  1014. (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/elements.css"))))
  1015. (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/common.css"))))
  1016. (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/messages.css"))))
  1017. (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/navbar.css"))))
  1018. (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/breadcrumbs.css"))))
  1019. (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/buttons.css"))))
  1020. (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/footer.css"))))
  1021. (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/page.css"))))
  1022. (link (@ (rel "stylesheet") (href ,(guix-url "static/base/css/post.css")))))
  1023. (body
  1024. (header (@ (class "navbar"))
  1025. (h1 (a (@ (class "branding")
  1026. (href #$%web-site-url)))
  1027. (span (@ (class "a11y-offset"))
  1028. "Guix"))
  1029. (nav (@ (class "menu"))))
  1030. (nav (@ (class "breadcrumbs"))
  1031. (a (@ (class "crumb")
  1032. (href #$%web-site-url))
  1033. "Home"))
  1034. ,body
  1035. (footer))))
  1036. (define (language-index language)
  1037. (define title
  1038. (translate #$title language))
  1039. (sxml-index
  1040. language title
  1041. `(main
  1042. (article
  1043. (@ (class "page centered-block limit-width"))
  1044. (h2 ,title)
  1045. (p (@ (class "post-metadata centered-text"))
  1046. #$version " — "
  1047. ,(seconds->string #$date language))
  1048. (div
  1049. (ul
  1050. (li (a (@ (href "html_node"))
  1051. "HTML, with a separate page per node"))
  1052. (li (a (@ (href
  1053. ,(string-append
  1054. #$manual
  1055. (if (string=? language
  1056. "en")
  1057. ""
  1058. (string-append "."
  1059. language))
  1060. ".html")))
  1061. "HTML, entirely on one page"))
  1062. ,@(if (member language '("ru" "zh_CN"))
  1063. '()
  1064. `((li (a (@ (href ,(string-append
  1065. #$manual
  1066. (if (string=? language "en")
  1067. ""
  1068. (string-append "."
  1069. language))
  1070. ".pdf"))))
  1071. "PDF")))))))))
  1072. (define (top-level-index languages)
  1073. (define title #$title)
  1074. (sxml-index
  1075. "en" title
  1076. `(main
  1077. (article
  1078. (@ (class "page centered-block limit-width"))
  1079. (h2 ,title)
  1080. (div
  1081. "This document is available in the following
  1082. languages:\n"
  1083. (ul
  1084. ,@(map (lambda (language)
  1085. `(li (a (@ (href ,(normalize language)))
  1086. ,(language-code->native-name language))))
  1087. languages)))))))
  1088. (define (write-html file sxml)
  1089. (call-with-output-file file
  1090. (lambda (port)
  1091. (display "<!DOCTYPE html>\n" port)
  1092. (sxml->xml sxml port))))
  1093. (setenv "GUIX_LOCPATH"
  1094. #+(file-append glibc-utf8-locales "/lib/locale"))
  1095. (setenv "LC_ALL" "en_US.utf8")
  1096. (setlocale LC_ALL "en_US.utf8")
  1097. (for-each (lambda (language)
  1098. (define directory
  1099. (string-append #$output "/"
  1100. (normalize language)))
  1101. (mkdir-p directory)
  1102. (write-html (string-append directory "/index.html")
  1103. (language-index language)))
  1104. '#$languages)
  1105. (write-html (string-append #$output "/index.html")
  1106. (top-level-index '#$languages)))))
  1107. (computed-file "html-indexes" build))
  1108. (define* (pdf+html-manual source
  1109. #:key (languages %languages)
  1110. (version "0.0")
  1111. (date (time-second (current-time time-utc)))
  1112. (mono-node-indexes (map list %languages))
  1113. (split-node-indexes (map list %languages))
  1114. (manual %manual))
  1115. "Return the union of the HTML and PDF manuals, as well as the indexes."
  1116. (directory-union (string-append manual "-manual")
  1117. (map (lambda (proc)
  1118. (proc source
  1119. #:date date
  1120. #:languages languages
  1121. #:version version
  1122. #:manual manual))
  1123. (list html-manual-indexes
  1124. (lambda (source . args)
  1125. (apply html-manual source
  1126. #:mono-node-indexes mono-node-indexes
  1127. #:split-node-indexes split-node-indexes
  1128. args))
  1129. pdf-manual))
  1130. #:copy? #t))
  1131. (define (latest-commit+date directory)
  1132. "Return two values: the last commit ID (a hex string) for DIRECTORY, and its
  1133. commit date (an integer)."
  1134. (let* ((repository (repository-open directory))
  1135. (head (repository-head repository))
  1136. (oid (reference-target head))
  1137. (commit (commit-lookup repository oid)))
  1138. ;; TODO: Use (git describe) when it's widely available.
  1139. (values (oid->string oid) (commit-time commit))))
  1140. ;;;
  1141. ;;; Guile manual.
  1142. ;;;
  1143. (define guile-manual
  1144. ;; The Guile manual as HTML, including both the mono-node "guile.html" and
  1145. ;; the split-node "html_node" directory.
  1146. (let ((guile guile-3.0-latest))
  1147. (computed-file (string-append "guile-manual-" (package-version guile))
  1148. (with-imported-modules '((guix build utils))
  1149. #~(begin
  1150. (use-modules (guix build utils)
  1151. (ice-9 match))
  1152. (setenv "PATH"
  1153. (string-append #+tar "/bin:"
  1154. #+xz "/bin:"
  1155. #+texinfo "/bin"))
  1156. (invoke "tar" "xf" #$(package-source guile))
  1157. (mkdir-p (string-append #$output "/en/html_node"))
  1158. (let* ((texi (find-files "." "^guile\\.texi$"))
  1159. (documentation (match texi
  1160. ((file) (dirname file)))))
  1161. (with-directory-excursion documentation
  1162. (invoke "makeinfo" "--html" "--no-split"
  1163. "-o" (string-append #$output
  1164. "/en/guile.html")
  1165. "guile.texi")
  1166. (invoke "makeinfo" "--html" "-o" "split"
  1167. "guile.texi")
  1168. (copy-recursively
  1169. "split"
  1170. (string-append #$output "/en/html_node")))))))))
  1171. (define %guile-manual-base-url
  1172. "https://www.gnu.org/software/guile/manual")
  1173. (define (for-all-languages index)
  1174. (map (lambda (language)
  1175. (list language index))
  1176. %languages))
  1177. (define guile-mono-node-indexes
  1178. ;; The Guile manual is only available in English so use the same index in
  1179. ;; all languages.
  1180. (for-all-languages
  1181. (html-manual-identifier-index (file-append guile-manual "/en")
  1182. %guile-manual-base-url
  1183. #:name "guile-html-index-en")))
  1184. (define guile-split-node-indexes
  1185. (for-all-languages
  1186. (html-manual-identifier-index (file-append guile-manual "/en/html_node")
  1187. (string-append %guile-manual-base-url
  1188. "/html_node")
  1189. #:name "guile-html-index-en")))
  1190. (define (merge-index-alists alist1 alist2)
  1191. "Merge ALIST1 and ALIST2, both of which are list of tuples like:
  1192. (LANGUAGE INDEX1 INDEX2 ...)
  1193. where LANGUAGE is a string like \"en\" and INDEX1 etc. are indexes as returned
  1194. by 'html-identifier-indexes'."
  1195. (let ((languages (delete-duplicates
  1196. (append (match alist1
  1197. (((languages . _) ...)
  1198. languages))
  1199. (match alist2
  1200. (((languages . _) ...)
  1201. languages))))))
  1202. (map (lambda (language)
  1203. (cons language
  1204. (append (or (assoc-ref alist1 language) '())
  1205. (or (assoc-ref alist2 language) '()))))
  1206. languages)))
  1207. (let* ((root (canonicalize-path
  1208. (string-append (current-source-directory) "/..")))
  1209. (commit date (latest-commit+date root))
  1210. (version (or (getenv "GUIX_MANUAL_VERSION")
  1211. (string-take commit 7)))
  1212. (select? (let ((vcs? (git-predicate root)))
  1213. (lambda (file stat)
  1214. (and (vcs? file stat)
  1215. ;; Filter out this file.
  1216. (not (string=? (basename file) "build.scm"))))))
  1217. (source (local-file root "guix" #:recursive? #t
  1218. #:select? select?)))
  1219. (define guix-manual
  1220. (html-manual source
  1221. #:manual "guix"
  1222. #:version version
  1223. #:date date))
  1224. (define guix-mono-node-indexes
  1225. ;; Alist of indexes for GUIX-MANUAL, where each key is a language code and
  1226. ;; each value is a file-like object containing the identifier index.
  1227. (html-identifier-indexes guix-manual ""
  1228. #:manual-name "guix"
  1229. #:base-url (if (string=? %manual "guix")
  1230. (const "")
  1231. (cut string-append
  1232. "/manual/devel/" <>))
  1233. #:languages %languages))
  1234. (define guix-split-node-indexes
  1235. ;; Likewise for the split-node variant of GUIX-MANUAL.
  1236. (html-identifier-indexes guix-manual "/html_node"
  1237. #:manual-name "guix"
  1238. #:base-url (if (string=? %manual "guix")
  1239. (const "")
  1240. (cut string-append
  1241. "/manual/devel/" <>
  1242. "/html_node"))
  1243. #:languages %languages))
  1244. (define mono-node-indexes
  1245. (merge-index-alists guix-mono-node-indexes guile-mono-node-indexes))
  1246. (define split-node-indexes
  1247. (merge-index-alists guix-split-node-indexes guile-split-node-indexes))
  1248. (format (current-error-port)
  1249. "building manual from work tree around commit ~a, ~a~%"
  1250. commit
  1251. (let* ((time (make-time time-utc 0 date))
  1252. (date (time-utc->date time)))
  1253. (date->string date "~e ~B ~Y")))
  1254. (pdf+html-manual source
  1255. ;; Always use the identifier indexes of GUIX-MANUAL and
  1256. ;; GUILE-MANUAL. Both "guix" and "guix-cookbook" can
  1257. ;; contain links to definitions that appear in either of
  1258. ;; these two manuals.
  1259. #:mono-node-indexes mono-node-indexes
  1260. #:split-node-indexes split-node-indexes
  1261. #:version version
  1262. #:date date))