dotemacs

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

editorconfig-core-handle.el (9806B)


      1 ;;; editorconfig-core-handle.el --- Handle Class for EditorConfig File  -*- lexical-binding: t -*-
      2 
      3 ;; Copyright (C) 2011-2021 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
     61          (editorconfig-core-handle-section-name section)
     62          dir)
     63     (editorconfig-core-handle-section-props section)))
     64 
     65 (cl-defstruct editorconfig-core-handle
     66   "Structure representing an .editorconfig file.
     67 
     68 Slots:
     69 `top-props'
     70   Alist of top properties like ((\"root\" . \"true\"))
     71 
     72 `sections'
     73   List of `editorconfig-core-handle-section' structure objects.
     74 
     75 `mtime'
     76   Last modified time of .editorconfig file.
     77 
     78 `path'
     79   Absolute path to .editorconfig file.'
     80 "
     81   (top-props nil)
     82   (sections nil)
     83   (mtime nil)
     84   (path nil))
     85 
     86 
     87 (defun editorconfig-core-handle (conf)
     88   "Return EditorConfig handle for CONF, which should be a file path.
     89 
     90 If CONF does not exist return nil."
     91   (when (file-readable-p conf)
     92     (let ((cached (gethash conf
     93                            editorconfig-core-handle--cache-hash))
     94           (mtime (nth 5
     95                       (file-attributes conf))))
     96       (if (and cached
     97                (equal (editorconfig-core-handle-mtime cached)
     98                       mtime))
     99           cached
    100         (let ((parsed (editorconfig-core-handle--parse-file conf)))
    101           (puthash conf
    102                    (make-editorconfig-core-handle :top-props (plist-get parsed :top-props)
    103                                                   :sections (plist-get parsed :sections)
    104                                                   :mtime mtime
    105                                                   :path conf)
    106                    editorconfig-core-handle--cache-hash))))))
    107 
    108 (defun editorconfig-core-handle-root-p (handle)
    109   "Return non-nil if HANDLE represent root EditorConfig file.
    110 
    111 If HANDLE is nil return nil."
    112   (when handle
    113     (string-equal "true"
    114                   (downcase (or (cdr (assoc "root"
    115                                             (editorconfig-core-handle-top-props handle)))
    116                                 "")))))
    117 
    118 (defun editorconfig-core-handle-get-properties (handle file)
    119   "Return list of alist of properties from HANDLE for FILE.
    120 The list returned will be ordered by the lines they appear.
    121 
    122 If HANDLE is nil return nil."
    123   (when handle
    124     (let ((dir (file-name-directory (editorconfig-core-handle-path handle))))
    125       (cl-loop for section in (editorconfig-core-handle-sections handle)
    126                for props = (editorconfig-core-handle-section-get-properties section
    127                                                                             file
    128                                                                             dir)
    129                when props collect (copy-alist props)))))
    130 (make-obsolete 'editorconfig-core-handle-get-properties
    131                'editorconfig-core-handle-get-properties-hash
    132                "0.8.0")
    133 
    134 
    135 (defun editorconfig-core-handle-get-properties-hash (handle file)
    136   "Return hash of properties from HANDLE for FILE.
    137 
    138 If HANDLE is nil return nil."
    139   (when handle
    140     (let ((hash (make-hash-table))
    141           (dir (file-name-directory (editorconfig-core-handle-path
    142                                      handle))))
    143       (dolist (section (editorconfig-core-handle-sections handle))
    144         (cl-loop for (key . value) in (editorconfig-core-handle-section-get-properties section file dir)
    145                  do (puthash (intern key) value hash)))
    146       hash)))
    147 
    148 (defun editorconfig-core-handle--fnmatch-p (name pattern dir)
    149   "Return non-nil if NAME match PATTERN.
    150 If pattern has slash, pattern should be relative to DIR.
    151 
    152 This function is a fnmatch with a few modification for EditorConfig usage."
    153   (if (string-match-p "/" pattern)
    154       (let ((pattern (replace-regexp-in-string "^/"
    155                                                ""
    156                                                pattern))
    157             (dir (file-name-as-directory dir)))
    158         (editorconfig-fnmatch-p name
    159                                 (concat dir
    160                                         pattern)))
    161     (editorconfig-fnmatch-p name
    162                             (concat "**/"
    163                                     pattern))))
    164 
    165 (defsubst editorconfig-core-handle--string-trim (str)
    166   "Remove leading and trailing whitespaces from STR."
    167   (replace-regexp-in-string "[[:space:]]+\\'"
    168                             ""
    169                             (replace-regexp-in-string "\\`[[:space:]]+"
    170                                                       ""
    171                                                       str)))
    172 
    173 (defun editorconfig-core-handle--parse-file (conf)
    174   "Parse EditorConfig file CONF.
    175 
    176 This function returns cons of its top properties alist and
    177 alist of patterns and its properties alist.
    178 The list returned will be ordered by the lines they appear.
    179 
    180 If CONF is not found return nil."
    181   (when (file-readable-p conf)
    182     (with-temp-buffer
    183       ;; NOTE: Use this instead of insert-file-contents-literally to enable
    184       ;; code conversion
    185       (insert-file-contents conf)
    186       (goto-char (point-min))
    187       (let ((point-max (point-max))
    188             (sections ())
    189             (top-props nil)
    190 
    191             ;; String of current line
    192             (line "")
    193             ;; nil when pattern not appeared yet, "" when pattern is empty ("[]")
    194             (pattern nil)
    195             ;; Alist of properties for current PATTERN
    196             (props ())
    197 
    198             ;; Current line num
    199             (current-line-number 1)
    200             )
    201         (while (not (eq (point) point-max))
    202           (setq line
    203                 (buffer-substring-no-properties (point-at-bol)
    204                                                 (point-at-eol)))
    205           (setq line
    206                 (replace-regexp-in-string "\\(^\\| \\)\\(#\\|;\\).*$"
    207                                           ""
    208                                           (editorconfig-core-handle--string-trim line)))
    209 
    210           (cond
    211            ((string-equal "" line)
    212             nil)
    213 
    214            ;; Start of section
    215            ((string-match "^\\[\\(.*\\)\\]$"
    216                           line)
    217             (when pattern
    218               (setq sections
    219                     `(,@sections ,(make-editorconfig-core-handle-section
    220                                    :name pattern
    221                                    :props props)))
    222               (setq pattern nil)
    223               (setq props nil))
    224             (setq pattern (match-string 1 line)))
    225 
    226            (t
    227             (let ((idx (string-match "=\\|:"
    228                                      line)))
    229               (unless idx
    230                 (error "Error while reading config file: %s:%d:\n    %s\n"
    231                        conf
    232                        current-line-number
    233                        line))
    234               (let (
    235                     (key (downcase (editorconfig-core-handle--string-trim
    236                                     (substring line
    237                                                0
    238                                                idx))))
    239                     (value (editorconfig-core-handle--string-trim
    240                             (substring line
    241                                        (1+ idx)))))
    242                 (when (and (< (length key) 51)
    243                            (< (length value) 256))
    244                   (if pattern
    245                       (when (< (length pattern) 4097)
    246                         (setq props
    247                               `(,@props (,key . ,value))))
    248                     (setq top-props
    249                           `(,@top-props (,key . ,value))))))))
    250            )
    251           (setq current-line-number
    252                 (1+ current-line-number))
    253           (goto-char (point-min))
    254           (forward-line (1- current-line-number))
    255           )
    256         (when pattern
    257           (setq sections
    258                 `(,@sections ,(make-editorconfig-core-handle-section
    259                                :name pattern
    260                                :props props))))
    261         (list :top-props top-props
    262               :sections sections)))))
    263 
    264 (provide 'editorconfig-core-handle)
    265 
    266 ;;; editorconfig-core-handle.el ends here