tagging: Allow using titles in for related stories.
[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://blosxom.sourceforge.net/
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 = 0;
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 This plugin is now maintained by the Blosxom Sourceforge Team,
338 <blosxom-devel@lists.sourceforge.net>.
339
340 =head1 BUGS
341
342 None known; please send bug reports and feedback to the Blosxom
343 development mailing list <blosxom-devel@lists.sourceforge.net>.
344
345 =head1 Customization
346
347 =head2 Configuration variables
348
349 C<$macrodir> is the name of the directory to look for macro definition files
350 in; defaults to $plugin_state_dir/.macros.  Each file in this directory
351 whose name matches /^\d*\w+$/ (that is, optional digits at the beginning,
352 followed by letters, numbers and underscores) is read, in order sorted by
353 filename.  See "Macro Definition" section for details on file contents.
354
355 C<$use_caching> controls whether or not to try to cache formatted results;
356 caching requires Storable, but the plugin will work just fine (although
357 possibly slowly, with lots of macros installed) without it.
358
359 C<$debug_level> can be set to a value between 0 and 5; 0 will output
360 no debug information, while 5 will be very verbose.  The default is 1,
361 and should be changed after you've verified the plugin is working
362 correctly.
363
364 =head2 Macro Definitions
365
366 The macro files are simply perl scripts that are read and executed.  
367 Normally, they consist simply of literal calls to define_macro(), but
368 any other perl content is allowed.
369
370 As with all perl scripts, loading this script needs to return a true value.
371 define_macro() returns 1, so in most cases this will be taken care of
372 automatically, but if you're doing something fancy you need to be aware of
373 this.
374
375 define_macro() takes a single argument, a reference to a hash.  The hash 
376 must contain a 'type' element, which must be one of "string", "pattern",
377 "tag" and "ctag".  The other elements depend on the type.
378
379 =head3 String Macros
380
381 To define a string macro, pass define_macros() a hash containing:
382
383    * type => "string", required
384    * string => string, required; the string to be replaced
385    * body => string, required; the string to replace with; no variables are
386      useful, but the same replacement method is used as others, so $ is magic.
387    * inhtml => boolean, optional; if 1, then the string will be replaced even 
388      if it appears in the HTML markup; of 0, the string will only be replaced
389      in content.  The default is 0 (this is reverse MT-Macros' option, and
390      apparently reverse MT-Macros' default)
391    * once => boolean, optional; if 1, then the string will only be replaced
392      the first time it's seen in a given piece of text (that is, story body).
393      The default is 0.
394    * name => string, optional; currently names aren't used for anything, but
395      they may be in the future.
396
397 =head3 Pattern Macros
398
399 To define a pattern macro, pass define_macros() a hash containing:
400
401    * type => "pattern", required
402    * pattern => pattern, required; the regular expression to be replaced
403    * body => string, required; the string to replace with; ${1} through ${9} 
404      are replaced with the RE match variables $1 through $9; $<1> through $<9>
405      are the same thing, URL encoded.
406    * inhtml => boolean, optional; if 1, then the string will be replaced even 
407      if it appears in the HTML markup; of 0, the string will only be replaced
408      in content.  The default is 0 (this is reverse MT-Macros' option, and
409      apparently reverse MT-Macros' default).  Note that if inhtml is 0, then
410      the pattern is matched against each chunk of content separately, and thus
411      the full pattern must be included in a single markup-less chunk to be 
412      seen.
413    * once => boolean, optional; if 1, then the pattern will only be replaced
414      the first time it's seen in a given piece of text (that is, story body).
415      The default is 0.
416    * name => string, optional; currently names aren't used for anything, but
417      they may be in the future.
418
419 =head3 Tag Macros
420
421 To define a tag macro, pass define_macros() a hash containing:
422
423    * type => "tag", required
424    * pattern => pattern, required; a regular expression matching the entity
425      tag to be replaced; in normal cases this will just be a string, but
426      something like pattern => 'smily(\d+)' could be used to define a whole
427      set of tags like <smily47> at once.
428    * defaults => hashref, optional; a hash reference mapping attribute names
429      to default values.  "$\w+" patterns in the default values are replaced
430      the same way "${\w}" patterns in body strings are
431    * body => string, required; the string to replace with; ${1} through ${9} 
432      are replaced with the RE match variables $1 through $9; $<1> through $<9>
433      are the same thing, URL encoded. ${attrib} and $<attrib> are replaced with
434      the values of the specified attributes, or with the default for that
435      attribute if the attribute wasn't specified.
436    * once => boolean, optional; if 1, then the tag will only be replaced
437      the first time it's seen in a given piece of text (that is, story body).
438      The default is 0.
439
440 =head3 Content Tag Macros
441
442 To define a content tag macro, pass define_macros() a hash containing:
443
444    * type => "ctag", required
445    * pattern => pattern, required; a regular expression matching the entity
446      tag to be replaced; in normal cases this will just be a string.  The
447      closing tag must exactly match the opening tag, not just match the
448      pattern.
449    * defaults => hashref, optional; a hash reference mapping attribute names
450      to default values.  "$\w+" patterns in the default values are replaced
451      the same way "${\w}" patterns in body strings are; in particular, $body
452      can be useful
453    * body => string, required; the string to replace with; ${1} through ${9} 
454      are replaced with the RE match variables $1 through $9; $<1> through $<9>
455      are the same thing, URL encoded. ${attrib} and $<attrib> are replaced with
456      the values of the specified attributes, or with the default for that
457      attribute if the attribute wasn't specified.  ${body} and $<body> are
458      replaced with the content of the tag.
459    * once => boolean, optional; if 1, then the tag will only be replaced
460      the first time it's seen in a given piece of text (that is, story body).
461      The default is 0.
462
463 =head3 examples
464
465 =head4 Tatu
466
467 This defines a macro that replaces the word "Tatu" with its proper (Cyrllic) 
468 spelling the first time it's seen in a story; it won't much with markup, so
469 URLs containting "Tatu" are safe.
470
471 define_macro {
472     type   => 'string',
473     string => "Tatu",
474     body   => qq!<acronym title=\"Tatu\">&#x0422;&#x0430;&#x0442;&#x0443;</acronym>!,
475     once   => 1
476 };
477
478 This is just like above, but is safer -- it won't match the "Tatu" in 
479 "Tatuuie".
480
481 define_macro {
482     type    => 'pattern',
483     pattern => qr/\bTatu\b/,
484     body    => qq!<acronym title=\"Tatu\">&#x0422;&#x0430;&#x0442;&#x0443;</acronym>!,
485     once    => 1
486 };
487
488 =head4 Line
489
490 This defines a <line> tag with an optional width= attribute
491
492 define_macro {
493     type => 'tag',
494     name => 'line',
495     defaults => {width => "100%"},
496     body => '<hr noshade="noshade" width="${width}">'
497 };
498
499 This can be used either as just <line> or as <line width="50%">.
500
501 =head4 Amazon
502
503 this defines a fairly fancy <amazon tag
504
505 define_macro {
506     type => 'ctag',
507     name => 'amazon',
508     defaults => {domain => 'com', assoc => 'mtmolel-20'},
509     body => '<a href="http://www.amazon.${domain}/exec/obidos/ASIN/${asin}/ref=nosim/${assoc}">${body}</a>'
510 };
511
512 In normal use, it's something like
513 <amazon asin=B00008OE6I>Canon Powershot S400</amazon>
514 but it can also be used to refer to something on one of the international 
515 Amazon sites, like
516 on asin=B000089AS9 domain=co.uk>Angel Season 3 DVDs</amazon>
517
518 If you wanted to give referral credit to someone else, you could with:
519 <amazon asin=B00008OE6I assoc=rael-20>Canon Powershot S400</amazon>
520  
521 =head4 Google
522
523 This defines a <google> tag with a completely optional query attribute; if
524 it's not given, then the phrase enclosed by the tag is what's searched for.
525
526 define_macro {
527     type => 'ctag',
528     name => 'google',
529     defaults => {query => "\$body"},
530     body => '<a href="http://www.google.com/search?q=$<query>">${body}</a>'
531 };
532
533 =head4 Programmatic Definitions
534
535 There's no reason the macro files need to be literal calls to define_macro.
536
537 This example defines its own simplified syntax for defining a set of similar 
538 macros, reads the definitions, and makmes the appropriate define_macro() 
539 calls.  It's directly translated from a similar MT-Macros definition file,
540 (with more macros defined) found at http://diveintomark.org/inc/macros2
541
542 while (<DATA>) {
543     chomp;
544     my ($name, $tag, $attrlist) = m/"(.+?)"\s+(\w+)(.*)/;
545     next if !$name;
546     my $attrs = '';
547     my (@attrs) = $attrlist =~ m/\s+(\w+)\s+"(.*?)"/g;
548     for ($i = 0; $i < scalar(@attrs); $i += 2) {
549         my ($attr, $value) = ($attrs[$i], $attrs[$i+1]);
550         $value =~ s/"/&quot;/g; #";
551         $attrs .= qq{ $attr="$value"};
552     }
553     if ($tag =~ /acronym/) {
554         define_macro({
555                       name    => "abbr_$name",
556                       type    => pattern,
557                       pattern => qr/\b$name\b/,
558                       body    => "<$tag$attrs>$name</$tag>",
559                       once    => 1
560                      });
561     } elsif ($tag =~ /img/) {
562         define_macro({
563                       name    => "img_$name",
564                       type    => string,
565                       string  => $name,
566                       body    => "<$tag$attrs>"
567                      });
568     } else {
569         define_macro({
570                       name    => "abbr_$name",
571                       type    => pattern,
572                       pattern => qr/\b$name\b/,
573                       body    => "<$tag$attrs>$name</$tag>"
574                      });
575     }
576 }
577
578 1;
579 __DATA__
580 "AOL" acronym title "America Online"
581 "API" acronym title "Application Interface"
582 "CGI" acronym title "Common Gateway Interface"
583 "CMS" acronym title "Content Management System"
584 "CSS" acronym title "Cascading Style Sheets"
585 "DMV" acronym title "Department of Motor Vehicles"
586 ":)"     img alt "[smiley face]"  title "" src "/images/smilies/smile.gif"   width "20" height "20"
587 ":-)"    img alt "[smiley face]"  title "" src "/images/smilies/smile.gif"   width "20" height "20"
588 "=)"     img alt "[smiley face]"  title "" src "/images/smilies/smile.gif"   width "20" height "20"
589 "=-)"    img alt "[smiley face]"  title "" src "/images/smilies/smile.gif"   width "20" height "20"
590 __END__
591
592 =head1 Possible Deficiencies
593
594    * MT-Macros 'recursion' option isn't available.  If this is a real problem
595      for you, please let me know, preferably with a good example of what you
596      can't accomplish currently (remember, macros are invoked in the order
597      they're defined, which you can control with filename naming)
598    * tag and ctag macros can't be used in HTML markup.  This would be a big
599      problem for Movable Type, where parameter replacement is done with
600      psuedo-HTML, but doesn't seem to be a problem for Blosxom.  If it is
601      for you, please let me know, again along with an example.
602    * MT-Macros 'no_case' option isn't available.  This can be done by
603      including (?i) in your patterns or defining them with qr//i, instead.
604    * tag and ctag macros can't be explicitely named, because the 'name'
605      parameter is already being used.  Future versions may change tag
606      and ctag to use 'string' or 'pattern' for what 'name' is currently
607      used for, and use 'name' to define a macro.  That will only be done
608      if there's a good use for names, though.
609    * Once defined, macros are always active.  They can't be deactivated on a
610      per-story basis.  This might be handled with a meta- header at some 
611      point, if someone gives me a reasonable example for why they need it.
612    * There's no built-in data-based macro definition syntax.  It's not clear
613      to me that a literal define_macro() call is any more difficult than
614      MT-Macros' HTML-looking (but not HTML-acting) definition syntax, though,
615      and as shown above simpler syntaxes ban be custom-built as appropriate.
616      I'd be more than happy to include a simpler syntax, though, if someone
617      were to develop one that were obviously better than define_syntax().
618
619 =head1 Caching
620
621 If the Storable module is available and $use_caching is set, formatted
622 stories will be cached; the cache is globally keyed off the list of macro
623 files and their modification date, and per-story on the contents of the
624 story itself.  It should thus not ever be necessary to manually flush the
625 cache, but it's always safe to do so, by removing the 
626 $plugin_state_dir/.macros.cache file.
627
628 =head1 LICENSE
629
630 this Blosxom Plug-in
631 Copyright 2003, Todd Larason
632
633 (This license is the same as Blosxom's)
634
635 Permission is hereby granted, free of charge, to any person obtaining a
636 copy of this software and associated documentation files (the "Software"),
637 to deal in the Software without restriction, including without limitation
638 the rights to use, copy, modify, merge, publish, distribute, sublicense,
639 and/or sell copies of the Software, and to permit persons to whom the
640 Software is furnished to do so, subject to the following conditions:
641
642 The above copyright notice and this permission notice shall be included
643 in all copies or substantial portions of the Software.
644
645 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
646 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
647 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
648 THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
649 OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
650 ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
651 OTHER DEALINGS IN THE SOFTWARE.
652
653