;;; bui.el --- Buffer interface library  -*- lexical-binding: t -*-

;; Copyright © 2014-2018 Alex Kost <alezost@gmail.com>

;; Author: Alex Kost <alezost@gmail.com>
;; Package-Version: 20210108.1141
;; Package-Revision: ab62fcefc3c7
;; URL: https://github.com/alezost/bui.el
;; Keywords: tools
;; Package-Requires: ((emacs "24.3") (dash "2.11.0"))

;; 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 <http://www.gnu.org/licenses/>.

;;; Commentary:

;; BUI (Buffer User Interface) is a library for making 'list' (similar
;; to "M-x list-packages") and 'info' (similar to customization buffers)
;; interfaces to display various data (packages, buffers, functions,
;; etc.).
;;
;; It is not an end-user package, it is a library that is intended to be
;; used by other packages.
;;
;; Basically, at first you define 'list'/'info' interface using
;; `bui-define-interface' macro, and then you can make user commands
;; that will display entries using `bui-get-display-entries' and similar
;; functions.
;;
;; See README at <https://github.com/alezost/bui.el> for more details.

;;; Code:

(require 'cl-lib)

;; Require all features, so a package maker can require only `bui'.
(require 'bui-button)
(require 'bui-core)
(require 'bui-entry)
(require 'bui-info)
(require 'bui-list)
(require 'bui-utils)

(defmacro bui-define-entry-type (entry-type &rest args)
  "Define variables for ENTRY-TYPE.
ARGS can be the same arguments as for `bui-define-interface'.
The difference is: arguments for `bui-define-interface' define
specific variables for different buffer types, while this macro
defines general variables used for any buffer type."
  (declare (indent 1))
  (bui-plist-let args
      ((reduced? :reduced?))
    `(progn
       ,@(bui-map-symbol-specifications
          (lambda (key suffix generate)
            (let ((val (plist-get %foreign-args key)))
              (when (or val (bui-symbol-generate? generate reduced?))
                (bui-inherit-defvar-clause
                 (bui-entry-symbol entry-type suffix)
                 (bui-make-symbol 'bui suffix)
                 :value val
                 :group entry-type))))
          bui-entry-symbol-specifications)

       ,@(bui-map-symbol-specifications
          (lambda (key suffix _generate)
            (let ((val (plist-get %foreign-args key)))
              (when val
                (bui-inherit-defvar-clause
                 (bui-entry-symbol entry-type suffix)
                 (bui-make-symbol 'bui suffix)
                 :value val
                 :group entry-type))))
          bui-symbol-specifications))))

(defmacro bui-define-interface (entry-type buffer-type &rest args)
  "Define BUFFER-TYPE interface for displaying ENTRY-TYPE entries.
Remaining arguments ARGS should have a form [KEYWORD VALUE] ...
They are used to generate variables specific for the defined
interface.  For more details and the available keywords, see
`bui-symbol-specifications', `bui-entry-symbol-specifications'
and `bui-BUFFER-TYPE-symbol-specifications'.

`:get-entries-function' is the only required keyword (if the
interface is reduced, all keywords become optional).

To denote that the interface is reduced, a special `:reduced?'
keyword may be specified.  If it is non-nil, generate only
customization group, faces group and specified variables.  If it
is nil, along with the mentioned groups and variables,
`ENTRY-TYPE-BUFFER-TYPE-mode' will be generated."
  (declare (indent 2))
  (cl-flet ((name (&rest symbols)
              (apply #'bui-symbol entry-type buffer-type symbols))
            (bui-name (&rest symbols)
              (apply #'bui-make-symbol 'bui symbols)))
    (let ((group              (name))
          (faces-group        (name 'faces))
          (mode               (name 'mode))
          (mode-map           (name 'mode-map))
          (bui-buffer-type    (bui-name buffer-type))
          (symbol-fun         (bui-name buffer-type 'symbol))
          (symbol-specs       (bui-name buffer-type 'symbol-specifications))
          (parent-mode        (bui-name buffer-type 'mode)))
      (bui-plist-let args
          ((mode-name         :mode-name (capitalize (symbol-name group)))
           (reduced?          :reduced?))
        `(progn
           (defgroup ,group nil
             ,(format "Displaying '%S' entries in '%S' buffer."
                      entry-type buffer-type)
             :group ',entry-type
             :group ',bui-buffer-type)

           (defgroup ,faces-group nil
             ,(format "Faces for displaying '%S' entries in '%S' buffer."
                      entry-type buffer-type)
             :group ',group
             :group ',(bui-entry-symbol entry-type 'faces)
             :group ',(bui-name buffer-type 'faces))

           ,@(bui-map-symbol-specifications
              (lambda (key suffix generate)
                (let ((val (plist-get %foreign-args key)))
                  (when (or val (bui-symbol-generate? generate reduced?))
                    (bui-inherit-defvar-clause
                     (name suffix)
                     (bui-name suffix)
                     :value val
                     :group group))))
              bui-symbol-specifications)

           ,@(bui-map-symbol-specifications
              (lambda (key suffix _generate)
                (let ((val (plist-get %foreign-args key)))
                  (when val
                    (bui-inherit-defvar-clause
                     (name suffix)
                     (bui-name suffix)
                     :value val
                     :group group))))
              bui-entry-symbol-specifications)

           ,@(bui-map-symbol-specifications
              (lambda (key suffix generate)
                (let ((val (plist-get args key)))
                  (when (or val (bui-symbol-generate? generate reduced?))
                    (bui-inherit-defvar-clause
                     (funcall symbol-fun entry-type suffix)
                     (bui-name buffer-type suffix)
                     :value val
                     :group group))))
              (symbol-value symbol-specs))

           ,(unless reduced?
              `(define-derived-mode ,mode ,parent-mode
                 '(,mode-name (bui-active-filter-predicates
                               bui-filter-mode-line-string))
                 ,(format "\
Major mode for displaying '%S' entries in '%S' buffer.

\\{%S}"
                          entry-type buffer-type mode-map)
                 (bui-mode-initialize ',entry-type ',buffer-type)))

           (bui-register-interface ',entry-type ',buffer-type))))))

(provide 'bui)

;;; bui.el ends here
