File Coverage

blib/lib/Text/Starfish.pm
Criterion Covered Total %
statement 39 943 4.1
branch 8 476 1.6
condition 0 72 0.0
subroutine 10 81 12.3
pod 31 60 51.6
total 88 1632 5.3


line stmt bran cond sub pod time code
1             # Starfish - Perl-based System for Preprocessing and Text-Embedded Programming
2             #
3             # (c) 2001-2021 Vlado Keselj https://web.cs.dal.ca/~vlado vlado@dnlp.ca
4             # and contributing authors
5             #
6             # See the documentation following the code. You can also use the
7             # command "perldoc Starfish.pm".
8              
9             package Text::Starfish;
10 1     1   1029 use vars qw($NAME $ABSTRACT $VERSION); use strict;
  1     1   3  
  1         84  
  1         5  
  1         2  
  1         36  
11             $NAME = 'Text::Starfish';
12             $ABSTRACT = 'Perl-based System for Preprocessing and Text-Embedded Programming';
13             $VERSION = '1.39';
14              
15 1     1   592 use POSIX;
  1         6697  
  1         6  
16 1     1   2987 use Carp;
  1         2  
  1         75  
17 1     1   8 use Cwd qw(cwd);
  1         1  
  1         45  
18 1     1   9 use Exporter;
  1         1  
  1         43  
19 1     1   7 use vars qw($VERSION @ISA @EXPORT @EXPORT_OK %EXPORT_TAGS); # Exporter vars
  1         2  
  1         123  
20              
21             @ISA = qw(Exporter);
22             %EXPORT_TAGS = ( 'all' => [qw(
23             add_hook appendfile echo file_modification_date
24             file_modification_time getfile getmakefilelist get_verbatim_file
25             getinclude htmlquote include
26             last_update putfile read_records read_starfish_conf rm_hook set_out_delimiters
27             sfish_add_tag sfish_ignore_outer starfish_cmd make_gen_dirs_to_generate
28             make_add_dirs_to_generate_if_needed
29             ) ] );
30             @EXPORT_OK = ( @{ $EXPORT_TAGS{'all'} } );
31             @EXPORT = @{ $EXPORT_TAGS{'all'} };
32              
33             # Used in starfishing Makefiles
34 1     1   7 use vars qw(@DirGenerateIfNeeded);
  1         2  
  1         48  
35              
36             # non-exported package globals
37 1     1   6 use vars qw($GlobalREPLACE);
  1         2  
  1         11559  
