~technomancy/scpaste

ref: 779b94d1159bba8dbcf2b1081df7c54a15577066 scpaste/scpaste.el -rw-r--r-- 10.6 KiB
779b94d1Phil Hagelberg Clarify the requirement of ssh-agent. 7 months ago
                                                                                
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
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
;;; scpaste.el --- Paste to the web via scp.

;; Copyright © 2008-2020 Phil Hagelberg and contributors

;; Author: Phil Hagelberg
;; URL: https://github.com/technomancy/scpaste
;; Version: 0.6.5
;; Created: 2008-04-02
;; Keywords: convenience hypermedia
;; EmacsWiki: SCPaste
;; Package-Requires: ((htmlize "1.39"))

;; This file is NOT part of GNU Emacs.

;;; Commentary:

;; This will place an HTML copy of a buffer on the web on a server
;; that the user has shell access on.

;; It's similar in purpose to services such as https://gist.github.com
;; or https://pastebin.com, but it's much simpler since it assumes the user
;; has an account on a publicly-accessible HTTP server. It uses `scp'
;; as its transport and uses Emacs' font-lock as its syntax
;; highlighter instead of relying on a third-party syntax highlighter
;; for which individual language support must be added one-by-one.

;;; Install

;; Requires htmlize; available at
;; https://github.com/hniksic/emacs-htmlize.  If htmlize is not
;; installed, by default it will fall back to Emacs's own htmlfontify.

;; Open the file and run `package-install-from-buffer', or put it on your
;; `load-path' and add these to your config:

;; (autoload 'scpaste "scpaste" nil t)
;; (autoload 'scpaste-region "scpaste" nil t)

;; Set `scpaste-http-destination' and `scpaste-scp-destination' to
;; appropriate values, and add this to your Emacs config:

;; (setq scpaste-http-destination "https://p.hagelb.org"
;;       scpaste-scp-destination "p.hagelb.org:p.hagelb.org")

;; If you have a different keyfile, you can set that, too:
;; (setq scpaste-scp-pubkey "~/.ssh/my_keyfile.pub")

;; If you use a non-standard ssh port, you can specify it by setting
;; `scpaste-scp-port'.

;; If you need to use alternative scp and ssh programs, you can set
;; `scpaste-scp' and `scpaste-ssh'. For example, scpaste works with the Putty
;; suite on Windows if you set these to pscp and plink, respectively.

;; Optionally you can set the displayed name for the footer and where
;; it should link to:

;; (setq scpaste-user-name "Technomancy"
;;       scpaste-user-address "https://technomancy.us/")

;;; Usage

;; M-x scpaste, enter a name, and press return. The name will be
;; incorporated into the URL by escaping it and adding it to the end
;; of `scpaste-http-destination'. The URL for the pasted file will be
;; pushed onto the kill ring.

;; You can autogenerate a splash page that gets uploaded as index.html
;; in `scpaste-http-destination' by invoking M-x scpaste-index. This
;; will upload an explanation as well as a listing of existing
;; pastes. If a paste's filename includes "private" it will be skipped.

;; You probably want to set up SSH keys for your destination to avoid
;; having to enter your password once for each paste. Also be sure the
;; key of the host referenced in `scpaste-scp-destination' is in your
;; known hosts file--scpaste will not prompt you to add it but will
;; simply hang and need you to hit C-g to cancel it.

;;; 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; see the file COPYING.  If not, write to the
;; Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
;; Boston, MA 02110-1301, USA.

;;; Code:

(require 'url)
(require 'htmlize nil t)

(defvar scpaste-html-converter
  (if (featurep 'htmlize)
      'htmlize-buffer
    'htmlfontify-buffer)
  "Name of the function to use to generate the HTML output.
By default, it will try to use `htmlize-buffer' from htmlize, and
will fall back to `htmlfontify-buffer' from Emacs's `htmlfontify'
if htmlize is not available.")

(defvar scpaste-scp-port
  nil)

(defvar scpaste-scp
  "scp"
  "The scp program to use.")

(defvar scpaste-ssh
  "ssh"
  "The ssh program to use when running remote shell commands.")

(defvar scpaste-http-destination
  "https://p.hagelb.org"
  "Publicly-accessible (via HTTP) location for pasted files.")

(defvar scpaste-scp-destination
  "p.hagelb.org:p.hagelb.org"
  "SSH-accessible directory corresponding to `scpaste-http-destination'.
You must have write access to this directory via `scp'.")

(defvar scpaste-scp-pubkey
  nil
  "Identity file for the server.
Corresponds to ssh’s `-i` option Example: \"~/.ssh/id.pub\".
It's better to set this in ~/.ssh/config than to use this setting.")

(defvar scpaste-user-name
  nil
  "Name displayed under the paste.")

(defvar scpaste-user-address
  nil
  "Link to the user’s homebase (can be a mailto:).")

(defvar scpaste-make-name-function
  'scpaste-make-name-from-buffer-name
  "The function used to generate file names, unless the user provides one.")

(defvar scpaste-el-location (replace-regexp-in-string "\.elc$" ".el"
                                                      (or load-file-name
                                                          (buffer-file-name))))

(defun scpaste-footer ()
  "HTML message to place at the bottom of each file."
  (concat "<p style='font-size: 8pt; font-family: monospace; "
          (mapconcat (lambda (c) (concat c "-select: none"))
                     '("-moz-user" "-webkit-user" "-ms-user" "user") "; ")
          "'>Generated by "
          (let ((user (or scpaste-user-name user-full-name)))
            (if scpaste-user-address
                (concat "<a href='" scpaste-user-address "'>" user "</a>")
              user))
          " using <a href='https://p.hagelb.org'>scpaste</a> at %s. "
          (cadr (current-time-zone)) ". (<a href='%s'>original</a>)</p>"))

(defun scpaste-read-name (&optional suffix)
  "Read the paste name from the minibuffer.

Defaults to the return value of `scpaste-make-name-function'
with SUFFIX as argument."
  (let* ((default (funcall scpaste-make-name-function suffix))
         (input (read-from-minibuffer (format "Name: (defaults to %s) "
                                              default))))
    (if (equal "" input) default input)))

