Emacs::Run - use emacs from perl via the shell


Emacs-Run documentation Contained in the Emacs-Run distribution.

Index


Code Index:

NAME

Top

Emacs::Run - use emacs from perl via the shell

SYNOPSIS

Top

   use Emacs::Run;
   my $er = Emacs::Run->new();
   my $major_version = $er->emacs_major_version;
   if ($major_version > 22) {
      print "You have a recent version of emacs\n";
   }

   # use extra emacs lisp libraries, then get 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))' );  # that's "4"




   # the eval_elisp_full_emacs method works with a full externally
   # spawned emacs (for unusual code that won't run under '--batch')
   my $elisp_initialize =
     qq{
         (defvar my-temp-var "$text")
         (insert "The initialize elisp has no effect on output: you won't see this.")
      };
   my $elisp =
     qq{
         (insert my-temp-var)
         (downcase-region (point-min) (point-max))
         (my-test-lib-do-something)
        };

   my @emacs_libs = ( $dot_emacs, 'my-test-lib' );

   my $er = Emacs::Run->new({
                             load_no_inits => 1,
                             emacs_libs    => \@emacs_libs,
                            });

   my $output_lines_aref =
     $er->eval_elisp_full_emacs( {
          elisp_initialize => $elisp_initialize,
          output_file      => $name_list_file,    # omit to use temp file
          elisp            => $elisp,

DESCRIPTION

Top

Emacs::Run is a module that provides portable utilities to run emacs from perl as an external process.

This module provides methods to allow perl code to:

Most of the routines here make use of the emacs "--batch" feature that runs emacs in a non-interactive mode. A few, such as eval_elisp_full_emacs work by opening a full emacs window, and then killing it when it's no longer needed.

MOTIVATION

Periodically, I find myself 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 essentially transforms emacs into a lisp interpreter. Additonal command-line options allow one to load files of elisp code and run pieces of code from the command-line.

I've found several uses for this tricks. You can use it to:

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

At present, using Emacs::Run has one large portability advantage over writing your own emacs invocation code: there are some versions of GNU emacs 21 that require the "--no-splash" option, but using this option would cause an error with earlier versions. Emacs::Run handles the necessary probing for you, and generates the right invocation string for the system's installed emacs.

There are also some other, smaller advantages (e.g. automatic adjustment of the load-path to include the location of a package loaded as a file), and there may be more in the future.

A raw "emacs --batch" run would suppress most of the usual init files (but does load the essentially deprecated "site-start.pl"). Emacs::Run has the opposite bias: here we try to load all three kinds of init files, though each one of these can be shut-off individually if so desired. This is because one of the main intended uses is to let perl find out about things such as the user's emacs settings (notably, the load-path). And depending on your application, the performance hit of loading these files may not seem like such a big deal these days.

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

Indicates how to find the emacs program. Defaults to 'emacs', which lets the system (e.g. the shell's PATH environment variable) find the program if it can. 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.

redirector

A code that specifies how the default way of handling the standard output and error streams for some methods, such as eval_elisp, run_elisp_on_file and eval_function.

This may be one of three values:

stdout_only
stderr_only
all_output (object default -- some methods may differ)

Alternately, one may enter Bourne shell redirection codes using the shell_output_director.

shell_output_director

A Bourne shell redirection code (e.g. '2>&1'). This is an alternative to setting redirector.

before_hook

A string inserted into the built-up emacs commands immediately after "--batch", but before any thing else is executed. This is a good place to insert additional invocation options such as "--multibyte" or "--unibyte". See </append_to_before_hook>.

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, which may be set to disable all three types of emacs init files in one step. Overrides the other three.

emacs_libs

A list of emacs libraries (with or without paths) to be loaded automatically. This is recommended for most uses, though to take full control over how your emacs libraries are handled, see lib_data.

default_priority

The global default for how all the emacs libraries should be loaded. Normally this is set to "requested", but it can be set to "needed".

A 'requested' library will be silently skipped if it is not available (and any elisp code using it may need to to adapt to it's absense, e.g. by doing 'featurep' checks).

A 'needed' file will cause an error to occur if it is not available.

Note: this error does not occur during object instantiation, but only after a method is called that needs to load the libraries (e.g. eval_function get_variable, eval_elisp, run_elisp_on_file, etc).

lib_data

Note: using emacs_libs is usually preferrable to lib_data.

lib_data is the internal representation that </emacs_libs> is converted into, but the client programmer is provided access to it to cover any unusual needs.

The structure of lib_data is 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, but if a 'needed' file is not available it's regarded as an error condition. The default priority is 'requested', but that can be changed via the default_priority attribute. See default_priority for more details.

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. When neither is specified, this module guesses the lib is a file if it looks that way (i.e 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.

These attributes are used to pass information to the client programmer, they should be regarded as read-only:

emacs_version

The version number of emacs in use: this is 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.

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.

Simple Emacs Invocations

Some simple methods for obtaining information from your emacs installation.

These methods default to returning STDOUT, suppressing anything sent to STDERR. This behavior can be overridden: see Controlling Output Redirection.

eval_function

Given the name of an emacs function, this runs the function and returns the value from emacs (when started with the the .emacs located in $HOME, if one is found). After the function name, an optional array reference may be supplied to pass through a list of simple arguments (limited to strings) to the elisp function. And further, an optional hash reference may follow that to specify options to the "eval_function" method.

By default the returned output is STDOUT only but this behavior can be overridden: See Controlling Output Redirection.

As with get_variable, this uses the emacs 'print' function internally.

Examples:

  my $name  = $er->eval_function( 'user-full-name' );

  $er->eval_function( 'extract-doctrings-generate-html-for-elisp-file',
                      [ "$input_elisp_file",
                        "$output_file",
                        "The extracted docstrings" ] );

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

See get_load_path below for a more perl-friendly way of doing this.

Ignores redirector/shell_output_director.

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.

Ignores redirector/shell_output_director.

probe_for_option_no_splash

Looks for the emacs command line option "--no-splash", returning true (1) if it exists, and false (0) otherwise.

Ignores redirector/shell_output_director.

Running Elisp

These are general methods that run pieces of emacs lisp code.

The detailed behavior of these methods have a number of things in common:

By default the 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.

Unless specified otherwise, the methods return the output from the elisp code with STDOUT and STDERR mixed together, though this behavior can be overridden. See Controlling Output Redirection.

(The advantage of intermixing STDOUT and STDERR is that the emacs functions 'message' as well as 'print' both may be used for interesting output. The disadvantage is that you may have many inane messages from emacs sent to STDERR such as 'Loading library so-and-so')

eval_elisp

Given a string containing a chunk of elisp code this method runs it by invoking emacs in batch mode.

Example:

  my $result = $er->eval_elisp( '(print (+ 2 2))' );

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.

Returns whatever value the elisp returns.

Example usage: $self->run_elisp_on_file( $filename, $elisp );

eval_elisp_full_emacs

Runs the given chunk(s) of elisp using a temporarily launched full scale emacs window (does not work via "--batch" mode).

Returns an array reference of lines of output.

Of necessity, this emacs sub-process must communicate through a file (similar to "run_elisp_on_file"), so the elisp run by this routine should be designed to output to the current buffer (e.g. via "insert" calls).

Any elisp functions such as "message" and "print" will have no direct effect on output, and neither the <L/redirector> or <L/shell_output_director> have any effect here.

As an option, a separate chunk of initialization elisp may also be passed in: it will be run before the output file buffer is opened, and hence any modification it makes to the current buffer will be ignored.

If the "output_filename" is not supplied, a temporary file will be created and deleted afterwards. If the name is supplied, the output file will be still exist afterwards (but note: any existing contents will be over-written).

The current buffer is saved at the end of the processing (so there's no need to include a "save-buffer" call in the elisp).

All arguments are passed into this method via a hashref of options. These are:

  elisp_initialize
  output_file
  elisp

Note that this last "option" is not optional: you need to supply some "elisp" if you want anything to happen.

Example use:

  my $er = Emacs::Run->new({
                load_no_inits = 1,
                emacs_libs => [ '~/lib/my-elisp.el',
                                '/usr/lib/site-emacs/stuff.el' ],
                });

  # Using just the 'elisp' argument:
  my $elisp =
    qq{ (insert-file "$input_file")
       (downcase-region (point-min) (point-max))
     };

  my $output =
    $er->eval_elisp_full_emacs( {
         elisp => $elisp,
     }
   );

  # Using all options:
  my $output =
    $er->eval_elisp_full_emacs( {
         elisp_initialize => $elisp_initialize,
         output_file      => $output_file,
         elisp            => $elisp,
         message_log      => '/tmp/message.log',
     }
   );

This method only uses some of the usual Emacs::Run framework:

The three individual init settings flags have no effect on this method ("load_emacs_init", "load_site_init", "load_default_init"). If "load_no_inits" is set, the emacs init files will be ignored (via "-q") unless, of course, they're passed in manually in the "emacs_libs" array reference.

Adding libraries to emacs_libs will not automatically add their locations to the load-path (because the "ec_lib_loader" system is not in use here).

Ignores redirector/shell_output_director.

If the option "message_log" contains the name of a log file the emacs '*Messages*' buffer will be appended to it.

full_emacs_done

Internally used routine.

When it looks as though the child process run by eval_elisp_full_emacs is finished, this returns true.

At present, this just watches to see when the output_file has been written.

INTERNAL METHODS

Top

The following methods are intended primarily for internal use.

Note: the common "leading underscore" naming convention is not used here.

Utility Methods

quote_elisp

Routine to quote elisp code before feeding it into an emacs batch shell command. Used internally by methods such as eval_elisp.

This just adds a single backslash to all the double-quote characters (essentially 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 };

progn_wrapper

Takes a chunk of elisp, and adds a "progn" around it, to help make multi-line chunks of elisp Just Work.

parse_ec_string

Takes a chunk of emacs command-line invocation in string form and converts it to a list form (suitable for feeding into "system" or "exec", stepping around the shell).

Returns an aref of tokens (filenames, options, option-arguments).

Limitation:

The '--' option (which indicates that all following tokens are file names, even if they begin with a hyphen) is not yet handled correctly here.

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. Note that it is limited to elisp with a single print of a result: multiple prints will leave embedded quote-newline pairs in the output.

redirector_to_sod

Convert a redirector code into the equivalent shell_output_director (Bourne shell).

Initialization Phase 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 (indirectly) by init during object initialization.

Generation of Emacs Command Strings to Load Libraries

These are routines run by set_up_ec_lib_loader 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.

Genec Methods Called Dynamically

The following is a set of four routines used by "set_up_ec_lib_loader" 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"

where type is 'lib' or 'file' and priority is 'requested' or 'needed'.

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

Ignores redirector/shell_output_director.

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

Utilities Used by Initialization

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.

Ignores redirector/shell_output_director.

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;

Ignores redirector/shell_output_director.

lib_or_file

Given the name of an emacs library, examine it to see if it looks like a file system path, or an emacs library (technically a "feature name", i.e. sans path or extension).

Returns a string, either "file" or "lib".

Routines in Use by Some External Libraries

These aren't expected to be generally useful methods, but they are in use by some code (notably 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).

Setters and getters exist for all of the object attributes which are documented with the new method (but note that these exist even for "internal" attributes that are not expected to be useful to the client coder).

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, this module uses the before_hook for internal purposes (for -Q and --no-splash), so using an ordinary setter might erase something you didn't realize was there. Typically it's safer to do an append to the before_hook by using this method.

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.

set_redirector

Setter for object attribute set_redirector. Automatically sets the shell_output_director field.

Controlling Output Redirection

As described under new, the redirector is a code used to control what happens to the output streams STDOUT and STDERR (or in elisp terms, the output from "print" or "message"): stdout_only, stderr_only or all_output.

The client programmer may not need to worry about the redirector at all, since some (hopefully) sensible defaults have been chosen for the major methods here:

  all_output   (using the object default)

     eval_elisp
     run_elisp_on_file

  stdout_only  (special method-level defaults)

     get_load_path
     get_variable
     eval_function

In addition to being able to set redirector at instantiation (as an option to new), redirector can also often be set at the method level to temporarily override the object-level setting.

For example, if "eval_elisp" is returning some messages to STDERR that you'd rather filter out, you could do that in one of two ways:

Changing the object-wide default:

   my $er = Emacs::Run->new( { redirector => 'stdout_only' } );
   my $result = $er->eval_elisp( $elisp_code );

Using an option specific to this method call:

   my $er = Emacs::Run->new();
   my $result = $er->eval_elisp( $elisp_code, { redirector => 'stdout_only' } );

If you need some behavior not supported by these redirector codes, it is possible to use a Bourne-shell style redirect, like so:

   # return stdout only, but maintain an error log file
   my $er = Emacs::Run->new( { shell_output_director => "2>$logfile" } );
   my $result = $er->eval_elisp( $elisp_code );

As with redirector, the shell_output_director can be set at the object-level or (often) at the method-level.

shell_output_director

The shell_output_director (sometimes called sod for short) is a string appended to the internally generated emacs invocation commands to control what happens to output.

Typical values (on a unix-like system) are:

'2>&1'

Intermix STDOUT and STDERR (in elisp: both "message" and "print" functions work).

'2>/dev/null'

return only STDOUT, throwing away STDERR (in elisp: get output only from "print"). But see File::Spec's devnull.

"> $file 2>&1"

send all output to the file $file

">> $file 2>&1"

append all output to the file $file, preserving any existing content.

"2 > $log_file"

return only STDOUT, but save STDERR to $log_file

internal documentation (how the code works, etc).

Top

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.

lib_data_initial

The initial setting of lib_data when the object is instantiated. As currently implemented, some operations here require resetting the state of lib_data and re-building it. This attribute facilitates that process.

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 features 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 present version of the "extract-docstrings.el" library (see Emacs::Run::ExtractDocs) contains code like this, that will break if the library you're looking for is not in the load-path:

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

Alternately, eval_elisp_full_emacs may be used to run elisp using a full, externally spawned emacs, without using the --batch option: you'll see another emacs window temporarily spring into life, and then get destroyed, after passing the contents of the current-buffer back by using a temporary file.

INTERNALS

The Tree of Method Calls

The potential tree of method calls now runs fairly deep. A bug in a primitive such as detect_site_init can have wide-ranging effects:

   new
     init
        append_to_before_hook
        process_emacs_libs_addition
        set_up_ec_lib_loader
           lib_or_file
           genec_load_emacs_init
              append_to_ec_lib_loader
              detect_site_init
              detect_lib
           genec_loader_lib_needed
              append_to_ec_lib_loader
           genec_loader_file_needed
              quote_elisp
              elisp_to_load_file
              append_to_ec_lib_loader
           genec_loader_lib_requested
              detect_lib
              append_to_ec_lib_loader
           genec_loader_file_requested
              quote_elisp
              elisp_to_load_file
              append_to_ec_lib_loader
        probe_emacs_version
           parse_emacs_version_string




   eval_elisp
     quote_elisp
     clean_return_value
     set_up_ec_lib_loader
        lib_or_file
        genec_load_emacs_init
           append_to_ec_lib_loader
           find_dot_emacs
           detect_site_init
           detect_lib
        genec_loader_lib_needed
           append_to_ec_lib_loader
        genec_loader_file_needed
           quote_elisp
           elisp_to_load_file
           append_to_ec_lib_loader
        genec_loader_lib_requested
           detect_lib
           append_to_ec_lib_loader
        genec_loader_file_requested
           quote_elisp
           elisp_to_load_file
           append_to_ec_lib_loader

Note that as of this writing (version 0.09) this code ensures that the ec_lib_loader string is up-to-date by continually re-generating it.

TODO

Top

BUGS & LIMITATIONS

Top

SEE ALSO

Top

Emacs::Run::ExtractDocs

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

OTHER EXAMPLES

Top

Examples of "advanced" features (i.e. ones you're unlikely to want to use):

   # Specify in detail how the emacs lisp libraries should be loaded
   # (initialization does not fail if a library that's merely "requested"
   # is unavailable):
   $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")) }
                );







   # using a "redirector" code (capture only stderr, ignore stdout, like '1>/dev/null')
   $er = Emacs::Run->new({
                         redirector => 'stderr_only'
                        });
   my $result = $er->eval_elisp( '(message "hello world") (print "you can't see me"))' );







   # View your emacs load_path from the command-line
   perl -MEmacs::Run -le'my $er=Emacs::Run->new; print for @{ $er->get_load_path }';

   # Note that the obvious direct emacs invocation will not show .emacs customizations:
   emacs --batch --eval "(print (mapconcat 'identity load-path \"\n\"))"

   # This does though
   emacs --batch -l ~/.emacs --eval "(prin1 (mapconcat 'identity load-path \"\n\"))" 2>/dev/null




AUTHOR

Top

Joseph Brenner, <doom@kzsu.stanford.edu>, 07 Mar 2008

COPYRIGHT AND LICENSE

Top


Emacs-Run documentation Contained in the Emacs-Run distribution.
package Emacs::Run;
use base qw( Class::Base );

use 5.8.0;
use strict;
use warnings;
use Carp;
use Data::Dumper;
use Hash::Util      qw( lock_keys unlock_keys );
use File::Basename  qw( fileparse basename dirname );
use File::Spec;
use Cwd qw( cwd abs_path );
use List::Util      qw( first );
use Env             qw( $HOME );
use List::MoreUtils qw( any );
use File::Temp      qw{ tempfile };

our $VERSION = '0.15';
my $DEBUG = 0;

# needed for accessor generation
our $AUTOLOAD;
my %ATTRIBUTES = ();

# Note: "new" is inherited from Class::Base and
# calls the following "init" routine automatically.

sub init {
  my $self = shift;
  my $args = shift;
  unlock_keys( %{ $self } );

  if ($DEBUG) {
    $self->debugging(1);
  }

  # object attributes here, including arguments that become attributes
  my @attributes = qw(
                       emacs_path
                       emacs_version
                       emacs_major_version
                       emacs_type

                       load_emacs_init
                       load_site_init
                       load_default_init
                       load_no_inits

                       emacs_libs
                       lib_data
                       lib_data_initial
                       default_priority

                       before_hook
                       ec_lib_loader
                       shell_output_director

                       redirector

                       message_log
                      );

  foreach my $field (@attributes) {
    $ATTRIBUTES{ $field } = 1;
    $self->{ $field } = $args->{ $field };
  }

  if( $self->{ redirector } && $self->{ shell_output_director } ) {
    carp "redirector takes precedence: shell_output_director setting ignored.";
  }
  if( $self->{ redirector } ) {
    $self->{ shell_output_director } = $self->redirector_to_sod( $self->redirector );
  } elsif ( $self->{ shell_output_director } ) {
    $self->{ redirector } = ''; # shouldn't matter now, in any case
  } else {
    # by default, we intermix STDOUT and STDERR
    $self->{ shell_output_director } = '2>&1';
    $self->{ redirector }            = 'all_output'; # redundant? what the hell.
  }

  # Define attributes (apply defaults, etc)
  $self->{ec_lib_loader} = '';

  # If we weren't given a path, let the $PATH sort it out
  $self->{ emacs_path } ||= 'emacs';

  # Determine the emacs version (if we haven't been told already - but why override TODO?)
  $self->{ emacs_version } ||= $self->probe_emacs_version;
  unless( $self->{ emacs_version } ) {  # if emacs is not found, just bail
    return;
  }

  # By default, we like to load all init files
  $self->{load_emacs_init}   = 1 unless defined( $self->{load_emacs_init}   );
  $self->{load_site_init}    = 1 unless defined( $self->{load_site_init}    );
  $self->{load_default_init} = 1 unless defined( $self->{load_default_init} );

  if( $self->{load_no_inits} ) { # ... but we make it easy to suppress all of them, too.
    $self->{load_emacs_init}   = 0;
    $self->{load_site_init}    = 0;
    $self->{load_default_init} = 0;
  }

  $self->{ before_hook } ||= '';
  if($self->{load_no_inits} ) {
    $self->append_to_before_hook( ' -Q ' );
  }

  $self->{ default_priority } ||= 'requested';

  # preserving any given lib_data in the event of a need to reset.
  $self->{lib_data_initial} = $self->{ lib_data };

  if( defined( my $emacs_libs = $self->{ emacs_libs } ) ) {
    $self->process_emacs_libs_addition( $emacs_libs );
  } else {
    # called indirectly by process_emacs_libs_addition.
    # no point in doing it again, *unless* no emacs_libs
    $self->set_up_ec_lib_loader;
  }

  lock_keys( %{ $self } );
  return $self;
}


sub eval_function {
  my $self     = shift;
  my $funcname = shift;
  my $arg2     = shift;
  my $subname = ( caller(0) )[3];

  my $devnull = File::Spec->devnull();

  my ($passthroughs, $opts, $passthru);
  if (ref( $arg2 ) eq 'ARRAY') {
    $passthroughs = $arg2;
    $passthru = join " ", map{ qq{"$_"} } @{ $passthroughs };
    $opts = shift;
  } elsif (ref( $arg2 ) eq 'HASH') {
    $opts     = $arg2;
  }

  my $redirector = $opts->{ redirector } || "stdout_only";
  my $sod =
    $opts->{ shell_output_director }                   ||
    $self->redirector_to_sod( $redirector )            ||
    "2>$devnull";

  my $elisp;
  if( $passthru ) {
    $elisp = qq{ (print ($funcname $passthru)) };
  } else {
    $elisp = qq{ (print ($funcname)) };
  }

  my $return = $self->eval_elisp( $elisp, {
                                           shell_output_director => $sod,
                                          } );
  return $return;
}

sub get_variable {
  my $self    = shift;
  my $varname = shift;
  my $opts    = shift;
  my $devnull = File::Spec->devnull();

  my $redirector = 'stdout_only';
  my $sod = $self->redirector_to_sod( $redirector );

  my $subname = ( caller(0) )[3];
  my $elisp = qq{ (print $varname) };
  my $return = $self->eval_elisp( $elisp, {
                                           shell_output_director => $sod,
                                          } );
  return $return;
}

sub get_load_path {
  my $self = shift;
  my $opts = shift;
  my $devnull = File::Spec->devnull();

  my $redirector = 'stdout_only';
  my $sod = $self->redirector_to_sod( $redirector );

  my $elisp = q{ (print (mapconcat 'identity load-path "\n")) };

  my $return = $self->eval_elisp( $elisp, {
                                           shell_output_director => $sod,
                                          } );
  my @load_path = split /\n/, $return;
  \@load_path;
}



# earlier versions called this "no_splash_p"
sub probe_for_option_no_splash {
  my $self = shift;
  my $subname = ( caller(0) )[3];

  my $emacs       = $self->emacs_path;
  my $before_hook = $self->before_hook;
  $before_hook .= ' --no-splash ';

  if ( $self->emacs_type eq 'XEmacs' ) {
    return 0; # xemacs has no --no-splash
  }

  my $sod = '2>&1';

  my $cmd = qq{ $emacs --batch $before_hook $sod };
  $self->debug("$subname: cmd: $cmd\n");
  my $retval = qx{ $cmd };
  $retval = $self->clean_return_value( $retval );

  my $last_line = ( split /\n/, $retval )[-1] || '';

  $self->debug( "$subname retval:\n===\n$retval\n===\n" );

  if( $retval =~ m{ Unknown \s+ option \s+ .*? --no-splash }xms ) {
    return 0;
  } else {
    return 1;
  }
}



sub eval_elisp {
  my $self     = shift;
  my $elisp    = shift;
  my $opts     = shift;
  my $subname  = ( caller(0) )[3];

  my $redirector = $opts->{ redirector } || $self->redirector;
  my $sod =
      $opts->{ shell_output_director }                    ||
      $self->redirector_to_sod( $opts->{ redirector } )   ||
      $self->shell_output_director                        ||
      $self->redirector_to_sod( $self->redirector );

  $elisp = $self->quote_elisp( $self->progn_wrapper( $elisp ));

  my $emacs = $self->emacs_path;
  my $before_hook = $self->before_hook;

  my $ec_head = qq{ $emacs --batch $before_hook };
  my $ec_tail = qq{ --eval "$elisp" };
  my $ec_lib_loader = $self->set_up_ec_lib_loader;

  my $cmd = "$ec_head $ec_lib_loader $ec_tail $sod";
  $self->debug("$subname: cmd:\n $cmd\n");

  my $retval = qx{ $cmd };
  $retval = $self->clean_return_value( $retval );
  $self->debug( "$subname retval:\n===\n$retval\n===\n" );

  return $retval;
}

sub run_elisp_on_file {
  my $self     = shift;
  my $filename = shift;
  my $elisp    = shift;
  my $opts     = shift;
   my $subname  = ( caller(0) )[3];

  my $redirector = $opts->{ redirector } || $self->redirector;

  my $sod =
      ( $opts->{ shell_output_director }                   ) ||
      ( $self->redirector_to_sod( $opts->{ redirector } )  ) ||
      ( $self->shell_output_director                       ) ||
      ( $self->redirector_to_sod( $self->redirector )      );

  $elisp = $self->quote_elisp( $elisp );

  my $emacs       = $self->emacs_path;
  my $before_hook = $self->before_hook;

  # Covering a stupidity with some versions of gnu emacs 21: "--no-splash"
  # to suppress an inane splash screen.
  if ( $self->emacs_major_version eq '21' &&
       $self->emacs_type eq 'GNU Emacs' &&
       $self->probe_for_option_no_splash ) {
    $before_hook .= ' --no-splash ';
  }

  my $ec_head = qq{ $emacs --batch $before_hook --file='$filename' };
  my $ec_tail = qq{ --eval "$elisp" -f save-buffer };
  my $ec_lib_loader = $self->ec_lib_loader;

  my $cmd = "$ec_head $ec_lib_loader $ec_tail $sod";
  $self->debug("$subname: cmd: $cmd\n");

  my $retval = qx{ $cmd };
  $retval = $self->clean_return_value( $retval );
  $self->debug( "$subname retval:\n===\n$retval\n===\n" );

  return $retval;
}

sub eval_elisp_full_emacs {
  my $self     = shift;
  my $opts     = shift;
  my $subname  = ( caller(0) )[3];

  # unpack options
#  my $elisp            = $opts->{ elisp };
  my $elisp_initialize = $self->progn_wrapper( $opts->{ elisp_initialize } );
  my $elisp            = $self->progn_wrapper( $opts->{ elisp } );
  my $output_file      = $opts->{ output_file };

  my $message_log      = $opts->{ message_log } || $self->message_log;

  # if $output_file is blank, need to pick a temp file to use.
  unless( $output_file ) {
    my $fh;
    my $unlink = not( $DEBUG );
    ($fh, $output_file) =
      tempfile( "emacs_run_eval_elisp_full_emacs-$$-XXXX",
                SUFFIX => '.txt',
                UNLINK => $unlink );
    close ($fh); # after it's written by the subprocess, we will read this file
  }

  # Have to do this to ensure that exit condition works
  unlink( $output_file ) if -e $output_file;

  my $emacs       = $self->emacs_path;
  my $before_hook = $self->before_hook;

  # need "--no-splash" for some versions of emacs
  if ( $self->emacs_major_version eq '21' &&
       $self->emacs_type eq 'GNU Emacs' &&
       $self->probe_for_option_no_splash ) {
    $before_hook .= ' --no-splash ';
  }

  my $elisp_log_messages = $self->progn_wrapper(
    $message_log ?
    qq{
        (find-file "$message_log")
        (insert (format "\n $0 logging *Messages* - %s\n" (current-time-string)))
        (goto-char (point-max))
        (insert-buffer "*Messages*")
        } : ''
   );
  ($DEBUG) && print STDERR "\n$elisp_log_messages\n\n";

  my $output_buffer = basename( $output_file );
  my $elisp_back_to_output =
    qq{
            (switch-to-buffer "$output_buffer")
        };
  ($DEBUG) && print STDERR "\n", $elisp_back_to_output, "\n\n";

  # Build up the command arguments
  my @cmd;
  push @cmd, ( "emacs_from_" . "$$" );  # just the process label, not the binary

  push @cmd, @{ $self->parse_ec_string( $before_hook ) } if $before_hook;

  foreach my $lib ( @{ $self->emacs_libs } ) {
    push @cmd, ( "-l", "$lib" ) if $lib;
  }

  push @cmd, ( "--eval", "$elisp_initialize" ) if $elisp_initialize;
  push @cmd, ( "--file", "$output_file" );
  push @cmd, ( "--eval", "$elisp" );

  if ($message_log) {
    push @cmd, ( "--eval", "$elisp_log_messages" );
    push @cmd, ( "-f", "save-buffer" );
  }

  push @cmd, ( "--eval", "$elisp_back_to_output" );
  push @cmd, ( "-f", "save-buffer" );

  $self->debug("$subname: cmd: " . Dumper( \@cmd ) . "\n");

  if ( my $pid = fork ) {
    # this is parent code
    ($DEBUG) && print STDERR "I'm the parent, the child pid is $pid\n";

    #  kill the child emacs when it's finished
    LOOP: while(1) { # TODO needs to time out (what if *nothing* is written?)
      if ( $self->full_emacs_done ({ output_file => $output_file,
                                     pid => $pid }) ){
        sleep 1; # a little time for things to settle down (paranoia)
        my $status =
          kill 1, $pid;
        ($DEBUG) && print STDERR "Tried to kill (1) pid $pid, status: $status \n";
        last LOOP;
      }
    }
 } else {
    # this is child code
    die "cannot fork: $!" unless defined $pid;

    ($DEBUG) && print STDERR "This is the child, about to exec:\n";
    exec { $emacs } @cmd;
  }

  open my $fh, '<', $output_file or die "$!";
  my @result = map{ chomp($_); s{\r$}{}xms; $_ } <$fh>;
  # Note: stripping CRs is a hack to deal with some windows-oriented .emacs
  return \@result;
}


### TODO - would be better to watch the process somehow and determine when it's idle.
sub full_emacs_done {
  my $self = shift;
  my $opts = shift;

  my $pid         = $opts->{ pid };
  my $output_file = $opts->{ output_file };

  my $cutoff = 0;      # could increase, if this seems flaky
  if ( (-e $output_file) && ( (-s $output_file) > $cutoff ) ) {
    return 1;
  } else {
    return 0;
  }
}


sub quote_elisp {
  my $self = shift;
  my $elisp = shift;

  $elisp =~ s{"}{\\"}xmsg; # add one backwhack to the double-quotes
  return $elisp;
}


sub progn_wrapper {
  my $self = shift;
  my $elisp = shift;

  $elisp = "(progn $elisp )" if $elisp;
  return $elisp;
}

# processing one char at a time
#   different tokens have different syntax - when we know we're
#   doing one type, we'll watch for it's ending

#   quoted strings are treated as an additional token-type
#   filenames are handled as option-arguments

#   remember when you see quotes (toggle state)
#   when you see an unquoted leading [-+], that begins an option
#      [\s=] closes the option
#        = means the following is an option-arg
#        otherwise, it's an option-arg or a file if no leading [-+]

sub parse_ec_string {
  my $self = shift;

  my $ec_string = shift;
  my (@ec, $part);

  # drop leading and trailing whitespace
  chomp($ec_string);
  $ec_string =~ s/^\s//;
  $ec_string =~ s/\s$//;

  # state flags
  my $quoted = 0;
  my $opted  = 1; # begin saving whatever is there at the outset
  my $arged = 0;
  # 1-char of memory
  my $prev   = '';

  my @chars = split '', $ec_string;
  foreach (@chars) {
    if ( /\s/ and $prev =~ /\s/ ) { # turn multiple spaces into one
      next;
    }
    if ( not( $quoted) and /"/ ) {
      $quoted = 1;
    } elsif( ($quoted) and /"/ and $prev ne '\\') { # matches: " but not \"
      $quoted = 0;
      push @ec, $part; $part = '';
    } elsif ($quoted) {
      # fold double backwhacks into one
      if( $_ eq '\\' and $prev eq '\\' ){
        chop( $part );
      }
      # drop escapes from escaped quotes
      if( $_ eq '"' and $prev eq '\\' ) {
        chop( $part );
      }
      $part .= $_;
    } elsif( not( $opted ) and /[-+]/ and $prev ne '\\' ) {
      $arged = 1;
      $part .= $_;
    } elsif ( $opted and /[\s=]/ ) {
      $opted = 0;
      push @ec, $part; $part = '';
    } elsif ($opted) {
      $part .= $_;
    } elsif( not( $arged ) and ( $prev =~ /\s/ or ($prev eq '=') )) { # looks back ar prev char
      $arged = 1;
      $part .= $_;  # must save-up this char (transition char was the previous one)
    } elsif ($arged and /\s/ ) {
      $arged = 0;
      push @ec, $part; $part = '';
    } elsif ($arged) {
      $part .= $_;
    }
    $prev = $_;
  }
  push @ec, $part unless( $part =~ /^\s*$/ );  # skipping blank lines (hack hack)
  return \@ec;
}


sub clean_return_value {
  my $self = shift;
  my $string = shift;
  $string =~ s{^[\s"]+}{}xms;
  $string =~ s{[\s"]+$}{}xms;
  return $string;
}



sub redirector_to_sod {
  my $self       = shift;
  my $redirector = shift;
  my $devnull = File::Spec->devnull;

  unless ( $redirector ) {
    return;
  }

  my $sod;
  if( $redirector eq 'stdout_only' ) {
    $sod = "2>$devnull";
  } elsif ( $redirector eq 'stderr_only' ) {
    $sod = "2>&1 1>$devnull";
  } elsif ( $redirector eq 'all_output' ) {
    $sod = '2>&1';
  }
  return $sod;
}

# Note: since set_up_ec_lib_loader qualifies the data and fills in
# likely values for type and priority, it need not be done here.
sub process_emacs_libs_addition {
  my $self = shift;
  my $libs = shift;

  my @new_lib_data;
  foreach my $name ( @{ $libs } ) {
    my $rec = [ $name,  { type=>undef, priority=>undef } ];
    push @new_lib_data, $rec;
  }

  my $lib_data = $self->lib_data;
  push @{ $lib_data }, @new_lib_data;
  $self->set_lib_data( $lib_data );  # called for side-effects:
                                     #    set_up_ec_lib_loader
  return \@new_lib_data;
}

sub process_emacs_libs_reset {
  my $self = shift;
  my $libs = shift || $self->emacs_libs;

  $self->reset_lib_data;

  my @new_lib_data;
  foreach my $name ( @{ $libs } ) {
    my $rec = [ $name,  { type=>undef, priority=>undef } ];
    push @new_lib_data, $rec;
  }

  my $lib_data = $self->lib_data;
  push @{ $lib_data }, @new_lib_data;
  $self->set_lib_data( $lib_data );  # called for side-effects:
                                     #   set_up_ec_lib_loader
  return \@new_lib_data;
}

sub set_up_ec_lib_loader {
  my $self = shift;

  $self->genec_load_emacs_init;  # zeroes out the ec_lib_loader string first

  my $lib_data = $self->lib_data;

  foreach my $rec (@{ $lib_data }) {

    my $name     = $rec->[0];
    my $type     = $rec->[1]->{type};      # file/lib
    my $priority = $rec->[1]->{priority};  # needed/requested

    # qualify the lib_data
    unless ( $type ) {
      $type = $self->lib_or_file( $name );
      $rec->[1]->{type} = $type;
    }
    unless ( $priority ) {
      $priority = $self->default_priority;
      $rec->[1]->{priority} = $priority;
    }

    my $method = sprintf "genec_loader_%s_%s", $type, $priority;
    $self->$method( $name );   # appends to ec_lib_loader
  }

  my $ec_lib_loader = $self->ec_lib_loader;

  return $ec_lib_loader;
}

sub genec_load_emacs_init {
  my $self = shift;

  # start from clean slate
  my $ec_lib_loader = $self->set_ec_lib_loader( '' );

  my $load_no_inits     = $self->load_no_inits;
  if ( $load_no_inits ) {
    return $ec_lib_loader; # empty string
  }

  my $load_emacs_init   = $self->load_emacs_init;
  my $load_site_init    = $self->load_site_init;
  my $load_default_init = $self->load_default_init;

  if ( ( $load_site_init ) && ( $self->detect_site_init() ) ) {
    my $ec = qq{ -l "site-start" };
    $self->append_to_ec_lib_loader( $ec );
  }

  if ($load_emacs_init) {
    my $dot_emacs = $self->find_dot_emacs;
    if ( $dot_emacs ) {
      my $ec = qq{ -l "$dot_emacs" };
      $self->append_to_ec_lib_loader( $ec );
    }
  }

  if ( ($load_default_init) && ($self->detect_lib( 'default' )) ) {
    my $ec = qq{ -l "default" };
    $self->append_to_ec_lib_loader( $ec );
  }

  $ec_lib_loader = $self->ec_lib_loader;
  return $ec_lib_loader;
}

# used by set_up_ec_lib_loader
sub genec_loader_lib_needed {
  my $self = shift;
  my $name = shift;

  unless( defined( $name) ) {
    return;
  }

  my $ec = qq{ -l "$name" };
  # TODO what happens with names that contain double-quotes?

  $self->append_to_ec_lib_loader( $ec );
  return $ec;
}

# used by set_up_ec_lib_loader
sub genec_loader_file_needed {
  my $self = shift;
  my $name = shift;

  unless ( -e $name ) {
    croak "Could not find required elisp library file: $name.";
  }
  my $elisp =
    $self->quote_elisp(
                       $self->elisp_to_load_file( $name )
                      );
  my $ec = qq{ --eval "$elisp" };
  $self->append_to_ec_lib_loader( $ec );
  return $ec;
}

# used by set_up_ec_lib_loader
sub genec_loader_lib_requested {
  my $self = shift;
  my $name = shift;

  unless ( $self->detect_lib( $name ) ) {
    return;
  }

  my $ec = qq{ -l "$name" };
  $self->append_to_ec_lib_loader( $ec );
  return $ec;
}

# used by set_up_ec_lib_loader
sub genec_loader_file_requested {
  my $self = shift;
  my $name = shift;

  unless( -e $name ) {
    return;
  }

  my $elisp =
    $self->quote_elisp(
                       $self->elisp_to_load_file( $name )
                      );

  my $ec = qq{ --eval "$elisp" };
  $self->append_to_ec_lib_loader( $ec );
  return $ec;
}

sub probe_emacs_version {
  my $self = shift;
  my $opts = shift;
  my $subname = ( caller(0) )[3];
  my $devnull = File::Spec->devnull();

  my $redirector = 'stdout_only';
  my $sod = $self->redirector_to_sod( $redirector );

  my $emacs = $self->emacs_path;
  my $cmd = "$emacs --version $sod";
  my $retval = qx{ $cmd };
  # $self->debug( "$subname:\n $retval\n" );

  my $version = $self->parse_emacs_version_string( $retval );
  return $version;
}


# Note, a Gnu emacs version string has a first line like so:
#   "GNU Emacs 22.1.1",
# followed by several other lines.
#
# For xemacs, the last line is important, though it's preceeded by
# various messages about for libraries loaded.

# Typical version lines.
#   GNU Emacs 22.1.1
#   GNU Emacs 22.1.92.1
#   GNU Emacs 23.0.0.1
#   GNU Emacs 21.4.1
#   XEmacs 21.4 (patch 18) "Social Property" [Lucid] (amd64-debian-linux, Mule) of Wed Dec 21 2005 on yellow

sub parse_emacs_version_string {
  my $self           = shift;
  my $version_mess   = shift;
  unless( $version_mess ) {
    return;
  }

  my ($emacs_type, $version);
  # TODO assuming versions are digits only (\d). Ever have letters, e.g. 'b'?
  if (      $version_mess =~ m{^ ( GNU \s+ Emacs ) \s+ ( [\d.]* ) }xms ) {
    $emacs_type = $1;
    $version    = $2;
  } elsif ( $version_mess =~ m{ ^( XEmacs )        \s+ ( [\d.]* ) }xms ) {
    $emacs_type = $1;
    $version    = $2;
  } else {
    $emacs_type ="not so gnu, not xemacs either";
    $version    = ''; # silence uninitialized value warnings
  }
  $self->debug( "version: $version\n" );

  $self->set_emacs_type( $emacs_type );
  $self->set_emacs_version( $version );

  my (@v) = split /\./, $version;

  my $major_version;
  if( defined( $v[0] ) ) {
    $major_version = $v[0];
  } else {
    $major_version = ''; # silence unitialized value warnings
  }
  $self->set_emacs_major_version( $major_version );
  $self->debug( "major_version: $major_version\n" );

  return $version;
}

sub elisp_to_load_file {
  my $self       = shift;
  my $elisp_file = shift;

  my $path = dirname( $elisp_file );

  my $elisp = qq{
      (progn
        (add-to-list 'load-path
            (expand-file-name "$path/"))
        (load-file "$elisp_file"))
    };
}

sub find_dot_emacs {
  my $self = shift;
  my @dot_emacs_candidates = (
                   "$HOME/.emacs",
                   "$HOME/.emacs.elc",
                   "$HOME/.emacs.el",
                   "$HOME/.emacs.d/init.elc",
                   "$HOME/.emacs.d/init.el",
                  );

  my $dot_emacs =  first { -e $_ } @dot_emacs_candidates;
  return $dot_emacs;
}

# runs an emacs batch process as a probe, and if the command
# runs "successfully", it checks the return value, and parses
# it to see if the library was in fact found.

# Note that this routine can not easily be written to use
# detect_lib below, because of the different handling of .emacs

sub detect_site_init {
  my $self = shift;
  my $opts = shift;
  my $subname = ( caller(0) )[3];

  my $redirector = 'all_output';
  my $sod = $self->redirector_to_sod( $redirector );

  my $emacs       = $self->emacs_path;
  my $before_hook = $self->before_hook;
  my $lib_name    = 'site-start';

  my $cmd = qq{ $emacs --batch $before_hook -l $lib_name $sod };
  $self->debug("$subname cmd:\n $cmd\n");

  my $retval = qx{ $cmd };
  $self->debug("$subname retval:\n $retval\n");

  if ( defined( $retval ) &&
       $retval =~
         m{\bCannot \s+ open \s+ load \s+ file: \s+ $lib_name \b}xms ) {
    return;
  } else {
    return $lib_name;
  }
}

sub detect_lib {
  my $self         = shift;
  my $lib          = shift;
  my $opts         = shift;
  my $subname = (caller(0))[3];

  return unless $lib;

  my $redirector = 'all_output';
  my $sod = $self->redirector_to_sod( $redirector );

  my $emacs       = $self->emacs_path;
  my $before_hook = $self->before_hook;
  my $ec_head = qq{ $emacs --batch $before_hook };
  # cmd string to load existing, presumably vetted, libs
  my $ec_lib_loader = $self->ec_lib_loader;

  my $cmd = qq{ $ec_head $ec_lib_loader -l $lib $sod};
  my $retval = qx{ $cmd };

  if ( defined( $retval ) &&
       $retval =~ m{\bCannot \s+ open \s+ load \s+ file: \s+ $lib \b}xms ) {
    return;
  } else {
    return $lib;
  }
}


sub lib_or_file {
  my $self = shift;
  my $name = shift;

  my $path_found;
  my ($volume,$directories,$file) = File::Spec->splitpath( $name );
  if( $directories || $volume ) {
    $path_found = 1;
  }

  my $ext_found =  ( $name =~ m{\.el[c]?$}xms );

  my $type;
  if ($path_found) {
    $type = 'file';
  } elsif ($ext_found) {
    $type = 'file';
  } else {
    $type = 'lib';
  }
  return $type;
}


# used externally by Emacs::Run::ExtractDocs
sub elisp_file_from_library_name_if_in_loadpath {
  my $self    = shift;
  my $library = shift;
  my $subname = (caller(0))[3];

  my $elisp = qq{
          (progn
              (setq codefile (locate-library "$library"))
              (message codefile))
    };

  my $return = $self->eval_elisp( $elisp );

  my $last_line = ( split /\n/, $return )[-1];

  my $file;
  if ( ($last_line) && (-e $last_line) ) {
    $self->debug( "$subname: $last_line is associated with $library\n" );
    $file = $last_line;
  } else {
    $self->debug( "$subname: no file name found for lib: $library\n" );
    $file = undef;
  }

  return $file;
}

# used externally by Emacs::Run::ExtractDocs
sub generate_elisp_to_load_library {
  my $self = shift;
  my $arg  = shift;

  my ($elisp, $elisp_file);
  if ($arg =~ m{\.el$}){
    $elisp_file = $arg;
    $elisp = $self->elisp_to_load_file( $elisp_file );
  } else {

    $elisp_file = $self->elisp_file_from_library_name_if_in_loadpath( $arg );

    unless( $elisp_file ) {
      croak "Could not determine the file for the named library: $arg";
    }

    $elisp = $self->elisp_to_load_file( $elisp_file );
  }
  return $elisp;
}


sub append_to_ec_lib_loader {
  my $self = shift;
  my $append_string = shift;

  my $ec_lib_loader = $self->{ ec_lib_loader } || '';
  $ec_lib_loader .= $append_string;

  $self->{ ec_lib_loader } = $ec_lib_loader;

  return $ec_lib_loader;
}


sub append_to_before_hook {
  my $self = shift;
  my $append_string = shift;

  my $before_hook = $self->{ before_hook } || '';
  $before_hook .= $append_string;

  $self->{ before_hook } = $before_hook;

  return $before_hook;
}

sub set_lib_data {
  my $self = shift;
  my $lib_data = shift;
  $self->{ lib_data } = $lib_data;
  $self->set_up_ec_lib_loader;
  return $lib_data;
}


sub reset_lib_data {
  my $self = shift;
  @{ $self->{ lib_data } } = @{ $self->{ lib_data_given } };
  return $self->{ lib_data };
}



sub set_emacs_libs {
  my $self        = shift;
  my $emacs_libs  = shift;
  $self->{ emacs_libs } = $emacs_libs;
  $self->process_emacs_libs_reset( $emacs_libs );
  return $emacs_libs;
}

sub push_emacs_libs {
  my $self = shift;
  my $newlib = shift;

  my $emacs_libs = $self->emacs_libs;
  push @{ $emacs_libs }, $newlib;
  $self->process_emacs_libs_addition( [ $newlib ] );
  return $emacs_libs;
}

sub set_redirector {
  my $self = shift;
  my $redirector = shift;
  $self->{ redirector } = $redirector;

  $self->{ shell_output_director } = $self->redirector_to_sod( $redirector );

  return $redirector;
}




# automatic generation of the basic setters and getters
sub AUTOLOAD {
  return if $AUTOLOAD =~ /DESTROY$/;  # skip calls to DESTROY ()

  my ($name) = $AUTOLOAD =~ /([^:]+)$/; # extract method name
  (my $field = $name) =~ s/^set_//;

  # check that this is a valid accessor call
  croak("Unknown method '$AUTOLOAD' called")
    unless defined( $ATTRIBUTES{ $field } );

  { no strict 'refs';

    # create the setter and getter and install them in the symbol table

    if ( $name =~ /^set_/ ) {

      *$name = sub {
        my $self = shift;
        $self->{ $field } = shift;
        return $self->{ $field };
      };

      goto &$name;              # jump to the new method.
    } elsif ( $name =~ /^get_/ ) {
      carp("Apparent attempt at using a getter with unneeded 'get_' prefix.");
    }

    *$name = sub {
      my $self = shift;
      return $self->{ $field };
    };

    goto &$name;                # jump to the new method.
  }
}


1;

###  end of code