-
Notifications
You must be signed in to change notification settings - Fork 4
/
germanium.el
216 lines (186 loc) · 8.69 KB
/
germanium.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
;;; germanium.el --- Generate image from source code using germanium -*- lexical-binding: t -*-
;; Author: Masaya Watanabe
;; Version: 0.1.0
;; Keywords: convenience
;; Package-Requires: ((emacs "26.1"))
;; URL: https://github.com/matsuyoshi30/germanium-el
;;; License:
;; 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, 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Tool for generating image from source code using germanium
;; <https://github.com/matsuyoshi30/germanium>. It requires you
;; to have the `germanium' command-line tool installed on your path.
;;; Code:
(require 'cl-lib)
(require 'subr-x)
(defgroup germanium nil
"tool for generating image from source code"
:group 'tools)
(defcustom germanium-executable-path "germanium"
"Executable command path for gernamium."
:type 'string
:group 'germanium)
(defcustom germanium-background-color "#aaaaff"
"Set background color to resulting PNG by default."
:type 'string
:group 'germanium)
(defcustom germanium-show-line-number t
"Add line numbers to resulting PNG by default."
:type 'boolean
:group 'germanium)
(defcustom germanium-show-window-access-bar t
"Add window access bar to the resulting PNG by default."
:type 'boolean
:group 'germanium)
(defcustom germanium-default-style "dracula"
"Set default style for generated PNG."
:type 'string
:group 'germanium)
(defcustom germanium-check-options-each-execute-command t
"Set Whether to check options each time the command is executed."
:type 'boolean
:group 'germanium)
(defcustom germanium-remove-extra-indentation t
"Set Whether to remove extra indentation when the command executed is for region."
:type 'boolean
:group 'germanium)
(defcustom germanium-completion-function 'ido-completing-read
"Function to use for completion.
Function needs to have a signature similar to `ido-completing-read', for example `ivy-completing-read'."
:type 'function
:group 'germanium)
(defvar germanium-available-styles nil
"List of available germanium styles.")
(when (commandp germanium-executable-path)
(setq-default germanium-available-styles
(split-string (shell-command-to-string
(mapconcat #'shell-quote-argument
(list germanium-executable-path "--list-styles")
" ")))))
(defun germanium--indent-length (str)
"Function to count indent in STR."
(letrec ((f (lambda (lst acc)
(if (eq 32 (car lst)) ;; only for whitespace
(funcall f (cdr lst) (1+ acc))
acc))))
(funcall f (cl-coerce str 'list) 0)))
(defun germanium--remove-extra-indentation (contents)
"Remove extra indentation from CONTENTS."
(if (and contents germanium-remove-extra-indentation)
(let* ((lines (split-string contents "\n"))
(minidx (apply #'min
(mapcar #'(lambda (x) (germanium--indent-length x))
(cl-remove-if (lambda (x) (string= x "")) lines)))))
(mapconcat #'identity
(mapcar #'(lambda (x) (if (>= (length x) minidx) (substring x minidx)))
lines)
"\n"))
contents))
(defun germanium--build-command-options-string (&rest args)
"Build germanium command options string from ARGS.
Supported options are `:line-number', `:window-access-bar' and `style'"
(let* ((background-color (or (plist-get args :background-color) germanium-background-color))
(show-line-number (and (plist-get args :line-number) germanium-show-line-number))
(show-window-access-bar (and (plist-get args :window-access-bar) germanium-show-window-access-bar))
(style (or (plist-get args :style) germanium-default-style)))
(seq-remove #'null
`(,(when (not show-line-number) "--no-line-number")
,(when (not show-window-access-bar) "--no-window-access-bar")
,(when background-color (format "--background=%s" background-color))
,(when style (format "--style=%s" style))))))
(defun germanium--build-exec-command (file-path contents options)
"Build germanium execute command from FILE-PATH or CONTENTS with OPTIONS.
Output file name is based on FILE-PATH default."
(let ((output
(concat (file-name-base file-path) ".png")))
(if contents
(mapconcat #'identity
(append
(list "echo"
"-E"
(shell-quote-argument contents)
"|"
germanium-executable-path
"--output" output
"-l" (file-name-extension file-path))
options)
" ")
(mapconcat #'shell-quote-argument
(append
(list germanium-executable-path
"--output" output
file-path)
options)
" "))))
(defun germanium--exec-command (file-path contents)
"Execute germanium command with FILE-PATH and CONTENTS."
(interactive)
(let ((command-string
(if germanium-check-options-each-execute-command
(let ((style
(funcall germanium-completion-function
"Style: "
germanium-available-styles
nil
germanium-available-styles
germanium-default-style))
(background-color
(read-string "Background color (RGB): "
germanium-background-color))
(show-line-number (yes-or-no-p "Add line number? "))
(show-window-access-bar (yes-or-no-p "Add window access bar? ")))
(germanium--build-exec-command file-path contents
(germanium--build-command-options-string :style style
:background-color background-color
:line-number show-line-number
:window-access-bar show-window-access-bar)))
(germanium--build-exec-command file-path contents (germanium--build-command-options-string)))))
(shell-command command-string)))
;;;###autoload
(defun germanium-install ()
"Install `germanium' via `go'."
(interactive)
(unless (yes-or-no-p "Install `germanium' via go?")
(user-error "Abort install"))
(unless (executable-find "go")
(user-error "Missing `go'. Please ensure Emacs's PATH and is installed"))
(shell-command "go install github.com/matsuyoshi30/germanium/cmd/germanium@latest"))
;;;###autoload
(defun germanium-region-to-png (start end)
"Generate a PNG file from current region between START and END."
(interactive (if (use-region-p)
(list (region-beginning) (region-end))
(list nil nil)))
(if (not (commandp germanium-executable-path))
(user-error "`germanium' executable path not found")
(if (and start end)
(if-let* ((file-name (buffer-file-name))
(file-path (expand-file-name file-name))
(contents
(germanium--remove-extra-indentation (replace-regexp-in-string "\n$" "" (buffer-substring-no-properties start end)))))
(germanium--exec-command file-path contents)
(user-error "Current buffer is not associated with any file"))
(user-error "Need to select region"))))
;;;###autoload
(defun germanium-buffer-to-png ()
"Generate a PNG file from current buffer."
(interactive)
(if (not (commandp germanium-executable-path))
(user-error "`germanium' executable path not found")
(if-let* ((file-name (buffer-file-name))
(file-path (expand-file-name file-name)))
(germanium--exec-command file-path nil)
(user-error "Current buffer is not associated with any file"))))
(provide 'germanium)
;;; germanium.el ends here