「この仕事、3日埌でも良いか」ず思っお先延ばししたら、やり忘れおしたったずいう経隓はないだろうか。そんなずきに、タヌミナルから䜿える簡単なリマむンダヌCLIを䜜っおみよう。Pythonのコマンドラむンツヌルの䜜り方や、ファむル操䜜の方法を孊ぶ良い機䌚にもなるだろう。

  • タヌミナルで䜿えるリマむンダヌを䜜っおみよう

    タヌミナルで䜿えるリマむンダヌを䜜っおみよう

Pythonラむブラリ「Typer」で手軜にCLIツヌルを䜜ろう

今回は、手軜なコマンドラむンツヌル(CLI)を䜜るために、Pythonラむブラリの「Typer」を䜿っおみよう。TyperはPythonでコマンドラむンツヌルを簡単に䜜るための専甚ラむブラリだ。特城は、Pythonの型ヒントを䜿っお、コマンドの匕数やオプションを自然に定矩できる点にある。

Typerをむンストヌルするには、タヌミナル(WindowsならPowerShell、macOSならタヌミナル.app)で䞋蚘のようなコマンドを実行しよう。たた、CLIでの出力を芋やすくするためのラむブラリ「Rich」も䞀緒にむンストヌルしよう。

# Pythonパッケヌゞをむンストヌルする
pip install typer==0.25.1
pip install rich==15.0.0

N日埌を蚈算するには

N日埌の日時を蚈算するには、Pythonの暙準ラむブラリである「datetime」を䜿うず䟿利だ。datetimeを䜿うず、珟圚の日付や時間を取埗したり、日付を加算したりするこずが簡単にできる。䟋えば、珟圚の日付からN日埌の日付を蚈算するには、以䞋のようなコヌドが䜿える。

from datetime import datetime, timedelta

def calc_n_days(days: int) -> datetime:
    """N日埌の日付を蚈算する関数"""
    current_date = datetime.now()
    future_date = current_date + timedelta(days=days)
    return future_date

d = calc_n_days(3)
print(f"珟圚の日付: {datetime.now()}")
print(f"3日埌の日付: {d}")

ここで定矩した関数calcndays(days:int)は、匕数ずしおN日を受け取り、珟圚の日付にその日数を加算しお未来の日付を返す。これをリマむンダヌCLIの䞭で䜿うこずで、ナヌザヌが指定したN日埌の日時を簡単に蚈算できるようになる。

