;;; lice.el --- License And Header Template

;; Copyright (C) 2012  Taiki Sugawara

;; Author: Taiki Sugawara <buzz.taiki@gmail.com>
;; Keywords: template, license, tools
;; URL: https://github.com/buzztaiki/lice-el

;; 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:

;; Overview
;; --------

;; `lice.el` provides following features:

;; - License template management.
;; - File header insertion.

;; Usage
;; -----

;; Usage is very easy, put `lice.el` in your emacs system, and open a new
;; file, and run:

;;     M-x lice

;; Then, `lice.el` tell to use which license (default is gpl-3.0). You
;; can select license on minibuffer completion.

;; When you select license, and enter the `RET`, license and copyright is
;; putted into a text.

;; More Information
;; ----------------

;; See the `README.md` file for more information.

;;; Code:

(require 'cl-lib)
(require 'newcomment)

(defconst lice:version "0.2")

(defconst lice:system-template-directory
  (expand-file-name "template"
                    (or (and load-file-name (file-name-directory load-file-name))
                        default-directory)))

(defvar lice:custom-template-directory
  (expand-file-name "lice" user-emacs-directory))


(defgroup lice nil
  "License And Header Template"
  :prefix "lice:"
  :group 'tools
  :link '(url-link "https://github.com/buzztaiki/lice-el"))

(define-widget 'lice:comment-style 'choice
  "The comment style selection widget."
  :args `(,@(cl-loop for x in comment-styles
                  collect `(const
                            :tag ,(replace-regexp-in-string
                                   "-" " " (capitalize (symbol-name (car x))))
                            ,(car x)))
          (other :tag "Mode Default" nil)))

(defcustom lice:license-directories
  (list lice:system-template-directory lice:custom-template-directory)
  "The location of License template directories"
  :group 'lice
  :type '(repeat directory))

(defcustom lice:comment-style 'extra-line
  "The comment style for license insertion.
When nil, `comment-style' value is used."
  :group 'lice
  :type 'lice:comment-style)

(defcustom lice:default-license "gpl-3.0"
  "The default license name"
  :group 'lice
  :safe 'stringp
  :type 'string)

(defcustom lice:copyright-holder (user-full-name)
  "The copyright holder"
  :group 'lice
  :safe 'stringp
  :type 'string)

(defcustom lice:header-spec '(lice:insert-copyright
                              lice:insert-license)
  "The license header spec.
Each element should be function.
These functions should take one argument, license object, and
should insert header string fragment."
  :group 'lice
  :type '(repeat function))

(defcustom lice:mode-comments
  (append
   (cl-loop for mode in '(c-mode c++-mode java-mode groovy-mode)
         collect (list mode
                       :comment-start "/*"
                       :comment-end "*/"))
   '((nxml-mode :comment-continue "   ")))
  "The definition of mode specific comments.
Each elements are follows:
  \(MODE . PROPERTIES))
Mode is a major-mode which is applied PROPERTIES.
PROPERTIES is a plist whitch has following properties:
  :comment-start - `comment-start' of this MODE.
  :comment-end   - `comment-end' of this MODE.
  :comment-style - `comment-style' of this MODE.
  :comment-continue - `comment-continue' of this MODE.
"
  :group 'lice
  :type '(repeat (cons :format "%v" :indent 9
                       (function :tag "Mode" :size 20)
                       (sexp :tag ""
                             :value-to-internal (lambda (widget value)
                                                  (prin1-to-string value))))))

(defvar lice:license-history nil)

(defun lice:licenses ()
  "Return a license list.
Each element are follows:
\(SIMPLE-NAME . FILE)"
  (cl-loop for dir in lice:license-directories
        with licenses
        if (and dir (file-directory-p dir))
        append (lice:directory-licenses dir) into licenses
        finally return (sort licenses
                             (lambda (a b) (string< (car a) (car b))))))

(defun lice:directory-licenses (dir)
  (cl-loop for file in (directory-files dir t)
        with licenses
        for name = (file-name-nondirectory file)
        if (and (file-regular-p file) (not (assoc name licenses)))
        collect (cons name file)))

;;;###autoload
(defun lice (name)
  "Insert license and headers.
NAME is a template name for insertion."
  (interactive (list (lice:read-license)))
  (let ((license (assoc name (lice:licenses))))
    (unless license
      (error "Unknown license name: %s" name))
    (save-restriction
      (narrow-to-region (point) (point))
      (cl-loop for component in lice:header-spec
            do (progn (funcall component license)
                      (goto-char (point-max))))
      (lice:comment-region (point-min) (point-max) major-mode)
      (goto-char (point-max)))))

(defun lice:insert-copyright (license)
  (insert (format "Copyright (C) %s  %s\n\n"
                  (format-time-string "%Y")  lice:copyright-holder)))

(defun lice:insert-license (license)
  (insert-file-contents (cdr license)))

(defun lice:read-license ()
  (completing-read (format "License Name (%s): " lice:default-license)
                   (lice:licenses)
                   nil t nil 'lice:license-history
                   lice:default-license))

(defun lice:mode-comment (mode)
  (and mode
       (or (cdr (assq mode lice:mode-comments))
           (lice:mode-comment (get mode 'derived-mode-parent)))))

(defun lice:comment-region (start end mode)
  (let* ((comment (lice:mode-comment mode))
         (comment-start (or (plist-get comment :comment-start) comment-start))
         (comment-end (or (plist-get comment :comment-end) comment-end))
         (comment-style (or (plist-get comment :comment-style)
                            lice:comment-style
                            comment-style))
         (comment-continue (or (plist-get comment :comment-continue) comment-continue)))
    (save-restriction
      (narrow-to-region start end)
      (comment-region (point-min) (point-max))
      (delete-trailing-whitespace (point-min) (point-max)))))

(provide 'lice)
;;; lice.el ends here
