上传文件至 /

This commit is contained in:
2025-11-11 00:21:18 +08:00
parent c5a38d1486
commit 315c2f40a5
5 changed files with 529 additions and 2 deletions

View File

@@ -1,3 +1,31 @@
# DynaNote
# DynaNote - 简单系统
简易复习规划CLI程序, HeurAMS 衍生项目
## 命令列表
- `list [all|整数]` - 列出记忆单元
- `list all` - 显示所有单元
- `list 5` - 显示今天需要复习≤5次的单元
- `list` - 等同于 `list 5`
- `select <id|前缀|名称>` - 选择记忆单元
- `attach <字符串>` - 为选中的单元附加字符串
- `new <名称>` - 创建新记忆单元
- `del <id|前缀|名称>` - 删除记忆单元
- `mark <评分>` - 使用评分(0-5)更新SM-2算法
- `show` - 显示选中单元的详细信息
- `edit` - 使用nano编辑器编辑选中单元
- `clear` - 清屏
- `help` - 显示帮助和SM-2评分标准
- `exit` - 退出shell
## 数据结构
记忆单元以以下结构存储在`data.toml`中:
```toml
[unit_id]
name = "单元名称"
attachments = [["附件文本", 时间戳]]
created_time = 日期戳
algodata = {"SM-2" = {"efactor" = 2.5, "real_rept" = 0, ...}}
```

408
dynanote.py Normal file
View File