38              
39             sub appendfile($@);
40             sub getfile($ );
41             sub getmakefilelist($$);
42             sub htmlquote($ );
43             sub putfile($@);
44             sub read_records($ );
45             sub starfish_cmd(@);
46              
47             sub new($@) {
48 0     0 1 0 my $proto = shift;
49 0   0     0 my $class = ref($proto) || $proto;
50 0         0 my $self = {};
51 0         0 bless($self, $class);
52              
53 0         0 $self->{Loops} = 1;
54 0         0 my $copyhooks = '';
55 0         0 foreach (@_) {
56 0 0       0 if (/^-infile=(.*)$/) { $self->{INFILE} = $1 }
  0 0       0  
57 0         0 elsif (/^-copyhooks$/) { $copyhooks = 1 }
58 0         0 else { _croak("unknown new option: $_") }
59             }
60              
61 0 0       0 if ($copyhooks) {
62 0 0       0 _croak("new: cannot copyhooks if Star is not there") unless
63             ref($::Star) eq 'Text::Starfish';
64 0         0 $self->{Style} = $::Star->{Style};
65 0         0 $self->{CodePreparation} = $::Star->{CodePreparation};
66 0         0 $self->{LineComment} = $::Star->{LineComment};
67 0         0 $self->{OutDelimiters} = $::Star->{OutDelimiters};
68 0         0 $self->{IgnoreOuter} = $::Star->{IgnoreOuter};
69 0         0 $self->{hook} = [ @{ $::Star->{hook} } ];
  0         0  
70             }
71              
72 0 0       0 $self->set_style() unless $copyhooks;
73              
74 0         0 return $self;
75             }
76              
77             sub starfish_cmd(@) {
78 0     0 1 0 my $sf = Text::Starfish->new();
79 0         0 my @tmp = ();
80 0         0 foreach (@_) {
81 0 0       0 if (/^-e=?/) { $sf->{INITIAL_CODE} = $' }
  0 0       0  
    0          
    0          
    0          
82 0         0 elsif (/^-mode=/) { $sf->{NEW_FILE_MODE} = $' }
83 0         0 elsif (/^-o=/) { $sf->{OUTFILE} = $' }
84 0         0 elsif (/^-replace$/) { $sf->{REPLACE} = $GlobalREPLACE = 1; }
85 0         0 elsif (/^-v$/) { print "$NAME, version $VERSION, $ABSTRACT\n"; exit 0; }
  0         0  
86 0         0 else { push @tmp, $_ }
87             }
88              
89 0 0 0     0 if (defined $sf->{NEW_FILE_MODE} and $sf->{NEW_FILE_MODE} =~ /^0/)
90 0         0 { $sf->{NEW_FILE_MODE} = oct($sf->{NEW_FILE_MODE}) }
91              
92 0         0 $sf->process_files(@tmp);
93 0         0 return $sf;
94             }
95              
96 0     0 1 0 sub include($@) { $::O .= getinclude(@_); return 1; }
  0         0  
97              
98             sub getinclude($@) {
99 0     0 1 0 my $sf = loadinclude(@_);
100 0 0       0 return '' unless defined $sf;
101 0         0 $sf->_digest();
102 0         0 return $sf->{Out}; }
103              
104             sub loadinclude($@) {
105 0     0 1 0 my $infile = '';
106 0         0 my @args = ();
107 0         0 my $replace = 1;
108 0         0 my $require = '';
109 0         0 foreach (@_) {
110 0 0 0     0 if (/^-replace$/) { $replace = 1 }
  0 0       0  
    0          
    0          
111 0         0 elsif (/^-noreplace$/) { $replace = '' }
112 0         0 elsif (/^-require$/) { $require = 1 }
113 0         0 elsif (!/^-/ && $infile eq '') { $infile = $_ }
114 0         0 else { push @args, $_ }
115             }
116              
117 0         0 my $sf = Text::Starfish->new("-infile=$infile", @args);
118 0         0 $sf->{REPLACE} = $replace;
119              
120 0 0 0     0 if ($sf->{INFILE} eq '' or ! -r $sf->{INFILE} ) {
121 0 0       0 if ($require) { _croak("cannot getinclude file: ($sf->{INFILE})") }
  0         0  
122 0         0 return undef;
123             }
124              
125 0         0 $sf->{data} = getfile $sf->{INFILE};
126 0         0 return $sf; }
127              
128             sub process_files {
129 0     0 1 0 my $self = shift;
130 0         0 my @args = @_;
131              
132 0 0 0     0 if (defined $self->{REPLACE} and !defined $self->{OUTFILE})
133 0         0 { _croak("Starfish:output file required for replace") }
134              
135 0         0 my $FileCount=0;
136 0         0 $self->eval1($self->{INITIAL_CODE}, 'initial');
137              
138 0         0 while (@args) {
139 0         0 $self->{INFILE} = shift @args;
140 0         0 ++$FileCount;
141 0         0 $self->set_style();
142 0         0 $self->{data} = getfile $self->{INFILE};
143              
144             # *123* we need to forbid defining an outfile externally as well as
145             # internally:
146 0 0       0 my $outfileExternal = exists($self->{OUTFILE}) ? $self->{OUTFILE} : '';
147              
148 0         0 my $ExistingText = '';
149 0 0       0 if (! defined $self->{OUTFILE}) {
    0          
    0          
150 0         0 $ExistingText = $self->{data};
151 0         0 $self->{LastUpdateTime} = (stat $self->{INFILE})[9];
152             }
153             elsif ($FileCount > 1) {
154 0         0 $ExistingText = '';
155 0         0 $self->{LastUpdateTime} = time;
156             }
157             elsif (! -f $self->{OUTFILE}) {
158 0         0 $ExistingText = '';
159 0         0 $self->{LastUpdateTime} = time;
160             }
161             else {
162 0         0 $ExistingText = getfile $self->{OUTFILE};
163 0         0 $self->{LastUpdateTime} = (stat $self->{OUTFILE})[9];
164             }
165              
166 0         0 $self->_digest();
167              
168             # see *123* above
169 0 0 0     0 if ($outfileExternal ne '' and $outfileExternal ne $self->{OUTFILE})
170 0         0 { _croak("OUTFILE defined externally ($outfileExternal) and ".
171             "internally ($self->{OUTFILE})") }
172              
173 0         0 my $InFile = $self->{INFILE};
174 0 0 0     0 if ($FileCount==1 && defined $self->{OUTFILE}) {
175             # touch the outfile if it does not exist
176 0 0       0 if ( ! -f $self->{OUTFILE} ) {
    0          
177 0         0 putfile $self->{OUTFILE};
178 0         0 my $infile_mode = (stat $InFile)[2];
179 0 0       0 if (defined $self->{NEW_FILE_MODE}) {
180 0         0 chmod $self->{NEW_FILE_MODE}, $self->{OUTFILE}}
181 0         0 else { chmod $infile_mode, $self->{OUTFILE} }
182             }
183             elsif (defined $self->{NEW_FILE_MODE}) {
184 0         0 chmod $self->{NEW_FILE_MODE}, $self->{OUTFILE} }
185             }
186              
187             # write the text if changed
188 0 0       0 if ($ExistingText ne $self->{Out}) {
    0          
189 0 0       0 if (defined $self->{OUTFILE}) {
190             # If the OutFile is defined, we may have to play with
191             # permissions in order to write. Be careful! Allow
192             # unallowed write only on outfile and if -mode is
193             # specified
194 0         0 my $mode = ((stat $self->{OUTFILE})[2]);
195 0 0 0     0 if (($mode & 0200) == 0 and defined $self->{NEW_FILE_MODE}) {
196 0         0 chmod $mode|0200, $self->{OUTFILE};
197 0 0       0 if ($FileCount==1) { putfile $self->{OUTFILE}, $self->{Out} }
  0         0  
198 0         0 else { appendfile $self->{OUTFILE}, $self->{Out} }
199 0         0 chmod $mode, $self->{OUTFILE};
200             } else {
201 0 0       0 if ($FileCount==1) { putfile $self->{OUTFILE}, $self->{Out} }
  0         0  
202 0         0 else { appendfile $self->{OUTFILE}, $self->{Out} }
203             }
204             }
205             else {
206 0         0 putfile $InFile, $self->{Out};
207 0 0       0 chmod $self->{NEW_FILE_MODE}, $InFile if defined $self->{NEW_FILE_MODE};
208             }
209             }
210             elsif (defined $self->{NEW_FILE_MODE}) {
211 0 0       0 if (defined $self->{OUTFILE}) {
212 0         0 chmod $self->{NEW_FILE_MODE}, $self->{OUTFILE} }
213 0         0 else { chmod $self->{NEW_FILE_MODE}, $InFile }
214             }
215             } # end of while (@args)
216              
217             } # end of method process_files
218              
219             sub _digest {
220 0     0   0 my $self = shift;
221 0         0 $self->{CurrentLoop} = 1;
222 0         0 my $savedcontent = $self->{data};
223            
224             START: # The main scanning loop
225 0         0 $self->{Out} = '';
226 0         0 $self->_scan();
227 0         0 while ($self->{ttype} != -1) {
228 0 0       0 if ($self->{ttype} > -1) {
229 0         0 $self->{Out} .= $self->_evf_dispatch;
230             } # else $ttype < -1 (outer text)
231 0         0 else { $self->_process_outer( $self->{currenttoken} ) }
232 0         0 $self->_scan(); }
233            
234 0 0       0 if ($self->{CurrentLoop} < $self->{Loops}) {
235 0         0 ++$self->{CurrentLoop};
236 0 0       0 if ($self->{REPLACE}) { # in replace mode interate with
237 0         0 $self->{data} = $savedcontent; # original input
238 0         0 goto START; }
239 0         0 $self->{data} = $self->{Out};
240 0 0       0 if ($savedcontent ne $self->{Out})
241 0         0 { $self->{LastUpdateTime} = time }
242 0         0 putfile 'sfish.debug', $self->{data};
243 0         0 goto START; }
244              
245             # Final routines, if defined
246 0 0       0 if (defined($self->{Final})) {
247 0         0 my @a = @{ $self->{Final} };
  0         0  
248 0         0 local $::Star = $self;
249 0         0 for my $f (@a) {
250 0         0 $self->{Out} = &{$f}($self->{Out}); }}
  0         0  
251            
252             # Related to the macro concept (e.g. code folding)
253 0 0       0 if (defined $self->{macrosdefined}) {
254 0         0 my ($m, $s);
255 0         0 for $m (sort keys %{$self->{Macros}}) {
  0         0  
256 0         0 $s = $self->{Macros}->{$m};
257 0 0       0 if ($s =~ /\n/) {
258 0         0 my $p1 = "$`$&"; $s = $';
  0         0  
259 0 0       0 if ($s) { $s = $p1.wrap($s) }
  0         0  
260 0         0 else { $s = $p1 }
261             }
262 0         0 $self->{Out}.= $self->{MprefAuxDefine}.$s.$self->{MsufAuxDefine};
263             }
264             }
265             } # end of sub _digest
266              
267             # process outer text; by default, it should be appended to $self->{Out}
268             sub _process_outer {
269 0     0   0 my $self = shift; my $outer = shift;
  0         0  
270 0 0 0     0 if ($self->{REPLACE} && $self->{IgnoreOuter}) { }
271 0         0 else { $self->{Out} .= $outer }
272             # Comment: something should be done if IgnoreOuter is true and
273             # not in REPLACE mode; maybe comment-out outer text? (todo?)
274             }
275              
276             # _index($str,$substr) or _index($str, qr/../)
277             # returns index and length of found match, index==-1 for not found
278             # third argument is an optional offset
279             sub _index {
280 6     6   4965 my $str = shift; my $subs = shift; my $off = shift;
  6         12  
  6         8  
281 6 100       21 if (ref($subs) eq 'Regexp') {
    100          
282 3 100       8 if ($off < 1) {
283 1 50       13 if ($str =~ $subs) { return (length($`),length($&)) }
  1         14  
284 0         0 else { return (-1,0) }
285             } else {
286 2         16 pos($str) = $off;
287 2 50       26 if ($str =~ /$subs/g) { return (length($`),length($&)) }
  2         24  
288 0         0 else { return (-1,0) }
289             }
290             }
291 2         10 elsif ($off < 1) { return (index($str, $subs), length($subs)) }
292 1         5 else { return (index($str, $subs, $off), length($subs)) }
293             }
294              
295             # Scanning for the best hook match
296             sub _scan_for_hook_match {
297 0     0     my $self = shift;
298 0           $self->{prefix} = $self->{suffix} = '';
299 0           $self->{args} = [];
300            
301 0           my $i1 = length($self->{data}) + 1; # distance to starting anchor
302 0           my $i2 = $i1; # distance to ending anchor
303 0           my $pl=0; my $sl=0; # prefix and suffix lengths
  0            
304 0           $self->{ttype} = -2; # token type == hook id
305 0           foreach my $ttype (0 .. $#{ $self->{hook} }) {
  0            
306 0           my $hook = $self->{hook}->[$ttype];
307 0           my ($j, $pl2, $j2, $sl2); # current token under consideration,
308             # $j dist to candidate starting achor
309             # $j2 dist to candidate ending anchor
310             # pl2 and sl2 - lengths of prefix and suffix
311 0           my $ht = $hook->{ht};
312 0 0         if ($ht eq '') { # guessing hook type if not defined
313 0 0         if (exists($hook->{begin})) { $ht = $hook->{ht} = 'be' }
  0            
314 0           else { $ht = $hook->{ht} = 'regex' } }
315              
316 0 0         if ($ht eq 'string') {
    0          
    0          
    0          
317 0           ($j,$pl2) = _index($self->{data}, $hook->{s});
318 0 0 0       next unless $j != -1 && $j <= $i1;
319 0 0 0       next if $j==$i1 and $i2<=$j+$pl2;
320 0           $i1 = $j; $pl = $pl2; $self->{ttype} = $ttype; $self->{args} = [];
  0            
  0            
  0            
321 0           $i2 = $i1+$pl; $sl = 0;
  0            
322             } elsif ($ht eq 'be') {
323 0           ($j,$pl2) = _index($self->{data}, $hook->{'begin'});
324 0 0 0       next unless $j != -1 && $j <= $i1;
325 0 0         if ($hook->{'end'} ne '') {
326 0           ($j2, $sl2) = _index($self->{data}, $hook->{'end'}, $j);
327 0 0         next if -1 == $j2;
328 0           } else { $j2 = length($self->{data}) + 1; $sl2 = 0; }
  0            
329 0 0 0       next if ($j==$i1 and $i2<=$j2);
330 0           $i1 = $j; $pl = $pl2; $i2 = $j2; $sl = $sl2;
  0            
  0            
  0            
331 0           $self->{ttype} = $ttype; $self->{args} = [];
  0            
332             } elsif ($ht eq 'regex') {
333 0           my @args = ($self->{data} =~ /$hook->{regex}/m);
334 0 0         next unless @args;
335 0           my $j = length($`);
336 0 0         next unless $j < $i1;
337 0           $i1 = $j; $i2 = $i1+length($&); $sl=$pl=0;
  0            
  0            
338 0           unshift @args, $&; # could be removed
339 0           $self->{ttype} = $ttype;
340 0           $self->{args} = \@args;
341             } elsif ($ht eq 'ht:re2') {
342 0           my @args = ($self->{data} =~ /$hook->{regex}/m);
343 0 0         next unless @args;
344 0           my $j = length($`);
345 0 0         next unless $j < $i1;
346 0           $i1 = $j; $i2 = $i1+length($&); $sl=$pl=0;
  0            
  0            
347 0           unshift @args, $&; # full match is prepended to @args !?to remove?
348 0           $self->{ttype} = $ttype;
349 0           $self->{args} = \@args;
350 0           } else { _croak("Unknown hook type: ($ht)"); }
351             }
352 0           $self->{match_ij} = [$i1,$i2,$pl,$sl];
353             }
354              
355             # $self->{ttype}: -1 EOF
356             # -2 outer text (but also handled directly)
357             sub _scan {
358 0     0     my $self = shift;
359 0           $self->{prefix} = $self->{suffix} = ''; $self->{args} = [];
  0            
360 0 0         if ($self->{data} eq '') { # no more data, EOF
361 0           $self->{ttype} = -1; # ttype==-1 is EOF
362 0           $self->{currenttoken} = '';
363             }
364             else {
365 0           $self->_scan_for_hook_match();
366 0           my ($i1,$i2,$pl,$sl) = @{ $self->{match_ij} };
  0            
367 0 0         if ($self->{ttype}==-2) {
368 0           $self->{currenttoken}=$self->{data}; $self->{data}='' }
  0            
369             else { # live code
370             # just copy type -2
371             # instead of returning as earlier, to
372             # support negative look-back for prefix
373             # $self->{Out} .= substr($self->{data}, 0, $i1);
374 0           $self->_process_outer( substr($self->{data}, 0, $i1) );
375 0           $self->{prefix} = substr($self->{data}, $i1, $pl);
376 0           $self->{currenttoken} = substr($self->{data}, $i1+$pl, $i2-$i1-$pl);
377 0           $self->{suffix} = substr($self->{data}, $i2, $sl);
378 0           $self->{data} = substr($self->{data}, $i2+$sl);
379             # Remove old output if it is there:
380 0 0         if (defined($self->{OutDelimiters})) {
381 0           my ($b1,$b2,$e1,$e2) = @{ $self->{OutDelimiters} };
  0            
382 0 0         if ($self->{data} =~ /^\Q$b1\E(\d*)\Q$b2\E.*?\Q$e1\E\1\Q$e2\E/s) {
383 0           $self->{data} = $'; }
384             }
385             }
386             }
387 0           return $self->{ttype};
388             }
389              
390             # _evf_dispatch should decide how to properly call the evaluator (evf), or just
391             # apply replacement. It should eventually be used for string-based evaluators.
392             sub _evf_dispatch {
393 0     0     my $self = shift;
394 0           my $hook = $self->{hook}->[$self->{ttype}]; my $ht = $hook->{ht};
  0            
395 0           local $::Star = $self;
396 0           local $::O = '';
397 0 0         if ($ht eq 'string') { $::O .= $hook->{evf_const};
  0 0          
    0          
    0          
398             } elsif ($hook->{ht} eq 'regex') {
399 0           $::O .= &{ $hook->{replace} } ( $self, @{ $self->{args} } ); #!!!
  0            
  0            
400             } elsif ($hook->{ht} eq 'ht:re2') { #!!! python/make style eval
401             # evaluation function uses its own output
402             # wrap and attachement.
403 0           return &{$hook->{replace}}( $self, @{ $self->{args} } );
  0            
  0            
404 0           } elsif ( @{$self->{args}} ) { # guessing regex hook type
405 0           return &{$hook->{replace}}( $self, @{ $self->{args} } );
  0            
  0            
406             } else {
407 0           return &{$hook->{f}}
408 0           ( $self, $self->{prefix}, $self->{currenttoken}, $self->{suffix});
409             }
410              
411 0 0         return $::O if $self->{REPLACE};
412 0 0         return $self->{currenttoken} if $::O eq '';
413             return $self->{currenttoken}.
414 0           $self->_output_wrap( $::O );
415             }
416              
417             # eval wrapper for string code
418             sub eval1 {
419 0     0 0   my $self = shift;
420 0 0         my $code = shift; $code = '' unless defined $code;
  0            
421 0           my $comment = shift;
422 0           eval("package main; no strict; $code");
423 0 0         if ($@) {
424 0           my ($code1, $linecnt);
425 0           foreach (split(/\n/, $code))
426 0           { ++$linecnt; $code1 .= sprintf("%03d %s\n", $linecnt, $_); }
  0            
427 0           _croak("$comment code error:$@\ncode:\n$code1");
428             }
429             }
430              
431             # The main subroutine for evaluating a snippet of string
432             sub _evaluate {
433 0     0     my $self = shift;
434              
435 0           my $pref = shift;
436 0           my $code = shift; my $c = $code;
  0            
437 0           my $suf = shift;
438 0 0 0       if (defined($self->{CodePreparation}) && $self->{CodePreparation}) {
439 0           local $_=$code;
440 0           $self->eval1($self->{CodePreparation},'preprocessing');
441 0           $code = $_; }
442              
443             # Evaluate code, first final preparation and then eval1
444 0           local $::Star = $self;
445 0           local $::O = '';
446 0           $self->eval1($code, 'snippet');
447            
448 0 0         if ($self->{REPLACE}) { return $::O }
  0            
449 0 0         if ($::O ne '') { $suf.= $self->_output_wrap($::O); }
  0            
450 0           return "$pref$c$suf"; }
451              
452             # Wrap output with output delimiters
453             sub _output_wrap {
454 0     0     my $self = shift; my $out = shift; my @d = ("#","+\n","#","-");
  0            
  0            
455 0 0         @d = @{ $self->{OutDelimiters} } if defined( $self->{OutDelimiters} );
  0            
456 0           my ($b,$e) = ($d[0].$d[1], $d[2].$d[3]); my $i;
  0            
457 0 0         if (index($out, $e) != -1) {
458 0 0         while(1) { $i++; $e=$d[2].$i.$d[3]; last if index($out, $e)==-1;
  0            
  0            
  0            
459 0 0         _croak("Problem finding ending delimiter!\n(O=$out)") if $i > 1000000;}
460 0           $b = $d[0].$i.$d[1]; }
461 0           return $b.$out.$e; }
462              
463             # Python-specific evaluator (used also for makefile style)
464             # used with hook type ht:re2
465             sub evaluate_py1 { #!!!py
466 0     0 0   my $self = shift;
467 0           my $allmatch = shift; #!!!py maybe to remove it!?
468 0           my $indent = shift;
469 0           my $prefix = shift;
470 0           my $code = shift; my $c = $code;
  0            
471 0           my $oldout = shift; #!?to remove it
472              
473 0 0 0       if (defined($self->{CodePreparation}) && $self->{CodePreparation}) {
474 0           local $_=$code;
475 0           $self->eval1($self->{CodePreparation},'preprocessing');
476 0           $code = $_;
477             }
478              
479             # Evaluate code, first final preparation and then eval1
480 0           local $::O = '';
481 0           local $::Star = $self;
482 0           $self->eval1($code, 'snippet');
483              
484 0 0         if ($self->{REPLACE}) { return $indent.$::O }
  0 0          
485 0           elsif ($::O eq '') { return "$indent#$prefix$c!>" }
486             else {
487 0           $::O =~ s/^/$indent/gmx;
488 0           my $r;
489 0           my ($b,$e); my @d = @{ $self->{OutDelimiters} };
  0            
  0            
490 0           $b = $d[0].$d[1]; my $i; $e = $d[2].$d[3];
  0            
  0            
491 0 0         if (index($::O, $e) != -1) {
492 0 0         while(1) { $i++; $e=$d[2].$i.$d[3]; last if index($::O, $e)==-1;
  0            
  0            
  0            
493 0 0         _croak("Problem finding ending delimiter!\n(O=$::O)")
494             if $i > 1000000; }
495 0           $b = $d[0].$i.$d[1];
496             }
497 0           $r= "$indent#$prefix$c!>$b".$::O;
498 0           $r =~ s/\n?$/\n/; $r.="$indent$e"; # no extra \n
  0            
499             }
500             }
501              
502             # predefined evaluator: echo
503             sub eval_echo {
504 0     0 0   my $self = shift;
505 0           my $pref = shift;
506 0           my $cont = shift;
507 0           my $suff = shift;
508 0           $::O = $cont;
509              
510             # to update OutDelimiters
511 0 0         return $::O if $self->{REPLACE};
512 0 0         return $pref.$cont.$suff if $::O eq '';
513 0           $suff.=$self->_output_wrap($::O);
514 0           return $pref.$cont.$suff;
515             }
516              
517             # predefined evaluator: ignore
518             sub eval_ignore {
519 0     0 0   my $self = shift;
520 0 0         return '' if $self->{REPLACE};
521              
522 0           my $pref = shift;
523 0           my $code = shift;
524 0           my $suf = shift;
525 0           return $pref.$code.$suf;
526             }
527              
528             # predefined ignore evaluator for regex hooks
529             sub repl_comment {
530 0     0 0   my $self = shift;
531 0 0         if ($self->{REPLACE}) { return '' }
  0            
532 0           return $self->{currenttoken};
533             }
534              
535             sub define {
536 0     0 0   my $self = shift;
537              
538 0           my $pref = shift;
539 0           my $data = shift;
540 0           my $suf = shift;
541              
542 0 0         if ($self->{CurrentLoop} > 1) { return "$pref$data$suf"; }
  0            
543              
544 0 0         $data =~ /^.+/ or _croak("expected macro spec");
545 0 0         _croak("no macro spec") unless $&;
546 0 0         _croak("double macro def (forbidden):$&") if ($self->{ForbidMacro}->{$&});
547 0           $self->{Macros}->{$&} = $data;
548 0           return '';
549             }
550              
551             sub MCdefine {
552 0     0 0   my $self = shift;
553 0           my $pref = shift;
554 0           my $data = shift;
555 0           my $suf = shift;
556              
557 0 0         if ($self->{CurrentLoop} > 1) { die "define in loop > 1 !?" }
  0            
558              
559 0 0         $data =~ /^.+/ or die "expected macro spec";
560 0 0         die "no macro spec" unless $&;
561 0 0         die "double macro def (forbidden):$&" if ($self->{ForbidMacro}->{$&});
562 0           $self->{Macros}->{$&} = $data;
563 0           return '';
564             }
565              
566             sub MCdefe {
567 0     0 0   my $self = shift;
568 0 0         die "(".ref($self).")" unless ref($self) eq 'Text::Starfish';
569              
570 0           my $pref = shift;
571 0           my $data = shift;
572 0           my $suf = shift;
573              
574 0 0         if ($self->{CurrentLoop} > 1) { die "defe in a loop >1!?" }
  0            
575              
576 0 0         $data =~ /^.+/ or die "expected macro spec";
577 0 0         die "no macro spec" unless $&;
578 0 0         die "def macro forbidden:$&\n" if (defined $self->{ForbidMacro}->{$&});
579 0           $self->{Macros}->{$&} = $data;
580 0           return $self->{MacroKey}->{'expand'}.$&.$self->{MacroKey}->{'/expand'};
581             }
582              
583             sub MCnewdefe {
584 0 0   0 0   my $self = shift; die "(".ref($self).")" unless ref($self) eq 'Text::Starfish';
  0            
585              
586 0           my $pref = shift;
587 0           my $data = shift;
588 0           my $suf = shift;
589              
590 0 0         if ($self->{CurrentLoop} > 1) { die "newdefe in second loop!?" }
  0            
591              
592 0 0         $data =~ /^.+/ or die "expected macro spec";
593 0 0         die "no macro spec" unless $&;
594 0 0 0       if (defined $self->{Macros}->{$&} || $self->{ForbidMacro}->{$&}) {
595 0           die "double def:$&" }
596 0           $self->{Macros}->{$&} = $data;
597 0           $self->{ForbidMacro}->{$&} = 1;
598 0           return $self->{MprefExpand}.$&.$self->{MsufExpand};
599             }
600              
601             sub expand {
602 0     0 0   my $self = shift;
603 0 0         die "(".ref($self).")" unless ref($self) eq 'Text::Starfish';
604              
605 0           my $pref = shift;
606 0           my $data = shift;
607 0           my $suf = shift;
608              
609 0 0 0       if ($self->{CurrentLoop} < 2 || $self->{HideMacros})
610 0           { return $self->{MacroKey}->{'expand'}.$data.$self->{MacroKey}->{'/expand'} }
611            
612 0 0         $data =~ /^.+/ or die "expected macro spec";
613 0 0         die "no macro spec" unless $&;
614             return $self->{MacroKey}->{'expanded'}.$self->{Macros}->{$&}.
615 0           $self->{MacroKey}->{'/expanded'};
616             }
617              
618             sub MCexpand {
619 0     0 0   my $self = shift;
620 0 0         die "(".ref($self).")" unless ref($self) eq 'Text::Starfish';
621              
622 0           my $pref = shift;
623 0           my $data = shift;
624 0           my $suf = shift;
625              
626 0 0 0       if ($self->{CurrentLoop} < 2 || $self->{HideMacros}) {
627 0           return "$pref$data$suf"; }
628            
629 0 0         $data =~ /^.+/ or die "expected macro spec";
630 0 0         die "no macro spec" unless $&;
631 0 0         die "macro not defined" unless defined $self->{Macros}->{$&};
632 0           return $self->{MprefExpanded}.$self->{Macros}->{$&}.$self->{MsufExpanded};
633             }
634              
635             sub fexpand {
636 0     0 0   my $self = shift;
637 0 0         die "(".ref($self).")" unless ref($self) eq 'Text::Starfish';
638              
639 0           my $pref = shift;
640 0           my $data = shift;
641 0           my $suf = shift;
642              
643 0 0         if ($self->{CurrentLoop} < 2) { return "$pref$data$suf"; }
  0            
644            
645 0 0         $data =~ /^.+/ or die "expected macro spec";
646 0 0         die "no macro spec" unless $&;
647 0 0         die "macro not defined:$&" unless defined $self->{Macros}->{$&};
648 0           return $self->{MpreffExpanded} . $self->{Macros}->{$&}.$self->{MsuffExpanded};
649             }
650              
651             sub MCfexpand {
652 0     0 0   my $self = shift;
653 0 0         die "(".ref($self).")" unless ref($self) eq 'Text::Starfish';
654              
655 0           my $pref = shift;
656 0           my $data = shift;
657 0           my $suf = shift;
658              
659 0 0         if ($self->{CurrentLoop} < 2) { return "$pref$data$suf"; }
  0            
660            
661 0 0         $data =~ /^.+/ or die "expected macro spec";
662 0 0         die "no macro spec" unless $&;
663 0 0         die "macro not defined:$&" unless defined $self->{Macros}->{$&};
664             return $self->{MacroKey}->{'fexpanded'}.$self->{Macros}->{$&}.
665 0           $self->{MacroKey}->{'/fexpanded'};
666             }
667              
668             sub expanded {
669 0     0 0   my $self = shift;
670 0 0         die "(".ref($self).")" unless ref($self) eq 'Text::Starfish';
671              
672 0           my $pref = shift;
673 0           my $data = shift;
674 0           my $suf = shift;
675            
676 0 0         $data =~ /^.+/ or die "expected macro name";
677 0 0         die "no macro spec" unless $&;
678 0           return $self->{MprefExpand}.$&.$self->{MsufExpand};
679             }
680              
681             sub MCexpanded {
682 0     0 0   my $self = shift;
683 0 0         die "(".ref($self).")" unless ref($self) eq 'Text::Starfish';
684              
685 0           my $pref = shift;
686 0           my $data = shift;
687 0           my $suf = shift;
688            
689 0 0         $data =~ /^.+/ or die "expected macro name";
690 0 0         die "no macro spec" unless $&;
691 0           return $self->{MacroKey}->{'expand'}.$&.$self->{MacroKey}->{'/expand'};
692             }
693              
694             sub fexpanded {
695 0     0 0   my $self = shift;
696 0 0         die "(".ref($self).")" unless ref($self) eq 'Text::Starfish';
697              
698 0           my $pref = shift;
699 0           my $data = shift;
700 0           my $suf = shift;
701            
702 0 0         $data =~ /^.+/ or die "expected macro name";
703 0 0         die "no macro spec" unless $&;
704 0           return $self->{MpreffExpand}.$&.$self->{MsuffExpand};
705             }
706              
707             sub MCfexpanded {
708 0     0 0   my $self = shift;
709 0 0         die "(".ref($self).")" unless ref($self) eq 'Text::Starfish';
710              
711 0           my $pref = shift;
712 0           my $data = shift;
713 0           my $suf = shift;
714            
715 0 0         if ($self->{CurrentLoop} < 2) { return "$pref$data$suf"; }
  0            
716 0 0         $data =~ /^.+/ or die "expected macro name";
717 0 0         die "no macro spec" unless $&;
718 0 0         die "Macro not defined:$&" unless defined $self->{Macros}->{$&};
719             return $self->{MacroKey}->{'fexpanded'}.$self->{Macros}->{$&}.
720 0           $self->{MacroKey}->{'/fexpanded'};
721             }
722              
723             sub MCauxdefine {
724 0     0 0   my $self = shift;
725 0 0         die "(".ref($self).")" unless ref($self) eq 'Text::Starfish';
726              
727 0           my $pref = shift;
728 0           my $data = shift;
729 0           my $suf = shift;
730            
731 0 0         $data =~ /^.+/ or die "expected macro name";
732 0 0         die "no macro spec" unless $&;
733 0           my $mn = $&;
734 0           $data = unwrap($data);
735 0 0         die "double macro def (forbidden):$mn\n" if ($self->{ForbidMacro}->{$mn});
736 0 0         if (! defined($self->{Macros}->{$mn}) ) { $self->{Macros}->{$mn}=$data }
  0            
737 0           return '';
738             }
739              
740             sub auxDefine {
741 0     0 0   my $self = shift;
742 0 0         die "(".ref($self).")" unless ref($self) eq 'Text::Starfish';
743              
744 0           my $pref = shift;
745 0           my $data = shift;
746 0           my $suf = shift;
747            
748 0 0         $data =~ /^.+/ or die "expected macro name";
749 0 0         die "no macro spec" unless $&; my $mn = $&;
  0            
750 0           $data = unwrap($data);
751 0 0         die "double macro def (forbidden):$mn\n" if ($self->{ForbidMacro}->{$mn});
752 0 0         if (! defined($self->{Macros}->{$mn}) ) { $self->{Macros}->{$mn}=$data }
  0            
753 0           return '';
754             }
755              
756             sub wrap {
757 0     0 0   my $d = shift;
758 0           $d =~ s/^/\/\/ /mg;
759 0           return $d;
760             }
761              
762             sub unwrap {
763 0     0 0   my $d = shift;
764 0           $d =~ s/^\/\/ //mg;
765 0           return $d;
766             }
767              
768             sub setGlobStyle {
769 0     0 0   my $self = shift;
770 0           my $s = shift;
771 0           $self->{STYLE} = $s;
772 0           $self->set_style($s);
773             }
774              
775             sub clearStyle {
776 0     0 0   my $self = shift;
777 0           foreach my $k (qw(Style CodePreparation LineComment OutDelimiters
778             IgnoreOuter)) {
779 0           delete $self->{$k} }
780 0           $self->{hook} = [];
781             }
782              
783 0     0 1   sub setStyle { return &set_style }
784              
785             # List of fields typically set in set_style:
786             # $self->{Style} = string, style
787             # $self->{CodePreparation} = scalar
788             # $self->{LineComment} = string, line comment
789             # $self->{OutDelimiters} = [] eg: "//" "+\n" "//" "-\n"
790             # $self->{IgnoreOuter} = 1 or ''
791             # $self->{hook} = [], list of hooks
792             sub set_style {
793 0     0 1   my $self = shift;
794 0 0         if (ref($self) ne 'Text::Starfish') { unshift @_, $self; $self = $::Star; }
  0            
  0            
795              
796 0 0         if ($#_ == -1) {
797 0 0 0       if (defined $self->{STYLE} && $self->{STYLE} ne '')
798 0           { $self->set_style($self->{STYLE}) }
799             else {
800 0           my $f = $self->{INFILE};
801              
802 0 0         if ($f =~ /\.(html\.sfish|sf)$/i) { $self->set_style('html.sfish') }
  0            
803             else {
804 0           $f =~ s/\.s(tar)?fish$//;
805 0 0         if ($f =~ /\.html?/i) { $self->set_style('html') }
  0 0          
    0          
    0          
    0          
    0          
806 0           elsif ($f =~ /\.(?:la)?tex$/i) { $self->set_style('tex') }
807 0           elsif ($f =~ /\.java$/i) { $self->set_style('java') }
808 0           elsif ($f =~ /^[Mm]akefile/) { $self->set_style('makefile') }
809 0           elsif ($f =~ /\.ps$/i) { $self->set_style('ps') }
810 0           elsif ($f =~ /\.py$/i) { $self->set_style('python') }
811 0           else { $self->set_style('perl') }
812             }
813             }
814 0           return;
815             }
816              
817 0           my $s = shift;
818 0 0 0       if ($s eq 'latex' or $s eq 'TeX') { $s = 'tex' }
  0            
819 0 0 0       if (defined $self->{Style} and $s eq $self->{Style}) { return }
  0            
820            
821             # default
822 0           $self->clearStyle();
823 0           $self->{'LineComment'} = '#';
824 0           $self->{'IgnoreOuter'} = '';
825 0           $self->{OutDelimiters} = [ "#", "+\n", "#", "-" ];
826 0           $self->{CodePreparation} = 's/\\n(?:#|%|\/\/+)/\\n/g';
827 0           $self->{hook} = [];
828 0           $self->add_hook('be','#');
829 0           $self->add_hook('be','');
830 0           $self->add_hook('be','');
831              
832             #!!!py
833             # Used for Python and Makefile with &evaluate_py1
834             # matched with hook type ht:re2
835 0           my $re_py1 = qr/([\ \t]*)\#(\ *<\?)([\000-\377]*?)!>/x;
836             # extension below was a bug for ...#+...#-
837             # ([\ \t]*\#+\n[\000-\377]*?\n[\ \t]*\#-\n)?/x;
838              
839 0 0         if ($s eq 'perl') { }
    0          
    0          
    0          
    0          
    0          
    0          
    0          
840             elsif ($s eq 'makefile') {
841 0           $self->{CodePreparation} = 's/\\n\\s*#/\\n/g';
842 0           $self->{hook} = [ ];
843 0           $self->add_hook('ht:re2', $re_py1, \&evaluate_py1); #!!!
844             }
845             elsif ($s eq 'python') {
846 0           $self->{hook} = [ ];
847 0           $self->{CodePreparation} = 's/\\n\\s*#/\\n/g';
848 0           $self->add_hook('ht:re2', $re_py1, \&evaluate_py1); #!!!
849             }
850             elsif ($s eq 'java') {
851 0           $self->{LineComment} = '//';
852 0           $self->{OutDelimiters} = [ "//", "+\n", "//", "-" ];
853 0           $self->{hook} = [{begin => '// '!>', f => \&_evaluate },
854             {begin => ' '!>', f => \&_evaluate }];
855 0           $self->{CodePreparation} = 's/^\s*\/\/+//mg';
856             }
857             elsif ($s eq 'tex') {
858 0           $self->{LineComment} = '%';
859             # change OutDelimiters ?
860             # $self->{OutDelimiters} = [ "%", "+\n", "%", "-\n" ];
861 0           $self->{OutDelimiters} = [ "%", "+\n", "\n%", "-\n" ];
862 0           $self->{hook}=[{ht=>'be', begin=>'%"!>\n", f=>\&_evaluate },
863             # change to this one?
864             #{ht=>'be', begin=>'%"!>", f=>\&_evaluate },
865             {ht=>'be', begin=>'"!>\n", f=>\&_evaluate },
866             {ht=>'be', begin=>'"!>", f=>\&_evaluate }];
867              
868 0           $self->{CodePreparation} = 's/^[ \t]*%//mg';
869             }
870             elsif ($s eq 'html.sfish') {
871 0           undef $self->{LineComment};
872 0           $self->{OutDelimiters} = [ "", "" ];
873 0           $self->{CodePreparation} = '';
874 0           $self->{hook}=[];
875 0           $self->add_hook('be', '');
876 0           $self->add_hook('be', qr/<\?starfish\b/, '?>');
877 0           $self->add_hook('be', qr/<\?sf\b/, '!>');
878 0           $self->add_hook('regex', qr/^#.*\n/m, 'comment');
879             }
880             elsif ($s eq 'html') {
881 0           undef $self->{LineComment}; # Changes
882 0           $self->{OutDelimiters} = [ "", "" ];
883             $self->{hook}=[
884 0           {ht=>'be', begin => '', f => \&_evaluate },
885             {ht=>'be', begin=>''?>', f=>\&_evaluate } ];
886 0           $self->{CodePreparation} = '';
887             }
888             elsif ($s eq 'ps') {
889 0           $self->{LineComment} = '%';
890 0           $self->{OutDelimiters} = [ "% ", "+\n", "% ", "-" ];
891             $self->{hook}=[
892 0           {ht=>'be', begin => ' '!>', f => \&_evaluate }];
893 0           $self->{CodePreparation} = 's/\\n%/\\n/g';
894             }
895 0           else { _croak("set_style:unknown style:$s") }
896 0           $self->{Style} = $s;
897             }
898              
899             # to be deprecated? Used only to make it available in name space
900 0     0 1   sub sfish_add_tag($$) { $::Star->add_tag(@_) }
901 0     0 1   sub sfish_ignore_outer { $::Star->ignore_outer(@_) }
902              
903             # adds tags such as %slide:.* and % by adding appropriate hooks
904             # eg: add_tag('slide', 'ignore')
905             # eg: add_tag('sl,l', 'echo')
906             sub add_tag {
907 0     0 1   my $self = shift;
908 0 0         if (ref($self) ne 'Text::Starfish')
909 0           { unshift @_, $self; $self = $::Star; }
  0            
910 0           my $tag = shift; my $fun = shift;
  0            
911 0           my $lc = $self->{LineComment};
912             #die "tag=($tag) fun=($fun) ref(fun)=".ref($fun);
913 0 0         if (ref($fun) eq '') {
914 0 0   0     if ( $fun eq 'ignore') { $fun = sub{''} }
  0 0          
  0            
915 0     0     elsif ( $fun eq 'echo') { $fun = sub{$_[2]} }
  0            
916             }
917 0 0         my $lc1 = ($lc eq '') ? $lc : "$lc?"; #!!!
918 0           $self->add_hook('regex', qr/$lc1<$tag>\n?((?:.|\n)*?)$lc1<\/$tag>\n?/, $fun);
919 0           $self->add_hook('regex', qr/$lc$tag:(.*\n)/, $fun);
920             }
921              
922             sub ignore_outer {
923 0     0 1   my $self = shift;
924 0 0         if (ref($self) ne 'Text::Starfish')
925 0           { unshift @_, $self; $self = $::Star; }
  0            
926 0           my $newignoreouter = 1;
927 0 0         $newignoreouter = $_[1] if $#_ > 0;
928 0           $self->{IgnoreOuter} = $newignoreouter;
929             }
930              
931             sub set_out_delimiters {
932 0     0 0   my $self = shift;
933 0 0         if (ref($self) ne 'Text::Starfish')
934 0           { unshift @_, $self; $self = $::Star; }
  0            
935 0 0         _croak("OutDelimiters must be array of 4 elements:(@_)") if scalar(@_)!=4;
936 0           $self->{OutDelimiters} = [ $_[0], $_[1], $_[2], $_[3] ]; }
937              
938             # eg: add_hook('string','somestring','replacement')
939             # add_hook('be', '');
940             sub add_hook { #!!! adding hooks
941 0     0 1   my $self = shift;
942 0 0         if (ref($self) ne 'Text::Starfish')
943 0           { unshift @_, $self; $self = $::Star; }
  0            
944              
945 0           my $ht = shift;
946 0           my $hooks = $self->{hook}; my $hook = { ht=>$ht };
  0            
947 0 0         if ($ht eq 'string') {
    0          
    0          
    0          
948 0           my $s=shift; my $replace = shift;
  0            
949 0           $hook->{s} = $s; $hook->{evf_const} = $replace;
  0            
950 0           push @{$hooks}, $hook;
  0            
951             } elsif ($ht eq 'be') {
952 0           my $b = shift; my $e = shift; my $f='default';
  0            
  0            
953 0 0         if ($#_>-1) { $f = shift }
  0            
954 0           $hook->{begin} = $b; $hook->{end} = $e;
  0            
955 0 0         if ($f eq 'default') { $hook->{f} = \&_evaluate;
  0 0          
    0          
    0          
956 0           push @{$hooks}, $hook; return;
  0            
  0            
957 0           } elsif ($f eq 'ignore') { $hook->{f} = \&eval_ignore;
958 0           push @{$hooks}, $hook; return;
  0            
  0            
959 0           } elsif ($f eq 'echo') { $hook->{f} = \&eval_echo;
960 0           push @{$hooks}, $hook; return;
  0            
  0            
961             } elsif (ref($f) eq 'CODE') {
962 0     0     $hook->{f} = sub { local $_; my $self=shift;
  0            
963 0           my $p=shift; $_=shift; my $s=shift;
  0            
  0            
964 0           &$f($p,$_,$s);
965 0 0         if ($self->{REPLACE}) { return $_ }
  0            
966 0           return "$p$_$s";
967 0           };
968 0           push @{$hooks}, $hook; return;
  0            
  0            
969             } else {
970 0           $hook->{ht} = '';
971 0           eval("\$hook->{f} = sub {\n".
972             "local \$_;\n".
973             "my \$self = shift;\n".
974             "my \$p = shift; \$_ = shift; my \$s = shift;\n".
975             "$f;\n".
976             'if ($self->{REPLACE}) { return $_ }'."\n".
977             "return \"\$p\$_\$s\"; };");
978 0 0         _croak("add_hook error:$@") if $@;
979 0           push @{$hooks}, $hook; return;
  0            
  0            
980             }
981             } elsif ($ht eq 'regex') {
982 0           my $regex=shift; my $replace = shift;
  0            
983 0           $hook->{regex} = $regex;
984 0 0 0       if (ref($replace) eq '' && $replace eq 'comment')
    0          
985 0           { $hook->{replace} = \&repl_comment }
986             elsif (ref($replace) eq 'CODE')
987 0           { $hook->{replace} = $replace }
988 0           else { _croak("add_hook, undefined regex format input ".
989             "(TODO?): ref regex(".ref($regex).
990             "), ref replace(".ref($replace).")" ) }
991 0           push @{$hooks}, $hook;
  0            
992             } elsif ($ht eq 'ht:re2') {
993 0           my $regex=shift; my $replace=shift;
  0            
994 0 0         die unless ref($replace) eq 'CODE';
995 0           $hook->{regex} = $regex; $hook->{replace} = $replace;
  0            
996 0           push @{$hooks}, $hook;
  0            
997 0           } else { _croak("add_hook error, unknown hook type `$ht'") }
998             }
999              
1000             # addHook is deprecated. Use add_hook, which contains the hook type
1001             # as the second argument, after $self.
1002             sub addHook {
1003 0     0 1   my $self = shift;
1004 0 0 0       if ($#_ == 2) {
    0          
1005 0           $self->add_hook('be', @_); return;
  0            
1006             } elsif ($#_ == 1 and ref($_[0]) eq 'Regexp') {
1007 0           my $regex=shift; my $replace = shift;
  0            
1008 0           $self->add_hook('regex', $regex, $replace); return;
  0            
1009 0           } else { _croak("addHook parameter error") }}
1010              
1011             sub rm_hook {
1012 0     0 1   my $self = shift;
1013 0 0         if (ref($self) ne 'Text::Starfish')
1014 0           { unshift @_, $self; $self = $::Star; }
  0            
1015              
1016 0           my $ht = shift; # hook type: be (begin-end)
1017 0 0         if ($ht eq 'be') {
1018 0           my $b=shift; my $e=shift;
  0            
1019 0           my @Hooks = @{ $self->{hook} }; my @Hooks1;
  0            
  0            
1020 0           foreach my $h (@Hooks) {
1021 0 0 0       if ($h->{begin} eq $b and $h->{end} eq $e) {}
1022 0           else { push @Hooks1, $h }
1023             }
1024 0           $self->{hook} = \@Hooks1;
1025             } else {
1026 0           _croak("rm_hook not implemented for type ht=($ht)") }
1027             }
1028              
1029             # rmHook to be deprecated. Needs to be replaced with rm_hook
1030             sub rmHook {
1031 0     0 1   my $self = shift; my $p = shift; my $s = shift;
  0            
  0            
1032 0           $self->rm_hook('be', $p, $s); return; }
  0            
1033              
1034 0     0 1   sub rmAllHooks { my $self = shift; $self->{hook} = []; }
  0            
1035              
1036 0     0 0   sub resetHooks { my $self = shift; $self->rmAllHooks(); $self->set_style(); }
  0            
  0            
1037              
1038             sub add_final {
1039 0     0 1   my $self = shift;
1040 0 0         my $f = shift; die "$f not a function" unless ref($f) eq 'CODE';
  0            
1041 0 0         if (!defined($self->{Final})) { $self->{Final} = [] }
  0            
1042 0           push @{ $self->{Final} }, $f;
  0            
1043             }
1044              
1045             sub defineMacros {
1046 0     0 0   my $self = shift;
1047              
1048 0 0         return if $self->{CurrentLoop} > 1;
1049 0 0         $self->{Loops} = 2 if $self->{Loops} < 2;
1050 0           $self->{MprefDefine} = '//define ';
1051 0           $self->{MsufDefine} = "//enddefine\n";
1052 0           $self->{MprefExpand} = '//expand ';
1053 0           $self->{MsufExpand} = "\n";
1054 0           $self->{MacroKey}->{'expand'} = '//m!expand ';
1055 0           $self->{MacroKey}->{'/expand'} = "\n";
1056 0           $self->{MacroKey}->{'expanded'} = '//m!expanded ';
1057 0           $self->{MacroKey}->{'/expanded'} = "//m!end\n";
1058 0           $self->{MpreffExpand} = '//fexpand ';
1059 0           $self->{MsuffExpand} = "\n";
1060 0           $self->{MacroKey}->{'fexpand'} = '//m!fexpand ';
1061 0           $self->{MacroKey}->{'/fexpand'} = "\n";
1062 0           $self->{MprefExpanded} = '//expanded ';
1063 0           $self->{MsufExpanded} = "//endexpanded\n";
1064 0           $self->{MpreffExpanded} = '//fexpanded ';
1065 0           $self->{MsuffExpanded} = "//endexpanded\n";
1066 0           $self->{MacroKey}->{'fexpanded'} = '//m!fexpanded ';
1067 0           $self->{MacroKey}->{'/fexpanded'} = "//m!end\n";
1068 0           $self->{MprefAuxDefine}='//auxdefine ';
1069 0           $self->{MsufAuxDefine}="//endauxdefine\n";
1070 0           $self->{MacroKey}->{'auxdefine'}='//m!auxdefine ';
1071 0           $self->{MacroKey}->{'/auxdefine'}="//m!endauxdefine\n";
1072 0           $self->{MacroKey}->{'define'} = '//m!define ';
1073 0           $self->{MacroKey}->{'/define'} = "//m!end\n";
1074 0           $self->{MacroKey}->{'defe'} = '//m!defe ';
1075 0           $self->{MacroKey}->{'/defe'} = "//m!end\n";
1076 0           $self->{MacroKey}->{'newdefe'} = '//m!newdefe ';
1077 0           $self->{MacroKey}->{'/newdefe'} = "//m!end\n";
1078 0           push @{$self->{hook}}, #!!!
1079             {begin=>$self->{MprefDefine}, end=>$self->{MsufDefine}, f=>\&define},
1080             {begin=>$self->{MprefExpand}, end=>$self->{MsufExpand}, f=>\&expand},
1081             {begin=>$self->{MpreffExpand}, end=>$self->{MsuffExpand}, f=>\&fexpand},
1082             {begin=>$self->{MprefExpanded}, end=>$self->{MsufExpanded}, f=>\&expanded},
1083             {begin=>$self->{MpreffExpanded},
1084             end=>$self->{MsuffExpanded},f=>\&fexpanded},
1085             {begin=>$self->{MprefAuxDefine},
1086             end=>$self->{MsufAuxDefine},f=>\&auxDefine},
1087             {begin=>$self->{MacroKey}->{'auxdefine'},
1088             end=>$self->{MacroKey}->{'/auxdefine'},f=>\&MCauxdefine},
1089             {begin=>$self->{MacroKey}->{'define'},
1090             end=>$self->{MacroKey}->{'/define'}, f=>\&MCdefine},
1091             {begin=>$self->{MacroKey}->{'expand'},
1092             end=>$self->{MacroKey}->{'/expand'}, f=>\&MCexpand},
1093             {begin=>$self->{MacroKey}->{'fexpand'},
1094             end=>$self->{MacroKey}->{'/fexpand'}, f=>\&MCfexpand},
1095             {begin=>$self->{MacroKey}->{'expanded'},
1096             end=>$self->{MacroKey}->{'/expanded'},f=>\&MCexpanded},
1097             {begin=>$self->{MacroKey}->{'fexpanded'},
1098             end=>$self->{MacroKey}->{'/fexpanded'},f=>\&MCfexpanded},
1099             {begin=>$self->{MacroKey}->{'defe'},
1100             end=>$self->{MacroKey}->{'/defe'}, f=>\&MCdefe},
1101             {begin=>$self->{MacroKey}->{'newdefe'},
1102 0           end=>$self->{MacroKey}->{'/newdefe'}, f=>\&MCnewdefe};
1103 0           $self->{macrosdefined} = 1;
1104             }
1105              
1106             sub getmakefilelist ($$) {
1107 0     0 1   my $f = getfile($_[0]); shift;
  0            
1108 0           my $l = shift;
1109 0 0         $f =~ /\b$l=(.*(?:(?<=\\)\n.*)*)/ or
1110             die "starfish:getmakefilelist:no list:$l";
1111 0           $f=$1; $f=~s/\\\n/ /g;
  0            
1112 0           $f =~ s/^\s+//; $f =~ s/\s+$//;
  0            
1113 0           return split(/\s+/, $f);
1114             }
1115              
1116 0     0 1   sub echo(@) { $::O .= join('', @_) }
1117              
1118             # used in LaTeX mode to include verbatim textual files
1119             sub get_verbatim_file {
1120 0     0 1   my $f = shift;
1121 0           return "\\begin{verbatim}\n".
1122             untabify(scalar(getfile($f))).
1123             "\\end{verbatim}\n";
1124             }
1125              
1126             sub untabify {
1127 0     0 0   local $_ = shift;
1128 0           my ($r, $l);
1129 0           while (/[\t\n]/) {
1130 0 0         if ($& eq "\n") { $r.="$l$`\n"; $l=''; $_ = $'; }
  0            
  0            
  0            
1131             else {
1132 0           $l .= $`;
1133 0           $l .= ' ' x (8 - (length($l) & 7));
1134 0           $_ = $';
1135             }
1136             }
1137 0           return $r.$l.$_;
1138             }
1139              
1140             sub getfile($) {
1141 0     0 1   my $f = shift;
1142 0           local *F;
1143 0 0         open(F, "<$f") or die "starfish:getfile:cannot open $f:$!";
1144 0           my @r = ;
1145 0           close(F);
1146 0 0         return wantarray ? @r : join ('', @r);
1147             }
1148              
1149             sub putfile($@) {
1150 0     0 1   my $f = shift;
1151 0           local *F;
1152 0 0         open(F, ">$f") or die "starfish:putfile:cannot open $f:$!";
1153 0 0         print F '' unless @_;
1154 0           while (@_) { print F shift(@_) }
  0            
1155 0           close(F);
1156             }
1157              
1158             sub appendfile($@) {
1159 0     0 1   my $f = shift;
1160 0           local *F;
1161 0 0         open(F, ">>$f") or die "starfish:appendfile:cannot open $f:$!";
1162 0 0         print F '' unless @_;
1163 0           while (@_) { print F shift(@_) }
  0            
1164 0           close(F);
1165             }
1166              
1167             sub htmlquote($) {
1168 0     0 1   local $_ = shift;
1169 0           s/&/&/g;
1170 0           s/
1171 0           s/\"/"/g;
1172 0           return $_;
1173             }
1174              
1175             sub read_records($ ) {
1176 0     0 1   my $arg = shift;
1177 0 0         if ($arg =~ /^file=/) {
1178 0 0         my $f = $'; local *F; open(F, $f) or croak "cannot open $f:$!";
  0            
  0            
1179 0           $arg = join('', );
1180 0           close(F);
1181             }
1182              
1183 0           my $db = [];
1184 0           while ($arg) {
1185 0 0         if ($arg =~ /^([ \t\r]*(#.*)?\n)+/) { $arg = $'; }
  0            
1186 0 0         last if $arg eq ''; my $record;
  0            
1187 0 0         if ($arg =~ /([ \t\r]*\n){2,}/) { $record = "$`\n"; $arg = $'; }
  0            
  0            
1188 0           else { $record = $arg; $arg = ''; }
  0            
1189 0           my $r = {}; my $recordsave = $record;
  0            
1190 0           while ($record) {
1191 0 0         if ($record =~ /^[ \t]*#.*\n/) { $record=$'; next; } # allow
  0            
  0            
1192             # comments in records
1193 0 0         $record =~ /^[ \t]*([^\n:]*?)[ \t]*:/ or
1194             croak "db8: no attribute in record: ($recordsave)";
1195 0           my $k = $1; $record = $'; my $v;
  0            
  0            
1196 0 0         croak "empty key in ($recordsave)" if $k eq '';
1197 0           while (1) { # .................... line continuation
1198 0 0         if ($record =~ /^(.*?)\\(\r?\n)/) { $v .= $1.$2; $record = $'; }
  0 0          
  0 0          
1199 0           elsif ($record =~ /^.*?\r?\n[ \t]/) { $v .= $&; $record = $'; }
  0            
1200 0           elsif ($record =~ /^(.*?)\r?\n/) { $v .= $1; $record = $'; last; }
  0            
  0            
1201 0           else { $v .= $record; $record = ''; last }
  0            
  0            
1202             }
1203 0 0         if (exists($r->{$k})) {
1204 0           my $c = 0;
1205 0           while (exists($r->{"$k-$c"})) { ++$c }
  0            
1206 0           $k = "$k-$c";
1207             }
1208 0           $r->{$k} = $v;
1209             }
1210 0           push @{ $db }, $r;
  0            
1211             }
1212 0 0         return wantarray ? @{$db} : $db;
  0            
1213             }
1214              
1215 0     0 1   sub current_year { return POSIX::strftime("%Y", localtime(time)) }
1216              
1217             sub last_update() {
1218 0 0   0 1   my $self = @_ ? shift : $::Star;
1219 0 0         if ($self->{Loops} < 2) { $self->{Loops} = 2 }
  0            
1220 0           return POSIX::strftime("%d-%b-%Y", localtime($self->{LastUpdateTime}));
1221             }
1222              
1223             sub file_modification_time() {
1224 0 0   0 1   my $self = @_ ? shift : $::Star;
1225 0           return (stat $self->{INFILE})[9];
1226             }
1227              
1228             sub file_modification_date() {
1229 0 0   0 1   my $self = @_ ? shift : $::Star;
1230              
1231 0           my $t = $self->file_modification_time();
1232 0           my @a = localtime($t); $a[5] += 1900;
  0            
1233 0           return qw/January February March April May June July
1234             August September October November December/
1235             [$a[4]]." $a[3], $a[5]";
1236             }
1237              
1238             sub read_starfish_conf() {
1239 0 0   0 1   return unless -e "starfish.conf";
1240 0           my @dirs = ( '.' );
1241 0           while ( -e "$dirs[0]/../starfish.conf" )
1242 0           { unshift @dirs, "$dirs[0]/.." }
1243              
1244 0           my $currdir = cwd();
1245 0           foreach my $d (@dirs) {
1246 0 0         chdir $d or die "cannot chdir to $d";
1247             package main;
1248 0           require "$currdir/$d/starfish.conf";
1249             package Text::Starfish;
1250 0 0         chdir $currdir or die "cannot chdir to $currdir";
1251             }
1252             }
1253              
1254             sub _croak {
1255 0     0     my $m = shift;
1256 0           require Carp;
1257 0           Carp::croak($m);
1258             }
1259              
1260             # used in makefile mode
1261             #kw:makefile
1262             sub make_add_dirs_to_generate_if_needed {
1263 0     0 0   for my $d (@_) {
1264 0 0         next if grep { $_ eq $d } @DirGenerateIfNeeded;
  0            
1265 0           push @DirGenerateIfNeeded, $d;
1266             } }
1267             sub make_gen_dirs_to_generate {
1268 0     0 0   foreach my $d (@DirGenerateIfNeeded) {
1269 0           echo "$d:; mkdir -p \$\@\n" } }
1270              
1271             1;
1272              
1273             __END__