jp_DesignDoc - social-snippet/social-snippet GitHub Wiki

Social Snippet Design Doc

1. What

Social Snippet は各種開発環境から横断的に利用することができるスニペットシステムです。主に ICPC などのプログラミングコンテストで出題される問題を解くために活用することを想定して開発しています。

2. Why

プログラミングコンテストのライブラリ事情について

プログラミングコンテストで解答となるソースコードを提出するときは、1つの問題に対して1つのソースファイルを提出するという形式が一般的です。また、プログラムを実行して採点するような形式のプログラミングコンテストでは、標準ライブラリ以外のライブラリファイルの使用は認められていません。そのため、競技者は各自で用意したライブラリをコピーしたり直接書き写すことによって1つのソースファイルにまとめる必要があります。

プログラミングコンテストにおける「ライブラリ」は、テンプレートもしくはスニペットのように断片的なテキスト形式として利用できる必要があり、一般的なプログラミングにおける「ライブラリ」と比べると、その在り方が限定されているのが特徴です。

ソーシャルなスニペットシステム

Social Snippet はライブラリをスニペットという形式で配布するためのシステムです。ここでいうスニペットとは、別のソースコードに埋め込むことが可能な断片的なソースコードを指しており、その中でも特にプログラミングコンテストでの利用を想定したものを主な対象として取り扱います。

システムでは、スニペットの集まりをリポジトリという単位で扱います。開発者は自分のライブラリをリポジトリにまとめることで、ユーザーにライブラリを配布することができるようになり、逆にユーザーは必要なリポジトリを検索してインストールするだけで、各自の環境でライブラリを利用することができるようになります。

スニペットの操作を行うシステムとは別に、リポジトリを配布・検索したりするためのサービスとしてレジストラシステムを提供します。このシステムはリポジトリのメタ情報(リポジトリの名前やURL、対象とするプログラミング言語など)を保持し、実際のソースファイルなどについては、"GitHub" などの外部サービスを補助的なインフラとして利用します。

3. How

スニペットの挿入機能について

基本的なアイデア

スニペットを挿入するときは、ソースコード中のコメントとしてスニペットファイルを指定するタグを記述します。例えば、以下のような snip_func という関数を含むソースファイル snip_func.c があり:

    // file: snip_func.c
    void snip_func() {
      // 処理
    }

同一のディレクトリに、以下のようなソースファイル main.c があるとき:

    // file: main.c
    #include <stdio.h>

    // @snip <./snip_func.c>

    int main() {
      snip_func();
      return 0;
    }

main.cの4行目にあるコメントに含まれる @snip <...> の部分がスニペットファイルを指定するためのタグとなります。この場合、snip_func.c は main.c に対して以下のように挿入されます:

    // file: main.c
    #include <stdio.h>

    // @snippet <./snip_func.c>
    // file: snip_func.c
    void snip_func() {
      // 処理
    }

    int main() {
      snip_func();
      return 0;
    }

スニペットを挿入した後も、@snippet <...> という形でタグが残ることに注意してください。このタグを残すことにより、既に挿入されているスニペットを再度挿入してしまうことを防ぐことができます。

CLI(コマンドラインインターフェース)

ssnip コマンド
    $ ssnip [options...]

ssnip コマンドは与えられたソースファイルに含まれるスニペットタグを実際のスニペットコードに展開します。このコマンドはアプリケーション(エディタの拡張機能など)から呼び出されるものであり、このコマンドをアプリケーションのユーザーが意識することなくアプリケーションを利用することができるものとします。

使用例: 標準入出力
    $ cat target_file | ssnip > snipped_file

基本的な使用例です。標準入力からスニペット挿入対象となるソースファイル(target_file)を受け取り、標準出力にスニペット挿入後のソースファイル(snipped_file)を書き出します。

オプション: ファイルパスを指定
    $ ssnip -i path/to/target_file -o path/to/snipped_file

操作対象の入出力ファイルを指定するためのオプションです。

オプション: パッチファイル
    $ cat target_file | ssnip -p > patch_file
    $ ssnip -p -i target_file -o patch_file

スニペット挿入前後の差分情報を含むテキストデータを出力するオプションです。通常 ssnip コマンドは与えられたソースファイルに対して、スニペット挿入後のソースファイル全体を返します。このオプションは巨大なファイルなどを操作するときに、パッチファイルを返すことで全体を書き換えずに済むようにするためのオプションです。