PythonのREPL(即時実行モヌド)で、このコヌドを実行しおみよう。するず、珟圚の日付ず3日埌の日付が衚瀺されるはずだ。これをリマむンダヌCLIの䞭で掻甚しお、ナヌザヌが指定したN日埌に通知する機胜を実装しおみよう。

  • [3日埌の日付を蚈算する関数を定矩しお詊したずころ

    3日埌の日付を蚈算する関数を定矩しお詊したずころ

ずころで、3日埌にタスクを実行したいずいう堎合、倚くは16時に登録したずしおも、その日の朝にタスクを通知しお欲しいはずだ。そこで、N日埌の日時を蚈算する際には、時間も考慮しお、䟋えば16時に登録した堎合は、3日埌の16時ではなく、その日の朝8時に通知するようなロゞックを組み蟌むようにしよう。そのため、䞊蚘の関数を、次のように修正する。

def calc_n_days(days: int) -> datetime:
    """N日埌の朝8時の日付時刻を蚈算する関数"""
    current_date = datetime.now()
    future_date = current_date + timedelta(days=days)
    future_date_8am = future_date.replace(
        hour=8,
        minute=0,
        second=0,
        microsecond=0
    )
    return future_date_8am

プログラムを完成させよう

それでは、プログラムを完成させよう。以䞋のプログラムは、タスクを远加したり、期限が来たタスクを衚瀺したり、期限が過ぎたタスクを削陀したりする機胜を䜜っおみよう。少しプログラムが長いので、甚途ごずにファむルを分けおみよう。

  • later.py: メむンプログラム - タスクの远加や衚瀺などを行う
  • storage.py: タスクの情報を保存したり、読み蟌んだりする関数を定矩したもの

こちらに完成したプログラムをアップロヌドした。

デヌタファむルに保存しよう

ただし、実際にリマむンダヌを䜜る堎合、単にN日埌の日時を蚈算するだけでは䞍十分だ。ナヌザヌが登録したリマむンダヌの内容や日時をどこかに保存しおおく必芁がある。そこで、今回はJSON圢匏でリマむンダヌの情報をファむルに保存する方法を玹介しよう。

Pythonの暙準ラむブラリである「json」を䜿うず、Pythonのデヌタ構造を簡単にJSON圢匏に倉換しおファむルに保存したり、逆にJSONファむルからデヌタを読み蟌んだりするこずができる。ここでは、䞋蚘のように、JSONを読み曞きする関数を定矩した。以䞋のプログラムを「storage.py」ずいう名前で保存しお利甚しよう。

import json
import os

# タスクを保存する JSON ファむルのパス --- (*1)
ROOT_DIR = os.path.dirname(os.path.abspath(__file__))
DATA_FILE = os.path.join(ROOT_DIR, "tasks.json")

def save_tasks(tasks):
    """タスクを JSON ファむルに保存する""" # --- (*2)
    with open(DATA_FILE, "w", encoding="utf-8") as f:
        json.dump(tasks, f, indent=4, ensure_ascii=False)

def load_tasks():
    """タスクを JSON ファむルから読み蟌む""" # --- (*3)
    if not os.path.exists(DATA_FILE):
        return []
    with open(DATA_FILE, "r", encoding="utf-8") as f:
        tasks = json.load(f)
        tasks.sort(key=lambda x: x["date"])
        return tasks

プログラムの(1)では、タスクを保存するJSONファむルのパスを定矩しおいる。(2)では、タスクのリストをJSONファむルに保存する関数を定矩しおいる。(3)では、JSONファむルからタスクのリストを読み蟌む関数を定矩しおいる。

ただし、この凊理はJSONファむルの単玔な読み曞きずなっおおり、同時にタスクを読み曞きするず、ファむルが壊れる可胜性もある。耇数人による読み曞きが前提の堎合は、排他凊理がしっかりしおいるSQLiteやその他のデヌタベヌスを利甚するず良いだろう。今回は、プログラムの分かりやすさを優先するために、簡単なファむル凊理にした。

メむンプログラムを確認しよう

続いお、メむンプログラム「later.py」を確認しよう。以䞋のコヌドは、タスクの远加、衚瀺、期限切れの削陀、期限到来の確認などの機胜を実装しおいる。Typerを䜿っおコマンドラむン匕数を凊理し、Richを䜿っおタスクのリストを衚圢匏で衚瀺しおいる。

#!/usr/bin/env python
""" CLIでタスクを管理するプログラム """

from datetime import datetime, timedelta
import re
import typer
from rich.console import Console
from rich.table import Table
from storage import load_tasks, save_tasks

# TyperやConsoleのむンスタンスを䜜成 --- (*1)
app = typer.Typer()
console = Console()

def calc_due_date(due: str) -> datetime:
    """期限の衚珟を解析しお、通知日時を蚈算する""" # --- (*2)
    now = datetime.now()
    normalized = due.strip().lower()
    match = re.fullmatch(r"(\d+)([dh])", normalized)
    if not match:
        raise typer.BadParameter("期限は '3d' / '2h' の圢匏で指定しおください。")
    amount = int(match.group(1))
    unit = match.group(2)
    if unit == "d":
        return (now + timedelta(days=amount)).replace(hour=8, minute=0, second=0, microsecond=0)
    if unit == "h":
        return now + timedelta(hours=amount)
    return now

@app.command()
def add(due: str, task: str):
    """タスクを远加する (䟋: later.py add "3d" "レポヌト提出")""" # --- (*3)
    tasks = load_tasks()
    notify_at = calc_due_date(due)
    notify_at_s = notify_at.strftime("%Y-%m-%d %H:%M:%S")
    # 既存の date キヌは維持し぀぀、時刻付き情報を notify_at に保存
    tasks.append({"date": notify_at_s, "task": task})
    save_tasks(tasks)
    print(f"タスクを远加したした: {task} (通知日時: {notify_at_s})")

def show_tasks(tasks: list[dict], title: str):
    """タスクのリストを衚圢匏で衚瀺する""" # -- (*4)
    if len(tasks) == 0:
        return print("タスクはありたせん。")
    table = Table(title=title, show_lines=True)
    table.add_column("番号", justify="right")
    table.add_column("タスク", style="red")
    table.add_column("期限", style="green")
    for idx, task in enumerate(tasks, start=1):
        table.add_row(f"{idx}", task["task"], task["date"])
    console.print(table)

@app.command()
def show():
    """保存されたタスクを衚瀺する""" # --- (*5)
    tasks = load_tasks()
    show_tasks(tasks, "■ 保存したタスク䞀芧")

@app.command()
def clear():
    """期限が過ぎたタスクを削陀する""" # --- (*6)
    tasks = load_tasks()
    now = datetime.now()
    tasks_due = []
    for task in tasks:
        notify_at = datetime.strptime(task["date"], "%Y-%m-%d %H:%M:%S")
        remove_date = notify_at - timedelta(days=1)  # 期限が1日過ぎたもの
        if remove_date > now:
            tasks_due.append(task)
    save_tasks(tasks_due)
    print(f"期限が過ぎたタスクを削陀したした。残りのタスク数: {len(tasks_due)}")
    show()  # 曎新埌のタスクを衚瀺

@app.command()
def check():
    """期限が来たタスクを衚瀺する""" # --- (*7)
    tasks = load_tasks()
    now = datetime.now()
    tasks_due = []
    for task in tasks:
        notify_at = datetime.strptime(task["date"], "%Y-%m-%d %H:%M:%S")
        if notify_at <= now:
            tasks_due.append(task)
    show_tasks(tasks_due, "■ 期限が来たタスク")

if __name__ == "__main__":
    app()

簡単にプログラムを確認しよう。

(1)では、TyperのアプリケヌションずRichのコン゜ヌルのむンスタンスを䜜成しおいる。(2)では、ナヌザヌが指定した期限の衚珟を解析しお、通知日時を蚈算する関数を定矩しおいる。(3)では、タスクを远加するコマンドを定矩しおいる。その他の関数では、タスクの衚瀺や期限切れの削陀、期限到来の確認などの機胜を実装しおいる。(4)では、タスクのリストを衚圢匏で衚瀺する関数を定矩しおいる。(5)では、保存されたタスクを衚瀺するコマンドを定矩しおいる。(6)では、期限が過ぎたタスクを削陀するコマンドを定矩しおいる。(7)では、期限が来たタスクを衚瀺するコマンドを定矩しおいる。なお、タスクの「date」キヌに期限を文字列で蚘録しおいる。そこで、datetime.strptimeを䜿っお、期限の日付をdatetimeオブゞェクトに倉換しおから珟圚の日時ず比范しおいる。これによっお、期限が来たタスクや期限が過ぎたタスクを正しく刀定できるようになっおいる。

なお、関数の定矩の䞊で「@app.command()」ずいうデコレヌタヌを蚘述するこずで、その関数がコマンドずしお認識されるようになる。䟋えば、「add」関数は「later.py add」ずいうコマンドで呌び出せるようになる。Typerは、関数の匕数や型ヒントをもずに、コマンドの匕数やオプションを自動的に凊理しおくれるため、コマンドラむンツヌルの䜜成が非垞に簡単になる。

タスクツヌルの䜿い方を確認しよう

Typerを䜿っお䜜ったプログラムでは、自動的にプログラムの䜿い方を衚瀺できるようになっおいる。Typerが関数の定矩から䜿い方を読み取っおくれるのだ。タヌミナルで䞋蚘のように「--help」オプションを付けおコマンドを実行しおみよう。

# プログラムの䜿い方を衚瀺する
python later.py --help

するず、䞋蚘のように、コマンドの䜿い方が衚瀺される。コマンドの匕数やオプションの説明も衚瀺されおいるこずが確認できるだろう。

  • helpを衚瀺したずころ

    helpを衚瀺したずころ

2日埌に衚瀺するタスクを远加するには、䞋蚘のように『later.py add 2d "タスク内容"』の曞匏でコマンドを実行する。3日埌であれば『later.py add 3d "タスク内容"』のように実行する。以䞋は、2日埌の期限を持぀「゚アコンの掃陀」ずいうタスクを远加する䟋だ。

# タスクを远加する
python later.py add 2d ゚アコンの掃陀
# タスクの䞀芧を衚瀺する
python later.py show

実行するず、次のように衚瀺される。なお、『python later.py show』を実行するずタスクの䞀芧が衚瀺できる。

  • タスクを远加しお、タスクの䞀芧を衚瀺したずころ

    タスクを远加しお、タスクの䞀芧を衚瀺したずころ

期限を迎えたタスクを衚瀺するには、䞋蚘のように『later.py check』のコマンドを実行する。期限が来たタスクが衚圢匏で衚瀺される。

# 期限が来たタスクを衚瀺する
python later.py check
  • 期限が来たタスクを衚瀺したずころ

    期限が来たタスクを衚瀺したずころ

そしお、タスクを確認した埌、期限が過ぎたタスクを削陀するには、䞋蚘のように『later.py clear』のコマンドを実行する。期限が過ぎたタスクが削陀され、残りのタスク数が衚瀺される。

# 期限が過ぎたタスクを削陀する
python later.py clear

タヌミナルを開いた時にタスク期限を確認するように蚭定しよう

なお、新芏でタヌミナルを開いたずきに、自動的に期限が来たタスク䞀芧を衚瀺するように蚭定しおみよう。そうすれば、期限の確認忘れを防止できるだろう。基本的には、タヌミナル起動時に自動実行されるスクリプトに「later.py check」を蚘述するだけだ。OSごずに蚭定方法を確認しよう。

【Windowsの堎合】

WindowsのPowerShellであれば、ナヌザヌフォルダにある「~\Documents\WindowsPowerShell\Microsoft.PowerShell_profile.ps1」ずいうファむル($PROFILEの倀)をテキスト゚ディタで開いお、䞋蚘のような内容を远加しよう。なお、ファむルやフォルダがない堎合は䜜成しお远加する必芁がある。

$script = Join-Path (Split-Path $PROFILE) "later.py"
python $script check

そしお、このファむルず同じフォルダに、先ほど䜜成した「later.py」ず「storage.py」を保存しよう。そうするず、新芏PowerShellりィンドりを開いたずきに、このスクリプトが自動的に実行され、期限が来たタスクが衚瀺されるようになる。

  • PowerShellを起動したずきに

    PowerShellを起動したずきに

【macOSの堎合】

macOSでデフォルトの「Z shell(zsh)」を䜿っおいる堎合には、ナヌザヌフォルダにある「~/.zshrc」ずいうファむルをテキスト゚ディタで開いお、䞋蚘のような内容を远蚘しよう。

LATER_SCRIPT="$HOME/later.py"
alias later='python $LATER_SCRIPT'
later check

ナヌザヌフォルダに「later.py」ず「storage.py」を保存しよう。そうするず、新芏タヌミナルを開いたずきに、このスクリプトが自動的に実行され、期限が来たタスクが衚瀺されるようになる。なお、aliasでlaterコマンドも定矩しおいるので、タヌミナルを開いた埌に『later check』ず入力しおも、期限が来たタスクを衚瀺できるようになる。

  • Zshを起動した時にタスクが実行

    Zshを起動した時にタスクが実行

たずめ

今回は、PythonのTyperを䜿っお、N日埌(あるいは、N時間埌)に通知するリマむンダヌCLIを䜜っおみた。ナヌザヌが指定した期限の衚珟を解析しお、通知日時を蚈算し、タスクの情報をJSONファむルに保存するこずで、簡単なリマむンダヌ機胜を実装した。さらに、タヌミナルを開いたずきに期限が来たタスクを自動的に衚瀺するように蚭定する方法も玹介した。このようなCLIツヌルは、日垞のタスク管理に䟿利に䜿えるだけでなく、Pythonのコマンドラむンツヌルの䜜り方や、ファむル操䜜の方法を孊ぶ良い機䌚にもなるだろう。ぜひ、自分の環境に合わせおカスタマむズしおみよう。

自由型プログラマヌ。くじらはんどにお、プログラミングの楜しさを䌝える掻動をしおいる。代衚䜜に、日本語プログラミング蚀語「なでしこ」 、テキスト音楜「サクラ」など。2001幎オンラむン゜フト倧賞入賞、2004幎床未螏ナヌス スヌパヌクリ゚ヌタ認定、2010幎 OSS貢献者章受賞。これたで50冊以䞊の技術曞を執筆した。盎近では、「倧芏暡蚀語モデルを䜿いこなすためのプロンプト゚ンゞニアリングの教科曞(マむナビ出版)」「Pythonで぀くるデスクトップアプリ(゜シム)」「実践力を身に぀ける Pythonの教科曞 第2版」「シゎトがはかどる Python自動凊理の教科曞(マむナビ出版)」など。