対話シェルをカスタムしてメモアプリを作ってみる



ScrapyやDjangoにあるような専用の対話シェルを再現してみます。
あと簡単なメモアプリを作ってみます。

環境
python3.9

参考
Scrapyのソースコード
https://github.com/scrapy/scrapy/tree/master/scrapy

##codeモジュールでインタラクティブシェルを起動する
https://docs.python.org/ja/3/library/code.html

codeモジュールによると、
`code モジュールはread-eval-print (読み込み-評価-表示)ループをPythonで実装するための機能を提供します。対話的なインタプリタプロンプトを提供するアプリケーションを作るために使える二つのクラスと便利な関数が含まれています。`

とのことらしいです。

どうやらこのモジュールを使えばインタラクティブシェルをあれやこれやできるらしいです。


```python:scrapy/utils/console.py
def _embed_standard_shell(namespace={}, banner=''):
    """Start a standard python shell"""
    import code
    try:  # readline module is only available on unix systems
        import readline
    except ImportError:
        pass
    else:
        import rlcompleter  # noqa: F401
        readline.parse_and_bind("tab:complete")

    @wraps(_embed_standard_shell)
    def wrapper(namespace=namespace, banner=''):
        code.interact(banner=banner, local=namespace)
    return wrapper

```


interactメソッドを使用することで対話シェルをエミュレートしているようですね。

```python3
import code

code.interact()
```


試してみましょう。
![スクリーンショット 2020-12-12 12.42.37.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573500/1b54bcd2-944a-708c-d201-86d5c6bc58cc.png)

たった2行のcodeでインタラクティブシェルをエミュレートできました。

```python3
code.interact(banner=None, readfunc=None, local=None, exitmsg=None)
```

https://docs.python.org/ja/3/library/code.html

この引数のうち、scrapyで使われてるbannnerとlocalについて説明します。

###banner
string型で渡します。
この引き数に渡した文字がシェル起動時に表示されます。

先ほどのコードを少し改変してbannerを変えてみましょう。

```python
import code

banner = """
Hello!!
My Python Interactive Shell!!
"""

code.interact(banner=banner)
```

![スクリーンショット 2020-12-12 13.06.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573500/a8b50f6a-8208-032a-e2de-b0e2d9e3e773.png)

表示画面が切り替わりました!
これで好きな文章を表示させることができますね。

###local

local引数には辞書型を渡します。

```python
import code

#起動時に表示される
banner = """
Hello!!
My Python Interactive Shell!!
"""

#メソッドの実行
local = {}
def hello_hi():
    return "Hello"

local["hello_hi"] = hello_hi #キーにコマンド名を指定
code.interact(banner=banner, local=local)
```

先程のコードを少し変えてみました。
辞書方として定義したlocalにhello_hiをキーとして、
hello_hiメソッドを参照します。

local変数に上記のような形で渡すことで、インタラクティブシェル側でimportしなくても
直接メソッドを実行できるようになります。

では実行してみましょう。

![スクリーンショット 2020-12-13 11.00.43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573500/23800147-87cd-12f8-8ffa-2ff553f85868.png)

実行できました！！
わざわざimportする必要なんてありませんね。


##簡易メモアプリを作ってみる

上記を踏まえて自分だけの対話型一言メモアプリを作ってみます。

実装する機能は以下2つだけです。

・書き込み
・内容の一覧の表示
・各メソッドの説明

内容の一覧は、毎回消えてしまってはメモとして意味がありません。
なので、書き込んだ内容はsqliteに保存するようにします。

上記の機能を備えたMEMOクラスをいかに示します。
###MEMOクラス

```python:shell.py
import sqlite3
import pandas as pd


class MEMO:
    def __init__(self):
        self.conn = sqlite3.connect("memo.db")
        self.c = self.conn.cursor()
        sql = 'SELECT count(*) FROM sqlite_master WHERE type="table" AND name="memo_table"'
        self.c.execute(sql)
        sql = self.c.fetchone()[0]
        if sql == 0:
            self.c.execute("""
            CREATE TABLE memo_table (id INTEGER PRIMARY KEY, 
            date TIMESTAMP DEFAULT (datetime(CURRENT_TIMESTAMP,'localtime')),
            memo TEXT)
            """)


    def write(self, text: str = ""):
        """
        一言メモを記入する
        """
        self.c.execute("""
        INSERT INTO memo_table (memo) values (?)        
        """, (text,))
        self.conn.commit()
        return "記入しました。"

    def display(self):
        """
        メモの一覧を表示
        """
        self.c.execute("""
        SELECT * FROM memo_table 
        """)
        val = {"Date": [], "Memo": []}
        for id, date, memo in self.c.fetchall():
            val["Date"].append(date)
            val["Memo"].append(memo)
        return pd.DataFrame(val)

    def help_memo(self):
        """
        メソッドの説明を表示
        """
        help_comment = \
            """
        write(text: str)
            arg:text
            一言メモを記入する
        display()
            メモの一覧を表示する
        """
        print(help_comment)


```

まず,__init__でdbに接続します。
このとき、テーブルがなかったら新規に作成するようにします。


記入はwrite("記入するメモ")というふうに書き込みます。
うまく行くとシェル上に"記入しました"と表示されます。


表示はdisplay()を使用します。
なんとなくデータフレームの方がみやすそうだったので、pandasを使ってデータフレームに指定ます。


help_memoで各メソッドの簡単な説明を表示できます。


##シェルを起動しよう

```python
def Shell(banner='', namespace={}):
    import code
    code.interact(banner=banner, local=namespace)  # namespace: dictを渡す
```

bannerは起動時に表示する文字です。
localにコマンドをキーとした辞書型を渡してやります。


```python

if __name__ == '__main__':
    var = {}  # キー(実行コマンド): メソッドの形で渡す
    m = MEMO()
    # コマンドを設定する
    var["write"] = m.write
    var["display"] = m.display
    var["help_memo"] = m.help_memo

    banner = """
　＿人人人人＿
　＞　 My Python Interactive Shell!! 　＜
　￣Y^Y^Y^Y￣
    """  # 起動時に表示される

    Shell(banner=banner, namespace=var)  # shellの起動
```

メイン部分はこんな感じです。
辞書型のvarに実行コマンドを渡していきます。

また、ここでインスタンスを作成しておくことで、シェル側でわざわざインスタンスを作成する必要が内容にしておきます。

こうして定義した辞書をShellに渡してやります。

準備が整ったのでシェルを起動してみましょう。

![スクリーンショット 2020-12-13 12.58.36.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/573500/99821ec0-7287-c86c-870b-ff679629a35c.png)


ちゃんと記入できていますね。
もちろん元は対話シェルなので、他のpythonのモジュールも問題なく使えます。

今回作成したメモアプリのソースコードはこちらです。
https://github.com/Ru-1218/Memo-Shell