UP
py-gfm - Python-MarkdownでGFM
前のページへ
python DE markdown


チュウトリアル: Python MarkdownのExtensionを記述する

初回公開:2018/01/28
最終更新:未

【 目次 】

Tutorial: Writing Extensions for Python Markdown · Python-Markdown/markdown Wiki · GitHub」のつたない翻訳。

イントロダクション

多くの組み込みextensionを提供するだけでなく、Python-Markdownは、既存の動作を変更したり、新しい動作を追加するために、誰でも独自の拡張機能を書くことができるアプリケーションプログラミングインターフェイス(API)を提供します。
始めるにあたって、APIドキュメントが少し難解かもしれないので、次のチュートリアルでは、簡単なextensionを作成するプロセスをとうして段階的に、さらにより以上の機能を追加していきます。
さまざまなステップをさまざまな方法で繰り返し、APIのさまざまな部分をデモンストレーションします。

まず、実装しようとする構文を決める必要があります。
実際には、txt2tagsマークアップ言語で使われているインライン構文のサブセットを実装します。

  • ストライキ・2つのハイフン:--del--=><del>del</del>=>del
  • アンダーライン・2つの下線:__ins__=><ins>ins</ins>=>ins
  • 太字・2つのアスタリスク:**strong**=><strong>strong</strong>=>strong
  • イタリック体・2つのスラッシュ://emphasis//=><em>emphasis</em>=>emphasis

ボイラープレートコード

最初のステップは、Python-Markdown Extensionが必要とする定型コードを作成することです。

警告:このチュートリアルは非常に一般的なもので、開発環境についての前提はありません。
以下のコマンドの中には、適切な権限を持つユーザーが実行しない限り、一部のシステム(すべてではない)でエラーが発生するものがあります。
これらのタイプの問題を回避するには、必ずしもそうする必要はありませんが、virtualenvをプライマリシステムから隔離された環境での開発するために使用することをお勧めします。
どんなPythonの開発に適用する適切な開発環境を設定することは、このチュートリアルの範囲を超えています。(Markdownのextensionの開発には追加の要件はありません)
Python開発の基本的な理解は必要です。

まず、拡張ファイルを保存するための新しいディレクトリを作成します。
コマンドラインから次の操作を行います。

mkdir myextension
cd myextension

作成したばかりの "myextension"ディレクトリにすべてのファイルを保存してください。
extensionに"myextension"という名前を付けたことに注意してください。
別の名前を使用することもできますが、あなたの名付けた一貫した名前を必ず使用してください。

最初のPythonファイルを作成し、myextension.pyという名前を付けて、次のボイラープレートコード(お決まりコード)を追加します。

from markdown.extensions import Extension

class MyExtension(Extension):
   def extendMarkdown(self, md, md_globals):
       # ここにmarkdownの動作を実装
       pass

そのファイルを保存した後、2番目のPythonファイルを作成し、setup.pyという名前を付けて次のコードを追加します:

from setuptools import setup
setup(
    name='myextension',
    version='1.0',
    py_modules=['myextension'],
    install_requires = ['markdown>=2.5'],
)

最後に、コマンドラインから以下のコマンドを実行して、Pythonにあなたの新しいextensionを伝えてください:

python setup.py develop

installサブコマンドではなく、developサブコマンドが実行されていることに注意してください。
プラグインがまだ終了していないので、この特別な開発モードは、Pythonのsite-packagesディレクトリではなく、ソースファイルからプラグインを実行するパスを設定します。
こうすることで、ファイルに加えられた変更は、そのextensionを再インストールする必要がなくなり、直ちに有効になります。
また、setupスクリプトは、setuptoolsがインストールされていることを前提としている事にも注意してください。
setuptoolsは必要ありませんが(代わりにfrom distutils.core import setupを実行してください)、setuptoolsを使用する場合は、developサブコマンドを取得します。
pipおよび/またはvirtualenvがインストールされているシステム(どちらも推奨)は、setuptoolsもインストールされています。

すべてが正しく機能するようにするには、extensionをMarkdownに渡してみてください。 Pythonインタプリタを開き、以下を試してみてください:

>>> import markdown
>>> from myextension import MyExtension
>>> markdown.markdown('foo bar', extensions=[MyExtension()])
'<p>foo bar</p>'

明らかに、extensionは何も役に立たないが、エラーなしでその拡張機能を使用できるようになったので、実際に新しい構文を実装することができます。

