PySImpleGUIでファイルの更新履歴や簡単なリリースノートを作成

#はじめに
仕事ではチーム開発などはなく、個人でコードを作成することが多いので、
いままでほとんどコードの更新履歴やリリースノートを書いたことがありませんでした。
(リリースしてないのでリリースノートを書いたことがない)

ふとリリース前にバージョン管理はしっかりしておいた方がいいと思い立ちました。

GitHubをローカルで使う方法もありますが、
使うOSがばらばらで使い方を覚えるのも億劫だったので、
簡易的なバージョン管理ツールを作るに至りました。

#開発環境
Windows10
PySImpleGUI 4.29.0
Python 3.7.6
pyinstaller(exe化で使用) 4.1

#GUI
GUIは以下のようになります。今回はテーマがLightBrown3です。

更新履歴は事細かに記載する予定なので、リリースより細かい更新が入ります。
そのため、同じファイル名で二度以上更新する場合もありますので、
更新時間をファイル名に追加し、更新したファイルを別のフォルダにコピーします。
それにより、テキストの履歴と実際のファイルの紐づけを取ります。

リリースノートの「タイトル」は、なんとなく変更の概要が書けるようにしました。
![makereleasenote_maindisp.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680120/7f65e1d1-7cf9-af46-3afd-ac0a67a61394.png)
[更新]
履歴保存時の時間を更新します。
(※押し忘れ防止のために5分おきの自動更新を追加予定)

[選択]
更新対象のファイルを選択します。

[反映]
更新履歴をhistory.txtに書き込みます。
**開発者が具体的に変更点がわかるように書きます。**
同時に、更新時間をファイル名に追加した更新対象ファイルを
historyフォルダにコピーします。

[書き込み]
release.txtにアップデート内容を書き込みます。
**ユーザーに分かりやすい言葉で書きます。**


#コード
以下コードです。

```python:make_note_app.py

import PySimpleGUI as sg
import datetime
import os
import shutil

class MainDisp:
    def __init__(self,now):
        sg.theme("LightBrown3")  #好きなテーマ設定

        self.layout = [
        [sg.Text(text="更新時間"),
         sg.Input(default_text=now, size=(20,1), key=("-TIME-"),
                  use_readonly_for_disable=True),
         sg.Button(button_text="更新", key=("-UPDATE-"))],

        [sg.Text(text="ファイル"),
         sg.InputText(size=(50,1), key='-FILE-'),
         sg.FileBrowse("選択", target=("-FILE-"))],

        [sg.Text(text="更新履歴")],

        [sg.Multiline(size=(200,5), key=("-HIST-"))],

        [sg.Button(button_text="反映", key=("-HANEI-"))],

        [sg.Text(text="リリースノート")],

        [sg.Text(text="タイトル"),
         sg.Input(size=(50,1), key=("-TITLE-"))],

        [sg.Multiline(size=(200,5), key=("-RELEASE-"))],

        [sg.Button(button_text="書き込み", key=("-WRITE-"))]
        ]

        self.window = sg.Window(title="MakeReleaseNote",
                                layout=self.layout,
                                size=(500,400))

    def writefile(self,type):
        time = self.window["-TIME-"].get()
        filename = self.window["-FILE-"].get()

        #更新履歴ver
        if type == "history":
            hist = self.window["-HIST-"].get()
            path = "../history.txt"
    
            if os.path.exists(path):
                '''
                更新した情報をファイルの上部に書き込みたいので、
                一度ファイルの中身を全部読んでから、頭に新しい情報を付け足して再度書き込む
                '''
                with open(path,"r") as f:
                    all = f.read()

                write = "更新時間:{}\n更新ファイル:{}\n変更内容:\n{}\n".format(
                    time,os.path.basename(filename),hist) + all
            else:
                write = "更新時間:{}\n更新ファイル:{}\n変更内容:\n{}\n".format(
                    time,os.path.basename(filename),hist)

            #更新したファイルをhistoryフォルダにコピー
            #ただしファイル名に時刻を反映するために一回形を整え直す
            s_time = datetime.datetime.strptime(time,"%y/%m/%d %H:%M:%S")
            shutil.copy2(filename,"../history/"+
                s_time.strftime("%y%m%d_%H%M%S_")+os.path.basename(filename))

        #リリースノートver
        elif type == "release":
            hist = self.window["-RELEASE-"].get()
            title = self.window["-TITLE-"].get()
            path = "../release.txt"
           
            '''
            更新した情報をファイルの上部に書き込みたいので、
            一度ファイルの中身を全部読んでから、頭に新しい情報を付け足して再度書き込む
            '''
            if os.path.exists(path):
                with open(path,"r") as f:
                    all = f.read()

                write = "File:{}\nTitle:{}\nComment:\n{}\n".format(
                    os.path.basename(filename),title,hist) + all
            else:
                write = "File:{}\nTitle:{}\nComment:\n{}\n".format(
                    os.path.basename(filename),title,hist)

        with open(path,"w") as f:
            f.write(write)

    def main(self):
        while True:
            event, values = self.window.read()
            if event == None:  #closeボタン
                break
            elif event == "-UPDATE-":  #時刻更新ボタン
                self.window["-TIME-"].update(gettimenow())
            elif event == "-HANEI-":  #更新履歴反映ボタン
                self.writefile("history")
            elif event == "-WRITE-":  #リリースノート書き込みボタン
                self.writefile("release")
  
        self.window.close()


#現在時刻をstrで返す関数
def gettimenow():
    time = datetime.datetime.now()
    return time.strftime("%y/%m/%d %H:%M:%S")

if __name__ == "__main__":
    maindisp = MainDisp(gettimenow())
    maindisp.main()

```

