-
Notifications
You must be signed in to change notification settings - Fork 0
/
inline-annotate-parser.el
151 lines (119 loc) · 5.03 KB
/
inline-annotate-parser.el
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
;;; inline-annotate-parser.el --- Parser of GIT Blame output -*- lexical-binding: t; -*-
;;; Commentary:
;; To be able to be loaded with emacs-async
;;
;;; Code:
(require 'cl-lib)
(require 'parsec)
(cl-defstruct commit
;; hash
author
author-mail
author-time
author-tz
;; committer
;; committer-mail
;; committer-time
;; committer-tz
;; previous
;; filename
summary)
(defmacro ia-parser--setter (key struct)
"Create lambda v -> (setf (KEY STRUCT) v)."
`(lambda (val) (setf (,key ,struct) val)))
;;
;; Parsec Helpers
;;
(defun ia-parser--collect-line ()
"Collect the rest of the line."
(prog1 (parsec-re "[^\n]*")
(parsec-eol-or-eof)))
(defun ia-parser--skip-ws ()
"Consume one or more whitespace characters."
(parsec-many (parsec-ch ?\s)))
(defun ia-parser--next-word ()
"Consume the next word, as well as any whitespace before it."
(ia-parser--skip-ws)
(parsec-re "[[:alnum:]]*"))
(defun ia-parser--next-num ()
"Consume the next number, as well as any whitespace before it."
(string-to-number (ia-parser--next-word)))
;;
;; Parser helpers
;;
(defun ia-parser--parse-key-value-line (key setter &optional cast-to-num)
"If the next string is KEY call SETTER with the rest of the line as parameter. CAST-TO-NUM when set."
(when (parsec-str key)
(ia-parser--skip-ws)
(funcall setter (if cast-to-num
(string-to-number (ia-parser--collect-line))
(ia-parser--collect-line)))))
(defun ia-parser--parse-header-line (commit-struct)
"Parse a line and store it in COMMIT-STRUCT."
(parsec-or
(ia-parser--parse-key-value-line "author-mail" (ia-parser--setter commit-author-mail commit-struct))
(ia-parser--parse-key-value-line "author-time" (ia-parser--setter commit-author-time commit-struct) t)
(ia-parser--parse-key-value-line "author-tz" (ia-parser--setter commit-author-tz commit-struct))
(ia-parser--parse-key-value-line "author" (ia-parser--setter commit-author commit-struct))
(ia-parser--parse-key-value-line "summary" (ia-parser--setter commit-summary commit-struct))
(ia-parser--collect-line)))
(defun ia-parser--parse-commit-section (cur-commit separator)
"Parse git blame header, store in CUR-COMMIT, stop at SEPARATOR."
;; parse headers
(parsec-many-till
(ia-parser--parse-header-line cur-commit)
(funcall separator))
;; discard line contents
(ia-parser--collect-line))
(defun ia-parser--abstract-parse(str num-lines commit-parser)
"Parse git blame STR, with NUM-LINES the number of lines in the buffer. delegate parsing of individual commits to COMMIT-PARSER."
(let* ((hash-table (make-hash-table :test 'equal :size (/ num-lines 4)))
(line-lookup (make-vector num-lines nil)))
(parsec-with-input str
(parsec-many-till
(funcall commit-parser hash-table line-lookup)
(parsec-eof)))
(cons hash-table line-lookup)))
;;
;; Porcelain Parser
;;
(defun ia-parser--porcelain-parse-commit (hash-table line-lookup)
"Parse git blame --porcelain, Store information in HASH-TABLE and LINE-LOOKUP."
(let* ((hash (ia-parser--next-word))
(_old-line (ia-parser--next-word))
(cur-line (ia-parser--next-num))
(lines (ia-parser--next-num))
(cur-commit (gethash hash hash-table (make-commit))))
(dotimes (n lines)
(aset line-lookup (+ cur-line n -1) hash)
(ia-parser--collect-line) ;; drop the other headers, we don't need them
(ia-parser--parse-commit-section cur-commit #'(lambda () (parsec-ch ?\t))))
(puthash hash cur-commit hash-table)))
(defun ia-parser--porcelain-parse (str num-lines)
"Parse output of git blame --porcelain as STR and NUM-LINES the number of lines in the buffer."
(ia-parser--abstract-parse str num-lines 'ia-parser--porcelain-parse-commit))
;;
;; Incremental Parser
;;
(defun ia-parser--incremental-parse-commit (hash-table line-lookup)
"Parse git blame --incremental, Store information in HASH-TABLE and LINE-LOOKUP."
(let* ((hash (ia-parser--next-word))
(_old-line (ia-parser--next-word))
(cur-line (ia-parser--next-num))
(lines (ia-parser--next-num))
(cur-commit (gethash hash hash-table (make-commit))))
(dotimes (n lines)
(aset line-lookup (+ cur-line n -1) hash))
(ia-parser--collect-line)
(ia-parser--parse-commit-section cur-commit #'(lambda () (parsec-str "filename")))
(puthash hash cur-commit hash-table)))
(defun ia-parser--incremental-parse (str num-lines)
"Parse output of git blame --incremental as STR and NUM-LINES the number of lines in the buffer."
(ia-parser--abstract-parse str num-lines 'ia-parser--incremental-parse-commit))
(defun ia-parser--parse (str num-lines incremental)
"Parse STR. NUM-LINES upper bound for number of lines in str, Use INCREMENTAL strategy if set."
(if incremental
(ia-parser--incremental-parse str num-lines)
(ia-parser--porcelain-parse str num-lines)))
(provide 'inline-annotate-parser)
;;; inline-annotate-parser.el ends here