【Python】バレットジャーナルをDynalistで実現する
どんなバレットジャーナルを作ったか
カレンダーやタスクリストなどは、「何が使いやすいと感じるか」に個人差が大きい。以前は、私は予定日の決まっているものと繰り返しのあるものはGoogleカレンダー、日々のタスクリストはB6判の横罫ノートに手書きで書いておき、終わったら消すという方法をとっていた。
その後、クアデルノ用に富士通が配っているスケジューラや、YouTubeチャンネル「My Deep Guide」の運営者Vojaの販売している"MDO"(My Deep Guide Organizer)を使ってみたが、日々の予定ページは使うものの、週間予定や月間予定など、それ以上のものはどうしても馴染まなかった。
そこで試してみたのが、バレットジャーナルみたいなものをアウトラインプロセッサーの「Dynalist」で実現したもの。こんなのだ。
さて、ここで問題になるのが、この雛形を作る手間だ。これを全部タイプしていたら、途中で嫌になってくる。そこでPythonを使ってみよう。
なお、コードを見たい人は読み飛ばして、最後のところにジャンプしてほしい。コード生成には、「Claude 3.5 Sonnet」を利用した。
とりあえずExcel VBAでやってみる
自分では最初から最後までコードを書くことはできない。そこで、今回は日付から文字列を作るところまで、まずExcel VBAでオリジナル関数を作って「29/Jul 2024 Mo」などの文字列を簡単に出力できるようにした。VBA無しでも関数を駆使すればなんとかなるが、IF関数の入れ子が鬱陶しくて、むしろ面倒。
作成できた項目をPythonで処理する
E列の値をコピーしてAIに与え、OPML形式に変換して、Dynalistで読み込めばよい。実際には、E列には数式が入っているので、F列に値だけコピーしてそちらをコピーする。
どんなOPML形式に変換すればよいかは、自分でDynalistからの出力を見て、AIに指示を与えた。
で、できたものがこれ。
import re
from datetime import datetime, timedelta
import pyperclip
import xml.etree.ElementTree as ET
def process_date_list(date_list):
grouped_dates = []
current_group = []
current_year = ""
for date_str in date_list:
date, day = date_str.rsplit(' ', 1)
current_group.append(date_str)
if day == 'So': # End of the week
start_date = current_group[0].split(' ')[0]
end_date, year = current_group[-1].rsplit(' ', 1)
if year != current_year:
weekly_header = f"{start_date} - {end_date} {year} wöchentlich"
daily_header = f"{start_date} - {end_date} {year} täglich"
current_year = year
else:
weekly_header = f"{start_date} - {end_date} wöchentlich"
daily_header = f"{start_date} - {end_date} täglich"
grouped_dates.append(('weekly', weekly_header, daily_header, current_group))
current_group = []
# Handle the last group if it doesn't end with 'So'
if current_group:
start_date = current_group[0].split(' ')[0]
end_date, year = current_group[-1].rsplit(' ', 1)
if year != current_year:
weekly_header = f"{start_date} - {end_date} {year} wöchentlich"
daily_header = f"{start_date} - {end_date} {year} täglich"
else:
weekly_header = f"{start_date} - {end_date} wöchentlich"
daily_header = f"{start_date} - {end_date} täglich"
grouped_dates.append(('weekly', weekly_header, daily_header, current_group))
return grouped_dates
def create_opml(grouped_dates):
root = ET.Element("opml", version="1.0")
head = ET.SubElement(root, "head")
ET.SubElement(head, "title").text = "Grouped Dates"
body = ET.SubElement(root, "body")
for _, weekly_header, daily_header, dates in grouped_dates:
weekly_outline = ET.SubElement(body, "outline", text=weekly_header, _note="", heading="2", colorLabel="3")
daily_outline = ET.SubElement(body, "outline", text=daily_header, _note="", heading="2", colorLabel="5")
for date in dates:
ET.SubElement(daily_outline, "outline", text=date, _note="", heading="3")
return ET.tostring(root, encoding="unicode", method="xml")
# Input date list
date_list = [
"5/Aug 2024 Mo", "6/Aug 2024 Di", "7/Aug 2024 Mi", "8/Aug 2024 Do", "9/Aug 2024 Fr",
"10/Aug 2024 Sa", "11/Aug 2024 So", "12/Aug 2024 Mo", "13/Aug 2024 Di", "14/Aug 2024 Mi",
"15/Aug 2024 Do", "16/Aug 2024 Fr", "17/Aug 2024 Sa", "18/Aug 2024 So", "19/Aug 2024 Mo",
"20/Aug 2024 Di", "21/Aug 2024 Mi", "22/Aug 2024 Do", "23/Aug 2024 Fr", "24/Aug 2024 Sa",
"25/Aug 2024 So", "26/Aug 2024 Mo", "27/Aug 2024 Di", "28/Aug 2024 Mi", "29/Aug 2024 Do",
"30/Aug 2024 Fr", "31/Aug 2024 Sa", "1/Sep 2024 So", "2/Sep 2024 Mo", "3/Sep 2024 Di",
"4/Sep 2024 Mi", "5/Sep 2024 Do", "6/Sep 2024 Fr", "7/Sep 2024 Sa", "8/Sep 2024 So"
]
# Process the date list
result = process_date_list(date_list)
# Create OPML
opml_output = create_opml(result)
# Copy the result to clipboard
pyperclip.copy(opml_output)
print("OPML形式の結果がクリップボードにコピーされました。")
このコードを実行すると、出力がクリップボードにコピーされるので、テキストファイルに貼り付けて、拡張子を「.opml」とすればいい。あとは、Dynalistで読み込むだけ。
定数が目障りなので修正
ただ、date_listにゾロゾロと定数が並んでいて、どうも不全感がある。このままでは、次に更新する際、Excelでdate_listを作って、PythonでOPMLを作成し…となってしまう。ということで、まず日付を入力したらdate_listを出力するプログラムを作成。前項で作成したプログラムと連結させた。
さて、これでOPML形式のテキストができたので、Dynalistに読み込んでみたところ、ちゃんと動作した。
出力先を新規OPMLファイルに変更
あとは、OPMLのテキストをコピーするのは面倒なので、ファイルを「./Downloads」フォルダーに作ってもらうようにさらに修正。
最終的にできたのが以下のコード。
rom datetime import datetime, timedelta
import xml.etree.ElementTree as ET
import os
import random
def get_date_input(prompt):
while True:
date_str = input(prompt)
try:
return datetime.strptime(date_str, "%Y/%m/%d")
except ValueError:
print("無効な日付形式です。YYYY/MM/DD の形式で入力してください。")
def german_month_abbr(month):
german_months = ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun',
'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez']
return german_months[month - 1]
def generate_date_list(start_date, end_date):
date_list = []
current_date = start_date
weekdays = ['Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa', 'So']
while current_date <= end_date:
date_str = f"{current_date.day}/{german_month_abbr(current_date.month)} {current_date.year} {weekdays[current_date.weekday()]}"
date_list.append(date_str)
current_date += timedelta(days=1)
return date_list
def process_date_list(date_list):
grouped_dates = []
current_group = []
current_year = ""
for date_str in date_list:
date, day = date_str.rsplit(' ', 1)
current_group.append(date_str)
if day == 'So': # End of the week
start_date = current_group[0].split(' ')[0]
end_date, year = current_group[-1].rsplit(' ', 1)
if year != current_year:
weekly_header = f"{start_date} - {end_date} {year} wöchentlich"
daily_header = f"{start_date} - {end_date} {year} täglich"
current_year = year
else:
weekly_header = f"{start_date} - {end_date} wöchentlich"
daily_header = f"{start_date} - {end_date} täglich"
grouped_dates.append(('weekly', weekly_header, daily_header, current_group))
current_group = []
# Handle the last group if it doesn't end with 'So'
if current_group:
start_date = current_group[0].split(' ')[0]
end_date, year = current_group[-1].rsplit(' ', 1)
if year != current_year:
weekly_header = f"{start_date} - {end_date} {year} wöchentlich"
daily_header = f"{start_date} - {end_date} {year} täglich"
else:
weekly_header = f"{start_date} - {end_date} wöchentlich"
daily_header = f"{start_date} - {end_date} täglich"
grouped_dates.append(('weekly', weekly_header, daily_header, current_group))
return grouped_dates
def create_opml(grouped_dates):
root = ET.Element("opml", version="1.0")
head = ET.SubElement(root, "head")
ET.SubElement(head, "title").text = "Grouped Dates"
body = ET.SubElement(root, "body")
for _, weekly_header, daily_header, dates in grouped_dates:
weekly_outline = ET.SubElement(body, "outline", text=weekly_header, _note="", heading="2", colorLabel="3")
daily_outline = ET.SubElement(body, "outline", text=daily_header, _note="", heading="2", colorLabel="5")
for date in dates:
ET.SubElement(daily_outline, "outline", text=date, _note="", heading="3")
return ET.tostring(root, encoding="unicode", method="xml")
def generate_filename():
now = datetime.now()
random_number = random.randint(100, 999)
return f"{now.strftime('%Y%m%d_%H%M%S')}_{random_number}.opml"
def save_to_file(content):
downloads_folder = os.path.expanduser("~/Downloads")
filename = generate_filename()
full_path = os.path.join(downloads_folder, filename)
with open(full_path, 'w', encoding='utf-8') as f:
f.write(content)
return full_path
def main():
print("日付範囲を指定してください(形式: YYYY/MM/DD)")
start_date = get_date_input("開始日: ")
end_date = get_date_input("終了日: ")
if start_date > end_date:
print("エラー: 開始日は終了日より前である必要があります。")
return
date_list = generate_date_list(start_date, end_date)
# Process the date list
result = process_date_list(date_list)
# Create OPML
opml_output = create_opml(result)
# Save to file
saved_path = save_to_file(opml_output)
print(f"OPML形式の結果が以下のファイルに保存されました: {saved_path}")
if __name__ == "__main__":
main()
これで問題なくプログラムが動作した。
なお、最初の画像の上2行は、月をどこで切るかを自分で判断したかったので、その部分の操作はプログラムに入っていない。また、インデントをちょこちょこ調整しないといけないのは、ちょっと面倒かもしれない。
とはいえ、Excelで「31/Okt 2024 Do」などの文字列を作るところからやるよりも、作業は数百倍は楽だ。生成AIがプログラミングに使えるようになって、本当に良かったと思う。
この記事が気に入ったらサポートをしてみませんか?