NAME
Emacs::Run - utilities to assist in using emacs from perl via the shell
SYNOPSIS
use Emacs::Run;
my $er = Emacs::Run->new();
my $major_version = $er->emacs_version;
if ($major_version > 22) {
print "You have a recent version of emacs\n";
}
# quickly specify additional elisp libraries to use, then get information about emacs settings
my $er = Emacs::Run->new({
emacs_libs => [ '~/lib/my-elisp.el', '/usr/lib/site-emacs/stuff.el' ],
});
my $emacs_load_path_aref = $er->get_load_path;
my $email = $er->get_variable( 'user-mail-address' );
my $name = $er->eval_function( 'user-full-name' );
# suppress the use of the usual emacs init (e.g. ~/.emacs)
my $er = Emacs::Run->new({
load_emacs_init => 0,
});
my $result = $er->eval_elisp( '(print (+ 2 2))' ); # "4", in case you were wondering
# Specify in detail which emacs lisp libraries should be loaded
$lib_data = [
[ 'dired', { type=>'lib', priority=>'needed' } ],
[ '/tmp/my-load-path.el', { type=>'file', priority=>'requested' } ],
[ '/tmp/my-elisp.el', { type=>'file', priority=>'needed' } ],
];
my $er = Emacs::Run->new({
lib_data => $lib_data,
});
my $result = $er->eval_lisp( qq{ (print (my-elisp-run-my-code "$perl_string")) } );
DESCRIPTION
Emacs::Run is a module that provides utilities to work with emacs when run from perl as an external process.
The emacs "editor" has some command-line options ("--batch" and so on) that allow you to use it as a lisp interpreter and run elisp code non-interactively.
This module provides methods that use these features of emacs for two types of tasks:
Probe your emacs installation to get the installed version, the user's current load-path, and so on.
Run chunks of emacs lisp code without worrying too much about the details of quoting issues and loading libraries and so on.
METHODS
- new
-
Creates a new Emacs::Run object.
Takes a hashref as an argument, with named fields identical to the names of the object attributes. These attributes are:
- emacs_path
-
By default, this code looks for an external program in the PATH envar called 'emacs'. If you have multiple emacsen installed in different places and/or under different names, you can choose which one will be used by setting this attribute.
- emacs_version
-
The version of emacs in use. Set automatically by the "probe_emacs_version" method during object initialization.
- emacs_type
-
The flavor of emacs in use, e.g. 'Gnu Emacs'. Set automatically by the "probe_emacs_version" method during object initialization.
- load_emacs_init
-
Defaults to 1, if set to a false value, will suppress the use of the user's emacs init file (e.g. "~/.emacs").
- load_site_init
-
Defaults to 1, if set to a false value, will suppress the use of the system "site-start.el" file (which loads before the user's init file).
- load_default_init
-
Defaults to 1, if set to a false value, will suppress the use of the system "default.el" file (which loads after the user's init file).
- load_no_inits
-
A convenience flag, used to disable all three types of emacs init files in one step when set to a true value. Overrides the other three.
- emacs_libs
-
A list of emacs libraries (with or without paths) to be loaded automatically. This is provided as a convenience for quick use. To take full control over how your emacs libraries are handled, see "lib_data".
- lib_data
-
This is a more complicated data structure than "emacs_libs": an array of arrays of two elements each, the first element is the library name (a string, with or without path), the second element is a hash of library attributes: 'priority' which can be 'requested' or 'needed' and 'type' which can be 'file' or 'lib'.
Example:
$lib_data = [ [ 'dired', { type=>'lib', priority=>'needed' } ], [ '/tmp/my-load-path.el', { type=>'file', priority=>'requested' } ], [ '/tmp/my-elisp.el', { type=>'file', priority=>'needed' } ], ];
emacs library attributes:
- priority
-
A 'requested' library will be silently skipped if it is not available (and any elisp code using it may need to do something like 'featurep' checks to adapt to it's absence). A 'needed' file will cause an error to occur if it is not available. The default priority is 'requested', but that can be changed via the "default_priority" attribute.
- type
-
A library of type 'file' should be a filesystem path to a file containing a library of emacs lisp code. A library of type 'lib' is specified by just the basename of the file (sans path or extension), and we will search for it looking in the places specified in the emacs variable load-path. If neither is specified, the system will guess the lib is a file if it looks like it has a path and/or extension.
If both lib_data and emacs_libs are used, the lib_data libraries are loaded first, followed by the emacs_libs libraries.
- default_priority
-
Normally this is set to "requested", but it can be set to "needed".
- before_hook
-
A string inserted into the built-up emacs commands immediately after "--batch", but before any other pieces are executed. This is a good place to insert additional invocation options such as "--multibyte" or "--unibyte".
There are also a number of object attributes intended largely for internal use. The client programmer has access to these, but is not expected to need it. These are documented in "internal attributes".
- init
-
Method that initializes object attributes and then locks them down to prevent accidental creation of new ones.
Any class that inherits from this one should have an "init" of it's own that calls this "init".
- get_load_path
-
Returns the load-path from emacs (by default, using the user's .emacs, if it can be found) as a reference to a perl array.
Changing the $HOME environment variable before running this method results in loading the .emacs file located in the new $HOME.
- get_variable
-
Given the name of an emacs variable, returns the value from emacs (when started with the the .emacs located in $HOME, if one is found),
Internally, this uses the emacs 'print' function, which can handle variables containing complex data types, but the return value will be a "printed representation" that may make more sense to emacs than to perl code. For example, the "load-path" variable might look like:
("/home/grunt/lib" "/usr/lib/emacs/site-lisp" "/home/xtra/lib")
The returned output includes just STDOUT and not STDERR, (but this behavior is under revision, and may change shortly TODO).
- eval_function
-
Given the name of an emacs function, this runs the function (without arguments) and returns the value from emacs (when started with the the .emacs located in $HOME, if one is found).
As with "get_variable", this uses the emacs 'print' function internally.
The returned output includes just STDOUT and not STDERR, (but this behavior is under revision, and may change shortly TODO).
running elisp
- run_elisp_on_file
-
Given a file name, and some emacs lisp code (which presumably modifies the current buffer), this method opens the file, runs the code on it, and then saves the file.
Example usage: $self->run_elisp_on_file( $filename, $elisp );
- eval_elisp
-
Given a string containing a chunk of elisp code this method runs it by invoking emacs in batch mode.
(TODO this discussion should be moved to a place that describes the entire group of methods that work similarly.)
By default, this method first loads the user's initialization file ("$HOME/.emacs") if it can be found. It will also try to load the libraries listed in the "emacs_libs" and/or "lib_data" attributes.
There are object attribute settings that can be used to suppress loading any of the various init files. See "new" for the full list. In particular, if the "load_emacs_init" attribute has been turned off, it will not try to load the .emacs file.
This method returns the output from the elisp code with STDOUT and STDERR mixed together (but this behavior is under revision, and may change shortly TODO).
(The advanatage of intermixing STDOUT and STDERR is that the emacs functions 'message' and 'print' both work to generate output.)
Example:
my $result = $er->eval_elisp( '(print (+ 2 2))' );
- eval_elisp_skip_err
-
Identical to "eval_elisp", except that it returns only the standard output, ignoring any messages sent to STDERR.
utility methods (largely, but not entirely for internal use)
- quote_elisp
-
Routine to quote elisp code before feeding it into an emacs batch shell command. Used internally by "eval_elisp".
This just adds a single backslash to all the double-quote characters (an empirically determined algorithm, i.e. hack).
Example usage:
$elisp = $self->quote_elisp( $elisp ); $emacs_cmd = qq{ emacs --batch --eval "$elisp" 2>&1 }; my $return = qx{ $emacs_cmd };
- qx_clean
-
Executes the given emacs shell invocation string, and returns a cleaned up version of it's returned value. This is intended to be used with elisp that uses the 'print' function, which has spurious leading and trailing newlines and double-quotes.
- clean_return_value
-
Cleans up a given string, trimming unwanted leading and trailing blanks and double quotes.
This is intended to be used with elisp that uses the 'print' function.
internal methods
The following routines are largely used internally in the object initialization phase.
- process_emacs_libs_addition
-
Goes through the given list of emacs_libs, and converts the names into the lib_data style of data structure, appending it to lib_data.
Note that this method works on the given argument, without reference to the object's "emacs_libs" field.
Returns a reference to a structure containing the new additions to lib_data.
- process_emacs_libs_reset
-
This converts the list of emacs_libs into the lib_data style of structure much like "process_emacs_libs_addition", but this method resets the lib_data field to the given value at init time (if any) before appending the new data.
Defaults to using the object's "emacs_libs" setting.
Returns a reference to a structure containing the additions to lib_data from emacs_libs.
- set_up_ec_lib_loader
-
Initializes the ec_lib_loader attribute by scanning for the appropriate emacs init file(s) and processing the list(s) of emacs libraries specified in the object data.
Returns the newly defined $ec_lib_loader string.
This routine is called by "init" during object initialization.
- guess_type_from_name
-
Given the name of an emacs library, examine it to see if it looks like a file system path, or an emacs feature name (sans path or extension)
generation of emacs command strings to load libraries
These are routines that generate a string that can be included in an emacs command line invocation to load certain libraries.
Note the naming convention: "generate emacs command-line" => "genec_".
- genec_load_emacs_init
-
Generates an emacs command line fragment to load the emacs_init file(s) as appropriate.
Side effect: zeroes out the ec_lib_loader before rebuilding with inits only.
The following is a set of four routines to generate a string that can be included in an emacs command line invocation to load the given library. The methods here are named according to the pattern:
"genec_loader_$type_$priority"
All of these methods return the generated string, but also append it to the "ec_lib_loader" attribute,
- genec_loader_lib_needed
- genec_loader_file_needed
- genec_loader_lib_requested
- genec_loader_file_requested
Emacs probes
Methods that return information about the emacs installation.
- probe_emacs_version
-
Returns the version of the emacs program stored on your system. This is called during the object initialization phase.
It checks the emacs specified in the object's emacs_path (which defaults to the simple command "emacs", sans any path), and returns the version.
As a side-effect, it sets a number of object attributes with details about the emacs version:
emacs_version emacs_major_version emacs_type
- parse_emacs_version_string
-
The emacs version string returned from running "emacs --version" is parsed by this routine, which picks out the version numbers and so on and saves them as object data.
See probe_emacs_version (which uses this internally).
internal utilities (used by initialization code)
- elisp_to_load_file
-
Given the location of an emacs lisp file, generate the elisp that ensures the library will be available and loaded.
- find_dot_emacs
-
Looks for one of the variants of the user's emacs init file (e.g. "~/.emacs") in the same order that emacs would, and returns the first one found.
Note: this relies on the environment variable $HOME. (This can be changed first to cause this to look for an emacs init in some arbitrary location, e.g. for testing purposes.)
This code does not issue a warning if the elc is stale compared to the el, that's left up to emacs.
- detect_site_init
-
Looks for the "site-start.el" file in the raw load-path without loading the regular emacs init file (e.g. ~/.emacs).
Emacs itself normally loads this file before it loads anything else, so this method replicates that behavior.
Returns the library name ('site-start') if found, undef if not.
- detect_lib
-
Looks for the given elisp library in the load-path after trying to load the given list of context_libs (that includes .emacs as appropriate, and this method uses the requested_load_files as context, as well).
Returns $lib if found, undef if not.
Example usage:
if ( $self->detect_lib("dired") ) { print "As expected, dired.el is installed."; } my @good_libs = grep { defined($_) } map{ $self->detect_lib($_) } @candidate_libs;
routines in use by Emacs::Run::ExtractDocs
- elisp_file_from_library_name_if_in_loadpath
-
Identifies the file associated with a given elisp library name by shelling out to emacs in batch mode.
- generate_elisp_to_load_library
-
Generates elisp code that will instruct emacs to load the given library. It also makes sure it's location is in the load-path, which is not precisely the same thing: See "loaded vs. in load-path".
Takes one argument, which can either be the name of the library, or the name of the file, as distinguished by the presence of a ".el" extension on a file name. Also, the file name will usually have a path included, but the library name can not.
basic setters and getters
The naming convention in use here is that setters begin with "set_", but getters have *no* prefix: the most commonly used case deserves the simplest syntax (and mutators are deprecated).
These accessors exist for all of the object attributes (documented above) irrespective of whether they're expected to be externally useful.
special accessors
- append_to_ec_lib_loader
-
Non-standard setter that appends the given string to the the "elisp_to_load_file" attribute.
- append_to_before_hook
-
Non-standard setter that appends the given string to the the "before_hook" attribute.
Under some circumstances, the code here uses the "before_hook" (for -Q and --no-splash), so using a setter is mildly dangerous. Typically it's better to just append to the "before_hook".
accessors that effect the "ec_lib_loader" attribute
If either lib_data or emacs_libs is modified, this must trigger another run of "set_up_ec_lib_loader" to keep the "ec_lib_loader" string up-to-date.
- set_lib_data
-
Setter for lib_data.
- reset_lib_data
-
Reverts lib_data to the value supplied during initization (it empties it entirely, if none was supplied).
Note: this does not (at present) trigger a re-build of "ec_lib_loader", because it's presumed that this will be triggered by some step following this one.
- set_emacs_libs
-
Setter for emacs_libs.
Side effect: runs process_emacs_libs_rest on the given emacs_libs list.
process_emacs_libs_reset indirectly calls set_up_ec_lib_loader so we don't need to do so explicitly here.
- push_emacs_libs
-
Pushes a new lib to the emacs_libs array.
Takes a string, returns aref of the full list of emacs_libs.
Side-effect: runs process_emacs_libs_addition on the new lib, (appending the new info to lib_data).
process_emacs_libs_addition indirectly calls set_up_ec_lib_loader so we don't need to do so explicitly here.
automatic generation of standard accessors
- AUTOLOAD
MOTIVATION
Periodically, I get interested in the strange world of running emacs code from perl. There's a mildly obscure feature of emacs command line invocations called "--batch" that lets one use it non-interactively, and a number of other command-line options to load files of elisp code or run snippets of code from the command-line.
You might think that there isn't much use for this trick, but I can think of many reasons:
- to probe your emacs set-up from perl, e.g. for automated installation of elisp using perl tools
- to test elisp code using a perl test harness.
- to use tools written in elisp that you don't want to rewrite in perl (e.g. extract-docstrings.el)
Emacs command line invocation is a little language all of it's own, with just enough twists and turns to it that I've felt the need to write perl routines to help drive the process.
emacs invocation vs Emacs::Run
By default an "emacs --batch" run suppresses most of the usual init files (but does load the essentially deprecated "site-start.pl", presumably for backwards compatibility). Emacs::Run has the opposite bias: here we try to load all three of the types of init files, if they're available, though each one of these can be shut-off individually if so desired. This is because one of the main things this code is for is to let perl find out about things such as the user's emacs load-path settings (and in any case, the performance hit of loading these files is no-longer such a big deal).
internal documentation (how the code works, etc).
internal attributes
Object attributes intended largely for internal use. The client programmer has access to these, but is not expected to need it. Note: the common "leading underscore" naming convention is not used here.
- ec_lib_loader
-
A fragment of an emacs command line invocation to load emacs libraries. Different attributes exist to specify emacs libraries to be loaded: as these are processed, the ec_lib_loader string gradually accumulates code needed to load them (so that when need be, the process can use the intermediate value of the ec_lib_loader to get the benefit of the previously processed library specifications).
The primary reason for this approach is that each library loaded has the potential to modify the emacs load-path, which may be key for the success of later load attempts.
The process of probing for each library in one of the "requested" lists has to be done in the context of all libraries that have been previously found. Without some place to store intermediate results in some form, this process might need to be programmed as one large monolithic routine.
strategies in shelling out
Perl has a good feature for running a shell command and capturing the output: qx{} (aka back-quotes), and it's easy enough to append "2>&1" to a shell command when you'd like to see the STDERR messages intermixed with the STDOUT. However, there does not appear to be any simple method of distinguishing between the messages from STDERR and STDOUT later; so, this project almost always works with them intermixed.
The method "eval_elisp" intermixes, though there's an alternate form "eval_elisp_skip_err" that only returns STDOUT.
NOTES
loaded vs. in load-path
The emacs variable "load-path" behaves much like the shell's $PATH (or perl's @INC): if you try to load a library called "dired", emacs searches through the load-path in sequence, looking for an appropriately named file (e.g. "dired.el"), it then evaluates it's contents, and the objects defined in the file become available for use. It is also possible to load a file by telling emacs the path and filename, and that works whether or not it is located in the load-path.
There is at least a slight difference between the two, however. For example, the "extract-docstrings.el" package contains code like this, that will break in the later case:
(setq codefile (locate-library library))
So some of the routines here (notably "elisp_to_load_file") generate elisp with an extra feature that adds the location of the file to the load-path as well as just loading it.
interactive vs. non-interactive elisp init
Emacs::Run tries to use the user's normal emacs init process even though it runs non-interactively. Unfortunately, it's possible that the init files may need to be cleaned up in order to run non-interactively. In my case I found that I needed to check the "x-no-window-manager" variable and selectively disable some code that sets X fonts for me:
;; We must do this check to allow "emacs --batch -l .emacs" to work
(unless (eq x-no-window-manager nil)
(zoom-set-font "-Misc-Fixed-Bold-R-Normal--15-140-75-75-C-90-ISO8859-1"))
TODO
Eliminate unixisms, if possible. A known one: there's a heuristic that spots file paths by looking for "/". Use File::Spec.
Modify the eval_function method to allow for arguments to be passed through to the function.
I suspect some quoting issues still lurk e.g. a library filename containing a double-quote will probably crash things.
Add a method to match a emacs regexp against a string. See: http://obsidianrook.com/devnotes/talks/test_everything/bin/1-test_emacs_regexps.t.html
(goto-char (point-min)) (re-search-forward "$elisp_pattern") (setq first_capture (match-string 1))
In "run_elisp_on_file", add support for skipping to a line number after opening a file
Provide additional methods such as "eval_elisp_skip_err" to allow the client coder more choice about whether STDOUT and STDERR will be returned intermixed. Possibly: provide a facility to send STDERR to a log file, rather than capture it
SEE ALSO
Emacs Documentation: Command Line Arguments for Emacs Invocation http://www.gnu.org/software/emacs/manual/html_node/emacs/Emacs-Invocation.html
A lightning talk about (among other things) using perl to test emacs code: "Using perl to test non-perl code":
http://obsidianrook.com/devnotes/talks/test_everything/index.html
AUTHOR
Joseph Brenner, <doom@kzsu.stanford.edu>, 07 Mar 2008
COPYRIGHT AND LICENSE
Copyright (C) 2008 by Joseph Brenner
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.8.2 or, at your option, any later version of Perl 5 you may have available.
BUGS & LIMITATIONS
When the client coder specifies that a library is "needed", failure occurs relatively late if it's not available: it does not happen during object instantiation, but waits until an attempted run with the object (e.g. "$er->eval_elisp").