use.perl blogging with win32 emacs + perl

jjohn on 2005-02-24T16:09:26

Since use.perl.org has become my de facto backup solution, I now post the scripts I use to blog from winders. These are modified versions of the scripts I mentioned in a use.perl.org article published a while ago.

The emacs file:

(defvar prog
   "C:/perl/bin/perl.exe F:/blog/use_perl_blog.pl"
   "use_perl_journal: A SOAP client for use.perl journaling"
)

(defun edit-entry ()
   "Add an entry or edit an existing one"
   (interactive)
   (setq cmd (concat prog " edit"))
   (widen)
   (shell-command-on-region (point-min) (point-max) cmd)
)

(defun get-entry (n)
  "Get journal entry from use.perl.org"
  (interactive "sJournal ID: ")
  (setq buffer (generate-new-buffer "*use_perl_journal*"))
  (switch-to-buffer buffer)
  (setq cmd (concat prog (concat " -i " (concat n " get"))))
  (shell-command-on-region (point-min) (point-max) cmd 1 nil nil) 
)

(defun list-entries (uid limit)
   "Get journal entries"
   (interactive "sUser ID: \nsLimit: ")
   (setq buffer (generate-new-buffer "*use_perl:list_entries*"))
   (switch-to-buffer buffer)
   (setq cmd (concat prog (concat " -l " (concat limit " -i " (concat uid " list")))))
   (shell-command-on-region (point-min) (point-max) cmd 1 nil nil)
)


(defun delete-entry (jid)
  "Delete journal entry"
  (interactive "nEntry ID: ")
  (setq cmd (concat prog (concat " -i " (concat jid (concat " delete")))))
  (shell-command-on-region (point-min) (point-max) cmd 1 nil nil)
)

;; don't use tabs
(setq-default indent-tabs-mode nil)

