dotemacs

My Emacs configuration
git clone git://git.entf.net/dotemacs
Log | Files | Refs | LICENSE

editorconfig-core-handle.el (9213B)


      1 ;;; editorconfig-core-handle.el --- Handle Class for EditorConfig File  -*- lexical-binding: t -*-
      2 
      3 ;; Copyright (C) 2011-2023 EditorConfig Team
      4 
      5 ;; Author: EditorConfig Team <editorconfig@googlegroups.com>
      6 
      7 ;; See
      8 ;; https://github.com/editorconfig/editorconfig-emacs/graphs/contributors
      9 ;; or the CONTRIBUTORS file for the list of contributors.
     10 
     11 ;; This file is part of EditorConfig Emacs Plugin.
     12 
     13 ;; EditorConfig Emacs Plugin is free software: you can redistribute it and/or
     14 ;; modify it under the terms of the GNU General Public License as published by
     15 ;; the Free Software Foundation, either version 3 of the License, or (at your
     16 ;; option) any later version.
     17 
     18 ;; EditorConfig Emacs Plugin is distributed in the hope that it will be useful,
     19 ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
     20 ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
     21 ;; Public License for more details.
     22 
     23 ;; You should have received a copy of the GNU General Public License along with
     24 ;; EditorConfig Emacs Plugin. If not, see <https://www.gnu.org/licenses/>.
     25 
     26 ;;; Commentary:
     27 
     28 ;; Handle structures for EditorConfig config file.  This library is used
     29 ;; internally from editorconfig-core.el .
     30 
     31 ;;; Code:
     32 
     33 (require 'cl-lib)
     34 
     35 (require 'editorconfig-fnmatch)
     36 
     37 (defvar editorconfig-core-handle--cache-hash
     38   (make-hash-table :test 'equal)
     39   "Hash of EditorConfig filename and its `editorconfig-core-handle' instance.")
     40 
     41 (cl-defstruct editorconfig-core-handle-section
     42   "Structure representing one section in a .editorconfig file.
     43 
     44 Slots:
     45 
     46 `name'
     47   String of section name (glob string).
     48 
     49 `props'
     50   Alist of properties: (KEY . VALUE)."
     51   (name nil)
     52   (props nil))
     53 
     54 (defun editorconfig-core-handle-section-get-properties (section file dir)
     55   "Return properties alist when SECTION name match FILE.
     56 
     57 DIR should be the directory where .editorconfig file which has SECTION lives.
     58 IF not match, return nil."
     59   (when (editorconfig-core-handle--fnmatch-p
     60          file (editorconfig-core-handle-section-name section) dir)
     61     (editorconfig-core-handle-section-props section)))
     62 
     63 (cl-defstruct editorconfig-core-handle
     64   "Structure representing an .editorconfig file.
     65 
     66 Slots:
     67 `top-props'
     68   Alist of top properties like ((\"root\" . \"true\"))
     69 
     70 `sections'
     71   List of `editorconfig-core-handle-section' structure objects.
     72 
     73 `mtime'
     74   Last modified time of .editorconfig file.
     75 
     76 `path'
     77   Absolute path to .editorconfig file.'
     78 "
     79   (top-props nil)
     80   (sections nil)
     81   (mtime nil)
     82   (path nil))
     83 
     84 
     85 (defun editorconfig-core-handle (conf)
     86   "Return EditorConfig handle for CONF, which should be a file path.
     87 
     88 If CONF does not exist return nil."
     89   (when (file-readable-p conf)
     90     (let ((cached (gethash conf editorconfig-core-handle--cache-hash))
     91           (mtime (nth 5 (file-attributes conf))))
     92       (if (and cached
     93                (equal (editorconfig-core-handle-mtime cached) mtime))
     94           cached
     95         (let ((parsed (editorconfig-core-handle--parse-file conf)))
     96           (puthash conf
     97                    (make-editorconfig-core-handle :top-props (plist-get parsed :top-props)
     98                                                   :sections (plist-get parsed :sections)
     99                                                   :mtime mtime
    100                                                   :path conf)
    101                    editorconfig-core-handle--cache-hash))))))
    102 
    103 (defun editorconfig-core-handle-root-p (handle)
    104   "Return non-nil if HANDLE represent root EditorConfig file.
    105 
    106 If HANDLE is nil return nil."
    107   (when handle
    108     (string-equal "true"
    109                   (downcase (or (cdr (assoc "root"
    110                                             (editorconfig-core-handle-top-props handle)))
    111                                 "")))))
    112 
    113 (defun editorconfig-core-handle-get-properties (handle file)
    114   "Return list of alist of properties from HANDLE for FILE.
    115 The list returned will be ordered by the lines they appear.
    116 
    117 If HANDLE is nil return nil."
    118   (when handle
    119     (let ((dir (file-name-directory (editorconfig-core-handle-path handle))))
    120       (cl-loop for section in (editorconfig-core-handle-sections handle)
    121                for props = (editorconfig-core-handle-section-get-properties section
    122                                                                             file
    123                                                                             dir)
    124                when props collect (copy-alist props)))))
    125 (make-obsolete 'editorconfig-core-handle-get-properties
    126                'editorconfig-core-handle-get-properties-hash
    127                "0.8.0")
    128 
    129 
    130 (defun editorconfig-core-handle-get-properties-hash (handle file)
    131   "Return hash of properties from HANDLE for FILE.
    132 
    133 If HANDLE is nil return nil."
    134   (when handle
    135     (let ((hash (make-hash-table))
    136           (dir (file-name-directory (editorconfig-core-handle-path
    137                                      handle))))
    138       (dolist (section (editorconfig-core-handle-sections handle))
    139         (cl-loop for (key . value) in (editorconfig-core-handle-section-get-properties section file dir)
    140                  do (puthash (intern key) value hash)))
    141       hash)))
    142 
    143 (defun editorconfig-core-handle--fnmatch-p (name pattern dir)
    144   "Return non-nil if NAME match PATTERN.
    145 If pattern has slash, pattern should be relative to DIR.
    146 
    147 This function is a fnmatch with a few modification for EditorConfig usage."
    148   (if (string-match-p "/" pattern)
    149       (let ((pattern (replace-regexp-in-string "^/" "" pattern))
    150             (dir (file-name-as-directory dir)))
    151         (editorconfig-fnmatch-p name (concat dir pattern)))
    152     (editorconfig-fnmatch-p name (concat "**/" pattern))))
    153 
    154 (defsubst editorconfig-core-handle--string-trim (str)
    155   "Remove leading and trailing whitespaces from STR."
    156   (replace-regexp-in-string "[[:space:]]+\\'"
    157                             ""
    158                             (replace-regexp-in-string "\\`[[:space:]]+"
    159                                                       ""
    160                                                       str)))
    161 
    162 (defun editorconfig-core-handle--parse-file (conf)
    163   "Parse EditorConfig file CONF.
    164 
    165 This function returns cons of its top properties alist and
    166 alist of patterns and its properties alist.
    167 The list returned will be ordered by the lines they appear.
    168 
    169 If CONF is not found return nil."
    170   (when (file-readable-p conf)
    171     (with-temp-buffer
    172       ;; NOTE: Use this instead of insert-file-contents-literally to enable
    173       ;; code conversion
    174       (insert-file-contents conf)
    175       (goto-char (point-min))
    176       (let ((point-max (point-max))
    177             (sections ())
    178             (top-props nil)
    179 
    180             ;; String of current line
    181             (line "")
    182             ;; nil when pattern not appeared yet, "" when pattern is empty ("[]")
    183             (pattern nil)
    184             ;; Alist of properties for current PATTERN
    185             (props ())
    186 
    187             ;; Current line num
    188             (current-line-number 1))
    189         (while (not (eq (point) point-max))
    190           (setq line
    191                 (buffer-substring-no-properties (line-beginning-position)
    192                                                 (line-end-position)))
    193           (setq line
    194                 (replace-regexp-in-string "\\(^\\| \\)\\(#\\|;\\).*$"
    195                                           ""
    196                                           (editorconfig-core-handle--string-trim line)))
    197 
    198           (cond
    199            ((string-equal "" line)
    200             nil)
    201 
    202            ;; Start of section
    203            ((string-match "^\\[\\(.*\\)\\]$"
    204                           line)
    205             (when pattern
    206               (setq sections
    207                     `(,@sections ,(make-editorconfig-core-handle-section
    208                                    :name pattern
    209                                    :props props)))
    210               (setq pattern nil)
    211               (setq props nil))
    212             (setq pattern (match-string 1 line)))
    213 
    214            (t
    215             (let ((idx (string-match "=\\|:" line)))
    216               (unless idx
    217                 (error "Error while reading config file: %s:%d:\n    %s\n"
    218                        conf current-line-number line))
    219               (let ((key (downcase (editorconfig-core-handle--string-trim
    220                                     (substring line 0 idx))))
    221                     (value (editorconfig-core-handle--string-trim
    222                             (substring line (1+ idx)))))
    223                 (when (and (< (length key) 51)
    224                            (< (length value) 256))
    225                   (if pattern
    226                       (when (< (length pattern) 4097)
    227                         (setq props
    228                               `(,@props (,key . ,value))))
    229                     (setq top-props
    230                           `(,@top-props (,key . ,value)))))))))
    231           (setq current-line-number (1+ current-line-number))
    232           (goto-char (point-min))
    233           (forward-line (1- current-line-number)))
    234         (when pattern
    235           (setq sections
    236                 `(,@sections ,(make-editorconfig-core-handle-section
    237                                :name pattern
    238                                :props props))))
    239         (list :top-props top-props
    240               :sections sections)))))
    241 
    242 (provide 'editorconfig-core-handle)
    243 ;;; editorconfig-core-handle.el ends here