やめた、Textile

決断できた。

「”quote” text “quote” text 」をリンクテキストに含めるなと言う
1 "quote" text "quote" text "link":http://google.com
square bracket なしでも曖昧でないなら反応せよと言う
1 This is a link to a "Wikipedia article about Textile":http://en.wikipedia.org/wiki/Textile_(markup_language)
2 
3 This is a link to a "Wikipedia article about Textile":http://en.wikipedia.org/wiki/Textile_(markup_language).
このアンバランスについては想定通り「Redcloth」のみをリンクテキストにしろと言う
1 "I first learned about "Redcloth":http://redcloth.org/ several years ago.
「He said it is “very unlikely” this works」全体をリンクテキストにしろと言う
1 "He said it is "very unlikely" this works":http://slashdot.org/
「He said it is “very unlikely” the “economic stimulus” works」全体をリンクテキストにしろと言う
1 "He said it is "very unlikely" the "economic stimulus" works":http://slashdot.org/
「”Open the pod bay doors please, HAL.”」全体をリンクテキストにしろと言う
1 ""Open the pod bay doors please, HAL."":http://www.youtube.com/watch?v=npN9l2Bd06s
python-textile がバグってるヤツ
1 ["Wikipedia article about Textile":http://en.wikipedia.org/wiki/Textile_(markup_language)]
これが許容されない意味がわからない(is veryからがリンクテキストとなってしまう)
1 ["He said it "is very unlikely the economic stimulus works":http://en.wikipedia.org/wiki/Textile_(markup_language)].

アンバランスにならない限り全体で反応せよ、という仕様までは理解できないこともなかったのだが。まず、実装しようとすれば、引用符の数が偶数個で最後の引用符にコロンが続く、というような判断を強いられる。これだけのことであれば、「単なるチャレンジ」として面白いのだが。それでも最後の2つで十分にイヤで、そして決断に至ったのは単にこれ:

これさえ見つけなければ、続けるつもりだったんだけどね、多少荒れてようが。

良くこんなんで誰も文句言わないな、Textile」で既に指摘した通り、そもそも仕様が荒れている。それと実際に 1/3 ほど取り組んでみてわかったのだが、非常に設計が場当たり的で破滅的である。感触としては、真面目に考えずに作り始めてはみたものの、色んな要望を闇雲に取り入れていったら埒もあかなくなった、という感じだろう、という感想。「メンテナンスが続けられなくなった」と、さも何か事情ありげに書いているが、おそらく作者が完全に興味をなくしたんだろう。あるいはもう作ってしまったことへの後悔フェーズに突入してるんじゃないかな。こんな仕様、保守が続けられるとは思えないもの。

ワタシが思うに、このプロジェクトは、このまま絶滅するに任せた方が、皆の幸せのためにはいいと思う。「バグフィックス」してもまともなものにはならない、これは。設計不全だから。ので、Pygments lexer を書くことでこれを支援しよう、なんてのは世のためにならぬ、と決断。












ちなみに個人的経験としては、なかなかにチャレンジングで面白いことも多かった。これに本気で立ち向かおうとすると、かなり正規表現の鍛錬になる。

せっかくなので、未完成のものを晒し者にしておく:

仕様のカバレッジは 1/3 未満、網羅したつもりのものもかなり未完
  1 # -*- coding: utf-8 -*-
  2 """
  3     pygments.lexers.markup
  4     ~~~~~~~~~~~~~~~~~~~~~~
  5 
  6     Lexers for non-HTML markup languages.
  7 
  8     :copyright: Copyright 2006-2015 by the Pygments team, see AUTHORS.
  9     :license: BSD, see LICENSE for details.
 10 """
 11 
 12 import re
 13 
 14 from pygments.lexers.html import HtmlLexer, XmlLexer
 15 from pygments.lexers.javascript import JavascriptLexer
 16 from pygments.lexers.css import CssLexer
 17 
 18 from pygments.lexer import RegexLexer, DelegatingLexer, include, bygroups, \
 19     using, this, do_insertions, default, words
 20 from pygments.token import Text, Comment, Operator, Keyword, Name, String, \
 21     Number, Punctuation, Generic, Other
 22 from pygments.util import get_bool_opt, ClassNotFound
 23 
 24 __all__ = ['TextileLexer']
 25 
 26 
 27 from sys import maxunicode
 28 upper_re_s = "".join([unichr(c) for c in
 29     xrange(maxunicode) if unichr(c).isupper()])
 30 
 31 
 32 class TextileLexer(RegexLexer):
 33     """
 34     A lexer that highlights `Textile <https://github.com/textile/textile-spec>`_ syntax.
 35 
 36     .. versionadded:: 2.2
 37     """
 38 
 39     name = 'Textile'
 40     aliases = ['textile']
 41     filenames = ['*.textile']
 42     mimetypes = ['text/x-textile']
 43 
 44     flags = re.MULTILINE | re.DOTALL | re.UNICODE
 45 
 46     pnct_re_s = r'[-!"#$%&()*+,/:;<=>?@\'\[\\\]\.^_`{|}~]'
 47     pnct_re_s2 = r'[-!"#$%&*+,/:;<=>?@\'\[\\\]\.^_`{|}~]'
 48 
 49     url_re = r"""(?xi)
 50           \b
 51           (                           # Capture 1: entire matched URL
 52             (?:
 53               [a-z][\w-]+:                # URL protocol and colon
 54               (?:
 55                 /{1,3}                        # 1-3 slashes
 56                 |                             #   or
 57                 [a-z0-9%]                     # Single letter or digit or '%'
 58                                               # (Trying not to match e.g. "URI::Escape")
 59               )
 60               |                           #   or
 61               www\d{0,3}[.]               # "www.", "www1.", "www2." … "www999."
 62               |                           #   or
 63               [a-z0-9.\-]+[.][a-z]{2,4}/  # looks like domain name followed by a slash
 64             )
 65             (?:                           # One or more:
 66               [^\s()<>]+                      # Run of non-space, non-()<>
 67               |                               #   or
 68               \(([^\s()<>]+|(\([^\s()<>]+\)))*\)  # balanced parens, up to 2 levels
 69             )+
 70             (?:                           # End with:
 71               \(([^\s()<>]+|(\([^\s()<>]+\)))*\)  # balanced parens, up to 2 levels
 72               |                                   #   or
 73               [^\s`!()\[\]{};:'".,<>?«»“”‘’]        # not a space or one of these punct chars
 74             )
 75           )"""
 76     url_term_re = r"""(?xi)
 77           (?:                           # One or more:
 78             [^\s()<>]+                      # Run of non-space, non-()<>
 79             |                               #   or
 80             \(([^\s()<>]+|(\([^\s()<>]+\)))*\)  # balanced parens, up to 2 levels
 81           )+
 82           (?:                           # End with:
 83             \(([^\s()<>]+|(\([^\s()<>]+\)))*\)  # balanced parens, up to 2 levels
 84             |                                   #   or
 85             [^\s`!()\[\]{};:'".,<>?«»“”‘’]        # not a space or one of these punct chars
 86           )"""
 87 
 88     tokens = {
 89         'root': [
 90             (r'<!--', Comment, 'comment'),
 91 
 92             # Heading
 93             (r'^(h[1-6]\b[^\n]*?\.)([^\n]*)$',
 94              bygroups(Generic.Heading, Name.Namespace)),
 95 
 96             # Links
 97             (r'(\[)("{1,2})([^"]+)(\2)(:)(\S+)(\])',
 98              bygroups(
 99                     Punctuation,
100                     Keyword, Keyword, Keyword, Keyword, String,
101                     Punctuation)),
102             (r'("{1,2})([^"]+)(\1)(:)(\S+%s)' % url_term_re,
103              bygroups(
104                     Keyword, Keyword, Keyword, Keyword, String)),
105 
106             # Block code
107             (r'^(bc|pre)\b[^\n]*?\.\.(\s|$)',
108              Generic.Subheading, 'extended blockcode'),
109             (r'^(bc|pre)\b[^\n]*?\.(\s|$)',
110              Generic.Subheading, 'blockcode'),
111 
112             # Inline code
113             #    python-textile allows newline...
114             (r'(@)([^@]+)(@)',
115              bygroups(Generic.Subheading, Comment.Preproc, Generic.Subheading)),
116 
117             # Block quotation
118             (r'^(bq)\b[^\n]*?\.\.(:%s|\s|$)' % url_re,
119              Generic.Subheading, 'extended blockquote'),
120             (r'^(bq)\b[^\n]*?\.(:%s|\s|$)' % url_re,
121              Generic.Subheading, 'blockquote'),
122 
123             # Inline citation
124             #    python-textile doesn't allow newline...
125             (r'(\?\?)([^\n]+?)(\?\?)',
126              bygroups(Generic.Subheading, Comment.Special, Generic.Subheading)),
127 
128             #
129             (r'^(p)\b[^\n]*?\.(\s|$)', Generic.Subheading),
130 
131             # Insertions and deletions
132             #    python-textile doesn't allow newline...
133             (r'\[\+[^\n]+\+\]', Generic.Inserted),
134             (r'(?<=\s)\+[^\n+]+\+(?=\s)', Generic.Inserted),
135             (r'\[\-[^\n]+\-\]', Generic.Deleted),
136             (r'(?<=\s)\-[^\n-]+\-(?=\s)', Generic.Deleted),
137 
138             # Strong importance, Stress emphasis, Stylistic offset, Alternate voice
139             (r'(?<=\s|%s)(\*_)[^\n](_\*)(?=\s|%s)' % (
140                     pnct_re_s, pnct_re_s), Generic.Strong),
141             (r'(?<=\s|%s)(_\*)[^\n](\*_)(?=\s|%s)' % (
142                     pnct_re_s, pnct_re_s), Generic.Strong),
143             (r'(?<=\s|%s)(\*{1,2})[^\n]+\1(?=\s|%s)' % (
144                     pnct_re_s, pnct_re_s), Generic.Strong),
145             (r'(?<=\s|%s)(_{1,2})[^\n]+\1(?=\s|%s)' % (
146                     pnct_re_s, pnct_re_s), Generic.Emph),
147             
148             # Glyphs
149             include('glyphs'),
150 
151             (r'.', Text),
152         ],
153         'extended blockcode': [
154             # ``it encounters another block signature such as @p.@''
155             # fuh, such is such??
156             (r'\n(?=(?:p|pre|bc|h[1-6])\b[^\n]*?\.(:|\s|$))', Comment.Preproc, '#pop'),
157             (r'.', Comment.Preproc),
158         ],
159         'blockcode': [
160             (r'^\n', Comment.Preproc, '#pop'),  # blank line
161             (r'.', Comment.Preproc),
162         ],
163         'extended blockquote': [
164             # ``it encounters another block signature such as @p.@''
165             # fuh, such is such??
166             (r'\n(?=(?:p|pre|bc|h[1-6])\b[^\n]*?\.(\s|$))', Comment.Special, '#pop'),
167             (r'.', Comment.Special),
168         ],
169         'blockquote': [
170             (r'^\n', Comment.Special, '#pop'),  # blank line
171             (r'.', Comment.Special),
172         ],
173         'glyphs': [
174             # apostrophe's
175             #(r"(^|\w)'(\w)",),
176             # back in '88
177             #(r"(\s)'(\d+\w?)\b(?!')",),
178             # single closing
179             #(r"(^|\S)'(?=\s|%s|$)" % pnct_re_s,),
180             # single opening
181             #(r"'",),
182             # double closing
183             #(r'(^|\S)"(?=\s|%s|$)' % pnct_re_s,),
184             # double opening
185             #(r'"',),
186             # ellipsis
187             (r'(?=[^.]?)\.{3}', Name.Entity),  #Q
188             # ampersand
189             (r'(\s+)(&)(\s+)', bygroups(Text, Name.Entity, Text)),  #Q
190             # em dash
191             (r'(\s*)(--)(\s*)', bygroups(Text, Name.Entity, Text)),  #Q
192             # en dash
193             (r'(\s+)(-)(\s+|$)', bygroups(Text, Name.Entity, Text)),  #Q
194             # dimension sign
195             (r'(\d+)( *)(x)( *)(\d+)',
196              bygroups(Number, Text, Name.Entity, Text, Number)),  #Q
197             # trademark
198             (r'\b( *)([([](?i)TM[])])', bygroups(Text, Name.Entity)),  #Q
199             # registered
200             (r'\b( *)([([](?i)R[])])', bygroups(Text, Name.Entity)),  #Q
201             # copyright
202             (r'\b( *)([([](?i)C[])])', bygroups(Text, Name.Entity)),  #Q
203             # 1/2
204             (r'[([]1/2[])]', Name.Entity),  #Q
205             # 1/4
206             (r'[([]1/4[])]', Name.Entity),  #Q
207             # 3/4
208             (r'[([]3/4[])]', Name.Entity),  #Q
209             # degrees
210             (r'[([](?i)o[])]', Name.Entity),  #Q
211             # plus/minus
212             (r'[([]\+/\-[])]', Name.Entity),  #Q
213             # 3+ uppercase acronym
214             (r'\b([%s][%s0-9]{2,})([(])([^)]*)([)])' % (upper_re_s, upper_re_s),
215              bygroups(Keyword, Punctuation, String, Punctuation)),  #Q
216             # 3+ uppercase
217             (r"""(?:(?<=^)|(?<=\s)|(?<=[>(;-]))([%s]{3,})(\w*)(?=\s|%s|$)(?=[^">]*?(<|$))""" % (
218                     upper_re_s, pnct_re_s),
219              bygroups(Keyword, Text)),  #Q
220         ],
221         'comment': [
222             ('[^-]+', Comment),
223             ('-->', Comment, '#pop'),
224             ('-', Comment),
225         ],
226     }

リンクの仕様の完成度を高めようとしていた最中に今回の記事の発端の仕様に気付いた、ので、「引用符の数が偶数個で最後の引用符にコロンが続く」をどうしようか、と考えるとこまで行ってない。Pygments RegexLexer 単体のワンパスでは無理じゃないかな。入れ子の lexer にして、外側の lexer が「”: から後ろ向きに行頭まで対になる ” を全部まとめあげる」とでもする感じかな、多分。あるいはスタックに積んで保留にしとくんだろうね。

あ、ちなみに「caps」の仕様もおかしいです。振る舞いが未定義、ちゅうんかな。「HAL」には期待通り反応。python-textile は「CFString」に「<span class="caps">CFS</span>tring」と反応、RedCloth は「<p>CFString</p>」と反応、これは「DVDs」にどう反応すべきなのか、という悩ましい問題を抱えており、何しても解決出来ないと思う。「TCP/IP」に至っては、ともに「<p><span class="caps">TCP</span>/IP</p>」と反応。これはもうさ、やろうとしたことに無理があった、ってことなんよ。こんなもん自動でやろうったって、うまくいかんさ。












2015-11-13 02:50追記
イキオイで間違ったこと言ってるね。「引用符の数が偶数個で最後の引用符にコロンが続く」ではないよね。

「”quote” text “quote” text 」をリンクテキストに含めるなと言う
1 "quote" text "quote" text "link":http://google.com
「He said it is “very unlikely” this works」全体をリンクテキストにしろと言う
1 "He said it is "very unlikely" this works":http://slashdot.org/

この2例から、引用符の「囲み方」を識別しなければならない、ってこと。例えば

この場合は「this works」のみをリンクテキストにしなければならない
1 "He said it is "very unlikely "this works":http://slashdot.org/

ということ。空白の位置が一つ違うだけで意味が違うわけです。知恵熱出そうだし、これが理由でやめるわけでもないけど続ける気がないので、どうすればいいかは考えないけどね。いずれ近いものは必要になるかもなぁ。

なお、この問題も「何でも自動でやろうと目論んだ」のが破滅の原因、ね。「typography quotes」としても自動で反応しつつ、執筆者に明示的に記述させることなく「リンクテキストのための囲み」としても機能させようとしたために、二重引用符の機能が過負荷になっちゃってるんです。そしてこれの本当の問題はさ、「機械処理しにくい」ことにあるのではなくて、むしろ人間が振る舞いを想像出来なくなってしまっている、ということにある。なんか書いててわかってきたけど、いわゆるこれ、伝統的に御馴染みの問題ね。