editorconfig-core-handle.el (9822B)
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 (line-beginning-position) 204 (line-end-position))) 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