This presentation is based on the ideas of Literate DevOps
and NetOps. That is the examples are given as source code blocks, and they will be evaluated during presentation.
(defvar foo 'foo "A global variable.")
(describe-variable 'foo)
(setq foo 'foofoo)
(describe-variable 'foo)
(let ((foo 'bla))
(describe-variable 'foo))
(describe-variable 'foo)
(with-temp-buffer
(setq-local foo 'baz)
(describe-variable 'foo))
(describe-variable 'foo)
a given file is visited in a buffer.
Specification happens either in the first line of the file or in a special block at the end of the file.
for every file visited in a given directory and its subdirectories.
Specification happens in a special file .dir-locals.el. When visiting a file in Emacs, directories are traversed upwards for that special file.
They cannot be emulated by directory-local variables.
a file. That’s not what we want.
connections by default (performance, security).
If we are working on a local directory, variable
indent-tabs-mode
is set in ~/src/emacs/.dir-locals.el.
(let* ((file "src/emacs/lisp/files.el")
(buffer (get-file-buffer file)))
(and buffer (kill-buffer buffer))
(find-file file)
(describe-variable 'indent-tabs-mode))
Per default, enable-remote-dir-locals
is nil.
<<Directory-local_indent-tabs-mode>>
Only when enable-remote-dir-locals
is non-nil, remote
directory-local variables are taken into account.
<<Directory-local_indent-tabs-mode>>
installed on most of the remote machines due to missing permissions.
And often, it is overruled by a subdirectory specific “.dir-locals.el”.
connection has been established already, and a file is visited in a buffer. Sometimes, that’s too late.
Example: calling shell
interactively.
A local shell uses shell-file-name
for determining the
shell program to be used.
;; Cleanup
(let (kill-buffer-query-functions)
(and (get-buffer "*shell*") (kill-buffer "*shell*")))
(call-interactively 'shell)
A remote shell (that is, default-directory
is a remote
file name) cannot use the local shell-file-name
.
Instead, it uses explicit-shell-file-name
, or (in the
interactive case, when it isn’t set) the path of the
remote shell is asked for.
<<Open_interactive_shell>>
for different variable settings in buffers with a remote connection. These buffer-local variables are bound and set depending on the remote connection a buffer is dedicated to.
In contrast to file-local and directory-local variables, connection-local variables are not only set for buffers visiting a file. Instead, they are set also in buffers bound to a remote process.
A remote connection is identified as a plist of :protocol, :user and :machine. All properties are optional, their values are strings. Example:
(:protocol "ssh" :user "albinus" :machine "localhost")
(a connection profile)
(connection-local-set-profile-variables
;; The connection profile name, just a symbol.
'remote-bash
;; An alist of (VARIABLE . VALUE) entries.
'((explicit-shell-file-name . "/bin/bash")
(explicit-bash-args . ("-i"))))
(connection-local-set-profile-variables
'remote-ksh
'((explicit-shell-file-name . "/bin/ksh")
(explicit-ksh-args . ("-i"))))
connections identified by a given criteria
(connection-local-set-profiles
`(:protocol "sudo" :user "root" :machine ,(system-name))
'remote-ksh)
(connection-local-set-profiles
'(:application tramp :protocol "ssh" :machine "localhost")
'remote-bash)
A criteria, the first parameter of
connection-local-set-profiles
, is a plist
of :application and the properties describing the
connection, :protocol, :user and :machine, all
optional. :application shall be used in case different
applications (for example, Tramp, GnuTLS) use the same
connection-local variable with a different meaning.
<<Open_interactive_shell>>
<<Open_interactive_shell>>
See also info:elisp#Connection Local Variables.
Sometimes, it is useful to handle small shell commands as source code blocks. Especially, when it goes remote.
df -h /
But this is too slow. Better, to keep a session open for all the remote commands.
df -h /
However, the result is shortened. See <a href=”elisp:(switch-to-buffer “@@html:@@session@@html:@@”)”>Buffer *session*. The “%”-character is regarded as prompt delimeter. So we must change the prompt.
;; Cleanup
(let (kill-buffer-query-functions)
(and (get-buffer "*session*") (kill-buffer "*session*")))
(connection-local-set-profile-variables
'comint-prompt-regexp-without-percent
'((comint-prompt-regexp . "^[^#%$>\n]*[#$>] *")
(comint-use-prompt-regexp . t)))
(connection-local-set-profiles
`(:protocol "sudo" :user "root" :machine ,(system-name))
'comint-prompt-regexp-without-percent)
(add-hook
'shell-mode-hook
'tramp-set-connection-local-variables-for-buffer)
Applying a major mode kills all buffer-local variables.
Therefore, we apply the connection-local variables in
shell-mode-hook
, run at the end of enabling major mode.
tramp-set-connection-local-variables-for-buffer is a
Tramp function which sets connection-local variables
according to default-directory
.
df -h /
This would avoid to add it to the mode specific actions
in *-mode-hook
yourself. File-local and
directory-local variables are already set there.
It would be convenient to support something like
(:machine ".+\\.dom\\.ain\\'")
/sudo::
corresponds to
`(:protocol "sudo" :user "root" :machine ,(system-name))
It is stupid to write all these values every time.
Further candidates are
- Gnus (server settings)
- GnuTLS (gnutls-boot-parameters)
- NSM (security policies)
- eww/shr (proxy settings)
- auth-source (server specific backends)
- …
A template could be used, collecting all relevant variables for a connection profile, and which could be extended to different profiles. Hooked into the customization group of what :application is specified.
http://www.howardism.org/Technical/Emacs/literate-devops.html