(defun scpaste-make-name-from-buffer-name (&optional suffix)
  "Make a name from buffer name and extension.

If non-nil, SUFFIX is inserted between name and extension."
  (concat (file-name-sans-extension (buffer-name))
          suffix
          (file-name-extension (buffer-name) t)))

(defun scpaste-make-name-from-timestamp (&optional _)
  "Make a name from current timestamp and current buffer's extension."
  (concat (format-time-string "%s") (file-name-extension (buffer-name) t)))

;;;###autoload
(defun scpaste (original-name)
  "Paste the current buffer via `scp' to `scpaste-http-destination'.
If ORIGINAL-NAME is an empty string, then the buffer name is used
for the file name."
  (interactive (list (scpaste-read-name)))
  (let* ((b (generate-new-buffer (generate-new-buffer-name "scpaste")))
         (pre-hl-line (and (featurep 'hl-line) hl-line-mode
                           (progn (hl-line-mode -1) t)))
         (hb (funcall scpaste-html-converter))
         (name (replace-regexp-in-string "[/\\%*:|\"<>  ]+" "_"
                                         original-name))
         (full-url (concat scpaste-http-destination
                           "/" (url-hexify-string name) ".html"))
         (tmp-file (concat temporary-file-directory name))
         (tmp-hfile (concat temporary-file-directory name ".html")))
    (when pre-hl-line
      (hl-line-mode 1))
    ;; Save the files (while adding a footer to html file)
    (save-excursion
      (copy-to-buffer b (point-min) (point-max))
      (switch-to-buffer b)
      (write-file tmp-file)
      (kill-buffer b)
      (switch-to-buffer hb)
      (goto-char (point-min))
      (search-forward "</body>\n</html>")
      (insert (format (scpaste-footer)
                      (current-time-string)
                      (substring full-url 0 -5)))
      (write-file tmp-hfile)
      (kill-buffer hb))

    (let* ((identity (if scpaste-scp-pubkey
                         (concat "-i " scpaste-scp-pubkey) ""))
           (port (if scpaste-scp-port (concat "-P " scpaste-scp-port)))
           (invocation (concat scpaste-scp " -q " identity " " port))
           (command (concat invocation " " tmp-file " " tmp-hfile " "
                            scpaste-scp-destination "/"))
           (error-buffer "*scp-error*")
           (retval (shell-command command nil error-buffer))
           (select-enable-primary t))

      (delete-file tmp-file)
      (delete-file tmp-hfile)
      ;; Notify user and put the URL on the kill ring
      (if (= retval 0)
          (progn (kill-new full-url)
                 (message "Pasted to %s (on kill ring)" full-url))
        (pop-to-buffer error-buffer)
        (help-mode-setup)))))

;;;###autoload
(defun scpaste-region (name)
  "Paste the current region via `scpaste'.
NAME is used for the file name."
  (interactive (list (scpaste-read-name (format "-%s-%s" (region-beginning)
                                                (region-end)))))
  (let ((region-contents (buffer-substring (mark) (point))))
    (with-temp-buffer
      (insert region-contents)
      (scpaste name))))

;;;###autoload
(defun scpaste-index ()
  "Generate an index of all existing pastes on server on the splash page."
  (interactive)
  (let* ((dest-parts (split-string scpaste-scp-destination ":"))
         (files (shell-command-to-string (concat scpaste-ssh " "
                                                 (car dest-parts) " ls "
                                                 (cadr dest-parts))))
         (file-list (split-string files "\n")))
    (save-excursion
      (with-temp-buffer
        (insert-file-contents scpaste-el-location)
        (goto-char (point-min))
        (search-forward ";;; Commentary")
        (forward-line -1)
        (insert "\n;;; Pasted Files\n\n")
        (dolist (file file-list)
          (when (and (string-match "\\.html$" file)
                     (not (string-match "private" file)))
            (insert (concat ";; * <" scpaste-http-destination "/" file ">\n"))))
        (emacs-lisp-mode)
        (if (fboundp 'font-lock-ensure)
            (progn (font-lock-mode nil)
                   (font-lock-ensure)
                   (jit-lock-mode t))
          (with-no-warnings ; fallback for Emacs 24
            (font-lock-fontify-buffer)))
        (rename-buffer "SCPaste")
        (write-file (concat temporary-file-directory "scpaste-index"))
        (scpaste "index")))))

(provide 'scpaste)
;;; scpaste.el ends here