オプション: デーモン
    $ ssnip -d

プロセスを立ち上げる時間を節約するために、バックグラウンドで入力を待ち受けて、ファイルが入力されたらスニペット挿入後のソースファイルもしくはパッチファイル($ ssnip -d -p)を出力するサービスを起動します。ライフサイクルはアプリケーションの起動から終了までとなります。

sspm コマンド
    $ sspm {sub-command} [options...]

sspm コマンド(S ni P pet M anager)はユーザーがスニペットリポジトリを管理するためのコマンド群です。リポジトリの管理操作ごとにサブコマンド($ sspm install など)を持ちます。

sspm-install
    $ sspm install {リポジトリ名}

レジストラから指定した名前で登録されているリポジトリを探して、そのようなリポジトリが存在する場合はローカルにインストールを行います。

sspm-publish
    $ sspm publish {リポジトリのURL}

レジストラに新しくリポジトリを登録するためのコマンドです。URL には GitHub の Git リポジトリなどを指定することができます。

sspm-unpublish
    $ sspm unpublish {削除リポジトリ名}

レジストラから指定したリポジトリを削除するためのコマンドです。削除するにはリポジトリの所有者であることを示す必要があり、GitHub を利用している場合であれば、オーナーかコラボレータの権限が必要となります。実際にはレジストラのウェブインターフェース上で OAuth 認証を行い、依頼者の権限を確認したうえで自動的に削除を行います。このコマンドは認証用の URL を表示します。

sspm-search
    $ sspm search {検索キーワード}

レジストラもしくはインストール済みのリポジトリから指定したキーワードにマッチするリポジトリを検索し一覧として表示します。例えば、以下のようにキーワードを指定したときは:

    $ sspm search math

以下のような検索結果を表示します:

    math-gcd: {説明}
    math-lcm: {説明}
    math-prime: {説明}
    super-math-library: {説明}
      .
      .
      .
sspm-complete
    $ sspm complete {補完キーワード}

sspm-complete コマンドはアプリケーションに対して、リポジトリやスニペットコードのパスを補完するときに必要な情報を提供するためのコマンドです。

リポジトリ

リポジトリについて
基本的な構成

リポジトリは設定ファイルやスニペットのソースコードなどから構成されます。以下は基本的な構成例です:

    my_repo/
      |
      |- snippet.json
      |- Snipfile
      |- file_1.py
      |- file_2.py
      -- sub_dir/
           |
           -- sub_file_3.py

ここで、snippet.json はリポジトリのメタ情報(リポジトリの名前や対象のプログラミング言語など)を記述する設定ファイルであり、レジストラはこのファイルに記述されている情報を元にリポジトリを管理します。

Snipfile はリポジトリが依存している外部リポジトリを指定する設定ファイルです。ユーザーがあるリポジトリをインストールするときに、依存しているリポジトリがインストールされていない場合は、そのリポジトリも同時にインストールされます。

ソースディレクトリを指定する場合

snippet.json によって、ソースディレクトリを指定することも可能です。以下はその場合の構成例となります:

    my_repo/
      |
      |- snippet.json
      |- Snipfile
      -- source_dir/
           |- file_1.py
           |- file_2.py
           -- sub_dir/
                |
                -- sub_file_3.py

この場合、source_dir の外部にあるファイルはスニペットとして挿入できなくなることに注意してください。例えば、上記のリポジトリにおいて source_dirsub_dir に含まれる sub_file_3.py というファイルをスニペットとして挿入する場合は @snip <my_repo:sub_dir/sub_file_3.py> と記述します。

設定ファイル: snippet.json

snippet.json は JSON 形式のファイルです。以下はその基本的な記述例になります:

    {
      "name": "my-snippet",
      "description": "ライブラリの説明",
      "language": "C++",
      "url": "https://github.com/my/repo",
      "main": "path/to/source_dir"
    }

name プロパティはリポジトリの名前を表します。このプロパティは必須の情報であり、レジストラに登録されている他のリポジトリと重複しないような名前を指定しなければなりません。

main プロパティにはスニペットのソースコードが置かれているディレクトリを指定することができます。ここで指定したディレクトリの外部にあるファイルをスニペットとして挿入することはできません。

⚠️ **GitHub.com Fallback** ⚠️