Stupid things to do with vim: run snippets

Ovid on 2009-11-08T10:42:38

Sometimes when I'm writing POD, I find that I want something like this:

 use Test::More 'no_plan';
 ok 1, '"1" is true';
 is "foo", "foo", '"foo" equals itself';
 __END__
 ok 1 - "1" is true
 ok 2 - "foo" equals itself
 1..2

In other words, I want a snippet of code with the output displayed after the __END__ block. However, that means copying the code to a file, pasting the code, fixing the inevitable errors, rerunning, copying the output, putting vim in paste mode, typing the __END__ token, pasting in the output and putting vim back in nopaste mode.

That's a pain. Now I can just type ,rs (run snippet) on a section of code which I've selected via cntl-v. The vim mapping is:

vnoremap  ,rs :!perl ~/bin/eval

And ~/bin/eval script is:

#!/usr/bin/env perl 

use strict;
use warnings;
use File::Temp 'tempfile';

# create a temporary perl script file
my ( $fh, $snippet ) = tempfile(
    'eval_XXXX',
    SUFFIX => '.pl',
    DIR    => '/var/tmp'
);
my $code = do { local $/;  };
print $fh $code or die "Could not print code to ($snippet): $!";
close $fh or die "Could not close ($snippet): $!";

my $perl = $^X;
print $code, " __END__\n";
my $output = ' ' . qx{ $perl $snippet 2>&1 };
$output =~ s/\n/\n /g;
print $output;

That will run the snippet and automatically put the results after an __END__ token. This is a very dangerous (and fragile) technique, so use it with care.


Nice, but Name Clash

Smylers on 2009-11-09T13:08:13

Hey, that's nifty.

But having ~/bin/eval may get confusing, the name clashing with the buit-in shell command eval. perleval would be less confusing (and a longer name doesn't really matter, since your mapping does the typing for you).

Re:Nice, but Name Clash

Ovid on 2009-11-09T13:45:04

Ah, good point. I'll make a change locally. Thanks!

Nifty

jjore on 2009-11-09T18:09:15

Nice idea. I used it in my .emacs but slightly differently. This evals the contents of the current region w/ perl-run-snippet-command and inserts it afterward as comments.

(defvar perl-run-snippet-command
  "perl"
  "Command for running perl for `perl-run-snippet'")
(defun perl-run-snippet (start end)
  "Execute `perl-run-snippet-command' on the current region. Insert the results as comments."
  (interactive "r")
  (let ((buffer (current-buffer))
        (snippet-result (generate-new-buffer "*perl-snippet*")))
    (save-excursion
      ;; Run the snippet
      (shell-command-on-region
        start end
        perl-run-snippet-command
        snippet-result nil snippet-result)
 
      ;; Comment it out
      (set-buffer snippet-result)
      (goto-char (point-min))
      (while (re-search-forward "^" nil t)
        (replace-match "# "))
      (set-buffer buffer)
 
      ;; Insert    the snippet
      (goto-char (max start end))
      (insert-buffer snippet-result)
 
      ;; Clean up
      (kill-buffer snippet-result))))

I find it a little more likely I'd rather just turn a snippet into example data for immediate use inline than something far removed at an __END__ tag. FWIW, to do something like that, you'd use commands like the below instead:

;; Either:
;; * goto the line after __END__
;; * goto the end and add an __END__ marker
;;
(if (re-search-forward "^__END__\n" nil t)
    nil
  (goto-char (point-max))
  (if (not (bolp)) (insert "\n"))
  (insert "__END__\n"))