Skip to content

Latest commit

 

History

History
339 lines (242 loc) · 9.79 KB

20170426-connection-local.org

File metadata and controls

339 lines (242 loc) · 9.79 KB

Emacs Berlin Meetup: Connection-Local Variables

Motivation

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.

Local variables are either let-bound or buffer-local

Global variables

(defvar foo 'foo "A global variable.")
(describe-variable 'foo)
(setq foo 'foofoo)
(describe-variable 'foo)

Let-bound variables

(let ((foo 'bla))
  (describe-variable 'foo))
(describe-variable 'foo)

Buffer-local variables

(with-temp-buffer
  (setq-local foo 'baz)
  (describe-variable 'foo))
(describe-variable 'foo)

File-local variables are buffer-local. They are set when

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.

Directory-local variables are buffer-local. They apply

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.

But what about global connection specific variables?

They cannot be emulated by directory-local variables.

Directory-local variables are applied for buffers visiting

a file. That’s not what we want.

Directory-local variables are not applied for remote

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>>

A directory-local file “/.dir-locals.el” cannot be

installed on most of the remote machines due to missing permissions.

And often, it is overruled by a subdirectory specific “.dir-locals.el”.

Directory-local variables are applied when a remote

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>>

Solution

Connection-local variables provide a general mechanism

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")

First step: Declare a set of variables and their settings

(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"))))

Second step: Assign connection profiles to remote

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 the interactive shell

<<Open_interactive_shell>>
<<Open_interactive_shell>>

How does it work: shell

See also info:elisp#Connection Local Variables.

Another example.

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 /

Work in progress. Bugs guaranteed :-)

Add connection-local variables setup to run-mode-hooks.

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.

Make :protocol, :user and :machine regexps.

It would be convenient to support something like (:machine ".+\\.dom\\.ain\\'")

Handle default values in Tramp.

/sudo:: corresponds to `(:protocol "sudo" :user "root" :machine ,(system-name)) It is stupid to write all these values every time.

So far, it has been applied to Tramp only.

Further candidates are

  • Gnus (server settings)
  • GnuTLS (gnutls-boot-parameters)
  • NSM (security policies)
  • eww/shr (proxy settings)
  • auth-source (server specific backends)

Extend it to a custom API.

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.

The End

Literate DevOps

http://www.howardism.org/Technical/Emacs/literate-devops.html

NetOps

https://vincent.bernat.im/en/blog/2017-netops-org-mode

Local Variables:

org-confirm-babel-evaluate: nil

org-return-follows-link: t

End: