;;; hib-doc-id.el --- Implicit button type for document id index entries  -*- lexical-binding: t; -*-
;;
;; Author:       Bob Weiner
;;
;; Orig-Date:    30-Sep-92 at 19:39:59
;; Last-Mod:     27-Aug-23 at 20:21:50 by Bob Weiner
;;
;; SPDX-License-Identifier: GPL-3.0-or-later
;;
;; Copyright (C) 1991-2022  Free Software Foundation, Inc.
;; See the "HY-COPY" file for license information.
;;
;; This file is part of GNU Hyperbole.

;;; Commentary:
;;
;;  This library defines the `doc-id' implicit button type which lets
;;  you create archives of reference documents by topic and then
;;  reference them within any Hyperbole readable file by use of simple
;;  identifiers, like [-Emacs-001-].  It functions like a rapid access
;;  card catalog with easy to embed document identifiers.
;;
;;  TO USE:
;;
;;   Pressing the Action Key on a doc id such as, [-Emacs-001-],
;;   displays the online version of the document, if any.  Pressing the
;;   Assist Key on it displays its document index entry.
;;
;;  TO CONFIGURE:
;;
;;   Set the value of `doc-id-indices' before using the `doc-id'
;;   implicit button type defined herein or you will get an error telling you
;;   to do so.  See the documentation for `doc-id-indices'.
;;
;;   You must explicitly load this library in order to use it, since
;;   Hyperbole does not load it by default.
;;
;;   The default setup uses doc ids of the form, [- Emacs-001 -], delimited
;;   by minus signs inside square brackets, the id starting with a topic name,
;;   followed by a minus sign and a multi-digit numeric identifier.
;;
;;   Typically an index entry should have links to all available forms of its
;;   document, e.g. online, printed, source.  Below is a sample index entry form
;;   to use.  The default variable settings herein work with this format.  If
;;   you prefer a different one, you must change all of the variable values.
;;
;;  --------------------------------------------------------------------------
;;  Title:                                                  ID: [- -]
;;  Email-To:
;;  Distribution:     
;;  
;;  Abstract:         
;;                    
;;                    
;;  References:       
;;  
;;  Author:           
;;  Copyright:        
;;  Keywords:         hypertext
;;  
;;  Online-Format:    
;;  Online-Loc:       ""
;;  Printed-Format:
;;  Printed-Loc:      Local Library
;;  Printable-Loc:    ""
;;  Source-Format:
;;  Source-Loc:       ""
;;  
;;  Date:             
;;  Version:          
;;  Version-Changes:  
;;  --------------------------------------------------------------------------

;;; Code:
;;; ************************************************************************
;;; Other required Elisp libraries
;;; ************************************************************************

(eval-and-compile (mapc #'require '(hactypes hypb hyrolo)))

;;; ************************************************************************
;;; Public variables
;;; ************************************************************************

(defvar doc-id-indices '()
  "List of pathnames in which to search for site-specific document index entries.
Each file must utilize a hyrolo record format, with each record start
delimited by `doc-id-index-entry-regexp'.")

;;; ************************************************************************
;;; Private variables
;;; ************************************************************************

(defvar doc-id-start "[-"
  "String which delimits start of a site-specific document id.")
(defvar doc-id-end   "-]"
  "String which delimits end of a site-specific document id.")

(defvar doc-id-index-entry-regexp "^------+\\s-+Title:"
  "Regexp which matches start of a site-specific document index entry.")

(defvar doc-id-match
  (lambda (doc-id)
    (concat "ID:[ \t]*"
	    (regexp-quote doc-id-start) "[ \t]*" (regexp-quote doc-id)
	    "[ \t]*" (regexp-quote doc-id-end)))
  "Single arg function returning regexp matching within DOC-ID's index entry only.")

(defvar doc-id-p (lambda (str)
		   (and (stringp str)
			(> (length str) 0)
			(eq ?w (char-syntax (aref str 0)))
			(string-match "\\`\\w+-[0-9][0-9][0-9]+\\'" str)))
  "Value is a function with a boolean result that tests whether `str' is a doc id.")