汎用パターンクラスを使う

まず、Markdownの標準構文と重複しない構文の一部を実装しましょう。
--del--構文で、<del>タグでテキストをラップします。

最初のステップは、del構文と一致する正規表現を書くことです。

DEL_RE = r'(--)(.*?)--'

最初のハイフンセット((--))は、かっこでグループ化されています。
これは、Python-Markdownが提供する汎用パターンクラスを使用するためです。

とりわけ、SimpleTextPatternでは正規表現のgroup(3)にテキストコンテンツが含まれることが予想される。
テキスト全体(マークアップを含む)がgroup(1)に入るので、group(3)に必要なコンテンツを強制的に追加する追加グループを追加します。

また、貪欲でないマッチ(.*?)を使用してコンテンツが照合されることにも注意してください。
それ以外の場合は、最初のオカレンスと最後のオカレンスの間のすべてがすべて<del>タグの1つの中に置かれます。

ですから、正規表現をMarkdownに組み込みましょう:

from markdown.extensions import Extension
from markdown.inlinepatterns import SimpleTagPattern

DEL_RE = r'(--)(.*?)--'

class MyExtension(Extension):
    def extendMarkdown(self, md, md_globals):
        # Create the del pattern
        del_tag = SimpleTagPattern(DEL_RE, 'del')
        # Insert del pattern into markdown parser
        md.inlinePatterns.add('del', del_tag, '>not_strong')

2行が追加されている事に気づいたでしょうか。
最初の行は、markdown.inlinepatterns.SimpleTagPattern のインスタンスを作成します。
このジェネリックパターンクラスは2つの引数をとります。 (この場合はDEL_RE)と照合する正規表現とgroup(3)のテキストを( 'del')に挿入するタグの名前を指定します。

2行目はMarkdownパーサーに新しいパターンを追加します。
?明らかではない場合は?、markdown.ExtensionクラスのextendsMarkdownメソッドに2つの引数"md"と "md_globals"が渡されます。
"md"はExtensionの機能を追加したいMarkdownクラスのインスタンスです。
ここでは、'>not_strong'という名前のパターンの後にPatternクラスのインスタンスdel_tagを使用して、新しい名前の 'del'というインラインパターンを挿入します(従って'>not_strong')。

さあ、新しい拡張機能をテストしましょう。 Pythonインタプリタを開き、以下を試してみてください:

>>> import markdown
>>> from myextension import MyExtension
>>> markdown.markdown('foo --deleted-- bar', extensions=[MyExtension()])
'<p>foo <del>deleted</del> bar</p>'

'myextension'モジュールからMyExtensionクラスをimportしたことに注目してください。
その後、そのクラスのインスタンスをmarkdown.markdownextensionsキーワードに渡しました。

返されたHTMLも見ることができます。これはブラウザに次のように表示されます:

foo deleted bar

<ins>タグを使用する__ins__の構文を追加しましょう。

DEL_RE = r'(--)(.*?)--'
INS_RE = r'(__)(.*?)__'

class MyExtension(Extension):
    def extendMarkdown(self, md, md_globals):
        del_tag = SimpleTagPattern(DEL_RE, 'del')
        md.inlinePatterns.add('del', del_tag, '>not_strong')
        ins_tag = SimpleTagPattern(INS_RE, 'ins')
        md.inlinePatterns.add('ins', ins_tag, '>del')

それは自明でなければなりません。
私たちは単純に私たちの 'ins'構文にマッチする新しいパターンを作成し、それを 'del'パターンの後に追加しました。

'ins'構文で行うことができる、2つのアンダースコアで囲まれたテキストに対して定義された2つの可能な結果が?除かれます?。
Markdownの既存のbold構文(__bold__)はパーサでまだ定義されていることを思い出してください。

しかし、boldパターンの前に新たに挿入された構文がinlinePatternsに挿入されると、boldパターンはそれを見つけるチャンスを持つ前に常に、挿入されたパターンが最初に実行され,2重下線マークアップは使われて無くなります。
それでも、既存のboldパターンはまだテキストに対して実行されており、パーサを不必要に減速させます。
したがって、もはや不要になった部品を取り除くことは、常に良い習慣です。

しかし、独自の新しいbold構文を定義するので、実際には古いパターンを新しいもので上書きもしくは置き換えることができます。
我々のemphasis(強調)パターンも同様です。

