PythonでSublimeTextのスニペットをAtom仕様に変換してみた


やること

SublimeTextで使用していたPythonの自作スニペットをAtom仕様に変換する

動機

最近Atomを使い始めたのだが、SublimeTextのスニペットを1つずつコピペするのが面倒なので自動化を試みた

SublimeTextとAtomのスニペットの違い

SublimeText

  • 1ファイルに1スニペット
  • ![CDATA[]]内の文字列は、raw文字列として扱われる
sublime_sample1.sublime-snippet
<snippet>
    <content><![CDATA[
print('hello world')
]]></content>
    <description>hello world</description>
    <tabTrigger>hello</tabTrigger>
    <scope>source.python</scope>
</snippet>
sublime_sample2.sublime-snippet
<snippet>
    <content><![CDATA[
a = 'foo'
b = a.replace(${1:'foo'}, $2)
c = '\n'
]]></content>
    <description>sample</description>
    <tabTrigger>test</tabTrigger>
    <scope>source.python</scope>
</snippet>

Atom

  • 1ファイル(snippets.cson)に全スニペットをまとめる
  • bodyはraw文字列として扱われない(例えば\nは改行として扱われる)
  • bodyが複数行に渡る場合には、'''(あるいは""")で閉じる必要がある
  • 特殊文字(例えば'\n')を、'\\\\n'のように表記しなければならない(参考
snippets.cson
'.source.python':
    'hello world':
        'prefix': 'hello' 
        'body': "print('hello world')"

    'sample':
        'prefix': 'test'
        'body': '''
            a = 'foo'
            b = a.replace(${1:'foo'}, $2)
            c = '\\\\n'
        '''

コード

  1. globでSublimeTextのスニペットファイルを取得
  2. 正規表現を使用し、必要な情報を抽出
  3. content以外(tabTriggerdescription)はそのまま使用
  4. contentは特殊文字を置換した後、複数行に渡るか、'"を含むかチェックしAtom仕様に加工
  5. 各情報をAtomのスニペットフォーマットに代入し、snippets.csonに書き込み
sblm2atom.py
import os
import glob
import re


def convert_body(body):

    # 特殊文字の置換(この他にも色々ありますが、、、)
    sc1 = [r'\n', r'\t', r'\'', r'\"']
    sc2 = [r'\\\\n', r'\\\\t', r"\\\\'", r'\\\\"']
    for x, y in zip(sc1, sc2):
        body = body.replace(x, y)

    # bodyが複数行の場合
    if len(body.split('\n')) > 1:

        # 各行にTabを追加
        body = '\n'.join(['\t\t\t' + x for x in body.split('\n')])
        body = "'''\n{}\n\t\t'''".format(body)

    # bodyが1行の場合
    else:
        if '\'' in body and '\"' in body:
            body = "'''\n{}\n\t\t'''".format('\t\t\t' + body)
        elif '\'' in body:
            body = '\"{}\"'.format(body)
        else:
            body = '\'{}\''.format(body)

    return body


def main():
    sblm_snippets = glob.glob('*.sublime-snippet')

    fname_out = 'python_snippets.cson'
    with open(fname_out, 'w', encoding='utf-8') as f:
        f.write("'.source.python':" + '\n')

    # 情報を抽出するために必要な正規表現
    body_pattern = r'<!\[CDATA\[(.*)\]\]></content>'
    prefix_pattern = r'<tabTrigger>(.*)</tabTrigger>'
    desc_pattern = r'<description>(.*)</description>'

    atom_snippet_fmt = '''
    '{}':
        'prefix': '{}'
        'body': {}
    '''

    for sblm_snippet in sblm_snippets:

        # snippetの読み込み
        with open(sblm_snippet, 'r', encoding='utf-8') as f:
            src = f.read()

        # 正規表現で必要な情報を抽出
        body = re.findall(body_pattern, src, flags=re.DOTALL)[0].strip()
        body = convert_body(body)
        prefix = re.findall(prefix_pattern, src, flags=re.DOTALL)[0]
        desc = re.findall(desc_pattern, src, flags=re.DOTALL)
        desc = desc[0] if desc else 'no description'

        print(desc)
        print(prefix)
        print(body)
        print()

        # Atom snippet formatに抽出した情報を挿入
        atom_snippet = atom_snippet_fmt.format(desc, prefix, body).strip('\n')

        # ファイルに書き込み
        with open(fname_out, 'a', encoding='utf-8') as f:
            f.write(atom_snippet + '\n')


if __name__ == '__main__':
    main()

テスト

試しに上述のsublime_sample1.sublime-snippetsublime_sample2.sublime-snippet
を変換してみた⇩

snippets.cson
'.source.python':
    'hello world':
        'prefix': 'hello'
        'body': "print('hello world')"

    'sample':
        'prefix': 'test'
        'body': '''
            a = 'foo'
            b = a.replace(${1:'foo'}, $2)
            c = '\\\\n'
        '''

正しく変換されていることが確認できた