package Emacs::Run::ExtractDocs;
use base qw( Class::Base );

=head1 NAME

Emacs::Run::ExtractDocs - extract elisp docstrings to html form

=head1 SYNOPSIS

   use Emacs::Run::ExtractDocs;
   my $reed = Emacs::Run::ExtractDocs->new({
              html_output_location => "/tmp/html",
   });

   # needed only if extract-docstrings.el isn't installed yet
   $reed->set_main_library("/tmp/new/elisp/extract-doctrings.el");

   $reed->elisp_docstrings_to_html("my-elisp-with-a-lot-of-docstrings.el");

=head1 DESCRIPTION

Emacs::Run::ExtractDocs is a module that provides ways of working
with the "docstrings" from emacs lisp packages and transforming
them to other formats (at present, just html).

The central feature is the elisp_docstrings_to_html method,
which can create a web page displaying the docstrings of any
given elisp package.

Note that this is a very unusual "perl" package, in that it
depends on having emacs installed (most likely, GNU/Emacs).
Also, the extract-docstrings.el file that is shipped with this
perl package must be installed somewhere in the emacs load-path.

=head2 METHODS

=over

=cut

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 Env qw( $HOME );

# For development only:
# use FindBin qw( $Bin );
# use lib ("$Bin/../../../../Emacs-Run/lib");

use Emacs::Run;

our $VERSION = '0.01';
my $DEBUG = 0;  # TODO change to 0 before shipping

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

=item new

Creates a new Emacs::Run::ExtractDocs object.

Takes a hashref as an argument, with named fields identical
to the names of the object attributes. These attributes are:

=over

=item html_output_location

Directory to put generated html.

=item main_library

Defaults to the elisp library name "extract-doctrings",
so the system can find "extract-doctrings.el" once it's
installed in the emacs load_path.

This can be set to a different library name, or more likely
to a full path to the extract-docstrings.el in an unusual
location.  (This is very useful for testing, so that the
code can run before it's installed.)

=item emacs_runner

An Emacs::Run object, used internally to call utility
routines to probe the emacs installation, and run pieces
of emacs lisp code.  This will normally be created automatically,
but if some unusual options are needed, one can be created
externally and passed in as an attribute.

=back

Takes an optional hashref as an argument, with named fields
identical to the names of the object attributes.

=cut

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

=item 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 B<init> of
it's own that calls this B<init>.  Otherwise, it's an internally
used routine that is not of much interest to client coders.

=cut

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

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

  my @attributes = qw(
                       html_output_location
                       main_library
                       emacs_runner
                      );

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

  # default to the lib name (works once extract-docstrings.el
  # is installed)
  $self->{ main_library } ||= 'extract-docstrings';
  my $main_library = $self->{ main_library };

  unless ( $self->emacs_runner ) {
    my $er = Emacs::Run->new({
                              emacs_libs => [ $main_library ],
                            });
    $self->set_emacs_runner( $er );
  }

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

=back

=head2 main methods

=over

=item elisp_docstrings_to_html

Given the name of an emacs lisp library (sans path or extension),
or the file name of the library (with extension *.el), generates
an html file in the standard location defined in the object:
html_output_location

The output file naming convention is: <lib_name>_el.html

=cut

sub elisp_docstrings_to_html {
  my $self    = shift;
  my $thingie = shift;  # either file or library
  my $progname = ( caller(0) )[3];
  my $er = $self->emacs_runner;

  # Add the given library to the ones loaded by the Emacs::Run object
  $er->push_emacs_libs( $thingie );

  my $loader_elisp = $er->generate_elisp_to_load_library( $thingie );
  $loader_elisp    = $er->quote_elisp( $loader_elisp );

  my ($elisp_file, $elisp_lib);
  if ( $thingie =~ m/\.el$/ ) {
    $elisp_file = $thingie;
    ($elisp_lib, undef, undef) = fileparse( $elisp_file, qr{ \.el$ }x );
  } else {
    $elisp_lib = $thingie;
    $elisp_file = $self->emacs_runner->elisp_file_from_library_name_if_in_loadpath( $elisp_lib );
  }

  my $output_loc = $self->html_output_location;
  my $output_file = "$output_loc/$elisp_lib" . '_el.html';

  unlink $output_file if -e $output_file; # redundant with elisp feature

  my $extractor_elisp = qq{
    (extract-doctrings-generate-html-for-elisp-file
       "$elisp_file"
       "$output_file"
       "Documentation for $elisp_lib.el (extracted docstrings)")
  };

  $self->emacs_runner->eval_elisp( $extractor_elisp );  # Note: eval_elisp does a quote_elisp internally.

  my $output_created_flag = -e $output_file;

  # check that there's a closing </HTML> at the bottom
  if ($output_created_flag) {
    open my $fh, '<', $output_file or die "Could not open $output_file for read: $!";
    local $/;
    my $content = <$fh>;
    close( $fh );
    unless( $content =~ m{ </HTML> \s* \z }xmsi ) {
      $output_created_flag = 0;
    }
  }

  return $output_created_flag;
}

=back

=head2 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.

=head2  automatic generation of accessors

=over

=item AUTOLOAD

=cut

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;

=back

=head1 MOTIVATION

Publishing code to a web site is essentially a systems
administration task that is a very good fit for perl, but when
the code you're publishing is emacs lisp, emacs lisp is
convenient for some tasks: hence this franken-project, gluing an
emacs lisp package (extract-docstrings.el) into

A practice started by Illya Zakharovich when he authored
cperl-mode was to abuse this system of docstrings slightly, in
order to fake "pod" in elisp.

If you're documentation is embedded in the emacs helps
system in the form of these docstrings, when creating web
pages about the code, it's useful to be able to extract
the docstrings and format them as an html page.

And that's the need this lash-up of a module fills.


=head1 TODO

o  With this version, I use the (rather cheesy, in my opinion)
   cop-out of instructing the user to manually install the elisp
   somewhere in the load-path.  Question: can this be automated?

o  Currently, this is file-oriented: one *.el in, one *.html out.
   Would like to work on a set of elisp file, and handle
   internal links inside the set appropriately.

o  Look into skeleton or tempo to do html headers and footers.
   At present, these are hardcoded strings (to dodge the old
   dependency on the non-standard template.el).

=head1 SEE ALSO

L<Emacs::Run>

=head1 AUTHOR

Joseph Brenner, E<lt>doom@kzsu.stanford.eduE<gt>,
02 Mar 2008

=head1 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.

=head1 BUGS

None reported... yet.

=cut
