Add various plugins from Mark Ivey.
[matthijs/upstream/blosxom-plugins.git] / general / macros
1 # Blosxom Plugin: macros                                           -*- perl -*-
2 # Author: Todd Larason (jtl@molehill.org)
3 # Version: 0+1i
4 # Blosxom Home/Docs/Licensing: http://www.raelity.org/blosxom
5 # Calendar plugin Home/Docs/Licensing:
6 #   http://molelog.molehill.org/blox/Computers/Internet/Web/Blosxom/Macros/
7 # Modelled on Brad Choate's MT-Macros, but no code in common
8 package macros; # -*- perl -*-
9
10 # --- Configuration Variables ---
11 $macrodir = "$blosxom::plugin_state_dir/.macros"
12     unless defined $macrodir;
13
14 $use_caching = 1;
15 $debug_level = 1;
16 # -------------------------------------------------------------------
17 # types:
18 #   string                              implemented
19 #   pattern                             implemented
20 #   tag (string or pattern)             implemented
21 #   ctag (string or pattern)            implemented
22
23 # attributes:
24 #   name                                implemented, auto defaults
25 #   once                                implemented
26 #   recurse
27 #   no_html                             implemented, default
28 #                                       for string and pattern, inhtml => 1 to
29 #                                       reverse; can't reverse for tag
30 #   no_case
31 #   * defaults for attribs              implemented
32 #   * body                              implemented
33
34 # in replacement:
35 #   pattern: ${1}-${9} matched () text, $<1>-$<9> escaped matched text
36 #   tag: as in pattern + ${name} tag attribute, $<name> escaped attribute
37 #   ctag: as in tag + ${body}
38
39 use bytes;
40 use File::stat;
41 \f
42 # XXX cache macro definitions?
43 my @macros = ();
44 my $cache;
45 my $package    = "macros";
46 my $cachefile  = "$blosxom::plugin_state_dir/.$package.cache";
47 my $save_cache = 0;
48 \f
49 sub debug {
50     my ($level, @msg) = @_;
51
52     if ($debug_level >= $level) {
53         print STDERR "$package debug $level: @msg\n";
54     }
55     1;
56 }
57
58 sub url_escape {
59     local ($_) = @_;
60
61     s/([^a-zA-Z0-9])/sprintf("%%%02x",ord($1))/eg;
62     s/%20/+/g;
63     return $_;
64 }
65 \f
66 sub define_macro {
67     my ($arg) = @_;
68     my $macro = {};
69
70     if ($arg->{type} eq "string") {
71         $macro->{type}     = 'pattern';
72         $macro->{pattern}  = qr{\Q$arg->{string}\E};
73         $macro->{body}     = $arg->{body};
74         $macro->{inhtml}   = $arg->{inhtml} if $arg->{inhtml};
75         $macro->{once}     = $arg->{once}   if $arg->{once};
76         $macro->{name}     = $arg->{name} || "string_$arg->{string}";
77     } elsif ($arg->{type} eq "pattern") {
78         $macro->{type}     = 'pattern';
79         $macro->{pattern}  = qr{$arg->{pattern}};
80         $macro->{body}     = $arg->{body};
81         $macro->{inhtml}   = $arg->{inhtml} if $arg->{inhtml};
82         $macro->{once}     = $arg->{once}   if $arg->{once};
83         $macro->{name}     = $arg->{name} || "pattern_$arg->{pattern}";
84     } elsif ($arg->{type} eq "tag") {
85         $macro->{type}      = 'tag';
86         $macro->{container} = 0;
87         $macro->{pattern}   = qr{$arg->{name}};
88         $macro->{body}      = $arg->{body};
89         $macro->{defaults}  = {%{$arg->{defaults}}};
90         $macro->{once}      = $arg->{once}   if $arg->{once};
91         $macro->{name}      = "tag_$arg->{name}";
92     } elsif ($arg->{type} eq "ctag") {
93         $macro->{type}      = 'tag';
94         $macro->{container} = 1;
95         $macro->{pattern}   = qr{$arg->{name}};
96         $macro->{body}      = $arg->{body};
97         $macro->{defaults}  = {%{$arg->{defaults}}};
98         $macro->{once}      = $arg->{once}   if $arg->{once};
99         $macro->{name}      = "tag_$arg->{name}";
100     }
101
102     push @macros, $macro;
103     return 1;
104 }
105
106 sub replace_pattern {
107     my ($macro, $ctx) = @_;
108
109     my $replacement = $macro->{body};
110     $replacement =~ s{
111         (?: \$ { ([\w]+) } |
112             \$ < ([\w]+) > |
113            (\$    [\w:]+) 
114          )
115         }{defined($1) ? $ctx->{$1} : 
116               defined($2) ? url_escape($ctx->{$2}) : 
117               eval "$3||''"}xge;
118
119     return $replacement;
120 }
121
122 sub apply_pattern_macro {
123     my ($state, $macro, $text) = @_;
124     $text =~ 
125         s{($macro->{pattern})}
126         {$macro->{once} && $state->{used}{$macro->{name}} ? $1 :
127          (++$state->{used}{$macro->{name}}
128           and replace_pattern($macro, {
129               1 => $2, 2 => $3, 3 => $4,
130               4 => $5, 5 => $6, 6 => $7,
131               7 => $8, 8 => $7
132               }))
133           }egms;
134     return $text;
135 }
136
137 sub apply_tag_macro {
138     my ($state, $macro, $entity, $attributes, $body) = @_;
139     my $ctx;
140
141     $ctx->{body} = $body;
142     $entity =~ $macro->{pattern};
143     @{$ctx}{qw/1 2 3 4 5 6 7 8 9/} = ($1, $2, $3, $4, $5, $6, $7, $8, $9);
144     while ($attributes =~ m{ (\w+)         # $1 = tag
145                              =
146                              (?:
147                               " ([^\"]+) " # $2 = quoted value
148                               |
149                                 ([^\s]+)   # $3 = unquoted value
150                              )
151                          }gx) {
152         $ctx->{$1} = ($+);
153     }
154     foreach (keys %{$macro->{defaults}}) {
155         next if defined($ctx->{$_});
156         if ($macro->{defaults}{$_} =~ m:\$(\w+):) {
157             $ctx->{$_} = $ctx->{$1};
158         } else {
159             $ctx->{$_} = $macro->{defaults}{$_};
160         }
161     }
162
163     my $text = $macro->{body};
164     $text =~ s{
165                (?: \$ { ([\w]+) } |
166                    \$ < ([\w]+) > |
167                   (\$    [\w:]+) 
168                 )
169                }{defined($1) ? $ctx->{$1} : 
170                      defined($2) ? url_escape($ctx->{$2}) : 
171                      eval "$3||''"}xge;
172     return $text;
173 }
174
175 sub apply_macro {
176     my ($state, $macro, $text) = @_;
177
178     if ($macro->{type} eq 'pattern') {
179         if ($macro->{inhtml}) {
180             $text = apply_pattern_macro($state, $macro, $text);
181         } else {
182             my @tokens = split /(<[^>]+>)/, $text;
183             $text = '';
184             foreach (@tokens) {
185                 if (!m/^</) {
186                     $_ = apply_pattern_macro($state, $macro, $_);
187                 }
188                 $text .= $_;
189             }
190         }
191     } elsif ($macro->{type} eq 'tag') {
192         my @tokens = split /(<[^>]+>)/, $text;
193         $text = '';
194         while (defined($_ = shift @tokens)) {
195             if (!($macro->{once} && $state->{used}{$macro->{name}})
196                 && (m/<($macro->{pattern})([\s>].*)/)) {
197                 my $tag = $_;
198                 my $entity = $1;
199                 my $attributes = $+;
200                 chop $attributes;
201                 if ($macro->{container}) {
202                     my $body;
203                     while (defined($_ = shift @tokens)) {
204                         last if (m:</$entity\s*>:);
205                         $body .= $_;
206                     }
207                     $_ = apply_tag_macro($state, $macro, $entity, $attributes, $body);
208                 } else {
209                     $_ = apply_tag_macro($state, $macro, $entity, $attributes);
210                 }
211                 $state->{used}{$macro->{name}}++;
212             }
213             $text .= $_;
214         }
215     } else {
216         debug(0, "ACK: unknown macro type $macro->{type}");
217     }
218     return $text;
219 }
220
221 sub apply_macros {
222     my ($state, $text) = @_;
223
224     foreach my $macro (@macros) {
225         $text = apply_macro($state, $macro, $text);
226     }
227     return $text;
228 }
229 \f
230 # caching support
231
232 sub prime_cache {
233     my ($macrokey) = @_;
234     return if (!$use_caching);
235     eval "require Storable";
236     if ($@) {
237         debug(1, "cache disabled, Storable not available");
238         $use_caching = 0;
239         return 0;
240     }
241     if (!Storable->can('lock_retrieve')) {
242         debug(1, "cache disabled, Storable::lock_retrieve not available");
243         $use_caching = 0;
244         return 0;
245     }
246     $cache = (-r $cachefile ? Storable::lock_retrieve($cachefile) : {});
247     if (defined $cache->{macrokey}) {
248         if ($cache->{macrokey} eq $macrokey) {
249             debug(1, "Using restored cache");
250             return 1;
251         }
252         $cache = {};
253         debug(1, "Macros changed, flushing cache");
254     } else {
255         debug(1, "Cache empty, creating");
256     }
257     $cache->{macrokey} = $macrokey;
258     return 0;
259 }
260 sub save_cache {
261     return if (!$use_caching || !$save_cache);
262     debug(1, "Saving cache");
263     Storable::lock_store($cache, $cachefile);
264 }
265 \f
266 sub story {
267     my($pkg, $path, $filename, $story_ref, $title_ref, $body_ref) = @_;
268     my $state = {};
269     use bytes;
270
271     my $r = $cache->{story}{"$path/$filename"};
272     if ($r && $r->{orig} eq $$body_ref) {
273         $$body_ref = $r->{expanded};
274         return 1;
275     }
276     debug(1, "Cache miss due to story change: $path/$filename") if $r;
277     $cache->{story}{"$path/$filename"}{orig} = $$body_ref;
278     $$body_ref = apply_macros($state, $$body_ref);
279     $cache->{story}{"$path/$filename"}{expanded} = $$body_ref;
280     $save_cache = 1;
281     return 1;
282 }
283
284 sub start {
285     my $macrokey = '';
286     if (opendir MACROS, $macrodir) {
287         foreach my $macrofile (grep { /^\d*\w+$/ && -f "$macrodir/$_" } 
288                                sort readdir MACROS) {
289             my $mtime = stat("$macrodir/$macrofile")->mtime;
290             $macrokey .= "$macrofile:$mtime|";
291             require "$macrodir/$macrofile";
292         }
293     }
294     prime_cache($macrokey);
295     return 1;
296 }
297
298 sub end {
299     save_cache();
300     1;
301 }
302 1;
303
304 =head1 NAME
305
306 Blosxom Plug-in: macros
307
308 =head1 SYNOPSIS
309
310 Purpose: Generalized macro system modelled on MT-Macros
311
312    * String macros: replace a string with another string
313
314    * Pattern macros: replace a regular-expression pattern with a
315      string optionally based on the replaced text
316
317    * Tag macros: replace html-style content-less tags (like img)
318      (specified with either a string or a pattern) with a string,
319      optionally based on the replaced entity and attributes, with
320      default attributes available
321
322    * Content Tag macros: relace html-style content tags (like a)
323      (specified with either a string or a pattern) with a string,
324      optionally based on the replaced entity, attributes, and
325      contents, with default attributes available
326
327 =head1 VERSION
328
329 0+1i
330
331 1st wide-spread test release
332
333 =head1 AUTHOR
334
335 Todd Larason  <jtl@molehill.org>, http://molelog.molehill.org/
336
337 =head1 BUGS
338
339 None known; address bug reports and comments to me or to the Blosxom
340 mailing list [http://www.yahoogroups.com/groups.blosxom].
341
342 =head1 Customization
343
344 =head2 Configuration variables
345
346 C<$macrodir> is the name of the directory to look for macro definition files
347 in; defaults to $plugin_state_dir/.macros.  Each file in this directory
348 whose name matches /^\d*\w+$/ (that is, optional digits at the beginning,
349 followed by letters, numbers and underscores) is read, in order sorted by
350 filename.  See "Macro Definition" section for details on file contents.
351
352 C<$use_caching> controls whether or not to try to cache formatted results;
353 caching requires Storable, but the plugin will work just fine (although
354 possibly slowly, with lots of macros installed) without it.
355
356 C<$debug_level> can be set to a value between 0 and 5; 0 will output
357 no debug information, while 5 will be very verbose.  The default is 1,
358 and should be changed after you've verified the plugin is working
359 correctly.
360
361 =head2 Macro Definitions
362
363 The macro files are simply perl scripts that are read and executed.  
364 Normally, they consist simply of literal calls to define_macro(), but
365 any other perl content is allowed.
366
367 As with all perl scripts, loading this script needs to return a true value.
368 define_macro() returns 1, so in most cases this will be taken care of
369 automatically, but if you're doing something fancy you need to be aware of
370 this.
371
372 define_macro() takes a single argument, a reference to a hash.  The hash 
373 must contain a 'type' element, which must be one of "string", "pattern",
374 "tag" and "ctag".  The other elements depend on the type.
375
376 =head3 String Macros
377
378 To define a string macro, pass define_macros() a hash containing:
379
380    * type => "string", required
381    * string => string, required; the string to be replaced
382    * body => string, required; the string to replace with; no variables are
383      useful, but the same replacement method is used as others, so $ is magic.
384    * inhtml => boolean, optional; if 1, then the string will be replaced even 
385      if it appears in the HTML markup; of 0, the string will only be replaced
386      in content.  The default is 0 (this is reverse MT-Macros' option, and
387      apparently reverse MT-Macros' default)
388    * once => boolean, optional; if 1, then the string will only be replaced
389      the first time it's seen in a given piece of text (that is, story body).
390      The default is 0.
391    * name => string, optional; currently names aren't used for anything, but
392      they may be in the future.
393
394 =head3 Pattern Macros
395
396 To define a pattern macro, pass define_macros() a hash containing:
397
398    * type => "pattern", required
399    * pattern => pattern, required; the regular expression to be replaced
400    * body => string, required; the string to replace with; ${1} through ${9} 
401      are replaced with the RE match variables $1 through $9; $<1> through $<9>
402      are the same thing, URL encoded.
403    * inhtml => boolean, optional; if 1, then the string will be replaced even 
404      if it appears in the HTML markup; of 0, the string will only be replaced
405      in content.  The default is 0 (this is reverse MT-Macros' option, and
406      apparently reverse MT-Macros' default).  Note that if inhtml is 0, then
407      the pattern is matched against each chunk of content separately, and thus
408      the full pattern must be included in a single markup-less chunk to be 
409      seen.
410    * once => boolean, optional; if 1, then the pattern will only be replaced
411      the first time it's seen in a given piece of text (that is, story body).
412      The default is 0.
413    * name => string, optional; currently names aren't used for anything, but
414      they may be in the future.
415
416 =head3 Tag Macros
417
418 To define a tag macro, pass define_macros() a hash containing:
419
420    * type => "tag", required
421    * pattern => pattern, required; a regular expression matching the entity
422      tag to be replaced; in normal cases this will just be a string, but
423      something like pattern => 'smily(\d+)' could be used to define a whole
424      set of tags like <smily47> at once.
425    * defaults => hashref, optional; a hash reference mapping attribute names
426      to default values.  "$\w+" patterns in the default values are replaced
427      the same way "${\w}" patterns in body strings are
428    * body => string, required; the string to replace with; ${1} through ${9} 
429      are replaced with the RE match variables $1 through $9; $<1> through $<9>
430      are the same thing, URL encoded. ${attrib} and $<attrib> are replaced with
431      the values of the specified attributes, or with the default for that
432      attribute if the attribute wasn't specified.
433    * once => boolean, optional; if 1, then the tag will only be replaced
434      the first time it's seen in a given piece of text (that is, story body).
435      The default is 0.
436
437 =head3 Content Tag Macros
438
439 To define a content tag macro, pass define_macros() a hash containing:
440
441    * type => "ctag", required
442    * pattern => pattern, required; a regular expression matching the entity
443      tag to be replaced; in normal cases this will just be a string.  The
444      closing tag must exactly match the opening tag, not just match the
445      pattern.
446    * defaults => hashref, optional; a hash reference mapping attribute names
447      to default values.  "$\w+" patterns in the default values are replaced
448      the same way "${\w}" patterns in body strings are; in particular, $body
449      can be useful
450    * body => string, required; the string to replace with; ${1} through ${9} 
451      are replaced with the RE match variables $1 through $9; $<1> through $<9>
452      are the same thing, URL encoded. ${attrib} and $<attrib> are replaced with
453      the values of the specified attributes, or with the default for that
454      attribute if the attribute wasn't specified.  ${body} and $<body> are
455      replaced with the content of the tag.
456    * once => boolean, optional; if 1, then the tag will only be replaced
457      the first time it's seen in a given piece of text (that is, story body).
458      The default is 0.
459
460 =head3 examples
461
462 =head4 Tatu
463
464 This defines a macro that replaces the word "Tatu" with its proper (Cyrllic) 
465 spelling the first time it's seen in a story; it won't much with markup, so
466 URLs containting "Tatu" are safe.
467
468 define_macro {
469     type   => 'string',
470     string => "Tatu",
471     body   => qq!<acronym title=\"Tatu\">&#x0422;&#x0430;&#x0442;&#x0443;</acronym>!,
472     once   => 1
473 };
474
475 This is just like above, but is safer -- it won't match the "Tatu" in 
476 "Tatuuie".
477
478 define_macro {
479     type    => 'pattern',
480     pattern => qr/\bTatu\b/,
481     body    => qq!<acronym title=\"Tatu\">&#x0422;&#x0430;&#x0442;&#x0443;</acronym>!,
482     once    => 1
483 };
484
485 =head4 Line
486
487 This defines a <line> tag with an optional width= attribute
488
489 define_macro {
490     type => 'tag',
491     name => 'line',
492     defaults => {width => "100%"},
493     body => '<hr noshade="noshade" width="${width}">'
494 };
495
496 This can be used either as just <line> or as <line width="50%">.
497
498 =head4 Amazon
499
500 this defines a fairly fancy <amazon tag
501
502 define_macro {
503     type => 'ctag',
504     name => 'amazon',
505     defaults => {domain => 'com', assoc => 'mtmolel-20'},
506     body => '<a href="http://www.amazon.${domain}/exec/obidos/ASIN/${asin}/ref=nosim/${assoc}">${body}</a>'
507 };
508
509 In normal use, it's something like
510 <amazon asin=B00008OE6I>Canon Powershot S400</amazon>
511 but it can also be used to refer to something on one of the international 
512 Amazon sites, like
513 on asin=B000089AS9 domain=co.uk>Angel Season 3 DVDs</amazon>
514
515 If you wanted to give referral credit to someone else, you could with:
516 <amazon asin=B00008OE6I assoc=rael-20>Canon Powershot S400</amazon>
517  
518 =head4 Google
519
520 This defines a <google> tag with a completely optional query attribute; if
521 it's not given, then the phrase enclosed by the tag is what's searched for.
522
523 define_macro {
524     type => 'ctag',
525     name => 'google',
526     defaults => {query => "\$body"},
527     body => '<a href="http://www.google.com/search?q=$<query>">${body}</a>'
528 };
529
530 =head4 Programmatic Definitions
531
532 There's no reason the macro files need to be literal calls to define_macro.
533
534 This example defines its own simplified syntax for defining a set of similar 
535 macros, reads the definitions, and makmes the appropriate define_macro() 
536 calls.  It's directly translated from a similar MT-Macros definition file,
537 (with more macros defined) found at http://diveintomark.org/inc/macros2
538
539 while (<DATA>) {
540     chomp;
541     my ($name, $tag, $attrlist) = m/"(.+?)"\s+(\w+)(.*)/;
542     next if !$name;
543     my $attrs = '';
544     my (@attrs) = $attrlist =~ m/\s+(\w+)\s+"(.*?)"/g;
545     for ($i = 0; $i < scalar(@attrs); $i += 2) {
546         my ($attr, $value) = ($attrs[$i], $attrs[$i+1]);
547         $value =~ s/"/&quot;/g; #";
548         $attrs .= qq{ $attr="$value"};
549     }
550     if ($tag =~ /acronym/) {
551         define_macro({
552                       name    => "abbr_$name",
553                       type    => pattern,
554                       pattern => qr/\b$name\b/,
555                       body    => "<$tag$attrs>$name</$tag>",
556                       once    => 1
557                      });
558     } elsif ($tag =~ /img/) {
559         define_macro({
560                       name    => "img_$name",
561                       type    => string,
562                       string  => $name,
563                       body    => "<$tag$attrs>"
564                      });
565     } else {
566         define_macro({
567                       name    => "abbr_$name",
568                       type    => pattern,
569                       pattern => qr/\b$name\b/,
570                       body    => "<$tag$attrs>$name</$tag>"
571                      });
572     }
573 }
574
575 1;
576 __DATA__
577 "AOL" acronym title "America Online"
578 "API" acronym title "Application Interface"
579 "CGI" acronym title "Common Gateway Interface"
580 "CMS" acronym title "Content Management System"
581 "CSS" acronym title "Cascading Style Sheets"
582 "DMV" acronym title "Department of Motor Vehicles"
583 ":)"     img alt "[smiley face]"  title "" src "/images/smilies/smile.gif"   width "20" height "20"
584 ":-)"    img alt "[smiley face]"  title "" src "/images/smilies/smile.gif"   width "20" height "20"
585 "=)"     img alt "[smiley face]"  title "" src "/images/smilies/smile.gif"   width "20" height "20"
586 "=-)"    img alt "[smiley face]"  title "" src "/images/smilies/smile.gif"   width "20" height "20"
587 __END__
588
589 =head1 Possible Deficiencies
590
591    * MT-Macros 'recursion' option isn't available.  If this is a real problem
592      for you, please let me know, preferably with a good example of what you
593      can't accomplish currently (remember, macros are invoked in the order
594      they're defined, which you can control with filename naming)
595    * tag and ctag macros can't be used in HTML markup.  This would be a big
596      problem for Movable Type, where parameter replacement is done with
597      psuedo-HTML, but doesn't seem to be a problem for Blosxom.  If it is
598      for you, please let me know, again along with an example.
599    * MT-Macros 'no_case' option isn't available.  This can be done by
600      including (?i) in your patterns or defining them with qr//i, instead.
601    * tag and ctag macros can't be explicitely named, because the 'name'
602      parameter is already being used.  Future versions may change tag
603      and ctag to use 'string' or 'pattern' for what 'name' is currently
604      used for, and use 'name' to define a macro.  That will only be done
605      if there's a good use for names, though.
606    * Once defined, macros are always active.  They can't be deactivated on a
607      per-story basis.  This might be handled with a meta- header at some 
608      point, if someone gives me a reasonable example for why they need it.
609    * There's no built-in data-based macro definition syntax.  It's not clear
610      to me that a literal define_macro() call is any more difficult than
611      MT-Macros' HTML-looking (but not HTML-acting) definition syntax, though,
612      and as shown above simpler syntaxes ban be custom-built as appropriate.
613      I'd be more than happy to include a simpler syntax, though, if someone
614      were to develop one that were obviously better than define_syntax().
615
616 =head1 Caching
617
618 If the Storable module is available and $use_caching is set, formatted
619 stories will be cached; the cache is globally keyed off the list of macro
620 files and their modification date, and per-story on the contents of the
621 story itself.  It should thus not ever be necessary to manually flush the
622 cache, but it's always safe to do so, by removing the 
623 $plugin_state_dir/.macros.cache file.
624
625 =head1 LICENSE
626
627 this Blosxom Plug-in
628 Copyright 2003, Todd Larason
629
630 (This license is the same as Blosxom's)
631
632 Permission is hereby granted, free of charge, to any person obtaining a
633 copy of this software and associated documentation files (the "Software"),
634 to deal in the Software without restriction, including without limitation
635 the rights to use, copy, modify, merge, publish, distribute, sublicense,
636 and/or sell copies of the Software, and to permit persons to whom the
637 Software is furnished to do so, subject to the following conditions:
638
639 The above copyright notice and this permission notice shall be included
640 in all copies or substantial portions of the Software.
641
642 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
643 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
644 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
645 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
646 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
647 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
648 OTHER DEALINGS IN THE SOFTWARE.
649
650