まず、新しい正規表現を定義する必要があります。
前回と同じ正規表現をいくつか変更して使うことができます。

STRONG_RE = r'(\*\*)(.*?)\*\*'
EMPH_RE = r'(\/\/)(.*?)\/\/'

これらをmarkdown パーサーに挿入する必要があります。
ただし、挿入と削除とは異なり、既存のインラインパターンをオーバーライドする必要があります。
Markdownのstrongとemphasisの構文は現在、5つのインラインパターン'emphasis''emphasis2''strong_em''em_strong'で実装されています。

最初に'strong'を上書きしましょう。

class MyExtension(Extension):
    def extendMarkdown(self, md, md_globals):
        ...
        # Create new strong pattern
        strong_tag = SimpleTagPattern(STRONG_RE, 'strong')
        # Override existing strong pattern
        md.inlinePatterns['strong'] = strong_tag

既存のパターンの前後に新しいパターンを追加するのではなく、単純に'strong'というパターンの値を再割り当てすることに注意してください。
これは、'strong'という名前の古いパターンがすでに存在しており、パーサーでのインラインパターン内の順序を変更する必要がないためです。
したがって、新しいパターンインスタンスを割り当てるだけです。

同じことを'emphasis'のためにすることができます:

class MyExtension(Extension):
    def extendMarkdown(self, md, md_globals):
        ...
        emph_tag = SimpleTagPattern(EMPH_RE, 'emphasis')
        md.inlinePatterns['emphasis'] = emph_tag

まだ、3つの古いパターン、'emphasis2','strong_em'そして'em_strong'が残っています。
パターン'Emphasis2'は、under_scored_wordsがemphasisのために誤りでは無い特別なケースでしたが、新しい構文では二重のアンダースコアが要求されるので、もはや必要ありません。
したがって、削除することができます。
'strong_em''em_strong'についても同様です。
Markdown構文では、strongとemphasisの両方に対して同じ文字が使われているため、ネストされた2つのものを一致させるために特殊なケースが必要でした (即ち: like this または likethis)

これも新しい構文では必要ありません。 私たちはdict項目を削除するのと同じ方法で3つを削除することができます:

class MyExtension(markdown.Extension):
    def extendMarkdown(self, md, md_globals):
        ...
        del md.inlinePatterns['strong_em']
        del md.inlinePatterns['em_strong']
        del md.inlinePatterns['emphasis2']

これは我々の新しい構文をすべて実装しています。
完全性のため、extension全体は次のようになります。

from markdown.extensions import Extension
from markdown.inlinepatterns import SimpleTagPattern

DEL_RE = r'(--)(.*?)--'
INS_RE = r'(__)(.*?)__'
STRONG_RE = r'(\*\*)(.*?)\*\*'
EMPH_RE = r'(\/\/)(.*?)\/\/'

class MyExtension(Extension):
    def extendMarkdown(self, md, md_globals):
        del_tag = SimpleTagPattern(DEL_RE, 'del')
        md.inlinePatterns.add('del', del_tag, '>not_strong')
        ins_tag = SimpleTagPattern(INS_RE, 'ins')
        md.inlinePatterns.add('ins', ins_tag, '>del')
        strong_tag = SimpleTagPattern(STRONG_RE, 'strong')
        md.inlinePatterns['strong'] = strong_tag
        emph_tag = SimpleTagPattern(EMPH_RE, 'emphasis')
        md.inlinePatterns['emphasis'] = emph_tag
        del md.inlinePatterns['strong_em']
        del md.inlinePatterns['em_strong']
        del md.inlinePatterns['emphasis2']

それが正しく動作していることを確認するために、Pythonインタプリタから次のコマンドを実行します:

>>> import markdown
>>> from myextension import MyExtension
>>> txt = """
... Some __underline__
... Some --strike--
... Some **bold**
... Some //italics//
... """
... 
>>> markdown.markdown(txt, extensions=[MyExtension()])
"<p>Some <ins>underline</ins>\nSome <del>strike</del>\nSome <strong>bold</strong>\nSome <em>italics</em>"

独自のPattern Classを作る。

しかし、そのコードには多くの繰り返しがあることに気付くかもしれません。
実際、新しい正規表現の4つすべてを簡単に1つの正規表現にまとめることができます。
そして、1つのパターンを走らせるだけで、4のパターン以上にパフォーマンスが良いでしょう。