#出力ファイル

更新履歴のテキストファイルはこのように最新の情報が上部に記載されます。
![history_txt.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680120/29cb2b94-bd5a-b89e-46d0-21abc21fbc7f.png)

historyフォルダ内は上記の履歴の更新時間と同じ時刻で
更新されたファイルの名前が変更され、コピーされています。
![history_folder.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680120/a09f0d2c-e166-fcdf-396f-354f20c3d74a.png)

リリースノートのテキストファイルは下記のようになります。
タイトルがある分、ちょっと体裁が違います。
リリースノートでは本来ファイル名にバージョンを付与した状態で作成する予定だったので、
更新時間は記載していません。ファイル名が更新時間のようなものです。
(下記の例ではファイル名を同じにしてしまったのでわかりにくいですが…)
![release.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/680120/cb249b0e-1f77-4190-0143-9e42b4fa7507.png)

#おまけ：exe化
pyinstallerをインストールします。

```
pip install pyinstaller
```

--noconsoleを入れると起動時にコンソールがでません。

```
pyinstaller [exe化したいファイルのパス] --noconsole
```

exe化するとdistと言うフォルダの中に、ファイル名と同じフォルダが作成されます。
その中に、ずらっと必要なファイルがあり、そこにexeも存在します。

**本来の位置からずれたディレクトリに作成されるので、
相対パスを使用していた場合エラーになりますのでご注意ください。**

相対パスでなければならない場合は、exeと同じディレクトリにあるファイル全てをコピーして、
あってほしい場所に展開すれば実行可能になります。
**exeだけの移動ではだめです。**

私はアプリ自体がどこにおいても使えるように汎用的であってほしかったので、
絶対パスに書き換えるのではなく、相対パスのまま環境を移動させました。

#おわりに
GitHubなどちゃんとバージョン管理している人にはこんなもの関係ないですが、
何が言いたいかと言うと、このくらいのことならPySImpleGUIでならすぐにできるということです。
思いついたことをすぐに実現できる、PythonとPySImpleGUIの組み合わせは本当におすすめです。