;; Insert a bibliography into a Markdown file, to produce XHTML or basic Markdown.
;;
;; This version uses the built-in support for (RMarkdown-style) citations
;; (cf, <https://bookdown.org/yihui/rmarkdown-cookbook/bibliography.html>).
;;
;; Usage:
;;
;;     beastie bibliography-in-markdown.scm [-bms] [-B bib.xml] [-A foo.aux] foo.md bibfile.bib
;;
;; With option `-b`, output the document body only, as XML, rather than a complete
;; XHTML document.
;;
;; With option `-s`, write out the document as an xexpr, rather than X(HT)ML
;;
;; With option `-m`, write out the document as Markdown (this is currently rather unreliable).
;;
;; With option `-B bib.xml`, write out a separate copy of the bibliography.
;;
;; With option `-A foo.aux`, write out a synthetic .aux file
;; corresponding to the citations found in the document.
;;
;; The citations are indicated by [@ref],
;; and the .bib database by
;;
;;     ----
;;     bibliography: foo.bib
;;
;; This works with beastie v0.14.
;; The functions it uses, and their interface, may change with later versions.
;;
;; This is intended to be a simple demo.  It has limitations:
;;
;;   * It currently supports only a subset of types explicitly.
;;   * There's no discrimination between colliding author-year pairs
;;     (ie, no 1905a/b/c)
;;   * No distinction between (the equivalents of) \cite, \citep, \citeyear
;;
;; Respects BIBINPUTS.
;;
;; This file is part of Beastie <https://purl.org/nxg/dist/beastie>
;; SPDX-FileCopyrightText: 2023 Norman Gray <https://nxg.me.uk>
;; SPDX-License-Identifier: BSD-2-Clause

(module 'markdown 'xexpr 'bibtex)

(define (Usage)
  (eprintf #"""Insert a bibliography into a Markdown file.
           The citations are identified by [@ref].

           Usage:
               beastie ~a [-bms] foo.md [database.bib ...]

           If option '-b' is present, then write out only the document body,
           in <div/>; if it is absent, then write a complete XHTML document.
           The bibliography can be specified either on the command line,
           or in a `bibliography` key in the metadata at the top of the input file.
           Respects the value of $BIBINPUTS.""" (car *command-line*))
  (exit 1))

(define document-output? #t)
(define output-format 'xml)
(define bib-output-file #f)
(define copy-aux-file #f)

;; Identify *INPUT-MARKDOWN* and *BIBTEX-DATABASES* from the command-line.
;; Set other options.
(define-values (*input-markdown* *bibtex-databases*)
  (receive (cmd opts args)
      (getopt '((#\b
                 "Produce only the body of an XHTML document, in <div/>, rather than a complete one"
                 (set! document-output? #f))
                (#\m
                 "Write out (basic) Markdown, rather than XML"
                 (set! output-format 'md))
                (#\s
                 "Write out a scheme xexpr, rather than X(HT)ML"
                 (set! output-format 'xexpr))
                (#\B bibfile
                 "Write a separate copy of the bibliography to the given file"
                 (set! bib-output-file bibfile))
                (#\A auxfile
                 "Write a synthetic .aux file."
                 (set! copy-aux-file auxfile))))
    (if (>= (length args) 1)
        (values (car args) (cdr args))
        (Usage))))

;; load plain.scm, which exports assemble-bibliography
(module "plain.scm")

;; Let's go...
(receive (parse-tree metadata)
    (parse-markdown-file/metadata *input-markdown*)
  (unless parse-tree
    (beastie-error "can't parse markdown file ~a" *input-markdown*))
  ;; parse-tree is a sexp representation of the input Markdown:
  ;;     eg (div (p "This is a " (a ((href "http://example.org")) "link")) ...)
  ;; metadata is information about the content of the file (links and annotations)
  ;;     which can be queried with function METADATA/TYPE

  ;; METADATA/TYPE with 'citation returns a list of lists -- see docs for that function
  (let ((citation-elements
         ;; a (string? -> list?) hash table, where the values are
         ;; elements in the parse markdown tree
         (metadata/type metadata 'citation))
        (annotations (apply hash-table (apply append (metadata/type metadata 'annotation)))))
    (when (annotations "bibliography")
      (set! *bibtex-databases*
            (cons (annotations "bibliography")
                  *bibtex-databases*)))

    (when copy-aux-file
      (with-output-to-file copy-aux-file
        (λ ()
          (printf "\\relax~%")
          (for-each (λ (cite)
                      (printf "\\citation{~a}~%" cite))
                    (map car citation-elements))
          (when (annotations "bibliography")
            (printf "\\bibdata{~a}~%" (annotations "bibliography"))))))

    (let* ((bib (assemble-bibliography
                 ;;...from plain.scm; produces a list of citation? objects
                 (map car citation-elements)
                 (map (λ (bib)
                        (resolve-file bib ".bib"))
                      *bibtex-databases*)))
           (citations/key
            ;; make a key -> reference lookup, as a (symbol? -> string?) hash table
            (apply hash-table
                   (apply append
                          (map (λ (citation)
                                 (list (citation-key citation) (citation-reference citation)))
                               bib)))))

      ;; Now modify the parse-tree, by running through the CITATION-ELEMENTS list,
      ;; editing the (cite "foo") elements it refers to.
      ;; An alternative approach would be to map tree->tree, producing
      ;; a new document.
      (for-each (λ (c)                  ;c is (ref (cite "foo"))
                  (let* ((ref (car c))
                         (el (cadr c))
                         (citation (citations/key ref)))
                    (unless citation
                      (eprintf "Undefined citation key: ~a~%" ref))
                    ;; now edit the tree in place
                    (set-cdr! el `((a ((href ,(sprintf "#~a" ref)))
                                      ,(or citation "???"))))))
                citation-elements)

      ;; ...and write out the result
      (let ((output-xexprs
             `(,@(cdr parse-tree)
               (h2 "Bibliography")
               (ul . ,(map citation-html bib)))))
        (case output-format
          ((xml)
           (if document-output?
               (let ((title (or (annotations "title") "TITLE")))
                 (xexpr-write/xhtml!
                  `(div
                    (h1 ,title)
                    . ,output-xexprs)))
               (xexpr-write/xml!
                `(div . ,output-xexprs))))
          ((xexpr)
           ;; dump the xexpr using built-on WRITE,
           ;; but telling s7 not to crop lists while doing so
           (let-temporarily (((*s7* 'print-length) (*s7* 'max-list-length)))
             (for-each (λ (xe)
                         (write xe)
                         (newline))
                       output-xexprs)))
          ((md)
           (xexpr-write/md! output-xexprs))
          (else
           (eprintf "Unexpected output format: ~s~%" output-format))))

      (when bib-output-file
        (with-output-to-file bib-output-file
          (λ ()
            (xexpr-write/xml!
             `(ul . ,(map citation-html bib)))))))))