4つの正規表現を1つの新しい表現にリファクタリングしましょう:

MULTI_RE = r'([*/_-]{2})(.*?)\2'

この正規表現を使用できる汎用のパターンクラスは存在しないため、独自のパターンクラスを定義する必要があります。
すべてのパターンクラスは、markdown.inlinpatterns.Patternベースクラスから継承する必要があります。
最低限、サブクラスでは、正規表現MatchObjectを受け取り、ElementTree要素を返すhandleMatchメソッドを定義する必要があります。

from markdown.inlinepatterns import Pattern
from markdown util import etree

class MultiPattern(Pattern):
    def handleMatch(self, m):
        if m.group(2) == '**':
            # Bold
            tag = 'strong'
        elif m.group(2) == '//':
            # Italics
            tag = 'em'
        elif m.group(2) == '__':
            # Underline
            tag = 'ins'
        elif m.group(2) == '--':
            # Strike
            tag = 'del'
        # Create the Element
        el = etree.Element(tag)
        el.text = m.group(3)
        return el

まず、Markdownに新しいパターンを伝え、不要な既存のパターンを削除する必要があります:

class MultiExtension(Extension):
    def extendMarkdown(self, md, md_globals):
        # Delete the old patterns
        del md.inlinePatterns['strong']
        del md.inlinePatterns['em']
        del md.inlinePatterns['strong_em']
        del md.inlinePatterns['em_strong']
        del md.inlinePatterns['emphasis2']
        del md.inlinePatterns['not_strong']
        # Add our new MultiPattern
        multi = MultiPattern(MULTI_RE)
        md.inlinePatterns['multi'] = multi

完全にするために、新しく追加されたコードは次のようになります:

from markdown.inlinepatterns import Pattern
from markdown.util import etree

MULTI_RE = r'([*/_-]{2})(.*?)\2'

class MultiPattern(Pattern):
    def handleMatch(self, m):
        if m.group(2) == '**':
            # Bold
            tag = 'strong'
        elif m.group(2) == '//':
            # Italics
            tag = 'em'
        elif m.group(2) == '__':
            # Underline
            tag = 'ins'
        elif m.group(2) == '--':
            # Strike
            tag = 'del'
        # Create the Element
        el = etree.Element(tag)
        el.text = m.group(3)
        return el

class MultiExtension(Extension):
    def extendMarkdown(self, md, md_globals):
        # Delete the old patterns
        del md.inlinePatterns['strong']
        del md.inlinePatterns['emphasis']
        del md.inlinePatterns['strong_em']
        del md.inlinePatterns['em_strong']
        del md.inlinePatterns['emphasis2']
        del md.inlinePatterns['not_strong']
        # Add our new MultiPattern
        multi = MultiPattern(MULTI_RE)
        md.inlinePatterns['multi'] = multi

ファイルmyextension.pyに上記のコードを追加した後、Pythonインタプリタを開きます:

>>> import markdown
>>> from myextension import MultiExtension
>>> txt = """
... Some __underline__
... Some --strike--
... Some **bold**
... Some //italics//
... """
... 
>>> markdown.markdown(txt, extensions=[MultiExtension()])
"<p>Some <ins>underline</ins>\nSome <del>strike</del>\nSome <strong>bold</strong>\nSome <em>italics</em>"

Configオプションの追加

ここで、extensionにいくつかのコンフィグレーションオプションを提供したいとします。
おそらく、ユーザーがオンとオフを切り替えることができるオプションとして、挿入と削除の構文を提供したいだけです。

まず、正規表現を2つに分割しましょう:

STRONG_EM_RE = r'([*/]{2})(.*?)\2'
INS_DEL_RE = r'([_-]{2})(.*?)\2'

次に、新しいExtensionサブクラスでconfigオプションを定義する必要があります。

class ConfigExtension(Extension):
    def __init__(self, *args, **kwargs):
        # Define config options and defaults
        self.config = {
            'ins_del': [False, 'Enable Insert and Delete syntax.']
        }
        # Call the parent class's __init__ method to configure options
        super(ConfigExtension, self).__init__(*args, **kwargs)