(defvar doc-id-online-regexp "^Online-Loc:[ \t]*\"\\([^\"\t\r\n\f]+\\)\""
  "Regexp with 1st grouping matching a double quoted index entry implicit button.
The implicit button displays an online document.")

;;; ************************************************************************
;;; Public implicit button types
;;; ************************************************************************
  
;;; ========================================================================
;;; Displays a documentation index entry given an ID.
;;; ========================================================================

(defact link-to-doc (doc-id)
  "Display an online version of a document given by DOC-ID (no delimiters).
If online version of document is not found in `doc-id-indices', signal
an error."
  (interactive "sID for document to link to (omit delimiters): ")
  (let ((hyrolo-display-buffer (hypb:help-buf-name "Doc ID"))
	(delim-doc-id (concat doc-id-start doc-id doc-id-end)))
    (cond ((null doc-id-indices)
	   (error "(doc-id-index-entry): You must set the `doc-id-indices' variable first"))
	  ((let ((hyrolo-hdr-and-entry-regexp doc-id-index-entry-regexp))
	     (zerop (hyrolo-grep (funcall doc-id-match doc-id)
				 1 doc-id-indices nil nil 'no-display)))
	   (error "(doc-id-index-entry): %s not found in document index"
		  delim-doc-id))
	  ;; Matching index entry has been put into `hyrolo-display-buffer'.
	  (t (with-current-buffer hyrolo-display-buffer
	       (goto-char (point-min))
	       (message "Searching for document %s..." delim-doc-id)
	       (if (re-search-forward doc-id-online-regexp nil t)
		   (progn
		     (goto-char (match-beginning 1))
		     (let ((doc-path (match-string 1))
			   (ibut (ibut:at-p)))
		       (if ibut
			   (progn (hbut:act ibut)
				  (message "Displaying %s." delim-doc-id))
			 (error
			  "(link-to-doc): %s online location is invalid: \"%s\""
			  delim-doc-id doc-path))))
		 (error "(link-to-doc): %s is unavailable in online form"
			delim-doc-id)))))))

(defib doc-id ()
  "Display a document from a local document library given its id.
Ids must be delimited by `doc-id-start' and `doc-id-end' and must
match the function stored in `doc-id-p'."
  (unless (bolp)
    (let* ((id-and-pos (hbut:label-p t doc-id-start doc-id-end t))
	   (id (car id-and-pos)))
      (when (funcall doc-id-p id)
	(ibut:label-set id-and-pos)
	(hact 'link-to-doc id)))))

;;; ************************************************************************
;;; Private functions
;;; ************************************************************************

(defun doc-id:help (but)
  "Display site-specific document index entry given by doc-id BUT.
Also display standard Hyperbole help for implicit button BUT."
  (let ((hyrolo-hdr-and-entry-regexp doc-id-index-entry-regexp)
	(hyrolo-display-buffer (hypb:help-buf-name "Doc ID"))
	(doc-id (hbut:key-to-label (hattr:get but 'lbl-key))))
    (cond ((null doc-id-indices)
	   (error "(doc-id-index-entry): You must set the `doc-id-indices' variable first"))
	  ((zerop (hyrolo-grep (funcall doc-id-match doc-id) 1 doc-id-indices
			       nil nil 'no-display))
	   (error
	     "(doc-id-index-entry): No document index entry found for %s%s%s"
		  doc-id-start doc-id doc-id-end)))
    (let* ((report-buf (hypb:help-buf-name))
	   (temp-buffer-show-hook
	    (lambda (buffer)
	      (setq hkey--wconfig (current-window-configuration))
	      (let ((wind (get-buffer-create buffer)))
		(setq minibuffer-scroll-window wind))))
	   (temp-buffer-show-function temp-buffer-show-hook))
      (hbut:report but)
      (with-current-buffer hyrolo-display-buffer
	(setq buffer-read-only nil)
	(goto-char (point-max))
	(insert-buffer-substring report-buf)
	(set-buffer-modified-p nil)
	(setq buffer-read-only nil)
	(goto-char (point-min)))
      (kill-buffer report-buf))))

(provide 'hib-doc-id)

;;; hib-doc-id.el ends here
