| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Emacs::Rep; | 
| 2 |  |  |  |  |  |  | #                                doom@kzsu.stanford.edu | 
| 3 |  |  |  |  |  |  | #                                15 May 2010 | 
| 4 |  |  |  |  |  |  | #                                 3 Jun 2012 | 
| 5 |  |  |  |  |  |  |  | 
| 6 |  |  |  |  |  |  | =head1 NAME | 
| 7 |  |  |  |  |  |  |  | 
| 8 |  |  |  |  |  |  | Emacs::Rep - a find & replace engine for rep.pl and rep.el | 
| 9 |  |  |  |  |  |  |  | 
| 10 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 11 |  |  |  |  |  |  |  | 
| 12 |  |  |  |  |  |  | use Emacs::Rep qw( do_finds_and_reps  parse_perl_substitutions ); | 
| 13 |  |  |  |  |  |  |  | 
| 14 |  |  |  |  |  |  | my $substitutions =>>'END_S'; | 
| 15 |  |  |  |  |  |  | s/jerk/iconoclast/ | 
| 16 |  |  |  |  |  |  | s/conniving/shrewd/ | 
| 17 |  |  |  |  |  |  | s/(t)asteless/$1alented/i | 
| 18 |  |  |  |  |  |  | END_S | 
| 19 |  |  |  |  |  |  |  | 
| 20 |  |  |  |  |  |  | my $find_replaces_aref = | 
| 21 |  |  |  |  |  |  | parse_perl_substitutions( \$substitutions ); | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | my $change_metatdata_aref = | 
| 24 |  |  |  |  |  |  | do_finds_and_reps( \$text, $find_replaces_aref ); | 
| 25 |  |  |  |  |  |  |  | 
| 26 |  |  |  |  |  |  | =head1 DESCRIPTION | 
| 27 |  |  |  |  |  |  |  | 
| 28 |  |  |  |  |  |  | Emacs::Rep is a module that acts as a back-end for the rep.pl | 
| 29 |  |  |  |  |  |  | script which in turn is used by the emacs library.  rep.el. | 
| 30 |  |  |  |  |  |  |  | 
| 31 |  |  |  |  |  |  | Emacs::Rep is a find and replace engine that can perform | 
| 32 |  |  |  |  |  |  | multiple perl substitution commands (e.g. s///g) on a given | 
| 33 |  |  |  |  |  |  | file, recording all metadata about each change so that an an | 
| 34 |  |  |  |  |  |  | external program (such as emacs) can interactively display | 
| 35 |  |  |  |  |  |  | and control the changes. | 
| 36 |  |  |  |  |  |  |  | 
| 37 |  |  |  |  |  |  | The end user isn't expected to need to use these routines | 
| 38 |  |  |  |  |  |  | directly. | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | An application programmer might use these routines to add | 
| 41 |  |  |  |  |  |  | support to an interactive front-end (Emacs or otherwise). | 
| 42 |  |  |  |  |  |  |  | 
| 43 |  |  |  |  |  |  | =head2 EXPORT | 
| 44 |  |  |  |  |  |  |  | 
| 45 |  |  |  |  |  |  | None by default.  Any of the following may be requested (or all | 
| 46 |  |  |  |  |  |  | with the ':all' tag). | 
| 47 |  |  |  |  |  |  |  | 
| 48 |  |  |  |  |  |  | =over | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | =cut | 
| 51 |  |  |  |  |  |  |  | 
| 52 | 4 |  |  | 4 |  | 485270 | use 5.008; | 
|  | 4 |  |  |  |  | 17 |  | 
|  | 4 |  |  |  |  | 207 |  | 
| 53 | 4 |  |  | 4 |  | 1397 | use strict; | 
|  | 4 |  |  |  |  | 640 |  | 
|  | 4 |  |  |  |  | 171 |  | 
| 54 | 4 |  |  | 4 |  | 25 | use warnings; | 
|  | 4 |  |  |  |  | 13 |  | 
|  | 4 |  |  |  |  | 2450 |  | 
| 55 |  |  |  |  |  |  | my $DEBUG = 0; | 
| 56 | 4 |  |  | 4 |  | 30 | use Carp; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 883 |  | 
| 57 | 4 |  |  | 4 |  | 23 | use Data::Dumper; | 
|  | 4 |  |  |  |  | 8 |  | 
|  | 4 |  |  |  |  | 191 |  | 
| 58 | 4 |  |  | 4 |  | 16494 | use PPI; | 
|  | 4 |  |  |  |  | 887258 |  | 
|  | 4 |  |  |  |  | 3845 |  | 
| 59 |  |  |  |  |  |  |  | 
| 60 |  |  |  |  |  |  | require Exporter; | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | our @ISA = qw(Exporter); | 
| 63 |  |  |  |  |  |  | our %EXPORT_TAGS = ( 'all' => [ | 
| 64 |  |  |  |  |  |  | qw( | 
| 65 |  |  |  |  |  |  | parse_perl_substitutions | 
| 66 |  |  |  |  |  |  | do_finds_and_reps | 
| 67 |  |  |  |  |  |  |  | 
| 68 |  |  |  |  |  |  | accumulate_find_reps | 
| 69 |  |  |  |  |  |  | check_versions | 
| 70 |  |  |  |  |  |  | ) ] ); | 
| 71 |  |  |  |  |  |  |  | 
| 72 |  |  |  |  |  |  | our @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } ); | 
| 73 |  |  |  |  |  |  | our @EXPORT = qw(  ); | 
| 74 |  |  |  |  |  |  | our $VERSION = '1.00';  # TODO manually sync-up rep.pl and rep.el versions | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | =item parse_perl_substitutions | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | Breaks down a set of perl substitution command (i.e. "s///;", | 
| 79 |  |  |  |  |  |  | "s{}{};", etc.)  into it's main components (the find pattern | 
| 80 |  |  |  |  |  |  | and the replace expression).  It returns this in an an array | 
| 81 |  |  |  |  |  |  | of arrays data structure (which is the form used by L). | 
| 82 |  |  |  |  |  |  |  | 
| 83 |  |  |  |  |  |  | Takes one argument, a scalar reference to a block of text | 
| 84 |  |  |  |  |  |  | containing one or more perl substitution commands, in any | 
| 85 |  |  |  |  |  |  | form (PPI is used internally to parse this). | 
| 86 |  |  |  |  |  |  |  | 
| 87 |  |  |  |  |  |  | The more elaborate "s{}{}xmsg;" is fine, as well as "s///g;". | 
| 88 |  |  |  |  |  |  |  | 
| 89 |  |  |  |  |  |  | End of line comments (after the closing semicolon) beginning with a "#", | 
| 90 |  |  |  |  |  |  | are allowed, as are embedded comments inside the find pattern | 
| 91 |  |  |  |  |  |  | if the /x modifier is in use. | 
| 92 |  |  |  |  |  |  |  | 
| 93 |  |  |  |  |  |  | Example usage: | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | my $substitutions =>>'END_S'; | 
| 96 |  |  |  |  |  |  | s/pointy-haired boss/esteemed leader/g; | 
| 97 |  |  |  |  |  |  | s/death spiral/minor adjustment/g; | 
| 98 |  |  |  |  |  |  | END_S | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | my $find_replaces_aref = | 
| 101 |  |  |  |  |  |  | parse_perl_substitutions( \$substitutions ); | 
| 102 |  |  |  |  |  |  |  | 
| 103 |  |  |  |  |  |  | Where the returned data should look like: | 
| 104 |  |  |  |  |  |  |  | 
| 105 |  |  |  |  |  |  | [ ['pointy-haired boss', 'esteemed leader'], | 
| 106 |  |  |  |  |  |  | ['death spiral',       'minor adjustment'], | 
| 107 |  |  |  |  |  |  | ] | 
| 108 |  |  |  |  |  |  |  | 
| 109 |  |  |  |  |  |  | Any trailing modifiers are automatically prefixed to the | 
| 110 |  |  |  |  |  |  | find_pattern, using the (? ... ) notation, *except* for /g | 
| 111 |  |  |  |  |  |  | and /e. | 
| 112 |  |  |  |  |  |  |  | 
| 113 |  |  |  |  |  |  | For purposes of L /e is always ignored | 
| 114 |  |  |  |  |  |  | (as of this writing), and /g is always assumed, irrespective | 
| 115 |  |  |  |  |  |  | of whether it was added explicitly. | 
| 116 |  |  |  |  |  |  |  | 
| 117 |  |  |  |  |  |  | =cut | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | sub parse_perl_substitutions { | 
| 120 | 4 |  |  | 4 | 1 | 747 | my $reps_text_ref = shift; | 
| 121 | 4 |  |  |  |  | 58 | my $Document = PPI::Document->new( $reps_text_ref ); | 
| 122 | 4 |  |  |  |  | 43232 | my $s_aref = $Document->find('PPI::Token::Regexp::Substitute'); | 
| 123 | 4 |  |  |  |  | 6904 | my @find_reps; | 
| 124 | 4 |  |  |  |  | 13 | foreach my $s_obj (@{ $s_aref }) { | 
|  | 4 |  |  |  |  | 14 |  | 
| 125 | 32 |  |  |  |  | 135 | my $find      = $s_obj->get_match_string; | 
| 126 | 32 |  |  |  |  | 504 | my $rep       = $s_obj->get_substitute_string; | 
| 127 | 32 |  |  |  |  | 407 | my $modifiers = $s_obj->get_modifiers; # href | 
| 128 | 32 |  |  |  |  | 372 | my @delims    = $s_obj->get_delimiters; | 
| 129 |  |  |  |  |  |  |  | 
| 130 | 32 |  |  |  |  | 713 | my $raw_mods = join '', keys %{ $modifiers }; | 
|  | 32 |  |  |  |  | 108 |  | 
| 131 |  |  |  |  |  |  |  | 
| 132 | 32 |  |  |  |  | 83 | accumulate_find_reps( \@find_reps, $find, $rep, $raw_mods ); | 
| 133 |  |  |  |  |  |  | } | 
| 134 | 4 |  |  |  |  | 53 | \@find_reps; | 
| 135 |  |  |  |  |  |  | } | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | =item accumulate_find_reps | 
| 138 |  |  |  |  |  |  |  | 
| 139 |  |  |  |  |  |  | For internal use.  Example usage: | 
| 140 |  |  |  |  |  |  |  | 
| 141 |  |  |  |  |  |  | accumulate_find_reps( \@find_reps, $find, $rep, $raw_mods ); | 
| 142 |  |  |  |  |  |  |  | 
| 143 |  |  |  |  |  |  | =cut | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | sub accumulate_find_reps { | 
| 146 | 32 |  |  | 32 | 1 | 39 | my $find_reps_aref = shift; | 
| 147 | 32 |  |  |  |  | 51 | my $find           = shift; | 
| 148 | 32 |  |  |  |  | 41 | my $rep            = shift; | 
| 149 | 32 |  |  |  |  | 38 | my $raw_mods       = shift; | 
| 150 |  |  |  |  |  |  |  | 
| 151 | 32 | 100 |  |  |  | 99 | if ($raw_mods) { | 
| 152 |  |  |  |  |  |  | # The modifiers we care about (screening out spurious g or e or ;) | 
| 153 | 17 |  |  |  |  | 41 | my @mods = qw( x m s i ); | 
| 154 | 17 |  |  |  |  | 20 | my $mods = ''; | 
| 155 | 17 |  |  |  |  | 24 | foreach my $m (@mods) { | 
| 156 | 68 | 100 |  |  |  | 12767 | if ( $raw_mods =~ qr{$m}x ) { | 
| 157 | 5 |  |  |  |  | 20 | $mods .= $m; | 
| 158 |  |  |  |  |  |  | } | 
| 159 |  |  |  |  |  |  | } | 
| 160 |  |  |  |  |  |  | # modify $find to incorporate the modifiers internally | 
| 161 | 17 | 100 |  |  |  | 59 | $find = "(?$mods)" . $find if $mods; | 
| 162 |  |  |  |  |  |  | } | 
| 163 |  |  |  |  |  |  |  | 
| 164 | 32 |  |  |  |  | 42 | push @{ $find_reps_aref }, [ $find, $rep ]; | 
|  | 32 |  |  |  |  | 181 |  | 
| 165 |  |  |  |  |  |  | } | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | =item do_finds_and_reps | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | Does a series of finds and replaces on some text and | 
| 170 |  |  |  |  |  |  | returns the beginning and end points of each of the | 
| 171 |  |  |  |  |  |  | modfied regions, along with some other information about | 
| 172 |  |  |  |  |  |  | the matches. | 
| 173 |  |  |  |  |  |  |  | 
| 174 |  |  |  |  |  |  | Takes two arguments: | 
| 175 |  |  |  |  |  |  |  | 
| 176 |  |  |  |  |  |  | (1) The text to be modified, usually as a reference, though a scalar is okay | 
| 177 |  |  |  |  |  |  | (2) A series of find and replace pairs in the form | 
| 178 |  |  |  |  |  |  | of an aref of arefs, e.g. | 
| 179 |  |  |  |  |  |  |  | 
| 180 |  |  |  |  |  |  | $find_replaces_aref = | 
| 181 |  |  |  |  |  |  | [ ['jerk',            'iconoclast'], | 
| 182 |  |  |  |  |  |  | ['conniving',       'shrewd']. | 
| 183 |  |  |  |  |  |  | ['(?i)(t)asteless', '$1alented']. | 
| 184 |  |  |  |  |  |  | ]: | 
| 185 |  |  |  |  |  |  |  | 
| 186 |  |  |  |  |  |  | (See L.) | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | Example usage: | 
| 189 |  |  |  |  |  |  |  | 
| 190 |  |  |  |  |  |  | $locations_aref = | 
| 191 |  |  |  |  |  |  | do_finds_and_reps( \$text, $find_replaces_aref ); | 
| 192 |  |  |  |  |  |  |  | 
| 193 |  |  |  |  |  |  | The returned change metadata is an aref of arefs of hrefs; | 
| 194 |  |  |  |  |  |  | an array of passes with an entry for each substitution pair, | 
| 195 |  |  |  |  |  |  | an an array of changes made by each pass.  The href has | 
| 196 |  |  |  |  |  |  | keys: 'beg', 'delta', 'orig', 'rep'. | 
| 197 |  |  |  |  |  |  |  | 
| 198 |  |  |  |  |  |  | The fields 'orig' and 'rep' contain the modified string, before | 
| 199 |  |  |  |  |  |  | and after the change. | 
| 200 |  |  |  |  |  |  |  | 
| 201 |  |  |  |  |  |  | 'delta' is the change in length due to the change. | 
| 202 |  |  |  |  |  |  |  | 
| 203 |  |  |  |  |  |  | 'beg' is the beginning of the region that was modified, an | 
| 204 |  |  |  |  |  |  | integer counting from the start of the file, where the first | 
| 205 |  |  |  |  |  |  | character is 1. | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | This numbering does not change while a s///g is in progress, | 
| 208 |  |  |  |  |  |  | even if it is changing the length of the strings. | 
| 209 |  |  |  |  |  |  | And further, these change locations are recorded *during* each pass, | 
| 210 |  |  |  |  |  |  | which means that later passes throw off the numbering. | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | In practice, for the rep.el application, we apply this data | 
| 213 |  |  |  |  |  |  | on the emacs side in inverse order, so that the numbering is | 
| 214 |  |  |  |  |  |  | correct in the context we use it. | 
| 215 |  |  |  |  |  |  |  | 
| 216 |  |  |  |  |  |  | Note, error messages are routed to stdout, labeled with the | 
| 217 |  |  |  |  |  |  | prefix "Problem:". The elisp call shell-command-to-string | 
| 218 |  |  |  |  |  |  | merges stdout and stderr, but we use the 'Problem' prefix to | 
| 219 |  |  |  |  |  |  | spot error messages | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | =cut | 
| 222 |  |  |  |  |  |  |  | 
| 223 |  |  |  |  |  |  | sub do_finds_and_reps { | 
| 224 | 3 |  |  | 3 | 1 | 2122 | my $arg      = shift; | 
| 225 | 3 | 50 |  |  |  | 15 | my $text_ref = ref( $arg ) ? $arg : \$arg; | 
| 226 | 3 |  |  |  |  | 7 | my $find_replaces = shift; # aref of aref: a series of pairs | 
| 227 |  |  |  |  |  |  |  | 
| 228 | 3 |  |  |  |  | 5 | my @change_metadata; | 
| 229 | 3 |  |  |  |  | 7 | eval { | 
| 230 | 3 |  |  |  |  | 53 | for ( my $pass = 0; $pass <= $#{ $find_replaces }; $pass++ ) { | 
|  | 11 |  |  |  |  | 38 |  | 
| 231 | 9 |  |  |  |  | 13 | my ($find_pat, $replace) = @{ $find_replaces->[ $pass ] }; | 
|  | 9 |  |  |  |  | 22 |  | 
| 232 | 9 |  |  |  |  | 13 | my @pass; # change_metadata for this pass | 
| 233 | 9 |  |  |  |  | 38 | ${ $text_ref } =~ | 
|  | 9 |  |  |  |  | 186 |  | 
| 234 |  |  |  |  |  |  | s{$find_pat} | 
| 235 |  |  |  |  |  |  | { | 
| 236 | 9 |  |  |  |  | 493 | my $new = eval "return qq{$replace}"; | 
| 237 | 9 |  |  |  |  | 28 | my $l1 = length( $& ); | 
| 238 | 9 |  |  |  |  | 11 | my $l2 = length( $new ); | 
| 239 | 9 |  |  |  |  | 14 | my $delta = $l2 - $l1; | 
| 240 |  |  |  |  |  |  | # pos points at the *start* of the match (inside of a s///eg) | 
| 241 |  |  |  |  |  |  | # And char numbering fixed at the start of the s///ge run | 
| 242 | 9 |  |  |  |  | 12 | my $p = pos( ${ $text_ref } ) + 1; | 
|  | 9 |  |  |  |  | 16 |  | 
| 243 | 9 |  |  |  |  | 14 | my $beg = $p; | 
| 244 |  |  |  |  |  |  |  | 
| 245 |  |  |  |  |  |  | # preserving some context | 
| 246 | 9 |  |  |  |  | 21 | my $post = substr( $',  0, 10 );  # Note: no BOF/EOF errors | 
| 247 | 9 |  |  |  |  | 22 | my $pre  = substr( $`, -10 ); | 
| 248 |  |  |  |  |  |  |  | 
| 249 | 9 |  |  |  |  | 44 | push @pass, { | 
| 250 |  |  |  |  |  |  | beg   => $beg, | 
| 251 |  |  |  |  |  |  | delta => $delta, | 
| 252 |  |  |  |  |  |  | orig  => $&, | 
| 253 |  |  |  |  |  |  | rep   => $new, | 
| 254 |  |  |  |  |  |  | }; | 
| 255 | 9 |  |  |  |  | 34 | $new | 
| 256 |  |  |  |  |  |  | }ge; | 
| 257 | 8 |  |  |  |  | 44 | push @change_metadata, \@pass; | 
| 258 |  |  |  |  |  |  | } | 
| 259 |  |  |  |  |  |  | }; | 
| 260 | 3 | 100 |  |  |  | 15 | if ($@) { | 
| 261 |  |  |  |  |  |  | # Send error message to STDOUT so that it won't mess up test output. | 
| 262 |  |  |  |  |  |  | # (and anyway, the elisp call shell-command-to-string merges in STDERR) | 
| 263 |  |  |  |  |  |  | # | 
| 264 |  |  |  |  |  |  | # The elisp function rep-run-perl-substitutions uses prefix "Problem". | 
| 265 |  |  |  |  |  |  | # to spot error messages | 
| 266 | 1 |  |  |  |  | 72 | print "Problem: $@\n"; | 
| 267 |  |  |  |  |  |  | # roll-back | 
| 268 | 1 |  |  |  |  | 4 | @change_metadata = (); | 
| 269 |  |  |  |  |  |  | } | 
| 270 |  |  |  |  |  |  |  | 
| 271 | 3 |  |  |  |  | 13 | return \@change_metadata; # array of array of hrefs (keys: beg, end, delta, orig, etc) | 
| 272 |  |  |  |  |  |  | } | 
| 273 |  |  |  |  |  |  |  | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | =item check_versions | 
| 276 |  |  |  |  |  |  |  | 
| 277 |  |  |  |  |  |  | Verify that all three pieces of the system have the same version: | 
| 278 |  |  |  |  |  |  | the *.pl, *.pm and *.el. | 
| 279 |  |  |  |  |  |  |  | 
| 280 |  |  |  |  |  |  | The elisp code is expected to run this command string with | 
| 281 |  |  |  |  |  |  | it's own version number: | 
| 282 |  |  |  |  |  |  |  | 
| 283 |  |  |  |  |  |  | perl rep.pl --check_versions="0.08" | 
| 284 |  |  |  |  |  |  |  | 
| 285 |  |  |  |  |  |  | rep.pl then runs this check_versions routine, passing along the | 
| 286 |  |  |  |  |  |  | elisp version and the rep.pl version number: | 
| 287 |  |  |  |  |  |  |  | 
| 288 |  |  |  |  |  |  | Example usage: | 
| 289 |  |  |  |  |  |  |  | 
| 290 |  |  |  |  |  |  | check_versions( $elisp_version, $script_version ); | 
| 291 |  |  |  |  |  |  |  | 
| 292 |  |  |  |  |  |  | This compares those two versions with the module's version, | 
| 293 |  |  |  |  |  |  | and warns if they're not all the same. | 
| 294 |  |  |  |  |  |  |  | 
| 295 |  |  |  |  |  |  | =cut | 
| 296 |  |  |  |  |  |  |  | 
| 297 |  |  |  |  |  |  | sub check_versions { | 
| 298 | 0 |  |  | 0 | 1 |  | my $elisp_version  = shift; | 
| 299 | 0 |  |  |  |  |  | my $script_version = shift; | 
| 300 | 0 |  |  |  |  |  | my $module_version = $VERSION; | 
| 301 |  |  |  |  |  |  |  | 
| 302 | 0 |  |  |  |  |  | my $mess; | 
| 303 | 0 | 0 | 0 |  |  |  | if ( not( | 
| 304 |  |  |  |  |  |  | ($elisp_version  == $script_version) && | 
| 305 |  |  |  |  |  |  | ($script_version == $module_version) ) ) { | 
| 306 | 0 |  |  |  |  |  | $mess = "Warning: all three versions should match: \n" . | 
| 307 |  |  |  |  |  |  | "rep.el: $elisp_version \n" . | 
| 308 |  |  |  |  |  |  | "rep.pl: $script_version \n" . | 
| 309 |  |  |  |  |  |  | "Rep.pm: $module_version \n"; | 
| 310 | 0 |  |  |  |  |  | return $mess; | 
| 311 |  |  |  |  |  |  | } else { | 
| 312 | 0 |  |  |  |  |  | return $module_version; | 
| 313 |  |  |  |  |  |  | } | 
| 314 |  |  |  |  |  |  | } | 
| 315 |  |  |  |  |  |  |  | 
| 316 |  |  |  |  |  |  |  | 
| 317 |  |  |  |  |  |  |  | 
| 318 |  |  |  |  |  |  | 1; | 
| 319 |  |  |  |  |  |  |  | 
| 320 |  |  |  |  |  |  | =back | 
| 321 |  |  |  |  |  |  |  | 
| 322 |  |  |  |  |  |  | =head1 SEE ALSO | 
| 323 |  |  |  |  |  |  |  | 
| 324 |  |  |  |  |  |  | The web page for this project is: | 
| 325 |  |  |  |  |  |  |  | 
| 326 |  |  |  |  |  |  | http://obsidianrook.com/rep | 
| 327 |  |  |  |  |  |  |  | 
| 328 |  |  |  |  |  |  | The code is available on github (as well as on CPAN): | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | http://github.com/doomvox/rep | 
| 331 |  |  |  |  |  |  |  | 
| 332 |  |  |  |  |  |  | Emacs::Rep is the back-end for the script rep.pl which | 
| 333 |  |  |  |  |  |  | in turn is the back-end for the emacs lisp code rep.el. | 
| 334 |  |  |  |  |  |  |  | 
| 335 |  |  |  |  |  |  | If rep.el is not installed, look in the "elisp" sub-directory | 
| 336 |  |  |  |  |  |  | of this CPAN package. | 
| 337 |  |  |  |  |  |  |  | 
| 338 |  |  |  |  |  |  | A good discussion forum for projects such as this is: | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | http://groups.google.com/group/emacs-perl-intersection | 
| 341 |  |  |  |  |  |  |  | 
| 342 |  |  |  |  |  |  | =head1 AUTHOR | 
| 343 |  |  |  |  |  |  |  | 
| 344 |  |  |  |  |  |  | Joseph Brenner, Edoom@kzsu.stanford.eduE | 
| 345 |  |  |  |  |  |  |  | 
| 346 |  |  |  |  |  |  | =head1 COPYRIGHT AND LICENSE | 
| 347 |  |  |  |  |  |  |  | 
| 348 |  |  |  |  |  |  | Copyright (C) 2010,2012 by Joseph Brenner | 
| 349 |  |  |  |  |  |  |  | 
| 350 |  |  |  |  |  |  | This program is free software; you can redistribute it and/or modify it | 
| 351 |  |  |  |  |  |  | under the terms of either: the GNU General Public License as published | 
| 352 |  |  |  |  |  |  | by the Free Software Foundation; or the Artistic License. | 
| 353 |  |  |  |  |  |  |  | 
| 354 |  |  |  |  |  |  | See http://dev.perl.org/licenses/ for more information. | 
| 355 |  |  |  |  |  |  |  | 
| 356 |  |  |  |  |  |  | =head1 BUGS | 
| 357 |  |  |  |  |  |  |  | 
| 358 |  |  |  |  |  |  | None reported... yet. | 
| 359 |  |  |  |  |  |  |  | 
| 360 |  |  |  |  |  |  | =cut |