| 1 | # --------------------------------------------------------------------------- |
|---|
| 2 | # MT-Textile Text Formatter |
|---|
| 3 | # A plugin for Movable Type |
|---|
| 4 | # |
|---|
| 5 | # Release 2.05 |
|---|
| 6 | # |
|---|
| 7 | # Brad Choate |
|---|
| 8 | # http://www.bradchoate.com/ |
|---|
| 9 | # --------------------------------------------------------------------------- |
|---|
| 10 | # This software is provided as-is. |
|---|
| 11 | # You may use it for commercial or personal use. |
|---|
| 12 | # If you distribute it, please keep this notice intact. |
|---|
| 13 | # |
|---|
| 14 | # Copyright (c) 2003-2008 Brad Choate |
|---|
| 15 | # --------------------------------------------------------------------------- |
|---|
| 16 | # $Id$ |
|---|
| 17 | # --------------------------------------------------------------------------- |
|---|
| 18 | |
|---|
| 19 | package MT::Plugin::Textile; |
|---|
| 20 | |
|---|
| 21 | use strict; |
|---|
| 22 | use MT; |
|---|
| 23 | use base qw( MT::Plugin ); |
|---|
| 24 | |
|---|
| 25 | our $VERSION = 2.05; |
|---|
| 26 | our ($_initialized, $Have_SmartyPants); |
|---|
| 27 | |
|---|
| 28 | MT->add_plugin(__PACKAGE__->new({ |
|---|
| 29 | name => "Textile", |
|---|
| 30 | description => '<MT_TRANS phrase="A humane web text generator.">', |
|---|
| 31 | author_name => "Brad Choate", |
|---|
| 32 | author_link => "http://bradchoate.com/", |
|---|
| 33 | version => $VERSION, |
|---|
| 34 | registry => { |
|---|
| 35 | text_filters => { |
|---|
| 36 | textile_2 => { |
|---|
| 37 | label => "Textile 2", |
|---|
| 38 | code => \&textile_2, |
|---|
| 39 | docs => "http://www.movabletype.org/documentation/author/textile-2-syntax.html", |
|---|
| 40 | }, |
|---|
| 41 | }, |
|---|
| 42 | tags => { |
|---|
| 43 | help_url => sub { MT->translate('http://www.movabletype.org/documentation/appendices/tags/%t.html') }, |
|---|
| 44 | block => { |
|---|
| 45 | Textile => \&Textile, |
|---|
| 46 | }, |
|---|
| 47 | function => { |
|---|
| 48 | TextileOptions => \&TextileOptions, |
|---|
| 49 | TextileHeadOffset => \&TextileHeadOffset, |
|---|
| 50 | }, |
|---|
| 51 | }, |
|---|
| 52 | }, |
|---|
| 53 | })); |
|---|
| 54 | |
|---|
| 55 | sub _init { |
|---|
| 56 | require Text::Textile; |
|---|
| 57 | @MT::Textile::ISA = qw(Text::Textile); |
|---|
| 58 | $Have_SmartyPants = defined &SmartyPants::SmartyPants ? 1 : 0; |
|---|
| 59 | $_initialized = 1; |
|---|
| 60 | } |
|---|
| 61 | |
|---|
| 62 | sub textile_2 { |
|---|
| 63 | my ($str, $ctx) = @_; |
|---|
| 64 | |
|---|
| 65 | _init() unless $_initialized; |
|---|
| 66 | |
|---|
| 67 | my $textile; |
|---|
| 68 | if ((defined $ctx) && (ref($ctx) eq 'MT::Template::Context')) { |
|---|
| 69 | $textile = $ctx->stash('TextileObj'); |
|---|
| 70 | unless ($textile) { |
|---|
| 71 | $textile = _new_textile($ctx); |
|---|
| 72 | $ctx->stash('TextileObj', $textile); |
|---|
| 73 | } |
|---|
| 74 | $textile->head_offset($ctx->stash('TextileHeadOffsetStart') || 0); |
|---|
| 75 | if (my $opts = $ctx->stash('TextileOptions')) { |
|---|
| 76 | $textile->set($_, $opts->{$_}) foreach keys %$opts; |
|---|
| 77 | # now clear the options from the stash so we don't |
|---|
| 78 | # repeat this for each invocation of textile... |
|---|
| 79 | $ctx->stash('TextileOptions', undef); |
|---|
| 80 | } |
|---|
| 81 | |
|---|
| 82 | # reduces circular references |
|---|
| 83 | # $textile->filter_param($ctx); |
|---|
| 84 | require MT::Util; |
|---|
| 85 | MT::Util::weaken( $textile->{filter_param} = $ctx ); |
|---|
| 86 | } else { |
|---|
| 87 | # no Context object... |
|---|
| 88 | $textile = _new_textile(); |
|---|
| 89 | } |
|---|
| 90 | |
|---|
| 91 | require MT::I18N; |
|---|
| 92 | $str = MT::I18N::encode_text( $str, MT->instance->config->PublishCharset, 'utf-8' ); |
|---|
| 93 | |
|---|
| 94 | $str = $textile->process($str); |
|---|
| 95 | |
|---|
| 96 | if ((defined $ctx) && (ref($ctx) eq 'MT::Template::Context')) { |
|---|
| 97 | my $entry = $ctx->stash('entry'); |
|---|
| 98 | if ($entry && $entry->id) { |
|---|
| 99 | my $link = $entry->permalink; |
|---|
| 100 | $link =~ s/#.+$//; |
|---|
| 101 | $str =~ s/(<a .*?(?<=[ ])href=")(#fn(?:\d)+".*?>)/$1$link$2/g; |
|---|
| 102 | } |
|---|
| 103 | } |
|---|
| 104 | |
|---|
| 105 | # invoke MT-CodeBeautifier for any <code> or <blockcode> tags that |
|---|
| 106 | # specify a 'language' attribute: |
|---|
| 107 | my $beautifier = defined &Voisen::CodeBeautifier::beautifier; |
|---|
| 108 | if ($beautifier) { |
|---|
| 109 | $str =~ s|<((block)?code)([^>]*?) language="([^"]+?)"([^>]*?)>(.+?)</\1>|_highlight($1, $3, $5, $4, $textile->decode_html($6))|ges; # " |
|---|
| 110 | } |
|---|
| 111 | |
|---|
| 112 | $str = MT::I18N::encode_text( $str, 'utf-8', MT->instance->config->PublishCharset ); |
|---|
| 113 | |
|---|
| 114 | $str; |
|---|
| 115 | } |
|---|
| 116 | |
|---|
| 117 | sub _new_textile { |
|---|
| 118 | my ($ctx) = @_; |
|---|
| 119 | |
|---|
| 120 | my $textile = new MT::Textile; |
|---|
| 121 | |
|---|
| 122 | # this copies the named filters from MT to TextileFormat |
|---|
| 123 | my %list; |
|---|
| 124 | my $filters = MT::all_text_filters(); |
|---|
| 125 | foreach my $name (keys %$filters) { |
|---|
| 126 | $list{$name} = $filters->{$name}{on_format}; |
|---|
| 127 | } |
|---|
| 128 | $textile->filters(\%list); |
|---|
| 129 | |
|---|
| 130 | my $cfg = MT::ConfigMgr->instance; |
|---|
| 131 | |
|---|
| 132 | if ($cfg->NoHTMLEntities) { |
|---|
| 133 | $textile->char_encoding(0); |
|---|
| 134 | } |
|---|
| 135 | |
|---|
| 136 | $textile->charset('utf-8'); |
|---|
| 137 | |
|---|
| 138 | $textile; |
|---|
| 139 | } |
|---|
| 140 | |
|---|
| 141 | sub Textile { |
|---|
| 142 | my ($ctx, $args, $cond) = @_; |
|---|
| 143 | _init() unless $_initialized; |
|---|
| 144 | local $ctx->{__stash}{TextileObj} = _new_textile($ctx); |
|---|
| 145 | local $ctx->{__stash}{TextileOptions} = $args if keys %$args; |
|---|
| 146 | my $str = $ctx->slurp; |
|---|
| 147 | textile_2($str, $ctx); |
|---|
| 148 | } |
|---|
| 149 | |
|---|
| 150 | sub TextileHeadOffset { |
|---|
| 151 | my ($ctx, $args, $cond) = @_; |
|---|
| 152 | my $start = $args->{start}; |
|---|
| 153 | if ($start && $start =~ m/^\d+$/ && $start >= 1 && $start <= 6) { |
|---|
| 154 | $ctx->stash('TextileHeadOffsetStart', $start); |
|---|
| 155 | } |
|---|
| 156 | ''; |
|---|
| 157 | } |
|---|
| 158 | |
|---|
| 159 | sub TextileOptions { |
|---|
| 160 | my ($ctx, $args, $cond) = @_; |
|---|
| 161 | $ctx->stash('TextileOptions', $args); |
|---|
| 162 | ''; |
|---|
| 163 | } |
|---|
| 164 | |
|---|
| 165 | sub _highlight { |
|---|
| 166 | my ($tag, $attr1, $attr2, $lang, $code) = @_; |
|---|
| 167 | my $tagopen = '<'.$tag; |
|---|
| 168 | $tagopen .= $attr1 if defined $attr1; |
|---|
| 169 | $tagopen .= $attr2 if defined $attr2; |
|---|
| 170 | $tagopen .= '>'; |
|---|
| 171 | if ($lang =~ m/perl/i) { |
|---|
| 172 | $code = Voisen::CodeBeautifier::highlight_perl($code); |
|---|
| 173 | } elsif ($lang =~ m/php/i) { |
|---|
| 174 | $code = Voisen::CodeBeautifier::highlight_php3($code); |
|---|
| 175 | } elsif ($lang =~ m/java/i) { |
|---|
| 176 | $code = Voisen::CodeBeautifier::highlight_java($code); |
|---|
| 177 | } elsif (($lang =~ m/actionscript/i) || ($lang =~ m/as/i)) { |
|---|
| 178 | $code = Voisen::CodeBeautifier::highlight_as($code); |
|---|
| 179 | } elsif ($lang =~ m/scheme/i) { |
|---|
| 180 | $code = Voisen::CodeBeautifier::highlight_scheme($code); |
|---|
| 181 | } |
|---|
| 182 | $code =~ s!^<pre>!!; |
|---|
| 183 | $code =~ s!</pre>$!!; |
|---|
| 184 | return $tagopen . $code .'</'.$tag.'>'; |
|---|
| 185 | } |
|---|
| 186 | |
|---|
| 187 | # This is a Text::Textile subclass that provides enhanced |
|---|
| 188 | # functionality for Movable Type integration |
|---|
| 189 | |
|---|
| 190 | package MT::Textile; |
|---|
| 191 | |
|---|
| 192 | sub image_size { |
|---|
| 193 | my $self = shift; |
|---|
| 194 | my ($src) = @_; |
|---|
| 195 | my $ctx = $self->filter_param; |
|---|
| 196 | if ($src !~ m|^http:| && $ctx) { |
|---|
| 197 | my $blog = $ctx->stash('blog'); |
|---|
| 198 | if ($blog) { |
|---|
| 199 | require File::Spec; |
|---|
| 200 | # local image -- calc size |
|---|
| 201 | my $file; |
|---|
| 202 | if (($src =~ m!^/!) && (exists $ENV{DOCUMENT_ROOT})) { |
|---|
| 203 | $file = File::Spec->catfile($ENV{DOCUMENT_ROOT}, $src); |
|---|
| 204 | } else { |
|---|
| 205 | $file = File::Spec->catfile($blog->site_path, $src); |
|---|
| 206 | } |
|---|
| 207 | if (-f $file) { |
|---|
| 208 | eval {require MT::Image;}; |
|---|
| 209 | if (!$@) { |
|---|
| 210 | my $img = MT::Image->new(Filename => $file); |
|---|
| 211 | if ($img) { |
|---|
| 212 | return $img->get_dimensions; |
|---|
| 213 | } |
|---|
| 214 | } |
|---|
| 215 | } |
|---|
| 216 | } |
|---|
| 217 | } |
|---|
| 218 | undef; |
|---|
| 219 | } |
|---|
| 220 | |
|---|
| 221 | sub format_link { |
|---|
| 222 | my $self = shift; |
|---|
| 223 | my (%args) = @_; |
|---|
| 224 | my $title = exists $args{title} ? $args{title} : ''; |
|---|
| 225 | my $url = exists $args{url} ? $args{url} : ''; |
|---|
| 226 | my $ctx = $self->filter_param; |
|---|
| 227 | if ($url =~ m/^\d+$/ && $ctx) { |
|---|
| 228 | my $blog = $ctx->stash('blog'); |
|---|
| 229 | if ($blog) { |
|---|
| 230 | require MT::Entry; |
|---|
| 231 | my $entry = MT::Entry->load({ blog_id => $blog->id, id => $url }); |
|---|
| 232 | if ($entry) { |
|---|
| 233 | local $ctx->{__stash}{entry} = $entry; |
|---|
| 234 | my $relurl = MT::Template::Context::_hdlr_blog_url($ctx); |
|---|
| 235 | my $regrelurl = quotemeta($relurl); |
|---|
| 236 | $args{url} = MT::Template::Context::_hdlr_entry_permalink($ctx); |
|---|
| 237 | $args{url} =~ s/^.*?($regrelurl)/$1/; |
|---|
| 238 | if ((!exists $args{title}) && ($entry->title)) { |
|---|
| 239 | require MT::Util; |
|---|
| 240 | my $title = MT::Util::remove_html($entry->title); # strip HTML |
|---|
| 241 | $title =~ s/"/"/g; # convert double quotes to entities |
|---|
| 242 | $args{title} = $title; |
|---|
| 243 | } |
|---|
| 244 | } |
|---|
| 245 | } |
|---|
| 246 | } |
|---|
| 247 | |
|---|
| 248 | $self->SUPER::format_link(%args); |
|---|
| 249 | } |
|---|
| 250 | |
|---|
| 251 | sub process_quotes { |
|---|
| 252 | my $self = shift; |
|---|
| 253 | my ($str) = @_; |
|---|
| 254 | return $str unless $self->{do_quotes}; |
|---|
| 255 | if ($plugins::textile2::Have_SmartyPants) { |
|---|
| 256 | $str = SmartyPants::SmartyPants($str, $self->{smarty_mode}); |
|---|
| 257 | } |
|---|
| 258 | $str; |
|---|
| 259 | } |
|---|
| 260 | |
|---|
| 261 | sub format_url { |
|---|
| 262 | my $self = shift; |
|---|
| 263 | my (%args) = @_; |
|---|
| 264 | my $url = exists $args{url} ? $args{url} : ''; |
|---|
| 265 | my $ctx = $self->filter_param; |
|---|
| 266 | if ($url =~ m/^\d+$/ && $ctx) { |
|---|
| 267 | # looks like an entry id, so let's link it |
|---|
| 268 | my $blog = $ctx->stash('blog'); |
|---|
| 269 | if ($blog) { |
|---|
| 270 | require MT::Entry; |
|---|
| 271 | my $entry = MT::Entry->load({'blog_id' => $blog->id, 'id'=>$url}); |
|---|
| 272 | if ($entry) { |
|---|
| 273 | local $ctx->{__stash}{entry} = $entry; |
|---|
| 274 | my $relurl = MT::Template::Context::_hdlr_blog_relative_url($ctx); |
|---|
| 275 | my $regrelurl = quotemeta($relurl); |
|---|
| 276 | $args{url} = MT::Template::Context::_hdlr_entry_permalink($ctx); |
|---|
| 277 | $args{url} =~ s/^.+?($regrelurl)/$1/; |
|---|
| 278 | } |
|---|
| 279 | } |
|---|
| 280 | } elsif ($url =~ m/^imdb(?::(.+))?$/) { |
|---|
| 281 | my $term = $1; |
|---|
| 282 | $term ||= MT::Util::remove_html($args{linktext}||''); |
|---|
| 283 | $args{url} = 'http://www.imdb.com/Find?for=' . $term; |
|---|
| 284 | } elsif ($url =~ m/^google(?::(.+))?$/) { |
|---|
| 285 | my $term = $1; |
|---|
| 286 | $term ||= MT::Util::remove_html($args{linktext}||''); |
|---|
| 287 | $args{url} = 'http://www.google.com/search?q=' . $term; |
|---|
| 288 | } elsif ($url =~ m/^dict(?::(.+))?$/) { |
|---|
| 289 | my $term = $1; |
|---|
| 290 | $term ||= MT::Util::remove_html($args{linktext}||''); |
|---|
| 291 | $args{url} = 'http://www.dictionary.com/search?q=' . $term; |
|---|
| 292 | } elsif ($url =~ m/^amazon(?::(.+))?$/) { |
|---|
| 293 | my $term = $1; |
|---|
| 294 | $term ||= MT::Util::remove_html($args{linktext}||''); |
|---|
| 295 | $args{url} = 'http://www.amazon.com/exec/obidos/external-search?index=blended&keyword=' . $term; |
|---|
| 296 | } |
|---|
| 297 | $self->SUPER::format_url(%args); |
|---|
| 298 | } |
|---|
| 299 | |
|---|
| 300 | 1; |
|---|
| 301 | |
|---|
| 302 | __END__ |
|---|
| 303 | |
|---|
| 304 | =head1 NAME |
|---|
| 305 | |
|---|
| 306 | Textile - A plugin for Movable Type. |
|---|
| 307 | |
|---|
| 308 | =head1 DESCRIPTION |
|---|
| 309 | |
|---|
| 310 | This plugin integrates the Perl Text::Textile module with Movable |
|---|
| 311 | Type. |
|---|
| 312 | |
|---|
| 313 | =head1 INSTALLATION |
|---|
| 314 | |
|---|
| 315 | To install, place the 'textile2.pl' file in your Movable Type |
|---|
| 316 | 'plugins' directory. Install the 'Textile.pm' file in a 'Text' |
|---|
| 317 | subdirectory underneath the Movable Type 'extlib' directory. |
|---|
| 318 | |
|---|
| 319 | Your installation should look like this: |
|---|
| 320 | |
|---|
| 321 | (mt home)/plugins/textile2.pl |
|---|
| 322 | (mt home)/extlib/Text/Textile.pm |
|---|
| 323 | |
|---|
| 324 | You may also consider installing the Smarty Pants plugin by |
|---|
| 325 | John Gruber. MT-Textile uses this plugin to translate normal |
|---|
| 326 | quote characters into typographic quotes. |
|---|
| 327 | |
|---|
| 328 | The Code Beautifier plugin by Sean Voisen is also supported |
|---|
| 329 | by MT-Textile. If you specify a language modifier for "bc" |
|---|
| 330 | blocks or for "@...@" strings, it will invoke the Code Beautifier |
|---|
| 331 | to colorize the code. |
|---|
| 332 | |
|---|
| 333 | =head1 TEXT FORMATTERS |
|---|
| 334 | |
|---|
| 335 | =head2 textile_2 |
|---|
| 336 | |
|---|
| 337 | The text formatter identified by "textile_2" invokes the Textile |
|---|
| 338 | formatter. You can select "Textile 2" from the Movable Type interface |
|---|
| 339 | to use this option to format your entries and/or comments. |
|---|
| 340 | |
|---|
| 341 | You may also invoke the formatter on any Movable Type tag, like this: |
|---|
| 342 | |
|---|
| 343 | <$MTBlogDescription filters="textile_2"$> |
|---|
| 344 | |
|---|
| 345 | For specific documentation on how this text formatter processes |
|---|
| 346 | text (a writing guide), please refer to the online documentation |
|---|
| 347 | available here: |
|---|
| 348 | |
|---|
| 349 | L<http://www.bradchoate.com/mt/docs/mtmanual_textile2.html> |
|---|
| 350 | |
|---|
| 351 | A copy of that document is available in the distribution of this |
|---|
| 352 | plugin. Located in the file "docs/mtmanual_textile2.html". |
|---|
| 353 | |
|---|
| 354 | =head1 TAGS |
|---|
| 355 | |
|---|
| 356 | =head2 E<lt>MTTextileE<gt> |
|---|
| 357 | |
|---|
| 358 | A container tag that also allows you to invoke the Textile formatter. |
|---|
| 359 | Anything placed within this tag will be fed into the formatter for |
|---|
| 360 | processing. Additionally, all attributes passed to this tag are |
|---|
| 361 | processed as options for the Textile engine. |
|---|
| 362 | |
|---|
| 363 | Attributes available: |
|---|
| 364 | |
|---|
| 365 | =over |
|---|
| 366 | |
|---|
| 367 | =item charset |
|---|
| 368 | |
|---|
| 369 | Set this to the character set of the incoming data. This value |
|---|
| 370 | will default to the "PublishCharset" value in your mt.cfg file. |
|---|
| 371 | If no charset is given anywhere, it will default to ISO-8859-1. |
|---|
| 372 | |
|---|
| 373 | =item char_encoding |
|---|
| 374 | |
|---|
| 375 | Set to 1 to encode special characters to HTML entities. If |
|---|
| 376 | you're outputting utf-8 data, this can be set to '0' to |
|---|
| 377 | output plaintext instead. In fact, if you set your PublishCharset |
|---|
| 378 | for Movable Type to utf-8, it will effectively set this |
|---|
| 379 | setting to '0'. Otherwise, the default value is 1. |
|---|
| 380 | |
|---|
| 381 | =item do_quotes |
|---|
| 382 | |
|---|
| 383 | Sets the option for processing quotes into curly quotes. Defaults |
|---|
| 384 | to 1. The "Smarty" plugin will be used as a default, if available. |
|---|
| 385 | |
|---|
| 386 | =item trim_spaces |
|---|
| 387 | |
|---|
| 388 | Set to '1' to cause any trailing whitespace on lines to be |
|---|
| 389 | trimmed. Default is 0 (disabled). |
|---|
| 390 | |
|---|
| 391 | =item smarty_mode |
|---|
| 392 | |
|---|
| 393 | Controls the Smarty plugin for processing quotes. The value |
|---|
| 394 | given is passed through to the Smarty plugin to control how it |
|---|
| 395 | behaves. Please refer to the documentation for the Smarty Pants |
|---|
| 396 | plugin for the available values for this attribute. You can |
|---|
| 397 | simply specify '1' for the default mode of operation. |
|---|
| 398 | |
|---|
| 399 | =item preserve_spaces |
|---|
| 400 | |
|---|
| 401 | Set to '1' to cause multiple, continuous spaces to be preserved |
|---|
| 402 | by changing them to the HTML entity  . If '0', spaces |
|---|
| 403 | are left as-is, which means they will appear as a single space |
|---|
| 404 | when rendered to HTML. Default is '0'. |
|---|
| 405 | |
|---|
| 406 | =item head_offset |
|---|
| 407 | |
|---|
| 408 | Set to a number from "1" to "5" to control the numbering of the |
|---|
| 409 | "h1" to "h6" paragraph block codes. For example, if a head_offset |
|---|
| 410 | of "1" is given, any "h1" paragraph blocks will produce "h2" HTML |
|---|
| 411 | tags. Or "h3" HTML tags if a head_offset of "2" is specified. |
|---|
| 412 | |
|---|
| 413 | =item flavor |
|---|
| 414 | |
|---|
| 415 | Use the flavor attribute to identify whether the HTML produced |
|---|
| 416 | should be HTML, XHTML 1.x or XHTML 2 compliant. Available |
|---|
| 417 | values include: |
|---|
| 418 | |
|---|
| 419 | html |
|---|
| 420 | html/css |
|---|
| 421 | xhtml1 |
|---|
| 422 | xhtml2 |
|---|
| 423 | |
|---|
| 424 | The default flavor is "xhtml1". |
|---|
| 425 | |
|---|
| 426 | =item css |
|---|
| 427 | |
|---|
| 428 | Set to '1' to allow the Textile formatter to use CSS classes |
|---|
| 429 | and style attributes to format any output. Specifying '0' will |
|---|
| 430 | avoid the use of style and class attributes. This attribute |
|---|
| 431 | will default to '1' when a flavor of "html/css" or "xhtml1" |
|---|
| 432 | or "xhtml2" is given. |
|---|
| 433 | |
|---|
| 434 | =back |
|---|
| 435 | |
|---|
| 436 | =head2 E<lt>$MTTextileOptions$E<gt> |
|---|
| 437 | |
|---|
| 438 | A standalone tag that can also be used to configure the Textile |
|---|
| 439 | text formatter. See the attributes available to the E<lt>MTTextileE<gt> |
|---|
| 440 | tag for what is available. |
|---|
| 441 | |
|---|
| 442 | =head2 E<lt>$MTTextileHeadOffset$E<gt> |
|---|
| 443 | |
|---|
| 444 | Another tag that can be used to set the "head_offset" attribute. |
|---|
| 445 | This tag is left for backward compability. Please use the |
|---|
| 446 | E<lt>MTTextileOptionsE<gt> tag in the future. |
|---|
| 447 | |
|---|
| 448 | =head1 LICENSE |
|---|
| 449 | |
|---|
| 450 | Released under the MIT license. Please see |
|---|
| 451 | |
|---|
| 452 | L<http://www.opensource.org/licenses/mit-license.php> |
|---|
| 453 | |
|---|
| 454 | for details. |
|---|
| 455 | |
|---|
| 456 | =head1 SUPPORT |
|---|
| 457 | |
|---|
| 458 | If you have questions or need assistance with this plugin, please |
|---|
| 459 | use the following link: |
|---|
| 460 | |
|---|
| 461 | L<http://www.bradchoate.com/mt-plugins/textile> |
|---|
| 462 | |
|---|
| 463 | =head1 COPYRIGHT |
|---|
| 464 | |
|---|
| 465 | Copyright 2002-2004, Brad Choate |
|---|
| 466 | |
|---|
| 467 | =cut |
|---|