@@ -0,0 +1,408 @@
#!/usr/bin/env python3
"""
DynaNote Shell - 使用 SM-2 算法的间隔重复闪卡系统
"""
import os
import sys
import cmd
import toml
import tempfile
import subprocess
from typing import Dict, List, Optional, Tuple
from tabulate import tabulate
# 导入现有模块
import hasher
import timer
import sm2
class MemoryUnit:
"""表示单个记忆单元(闪卡)"""
def __init__(self, name: str, unit_id: Optional[str] = None):
self.name = name
self.unit_id = unit_id or hasher.get_md5(name)
self.attachments = set() # 元组集合 (字符串, 创建时间戳)
self.created_time = timer.get_daystamp()
self.algodata = {
sm2.SM2Algorithm.algo_name: {
'efactor': 2.5,
'real_rept': 0,
'rept': 0,
'interval': 0,
'last_date': 0,
'next_date': 0,
'is_activated': 0,
'last_modify': timer.get_timestamp()
}
}
def to_dict(self) -> Dict:
"""将记忆单元转换为字典以便 TOML 序列化"""
return {
'name': self.name,
'attachments': list(self.attachments),
'created_time': self.created_time,
'algodata': self.algodata
}
@classmethod
def from_dict(cls, unit_id: str, data: Dict) -> 'MemoryUnit':
"""从字典创建记忆单元"""
unit = cls(data['name'], unit_id)
unit.attachments = set(tuple(att) for att in data['attachments'])
unit.created_time = data['created_time']
unit.algodata = data['algodata']
return unit
class DynaNoteShell(cmd.Cmd):
"""DynaNote 间隔重复系统的交互式 shell"""
intro = '欢迎使用 DynaNote Shell. 输入 help 或 ? 查看命令列表. \n'
prompt = '(dynanote) > '
def __init__(self):
super().__init__()
self.data_file = './data.toml'
self.memory_units: Dict[str, MemoryUnit] = {}
self.selected_unit: Optional[MemoryUnit] = None
self.load_data()
def load_data(self) -> None:
"""从 data.toml 文件加载记忆单元"""
if os.path.exists(self.data_file):
try:
with open(self.data_file, 'r', encoding='utf-8') as f:
data = toml.load(f)
for unit_id, unit_data in data.items():
self.memory_units[unit_id] = MemoryUnit.from_dict(unit_id, unit_data)
print(f"已加载 {len(self.memory_units)} 个记忆单元")
except Exception as e:
print(f"加载数据时出错: {e}")
else:
print("未找到数据文件. 从空数据库开始. ")
def save_data(self) -> None:
"""将记忆单元保存到 data.toml 文件"""
try:
data = {}
for unit_id, unit in self.memory_units.items():
data[unit_id] = unit.to_dict()
with open(self.data_file, 'w', encoding='utf-8') as f:
toml.dump(data, f)
print("数据保存成功")
except Exception as e:
print(f"保存数据时出错: {e}")
def find_unit_by_prefix(self, prefix: str) -> Optional[str]:
"""通过前缀查找单元 ID类似 docker rm"""
matches = [unit_id for unit_id in self.memory_units.keys()
if unit_id.startswith(prefix)]
if len(matches) == 1:
return matches[0]
elif len(matches) > 1:
print(f"多个单元匹配前缀 '{prefix}': {', '.join(matches)}")
return None
else:
# 尝试通过名称查找
name_matches = [unit_id for unit_id, unit in self.memory_units.items()
if unit.name == prefix]
if len(name_matches) == 1:
return name_matches[0]
elif len(name_matches) > 1:
print(f"多个单元具有名称 '{prefix}'")
return None
return None
def do_list(self, arg: str) -> None:
"""列出记忆单元
用法: list [all|整数]
- list all: 显示所有记忆单元
- list 5: 显示复习次数<=5且今天到期的单元
- list: 同 'list 5'
"""
if not arg:
arg = "5"
if arg.lower() == "all":
units_to_show = list(self.memory_units.values())
else:
try:
max_reviews = int(arg)
today = timer.get_daystamp()
units_to_show = [
unit for unit in self.memory_units.values()
if unit.algodata[sm2.SM2Algorithm.algo_name]['real_rept'] <= max_reviews
and unit.algodata[sm2.SM2Algorithm.algo_name]['next_date'] <= today
]
except ValueError:
print("无效参数. 使用 'all' 或整数. ")
return
if not units_to_show:
print("没有符合条件的记忆单元")
return
# 准备 tabulate 数据
table_data = []
for unit in units_to_show:
algodata = unit.algodata[sm2.SM2Algorithm.algo_name]
table_data.append([
unit.unit_id[:8] + "...",
unit.name,
algodata['real_rept'],
algodata['efactor'],
algodata['next_date'],
len(unit.attachments)
])
headers = ["ID", "名称", "复习次数", "EFactor", "下次复习", "附件数"]
print(tabulate(table_data, headers=headers, tablefmt="grid"))
def do_select(self, arg: str) -> None:
"""通过 ID、前缀或名称选择记忆单元
用法: select <单元ID|前缀|名称>
"""
if not arg:
print("请提供单元 ID、前缀或名称")
return
unit_id = self.find_unit_by_prefix(arg)
if unit_id:
self.selected_unit = self.memory_units[unit_id]
print(f"已选择单元: {self.selected_unit.name} ({unit_id[:8]}...)")
else:
print(f"未找到唯一匹配 '{arg}' 的单元")
def do_attach(self, arg: str) -> None:
"""将字符串附加到选定的记忆单元
用法: attach <字符串>
"""
if not self.selected_unit:
print("未选择单元. 请先使用 'select'. ")
return
if not arg:
print("请提供要附加的字符串")
return
attachment = (arg, int(timer.get_timestamp()))
self.selected_unit.attachments.add(attachment)
print(f"附件已添加到 {self.selected_unit.name}")
def do_new(self, arg: str) -> None:
"""创建新的记忆单元
用法: new <名称>
"""
if not arg:
print("请提供新单元的名称")
return
unit = MemoryUnit(arg)
self.memory_units[unit.unit_id] = unit
self.selected_unit = unit
print(f"已创建新单元: {unit.name} ({unit.unit_id[:8]}...)")
def do_del(self, arg: str) -> None:
"""删除记忆单元
用法: del <单元ID|前缀|名称>
"""
if not arg:
print("请提供单元 ID、前缀或名称")
return
unit_id = self.find_unit_by_prefix(arg)
if unit_id:
unit_name = self.memory_units[unit_id].name
del self.memory_units[unit_id]
# 如果删除的是当前选中的单元,则清除选择
if self.selected_unit and self.selected_unit.unit_id == unit_id:
self.selected_unit = None
print(f"已删除单元: {unit_name}")
else:
print(f"未找到唯一匹配 '{arg}' 的单元")
def do_mark(self, arg: str) -> None:
"""使用评分0-5更新 SM-2 算法
用法: mark <评分>
"""
if not self.selected_unit:
print("未选择单元. 请先使用 'select'. ")
return
try:
rating = int(arg)
if rating < 0 or rating > 5:
print("评分必须在 0 到 5 之间")
return
except ValueError:
print("请提供有效的整数评分0-5")
return
# 显示评分标准以确认
print("\nSM-2 评分标准:")
print(" 5 - 完美回答")
print(" 4 - 犹豫后正确回答")
print(" 3 - 经过严重困难后回忆起正确答案")
print(" 2 - 错误回答; 但记得正确答案")
print(" 1 - 错误回答; 但正确答案似乎熟悉")
print(" 0 - 完全遗忘")
# 请求确认
response = input(f"\n确认对 '{self.selected_unit.name}' 评分 {rating}(y/N): ")
if response.lower() not in ['y', 'yes']:
print("评分已取消")
return
sm2.SM2Algorithm.revisor(self.selected_unit.algodata, rating)
print(f"已使用评分 {rating} 更新 {self.selected_unit.name} 的 SM-2 参数")
def do_show(self, arg: str) -> None:
"""显示选定记忆单元的详细信息
用法: show
"""
if not self.selected_unit:
print("未选择单元. 请先使用 'select'. ")
return
unit = self.selected_unit
algodata = unit.algodata[sm2.SM2Algorithm.algo_name]
print(f"\n记忆单元: {unit.name}")
print(f"ID: {unit.unit_id}")
print(f"创建时间: {unit.created_time}")
print(f"\nSM-2 算法数据:")
print(f" EFactor: {algodata['efactor']}")
print(f" 实际复习次数: {algodata['real_rept']}")
print(f" 当前重复次数: {algodata['rept']}")
print(f" 间隔: {algodata['interval']}")
print(f" 上次复习: {algodata['last_date']}")
print(f" 下次复习: {algodata['next_date']}")
print(f" 是否激活: {algodata['is_activated']}")
if unit.attachments:
print(f"\n附件 ({len(unit.attachments)}):")
for i, (text, timestamp) in enumerate(unit.attachments, 1):
print(f" {i}. {text[:50]}{'...' if len(text) > 50 else ''} (于 {timestamp})")
else:
print("\n无附件")
def do_edit(self, arg: str) -> None:
"""使用 nano 编辑器编辑选定的记忆单元
用法: edit
"""
if not self.selected_unit:
print("未选择单元. 请先使用 'select'. ")
return
# 创建包含单元数据的临时文件
with tempfile.NamedTemporaryFile(mode='w', suffix='.toml', delete=False) as f:
temp_file = f.name
toml.dump({self.selected_unit.unit_id: self.selected_unit.to_dict()}, f)
try:
# 启动 nano 编辑器
subprocess.run(['nano', temp_file], check=True)
# 读取编辑后的文件
with open(temp_file, 'r', encoding='utf-8') as f:
edited_data = toml.load(f)
# 更新记忆单元
if self.selected_unit.unit_id in edited_data:
updated_data = edited_data[self.selected_unit.unit_id]
self.memory_units[self.selected_unit.unit_id] = MemoryUnit.from_dict(
self.selected_unit.unit_id, updated_data
)
self.selected_unit = self.memory_units[self.selected_unit.unit_id]
print("单元更新成功")
else:
print("错误: 在编辑后的文件中未找到单元 ID")
except subprocess.CalledProcessError:
print("编辑器已关闭但未保存")
except Exception as e:
print(f"编辑单元时出错: {e}")
finally:
# 清理临时文件
if os.path.exists(temp_file):
os.unlink(temp_file)
def do_clear(self, arg: str) -> None:
"""清屏
用法: clear
"""
os.system('clear')
def do_help(self, arg: str) -> None:
"""显示帮助和 SM-2 评分标准
用法: help
"""
print("\nDynaNote Shell 命令:")
print(" list [all|整数] - 列出记忆单元")
print(" select <id|前缀|名称> - 选择记忆单元")
print(" attach <字符串> - 将字符串附加到选定单元")
print(" new <名称> - 创建新记忆单元")
print(" del <id|前缀|名称> - 删除记忆单元")
print(" mark <评分> - 使用评分0-5更新 SM-2")
print(" show - 显示选定单元详情")
print(" edit - 使用 nano 编辑选定单元")
print(" clear - 清屏")
print(" help - 显示此帮助")
print(" exit - 退出 shell")
print("\nSM-2 评分标准:")
print(" 5 - 完美回答")
print(" 4 - 犹豫后正确回答")
print(" 3 - 经过严重困难后回忆起正确答案")
print(" 2 - 错误回答; 但记得正确答案")
print(" 1 - 错误回答; 但正确答案似乎熟悉")
print(" 0 - 完全遗忘")
def do_exit(self, arg: str) -> bool:
"""退出 shell
用法: exit
"""
self.save_data()
print("再见!")
return True
def do_quit(self, arg: str) -> bool:
"""退出 shellexit 的别名)
用法: quit
"""
return self.do_exit(arg)
# 别名
def do_sel(self, arg: str) -> None:
"""select 命令的别名"""
self.do_select(arg)
def do_ls(self, arg: str) -> None:
"""list 命令的别名"""
self.do_list(arg)
def main():
"""主入口点"""
try:
DynaNoteShell().cmdloop()
except KeyboardInterrupt:
print("\n用户中断")
except Exception as e:
print(f"错误: {e}")
if __name__ == '__main__':
main()