最初に、configオプションをdictとして定義しました。
キーは各オプションの名前です。
各値は2つの項目リストです(タプルはイミュータブル(変更不可)であり、configオプションはミュータブル(変更可能)である必要があるため使用できません)。
リストの最初の項目はオプションの値であり、2番目の項目はドキュメント化の目的で使用される説明です。
最後に、configオプションを考慮してextendMarkdownメソッドをリファクタリングします。

    def extendMarkdown(self, md, md_globals):
        ...
        # Add STRONG_EM pattern
        strong_em = MultiPattern(STRONG_EM_RE)
        md.inlinePatterns['strong_em'] = strong_em
        # Add INS_DEL pattern if active
        if self.getConfig('ins_del'):
            ins_del = MultiPattern(INS_DEL_RE)
            md.inlinePatterns['ins_del'] = ins_del

我々は、strongとemphasisのためのMultiPatternクラスのインスタンスを1つだけ作成し、configオプション'ins_del'がTrueの場合、MultiPatternクラスの2番目のインスタンスを作成します。
完全にするために、新しく追加されたコードのすべては次のようになります。

STRONG_EM_RE = r'([*/]{2})(.*?)\2'
INS_DEL_RE = r'([_-]{2})(.*?)\2'

class ConfigExtension(Extension):
    def __init__(self, *args, **kwargs):
        # Define config options and defaults
        self.config = {
            'ins_del': [False, 'Enable Insert and Delete syntax.']
        }
        # Call the parent class's __init__ method to configure options
        super(ConfigExtension, self).__init__(*args, **kwargs)

    def extendMarkdown(self, md, md_globals):
        # Delete the old patterns
        del md.inlinePatterns['strong']
        del md.inlinePatterns['emphasis']
        del md.inlinePatterns['strong_em']
        del md.inlinePatterns['em_strong']
        del md.inlinePatterns['emphasis2']
        del md.inlinePatterns['not_strong']
        # Add STRONG_EM pattern
        strong_em = MultiPattern(STRONG_EM_RE)
        md.inlinePatterns['strong_em'] = strong_em
        # Add INS_DEL pattern if active
        if self.getConfig('ins_del'):
            ins_del = MultiPattern(INS_DEL_RE)
            md.inlinePatterns['ins_del'] = ins_del

変更を保存したら、Pythonインタプリタを開きます:

>>> import markdown
>>> from multiextension import MultiExtension
>>> txt = """
... Some __underline__
... Some --strike--
... Some **bold**
... Some //italics//
... """
... 
>>> # First try it with ins_del set to True
>>> markdown.markdown(txt, extensions=[MultiExtension(ins_del=True)])
"<p>Some <ins>underline</ins>\nSome <del>strike</del>\nSome <strong>bold</strong>\nSome <em>italics</em>"
>>> # Now try it with ins_del defaulting to False
>>> markdown.markdown(txt, extensions=[MultiExtension()])
"<p>Some __underline__\nSome --strike--\nSome <strong>bold</strong>\nSome <em>italics</em>"

Extensionの名前を(文字列として)サポートする

extensionをテストするたびに毎回、extensionをインポートして、Extensionサブクラスのインスタンスを渡さなければならないことに気付かれたかもしれません。
これはextensionを呼び出すための推奨されている方法ですが、時には
ユーザがコマンドラインやテンプレートシステムからMarkdownを呼び出す必要があり、文字列を渡すことしかできないことがあります。

この機能は自由に解放されていて、組み込まれています。
ただし、あなたのユーザーがあなたが定義したExtensionクラスのimportパス(Pythonドット表記法)を知っていて、それを使用する必要があります。
たとえば、上で定義した3つのクラスのそれぞれは、次のように呼び出されます。

>>> markdown.markdown(txt, extensions=['myextension:MyExtension'])
>>> markdown.markdown(txt, extensions=['myextension:MultiExtension'])
>>> markdown.markdown(txt, extensions=['myextension:ConfigExtension'])

パスとクラスの間にはコロン(:)を使用する必要があります。
一方、残りのパスにはドット(.)を使用する必要があります。
"from" importステートメントのimport部分をコロンで置き換えると考えてください。
たとえば、ファイルsomepackage/extensions/foo.pyに定義されているextensionクラスFooExtensionがある場合、import文はfrom somepackage.extensions.foo import FooExtensionにそして文字列ベースの名前は 'somepackage.extensions.foo:FooExtension'になります。