(global-set-key "\C-xtl" `list-entries)
(global-set-key "\C-xtg" `get-entry)
(global-set-key "\C-xts" `edit-entry)
(global-set-key "\C-xtm" `edit-entry)
(global-set-key "\C-xtd" `delete-entry)

The perl script:

# -*-cperl-*-
# A SOAP client to post USE.PERL.ORG journal entries

use strict;
use HTTP::Cookies;
use SOAP::Lite;
use File::Basename;
use Digest::MD5 'md5_hex';
use Data::Dumper;
use Getopt::Std;

use constant DEBUG => 0;
use constant UID   => -1; # your UID here
use constant PW    => 's3cr3t'; # your pw here
use constant URI   => 'http://use.perl.org/Slash/Journal/SOAP';
use constant PROXY => 'http://use.perl.org/journal.pl';

my $Dispatch = {
                 'get'  => \&get_entry,
                 'list' => \&list_entries,
                 'add'  => \&add_entry,
                 'edit' => \&edit_entry,
                 'delete' => \&delete_entry,
               };

my $opts = {};
getopts('h?vi:u:l:', $opts);

my $action = pop @ARGV;

unless ($action) {
  print usage(), "\n";
  exit;
}

my $soap_client = make_soap();

my $exit_value = 0;
if (defined $Dispatch->{$action}) {
  $exit_value = !$Dispatch->{$action}->($opts, $soap_client);
} else {
  warn("Unknown action '$action'");
  print usage();
  $exit_value = 1;
}

exit $exit_value;

#------
# subs
#------

sub usage {
  my $base = basename($0);
  return qq[
$base - manage use.perl.org blog

 USAGE: 
   $base [options] [actions]

 OPTIONS:
   ?       print this screen
   h       print this screen
   v       verbose mode
   i   entry ID 
   l  limit the number of listed entries to this number
   u   use.perl.org user ID

 ACTIONS:  
  add
  delete 
  edit
  get
  list
 Input files take the following form:
	  id:
	  subject:
          body:
];
}

sub make_soap {
  my $cookie = HTTP::Cookies->new;
  $cookie->set_cookie( 0,
		       user => bakeUserCookie(&UID, &PW),
		       "/", 
		       "use.perl.org",
		     );

  return SOAP::Lite->uri(URI)->proxy(PROXY, cookie_jar => $cookie);
}

sub add_entry {
  my ($opts, $c, $in) = @_;
  
  $in ||= parse_input();

  my $ret;
  if ($in->{subject} && $in->{body}) {
    if ($in->{id}) {
      return edit_entry(@_, $in);
    } else {
      $ret = $c->add_entry($in->{subject}, $in->{body}); 
    }
  } else {
    $ret = $c->add_entry("Random thought #$$", $in->{all});
  }

  return if had_transport_error($ret);
  print "add_entry got articleID: ", $ret->result, "\n";
  return 1;
}

sub delete_entry {
  my ($opts, $c) = @_;

  my ($id) = $opts->{i} || die "delete requires a journal ID\n";
  my $ret = $c->delete_entry($id);
  return if had_transport_error($ret);
  print "Deleted article ID '$id'\n";
  return 1;
}

sub edit_entry {
  my ($opts, $c, $in) = @_;

  $in ||= parse_input(); # add_entry may have already read STDIN
 
  unless ($in->{id}) {
    # warn("No article ID\n");
    return add_entry($opts, $c, $in);
  }

  my $ret = $c->modify_entry($in->{id},
			     subject => $in->{subject},
			     body => $in->{body},
			    );

  return if had_transport_error($ret);

  print "Updated article $in->{id}\n";

  return 1;
}

sub get_entry {
  my ($opts, $c) = @_;

  my $id = $opts->{i} || die "get_entry requires a journal ID\n";
  my $ret = $c->get_entry($id);
  return if had_transport_error($ret);

  if (my $hr = $ret->result) {
    while (my ($k,$v) = each %{$hr}) {
      print "$k: $v\n";
    }

  } else {
    warn ("Couldn't fetch journal entry '$id'\n");
    return;
  }
  return 1;
}

sub list_entries {
  my ($opts, $c) = @_;
  my ($uid, $limit) = (($opts->{u} || &UID), $opts->{l});

  my $ret = $c->get_entries($uid, $limit);
  return if had_transport_error($ret);

  my $ar = $ret->result;
  for my $row (@{$ar}) {
    while (my ($k,$v) = each %{$row}) {
      print "$k: $v\n";
    }
    print "\n";
  }

  return 1;
}


sub parse_input {
  my %rec;

  my $last_field = 'all';
  while (defined ($_ = )) {
    chomp($_);
    if (/^(\w+):\s*(.*)/) {
      $last_field = $1;
      $rec{$last_field} = $2;
    } else {
      $rec{$last_field} .= "\n$_";
    }
  }

  return \%rec;
}

sub bakeUserCookie {
  my ($uid, $pw) = @_;
  my $c = $uid . "::" . md5_hex($pw);
  $c =~ s/(.)/sprintf("%%%02x", ord($1))/ge;
  $c =~ s/%/%25/g;
  return $c;
}

sub had_transport_error {
  my ($ret) = @_;

  if ($ret->fault) {
    warn ("Oops: ", $ret->faultString, "\n");
    return 1;
  }

  return;
}

To post:

  • M-x load-file
  • new buffer with "id:\nsubject:\nbody:";
  • add blog content to buffer
  • M-x t s to publish blog to use.perl


Elisp

Dom2 on 2005-02-25T08:52:25

I started working on the elisp for that a little while back. I was trying to get a major mode together for browsing the journal entries. This is what I have so far. Unfortunately, I haven't worked on it much since, because I'm not a terribly prolific poster, so I haven't had the impetus.
;;; use-perl --- interface to use.perl.org SOAP.

;;; Commentary:

;; Various functions for interacting with the SOAP interface on
;; use.perl.org.  Additionally requires a perl script for actually
;; communicating with the server.
;;
;; Originally from
;; <http://use.perl.org/article.pl?sid=02/10/25/007222&tid=11>.
;;
;; To use, drop this file into your site-lisp directory (or somewhere
;; else on your load-path) and put (require 'use-perl) in your .emacs
;; file.

;;; History:

;; @(#) $Id: use-perl.el,v 1.17 2004/12/20 13:49:51 dom Exp $

(require 'goto-addr)

;;; Code:

(defgroup use-perl nil
  "Talking to use.perl.org."
  :group 'tools)

(defcustom use-perl-progpath (expand-file-name "~/bin")
  "*Path to use.perl soap interface."
  :type '(string)
  :group 'use-perl)

(defcustom use-perl-default-uid nil
  "What user id to use when talking to use.perl.org by default."
  :type '(integer)
  :group 'use-perl)

(defcustom use-perl-browse-entries 20
  "*How many entries to request from use.perl.org for a browse list."
  :type '(integer)
  :group 'use-perl)

(defun use-perl-cmd (cmd &rest args)
  "Return the path to a specific CMD.  Optionally pass in ARGS to command."
  (mapconcat 'identity
             (cons (concat use-perl-progpath "/" cmd) args)
             " "))

(defun use-perl-run-replace-buffer (cmd)
  "Run the string CMD and replace the current buffer with the output."
  (shell-command-on-region (point-min) (point-max) cmd nil 1))

(defun use-perl-get-entry (n)
  "Get journal entry N from use.perl.org."
  (interactive "sJournal ID: ")
  (switch-to-buffer (generate-new-buffer (concat "*use-perl-journal-" n "*")))
  (use-perl-run-replace-buffer (use-perl-cmd "get_entry" n)))

(defun use-perl-browse (uid limit)
  "List use.perl journal entries for UID to a maximum of LIMIT."
  ;; If there is no prefix arg specified, then attempt to use the
  ;; values set by custom, otherwise prompt.  Always use the custom
  ;; setting for the limit, as that's less likely to want to be
  ;; changed.
  (interactive
   (list (or (and (not current-prefix-arg)
                  (number-to-string use-perl-default-uid))
             (read-string "User ID: "))
         (number-to-string use-perl-browse-entries)))
  (switch-to-buffer (generate-new-buffer "*use-perl-entry-list*"))
  (use-perl-run-replace-buffer (use-perl-cmd "list_entries" uid limit))
  (use-perl-browse-mode))

;; ---------------------------------------------------------------------
;; A major mode for browsing the list of use.perl journal entries.
;; ---------------------------------------------------------------------
(defvar use-perl-browse-mode-map nil
  "Map to be used in `use-perl-browse-mode'.")
(if use-perl-browse-mode-map
    nil
  (setq use-perl-browse-mode-map (make-sparse-keymap))
  (define-key use-perl-browse-mode-map "q" 'use-perl-browse-quit)
  (define-key use-perl-browse-mode-map "n" 'use-perl-browse-next-entry)
  (define-key use-perl-browse-mode-map "p" 'use-perl-browse-previous-entry)
  (define-key use-perl-browse-mode-map "w" 'use-perl-whois)
  (define-key use-perl-browse-mode-map "\C-m" 'use-perl-browse-view-entry))

(defun use-perl-browse-mode ()
  "Major mode for browsing a list of use.perl.org journal entries."
  (interactive)
  (kill-all-local-variables)
  (setq major-mode 'use-perl-browse-mode)
  (setq mode-name "MyJournals")
  (use-local-map use-perl-browse-mode-map)
  (goto-address)
  (goto-char (point-min)))

(defun use-perl-browse-quit ()
  "Finish looking at the list of use.perl.org journal entries."
  (interactive)
  (bury-buffer))

(defun use-perl-browse-view-entry ()
  "Bring up a buffer with the current entry in it."
  (interactive)
  (use-perl-get-entry (use-perl-browse-current-entry-id)))

(defun use-perl-browse-current-entry-id ()
  "Return the id of the entry nearest the point."
  (use-perl-browse-start-of-entry)
  (save-excursion
    (re-search-forward "^id: \\([0-9]+\\)$")
    (match-string 1)))

(defun use-perl-browse-start-of-entry ()
  "Move the point to the start of the current entry."
  (if (re-search-backward "^$" nil t)
      (forward-line)
    (goto-char (point-min))))

(defun use-perl-browse-previous-entry ()
  "Move to the previous entry in the list."
  (interactive)
  (use-perl-browse-start-of-entry)
  (forward-line -1)
  (backward-paragraph)
  (unless (bobp)
    (forward-line)))

(defun use-perl-browse-next-entry ()
  "Move to the previous entry in the list."
  (interactive)
  (use-perl-browse-start-of-entry)
  (let ((start (point)))
    (forward-paragraph)
    (forward-line)
    (if (eobp)
        (goto-char start))))

;; ---------------------------------------------------------------------

(defun use-perl-add-entry ()
  "Add current buffer to use.perl.org as a new entry."
  (interactive)
  (widen)
  (use-perl-run-replace-buffer (use-perl-cmd "add_entry")))

(defun use-perl-modify-entry ()
  "Modify an entry."
  (interactive)
  (widen)
  (use-perl-run-replace-buffer (use-perl-cmd "modify_entry")))

(defun use-perl-delete-entry (id)
  "Delete use.perl journal entry ID."
  (interactive "nEntry ID: ")
  (use-perl-run-replace-buffer (use-perl-cmd "delete_entry" id)))

(defun use-perl-whois (nick)
  "Lookup the uid of NICK."
  (interactive "sNickname: ")
  (message (shell-command-to-string (use-perl-cmd "whois_nick" nick))))

(provide 'use-perl)

;;; use-perl.el ends here

-Dom