dotfiles

Personal dotfiles
git clone https://git.bracken.jp/dotfiles.git
Log | Files | Refs | LICENSE

commit 31db6f573b36a2453f93136c04d1648de89e851b
parent 4eb407efff2ac5e4bff72a7ff1396130fe0e8132
Author: Chris Bracken <chris@bracken.jp>
Date:   Mon,  9 Mar 2020 18:08:36 -0700

Add kanji of the day script

Shamelessly stolen from https://git.sr.ht/~sircmpwn/dotfiles.

Diffstat:
M.gitignore | 5++++-
A.local/bin/.gitignore | 2++
A.local/bin/kotd | 21+++++++++++++++++++++
A.local/lib/python3/kotd/__init__.py | 114+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
A.local/lib/python3/kotd/config.py | 50++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 191 insertions(+), 1 deletion(-)

diff --git a/.gitignore b/.gitignore @@ -1,2 +1,5 @@ -# Ignore Karabiner backups +# Ignore Karabiner backups. .config/karabiner/automatic_backups/ + +# Ignore compiled python. +*.pyc diff --git a/.local/bin/.gitignore b/.local/bin/.gitignore @@ -0,0 +1,2 @@ +# Binary files we don't want to check in. +gn diff --git a/.local/bin/kotd b/.local/bin/kotd @@ -0,0 +1,21 @@ +#!/usr/bin/env python3 +import kotd +import sys + +if len(sys.argv) == 2: + if sys.argv[1] == "--precache": + from tqdm import tqdm + for kanji in tqdm(kotd.rtk.src): + kotd.jisho_get(kanji) + sys.exit(0) + if sys.argv[1] == "--kanji": + print("Today's kanji:") + print(" ".join(kotd.get_kanji_candidates())) + sys.exit(0) + if sys.argv[1] == "--words": + print("Today's words:") + for w in kotd.get_words(): + print(w) + sys.exit(0) + +print(kotd.get_kotd()) diff --git a/.local/lib/python3/kotd/__init__.py b/.local/lib/python3/kotd/__init__.py @@ -0,0 +1,114 @@ +#!/usr/bin/env python3 +from datetime import datetime +from kotd.config import load_config, cachedir +from itertools import chain +import random +import requests +import json +import os + +class CircularList: + def __init__(self, src): + self.src = list(src) + def __getitem__(self, key): + assert isinstance(key, int) or isinstance(key, slice) + if isinstance(key, slice): + key = slice( + key.start % len(self.src) if key.start else None, + key.stop % len(self.src) if key.stop else None, + key.step) + return self.src[key] + return self.src[key % len(self.src)] + +rtk = CircularList(""" +一二三四五六七八九十口日月田目古吾冒朋明唱晶品呂昌早旭世胃旦胆亘凹凸旧自白百中千舌升昇丸寸肘専博占上下卓朝嘲只貝唄貞員貼見児元頁頑凡負万句肌旬勺的首乙乱直具真工左右有賄貢項刀刃切召昭則副別丁町可頂子孔了女好如母貫兄呪克小少大多夕汐外名石肖硝砕砂妬削光太器臭嗅妙省厚奇川州順水氷永泉腺原願泳沼沖汎江汰汁沙潮源活消況河泊湖測土吐圧埼垣填圭封涯寺時均火炎煩淡灯畑災灰点照魚漁里黒墨鯉量厘埋同洞胴向尚字守完宣宵安宴寄富貯木林森桂柏枠梢棚杏桐植椅枯朴村相机本札暦案燥未末昧沫味妹朱株若草苦苛寛薄葉模漠墓暮膜苗兆桃眺犬状黙然荻狩猫牛特告先洗介界茶脊合塔王玉宝珠現玩狂旺皇呈全栓理主注柱金銑鉢銅釣針銘鎮道導辻迅造迫逃辺巡車連軌輸喩前煎各格賂略客額夏処条落冗冥軍輝運冠夢坑高享塾熟亭京涼景鯨舎周週士吉壮荘売学覚栄書津牧攻敗枚故敬言警計詮獄訂訃討訓詔詰話詠詩語読調談諾諭式試弐域賊栽載茂戚成城誠威滅減蔑桟銭浅止歩渉頻肯企歴武賦正証政定錠走超赴越是題堤建鍵延誕礎婿衣裁装裏壊哀遠猿初巾布帆幅帽幕幌錦市柿姉肺帯滞刺制製転芸雨雲曇雷霜冬天妖沃橋嬌立泣章競帝諦童瞳鐘商嫡適滴敵匕叱匂頃北背比昆皆楷諧混渇謁褐喝葛旨脂詣壱毎敏梅海乞乾腹複欠吹炊歌軟次茨資姿諮賠培剖音暗韻識鏡境亡盲妄荒望方妨坊芳肪訪放激脱説鋭曽増贈東棟凍妊廷染燃賓歳県栃地池虫蛍蛇虹蝶独蚕風己起妃改記包胞砲泡亀電竜滝豚逐遂家嫁豪腸場湯羊美洋詳鮮達羨差着唯堆椎誰焦礁集准進雑雌準奮奪確午許歓権観羽習翌曜濯曰困固錮国団因姻咽園回壇店庫庭庁床麻磨心忘恣忍認忌志誌芯忠串患思恩応意臆想息憩恵恐惑感憂寡忙悦恒悼悟怖慌悔憎慣愉惰慎憾憶惧憧憬慕添必泌手看摩我義議犠抹拭拉抱搭抄抗批招拓拍打拘捨拐摘挑指持拶括揮推揚提損拾担拠描操接掲掛捗研戒弄械鼻刑型才財材存在乃携及吸扱丈史吏更硬梗又双桑隻護獲奴怒友抜投没股設撃殻支技枝肢茎怪軽叔督寂淑反坂板返販爪妥乳浮淫将奨采採菜受授愛曖払広勾拡鉱弁雄台怠治冶始胎窓去法会至室到致互棄育撤充銃硫流允唆出山拙岩炭岐峠崩密蜜嵐崎崖入込分貧頒公松翁訟谷浴容溶欲裕鉛沿賞党堂常裳掌皮波婆披破被残殉殊殖列裂烈死葬瞬耳取趣最撮恥職聖敢聴懐慢漫買置罰寧濁環還夫扶渓規替賛潜失鉄迭臣姫蔵臓賢腎堅臨覧巨拒力男労募劣功勧努勃励加賀架脇脅協行律復得従徒待往征径彼役徳徹徴懲微街桁衡稿稼程税稚和移秒秋愁私秩秘称利梨穫穂稲香季委秀透誘稽穀菌萎米粉粘粒粧迷粋謎糧菊奥数楼類漆膝様求球救竹笑笠笹箋筋箱筆筒等算答策簿築篭人佐侶但住位仲体悠件仕他伏伝仏休仮伎伯俗信佳依例個健側侍停値倣傲倒偵僧億儀償仙催仁侮使便倍優伐宿傷保褒傑付符府任賃代袋貸化花貨傾何荷俊傍俺久畝囚内丙柄肉腐座挫卒傘匁以似併瓦瓶宮営善膳年夜液塚幣蔽弊喚換融施旋遊旅勿物易賜尿尼尻泥塀履屋握屈掘堀居据裾層局遅漏刷尺尽沢訳択昼戸肩房扇炉戻涙雇顧啓示礼祥祝福祉社視奈尉慰款禁襟宗崇祭察擦由抽油袖宙届笛軸甲押岬挿申伸神捜果菓課裸斤析所祈近折哲逝誓斬暫漸断質斥訴昨詐作雪録剥尋急穏侵浸寝婦掃当彙争浄事唐糖康逮伊君群耐需儒端両満画歯曲曹遭漕槽斗料科図用庸備昔錯借惜措散廿庶遮席度渡奔噴墳憤焼暁半伴畔判拳券巻圏勝藤謄片版之乏芝不否杯矢矯族知智挨矛柔務霧班帰弓引弔弘強弥弱溺沸費第弟巧号朽誇顎汚与写身射謝老考孝教拷者煮著箸署暑諸猪渚賭峡狭挟頬追阜師帥官棺管父釜交効較校足促捉距路露跳躍践踏踪骨滑髄禍渦鍋過阪阿際障隙随陪陽陳防附院陣隊墜降階陛隣隔隠堕陥穴空控突究窒窃窟窪搾窯窮探深丘岳兵浜糸織繕縮繁縦緻線綻締維羅練緒続絵統絞給絡結終級紀紅納紡紛紹経紳約細累索総綿絹繰継緑縁網緊紫縛縄幼後幽幾機畿玄畜蓄弦擁滋慈磁系係孫懸遜却脚卸御服命令零齢冷領鈴勇湧通踊疑擬凝範犯氾厄危宛腕苑怨柳卵留瑠貿印臼毀興酉酒酌酎酵酷酬酪酢酔配酸猶尊豆頭短豊鼓喜樹皿血盆盟盗温蓋監濫鑑藍猛盛塩銀恨根即爵節退限眼良朗浪娘食飯飲飢餓飾餌館餅養飽既概慨平呼坪評刈刹希凶胸離璃殺爽純頓鈍辛辞梓宰壁璧避新薪親幸執摯報叫糾収卑碑陸睦勢熱菱陵亥核刻該骸劾述術寒塞醸譲壌嬢毒素麦青精請情晴清静責績積債漬表俵潔契喫害轄割憲生星醒姓性牲産隆峰蜂縫拝寿鋳籍春椿泰奏実奉俸棒謹僅勤漢嘆難華垂唾睡錘乗剰今含貪吟念捻琴陰予序預野兼嫌鎌謙廉西価要腰票漂標栗慄遷覆煙南楠献門問閲閥間闇簡開閉閣閑聞潤欄闘倉創非俳排悲罪輩扉侯喉候決快偉違緯衛韓干肝刊汗軒岸幹芋宇余除徐叙途斜塗束頼瀬勅疎辣速整剣険検倹重動腫勲働種衝薫病痴痘症瘍痩疾嫉痢痕疲疫痛癖匿匠医匹区枢殴欧抑仰迎登澄発廃僚瞭寮療彫形影杉彩彰彦顔須膨参惨修珍診文対紋蚊斑斉剤済斎粛塁楽薬率渋摂央英映赤赦変跡蛮恋湾黄横把色絶艶肥甘紺某謀媒欺棋旗期碁基甚勘堪貴遺遣潰舞無組粗租狙祖阻査助宜畳並普譜湿顕繊霊業撲僕共供異翼戴洪港暴爆恭選殿井丼囲耕亜悪円角触解再講購構溝論倫輪偏遍編冊柵典氏紙婚低抵底民眠捕哺浦蒲舗補邸郭郡郊部都郵邦那郷響郎廊盾循派脈衆逓段鍛后幻司伺詞飼嗣舟舶航舷般盤搬船艦艇瓜弧孤繭益暇敷来気汽飛沈枕妻凄衰衷面麺革靴覇声眉呉娯誤蒸承函極牙芽邪雅釈番審翻藩毛耗尾宅託為偽畏長張帳脹髪展喪巣単戦禅弾桜獣脳悩厳鎖挙誉猟鳥鳴鶴烏蔦鳩鶏島暖媛援緩属嘱偶遇愚隅逆塑遡岡鋼綱剛缶陶揺謡鬱就蹴懇墾貌免逸晩勉象像馬駒験騎駐駆駅騒駄驚篤罵騰虎虜膚虚戯虞慮劇虐鹿麓薦慶麗熊能態寅演辰辱震振娠唇農濃送関咲鬼醜魂魔魅塊襲嚇朕雰箇錬遵罷屯且藻隷癒璽潟丹丑羞卯巳 +""".strip()) + +def jisho_get(kanji): + cache = os.path.join(cachedir, kanji + ".json") + if os.path.exists(cache): + with open(cache) as f: + return json.load(f) + r = requests.get(f"https://jisho.org/api/v1/search/words?keyword={kanji} %23common") + if r.status_code != 200: + raise Exception("jisho returned " + r.text) + with open(cache, "w") as f: + f.write(r.text) + return r.json() + +config = load_config() +now = datetime.utcnow() +span = now - config["start_date"] +random.seed(span.days) +todays_kanji_start = span.days * config["new_kanji"] + +def get_todays_kanji(): + return rtk[todays_kanji_start:todays_kanji_start + config["daily_kanji"]] + +def get_review_kanji(): + review_kanji_candidates = rtk[:todays_kanji_start] + if len(review_kanji_candidates) < config["review_kanji"] * 3: + review_kanji = [] + else: + review_kanji = random.sample(review_kanji_candidates, config["review_kanji"]) + return review_kanji + +def get_kanji_candidates(): + return get_todays_kanji() + get_review_kanji() + +def get_words(): + kanji_candidates = get_kanji_candidates() + + words = list() + + for c in kanji_candidates: + jisho = jisho_get(c) + jwords = jisho['data'][:config["max_words"]] + if not any(jwords): + # TODO: Get at least something useful out of this + words.append(f"{c}: uncommon") + else: + for w in jwords: + # Trust me, you don't want to read this + # <div style="color: black; background: black"> + def jwordfn(j): + if "word" in j: + return f"{j['word']} ({j['reading']})" + else: + return j['reading'] + def jengfn(s): + return s['english_definitions'] + def jposfn(s): + return s['parts_of_speech'] + def transform(pos): + m = { + "godan verb with ru ending": "る五段", + "godan verb with mu ending": "む五段", + "noun": "名詞", + "suru verb": "する動詞", + "intransitive verb": "自動詞", + "no-adjective": "の", + "na-adjective": "形容動詞", + "i-adjective": "形容詞", + "temporal noun": "時名詞", + "place": "場所", + "prefix": "接頭辞", + "numeric": "数字", + "adverbial noun": "副詞名詞" + } + return m.get(pos.lower()) or pos + japanese = list(map(jwordfn, w['japanese'])) + english = map(jengfn, w['senses']) + pos = list(filter(lambda p: p and p != "Wikipedia definition", chain(*map(jposfn, w['senses']))))[:2] + pos = sorted(set(map(transform, pos))) + japanese = sorted(list(set(japanese))[:3]) + english = sorted(list(set(e.lower() for e in chain(*english)))[:3]) + words.append(f"{c}: {', '.join(japanese)} [{', '.join(pos)}]: {', '.join(english)}") + # </div> + + return words + +def get_kotd(): + words = get_words() + word = words[(span.seconds // config["word_time"]) % len(words)] + return word diff --git a/.local/lib/python3/kotd/config.py b/.local/lib/python3/kotd/config.py @@ -0,0 +1,50 @@ +from datetime import datetime +import json +import os + +DATE_FORMAT = "%Y-%m-%dT%H:%M:%S" + +def dumps(*args, **kwargs): + def _date_handler(obj): + if hasattr(obj, 'strftime'): + return obj.strftime(DATE_FORMAT) + return obj + return json.dumps(*args, default=_date_handler, **kwargs) + +def loads(*args, **kwargs): + def _date_decoder(dct): + for k, v in dct.items(): + if isinstance(v, str): + try: + dct[k] = datetime.strptime(v, DATE_FORMAT) + except: + pass + return dct + return json.loads(*args, object_hook=_date_decoder, **kwargs) + +cachedir = os.path.expanduser("~/.cache/kotd/") +if not os.path.exists(cachedir): + os.makedirs(cachedir) +config_file = os.path.expanduser("~/.config/kotd.json") + +def load_config(): + if os.path.exists(config_file): + with open(config_file) as f: + config = loads(f.read()) + else: + config = { + "start_date": datetime.utcnow(), + # Number of kanji to show each day (not including review) + "daily_kanji": 10, + # Number of new kanji to show each day + "new_kanji": 3, + # Number of randomly old kanji to review each day + "review_kanji": 3, + # Maximum words to use with each day's kanji + "max_words": 5, + # Amount of time to show a word for before cycling to the next (seconds) + "word_time": 300, + } + with open(config_file, "w") as f: + f.write(dumps(config)) + return config