8
hasher.py Normal file
View File

@@ -0,0 +1,8 @@
# 哈希服务
import hashlib
def get_md5(text):
return hashlib.md5(text.encode('utf-8')).hexdigest()
def hash(text):
return hashlib.md5(text.encode('utf-8')).hexdigest()

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
toml
cmd2
tabulate

80
sm2.py Normal file
View File

@@ -0,0 +1,80 @@
import timer
from typing import TypedDict
class SM2Algorithm():
algo_name = "SM-2"
class AlgodataDict(TypedDict):
efactor: float
real_rept: int
rept: int
interval: int
last_date: int
next_date: int
is_activated: int
last_modify: float
defaults = {
'efactor': 2.5,
'real_rept': 0,
'rept': 0,
'interval': 0,
'last_date': 0,
'next_date': 0,
'is_activated': 0,
'last_modify': timer.get_timestamp()
}
@classmethod
def revisor(cls, algodata: dict, feedback: int = 5, is_new_activation: bool = False):
"""SM-2 算法迭代决策机制实现
根据 quality(0 ~ 5) 进行参数迭代最佳间隔
quality 由主程序评估
Args:
quality (int): 记忆保留率量化参数
"""
if feedback == -1:
return
algodata[cls.algo_name]['efactor'] = algodata[cls.algo_name]['efactor'] + (
0.1 - (5 - feedback) * (0.08 + (5 - feedback) * 0.02)
)
algodata[cls.algo_name]['efactor'] = max(1.3, algodata[cls.algo_name]['efactor'])
if feedback < 3:
algodata[cls.algo_name]['rept'] = 0
algodata[cls.algo_name]['interval'] = 0
else:
algodata[cls.algo_name]['rept'] += 1
algodata[cls.algo_name]['real_rept'] += 1
if is_new_activation:
algodata[cls.algo_name]['rept'] = 0
algodata[cls.algo_name]['efactor'] = 2.5
if algodata[cls.algo_name]['rept'] == 0:
algodata[cls.algo_name]['interval'] = 1
elif algodata[cls.algo_name]['rept'] == 1:
algodata[cls.algo_name]['interval'] = 6
else:
algodata[cls.algo_name]['interval'] = round(
algodata[cls.algo_name]['interval'] * algodata[cls.algo_name]['efactor']
)
algodata[cls.algo_name]['last_date'] = timer.get_daystamp()
algodata[cls.algo_name]['next_date'] = timer.get_daystamp() + algodata[cls.algo_name]['interval']
algodata[cls.algo_name]['last_modify'] = timer.get_timestamp()
@classmethod
def is_due(cls, algodata):
return (algodata[cls.algo_name]['next_date'] <= timer.get_daystamp())
@classmethod
def rate(cls, algodata):
return str(algodata[cls.algo_name]['efactor'])
@classmethod
def nextdate(cls, algodata):
return algodata[cls.algo_name]['next_date']