実際には、前のリファクタリングではなく上の各ステップで新しいクラスを作成した場合、3つのextensionはすべて同じモジュール内に存在することができ、それでもすべて別々に呼び出すことができます。
これは、内部でのみ使用される大規模なプロジェクト(おそらくCMS、静的ブログジェネレータなど)の一部として多くのextensionを構築した場合にうまく機能します。

ただし、他の人が彼らのプロジェクトに組み込むためのスタンドアロンモジュールとして、あなたのextensionを配布する
するつもりである場合は、短い名前のサポートを有効にしたい場合があります。
'myextension''myextension:MyExtension'よりも、ユーザーが入力しやすく(ドキュメント化するにも)簡単です。
そして、Python-Markdownに同梱されているすべての組み込みextensionがこのように動作するので、ユーザーは同じことを期待しているでしょう。
この機能を有効にするには、extensionモジュールの最後に以下を追加します。

def makeExtension(*args, **kwargs):
    return ConfigExtension(*args, **kwargs)

このモジュールレベルの関数は、単にExtensionサブクラスのインスタンスを返します。
Markdownに文字列が与えられると、その文字列はPythonのドット表記を使用して、モジュールのインポート可能なパスを指し示すものと見なされます。
文字列にコロンが見つからない場合は、そのモジュールにあるmakeExtension関数を呼び出します。

Pythonインタプリタを再度開いてextensionをテストしましょう:

>>> import markdown
>>> txt = """
... Some __underline__
... Some --strike--
... Some **bold**
... Some //italics//
... """
... 
>>> markdown.markdown(txt, extensions=['myextension'])
"<p>Some __underline__\nSome --strike--\nSome <strong>bold</strong>\nSome <em>italics</em>"

上記のConfigExtensionを使用したので、extensionにconfigオプションを渡してみましょう:

>>> markdown.markdown(
...     txt, 
...     extensions=['myextension'],
...     extension_configs = {
...         'myextension': {'ins_del': True}
...     }
... )
"<p>Some <ins>underline</ins>\nSome <del>strike</del>\nSome <strong>bold</strong>\nSome <em>italics</em>"

特別なことをしていないのにextension_configsキーワードがサポートされていることに注目してください。
extension_configsキーワードの詳細については、ドキュメントを参照してください。

デストリビュション(配布)の準備

setup.pyスクリプトがすでに作成されているので、配布の拡張を準備する上で最も重要な部分は完了しています。
しかし、セットアップスクリプトはかなり基本的でした。
もう少しメタデータを含めることをお勧めします。
具体的には、開発者の名前、電子メールアドレス、プロジェクトのURLです(例については、Pythonドキュメントのsetup スクリプトを書くを参照してください)。
READMEファイルとLICENSEファイルを最低限ディレクトリに含めることもお勧めします。

この時点で、バージョン管理システム(Git、Mercurial、Subversion、Bazaarなど)にコードをコミットし、選択したあなたのシステムをサポートするホストにアップロードすることができます。
その後、ユーザーはpipコマンドを使用してextensionをダウンロードして簡単にインストールできます。
また、もっと簡単なコマンドで、プロジェクトをPython Package Indexにアップロードすることもできます。
あるいは、setup.pyスクリプトで利用可能なサブコマンド(sdistなど)を使用して、ユーザーがダウンロードできるようにファイル(zipまたはtarファイルなど)を作成することもできます。
詳細はこのチュートリアルの範囲を超えていますが、Pythonモジュールの配布に関するPythonのドキュメントとパッケージのビルドと配布に関するSetuptoolsのドキュメントの両方で、利用可能なオプションの説明が提供されています。

結び

このチュートリアルでは、Inline Patternsの使用のみを示していましたが、extension APIには、Preprocessors, Blockprocessors, TreeprocessorsおよびPostprocessorsのサポートも含まれています。
各タイプのプロセッサは、異なる目的、つまり解析プロセスの異なるステージで動作しますが、同じ基本原則が各タイプのプロセッサに適用されます。
実際、単一のextensionが、複数の異なるタイプのプロセッサを変更する可能性があります。

APIドキュメントと様々なビルトインextensionのソースコードを見直すことで、素晴らしいextensionを構築するための十分な情報が得られます。
もちろん、
支援をご希望なら、メーリングリストにお問い合わせください。
そして、他の人々がそれらを見つけることができるように、wikiのリストにあなたのextensionを記載する事を忘れないでください。

ページのトップへ戻る