Tonus 1.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
tonus-1.1.1/PKG-INFO ADDED
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.4
2
+ Name: Tonus
3
+ Version: 1.1.1
4
+ Summary: a energy bar
5
+ Author-email: firefly <your.email@example.com>
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: typer>=0.9.0
15
+ Requires-Dist: rich
16
+ Requires-Dist: typing_extensions
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
19
+
20
+ # Mana-bar (法力槽)
21
+
22
+ Mana-bar 是一个基于命令行的任务流管理工具,旨在帮助你管理不同项目的“精力”或“疲劳值”。通过设定每个项目的疲劳上限和消耗速率,你可以更好地规划和追踪你的工作状态,避免过度劳累。
23
+
24
+ ## 简介
25
+
26
+ 在日常工作中,我们往往会在多个项目之间切换。Mana-bar 引入了“法力值”(Mana)或“疲劳值”的概念:
27
+ - 每个项目都有一个**疲劳上限**(Total Fatigue)。
28
+ - 当你开始在一个项目上工作时,疲劳值会随着时间积累。
29
+ - 不同的项目可以设置不同的**消耗速率**(Rate)。
30
+ - 当疲劳值达到上限时,意味着你需要休息或切换到其他项目。
31
+ - 疲劳值会随着时间的推移(休息)通过某种机制恢复(当前代码逻辑主要关注记录和显示)。
32
+
33
+ ## 安装
34
+
35
+ 确保你已经安装了 Python 3.10+。
36
+
37
+ ```bash
38
+ git clone <repository_url>
39
+ cd mana_bar
40
+ pip install .
41
+ ```
42
+
43
+ 或者使用 `pip` 安装依赖:
44
+ ```bash
45
+ pip install -e .
46
+ ```
47
+
48
+ ## 使用说明
49
+
50
+ 安装完成后,你可以使用 `mana` 命令来管理你的任务。
51
+
52
+ ### 1. 添加项目 (Add)
53
+
54
+ 开始管理一个新项目之前,你需要先添加它,并设置其疲劳上限和消耗速率。
55
+
56
+ ```bash
57
+ mana add <project_name> --total <total_fatigue> --rate <fatigue_rate>
58
+ ```
59
+
60
+ - `<project_name>`: 项目名称。
61
+ - `--total`, `-t`: 疲劳值上限,默认 156。
62
+ - `--rate`, `-r`: 疲劳消耗速率,默认 1.0。
63
+
64
+ 示例:
65
+ ```bash
66
+ mana add coding -t 200 -r 1.5
67
+ ```
68
+
69
+ ### 2. 开始任务 (Open)
70
+
71
+ 开始在一个项目上工作。
72
+
73
+ ```bash
74
+ mana open <project_name> [--task <task_desc>] [--extra <extra_id>]
75
+ ```
76
+
77
+ - `<project_name>`: 项目名称。
78
+ - `--task`, `-t`: (可选) 任务描述。
79
+ - `--extra`, `-e`: (可选) 额外ID。
80
+
81
+ 示例:
82
+ ```bash
83
+ mana open coding -t "Implement README"
84
+ ```
85
+
86
+ ### 3. 结束任务 (Close)
87
+
88
+ 停止当前项目的计时。
89
+
90
+ ```bash
91
+ mana close <project_name>
92
+ ```
93
+
94
+ 示例:
95
+ ```bash
96
+ mana close coding
97
+ ```
98
+
99
+ ### 4. 查看项目状态 (Check)
100
+
101
+ 查看指定项目的当前疲劳值状态。
102
+
103
+ ```bash
104
+ mana check <project_name>
105
+ ```
106
+
107
+ 输出将显示一个进度条,指示当前的精力和疲劳状态。
108
+
109
+ ### 5. 查看所有项目 (Checkall)
110
+
111
+ 查看所有项目的状态。
112
+
113
+ ```bash
114
+ mana checkall
115
+ ```
116
+
117
+ ## 开发
118
+
119
+ 本项目使用 [Typer](https://typer.tiangolo.com/) 构建 CLI,使用 [Rich](https://github.com/Textualize/rich) 进行终端美化。
120
+
121
+ ### 依赖安装
122
+ ```bash
123
+ pip install -r requirements.txt
124
+ # 或者
125
+ pip install .[dev]
126
+ ```
127
+
128
+ ### 运行测试
129
+ ```bash
130
+ pytest
131
+ ```
132
+
133
+ ## License
134
+
135
+ [MIT License](LICENSE)
tonus-1.1.1/README.md ADDED
@@ -0,0 +1,116 @@
1
+ # Mana-bar (法力槽)
2
+
3
+ Mana-bar 是一个基于命令行的任务流管理工具,旨在帮助你管理不同项目的“精力”或“疲劳值”。通过设定每个项目的疲劳上限和消耗速率,你可以更好地规划和追踪你的工作状态,避免过度劳累。
4
+
5
+ ## 简介
6
+
7
+ 在日常工作中,我们往往会在多个项目之间切换。Mana-bar 引入了“法力值”(Mana)或“疲劳值”的概念:
8
+ - 每个项目都有一个**疲劳上限**(Total Fatigue)。
9
+ - 当你开始在一个项目上工作时,疲劳值会随着时间积累。
10
+ - 不同的项目可以设置不同的**消耗速率**(Rate)。
11
+ - 当疲劳值达到上限时,意味着你需要休息或切换到其他项目。
12
+ - 疲劳值会随着时间的推移(休息)通过某种机制恢复(当前代码逻辑主要关注记录和显示)。
13
+
14
+ ## 安装
15
+
16
+ 确保你已经安装了 Python 3.10+。
17
+
18
+ ```bash
19
+ git clone <repository_url>
20
+ cd mana_bar
21
+ pip install .
22
+ ```
23
+
24
+ 或者使用 `pip` 安装依赖:
25
+ ```bash
26
+ pip install -e .
27
+ ```
28
+
29
+ ## 使用说明
30
+
31
+ 安装完成后,你可以使用 `mana` 命令来管理你的任务。
32
+
33
+ ### 1. 添加项目 (Add)
34
+
35
+ 开始管理一个新项目之前,你需要先添加它,并设置其疲劳上限和消耗速率。
36
+
37
+ ```bash
38
+ mana add <project_name> --total <total_fatigue> --rate <fatigue_rate>
39
+ ```
40
+
41
+ - `<project_name>`: 项目名称。
42
+ - `--total`, `-t`: 疲劳值上限,默认 156。
43
+ - `--rate`, `-r`: 疲劳消耗速率,默认 1.0。
44
+
45
+ 示例:
46
+ ```bash
47
+ mana add coding -t 200 -r 1.5
48
+ ```
49
+
50
+ ### 2. 开始任务 (Open)
51
+
52
+ 开始在一个项目上工作。
53
+
54
+ ```bash
55
+ mana open <project_name> [--task <task_desc>] [--extra <extra_id>]
56
+ ```
57
+
58
+ - `<project_name>`: 项目名称。
59
+ - `--task`, `-t`: (可选) 任务描述。
60
+ - `--extra`, `-e`: (可选) 额外ID。
61
+
62
+ 示例:
63
+ ```bash
64
+ mana open coding -t "Implement README"
65
+ ```
66
+
67
+ ### 3. 结束任务 (Close)
68
+
69
+ 停止当前项目的计时。
70
+
71
+ ```bash
72
+ mana close <project_name>
73
+ ```
74
+
75
+ 示例:
76
+ ```bash
77
+ mana close coding
78
+ ```
79
+
80
+ ### 4. 查看项目状态 (Check)
81
+
82
+ 查看指定项目的当前疲劳值状态。
83
+
84
+ ```bash
85
+ mana check <project_name>
86
+ ```
87
+
88
+ 输出将显示一个进度条,指示当前的精力和疲劳状态。
89
+
90
+ ### 5. 查看所有项目 (Checkall)
91
+
92
+ 查看所有项目的状态。
93
+
94
+ ```bash
95
+ mana checkall
96
+ ```
97
+
98
+ ## 开发
99
+
100
+ 本项目使用 [Typer](https://typer.tiangolo.com/) 构建 CLI,使用 [Rich](https://github.com/Textualize/rich) 进行终端美化。
101
+
102
+ ### 依赖安装
103
+ ```bash
104
+ pip install -r requirements.txt
105
+ # 或者
106
+ pip install .[dev]
107
+ ```
108
+
109
+ ### 运行测试
110
+ ```bash
111
+ pytest
112
+ ```
113
+
114
+ ## License
115
+
116
+ [MIT License](LICENSE)
@@ -0,0 +1,135 @@
1
+ Metadata-Version: 2.4
2
+ Name: Tonus
3
+ Version: 1.1.1
4
+ Summary: a energy bar
5
+ Author-email: firefly <your.email@example.com>
6
+ Classifier: Development Status :: 3 - Alpha
7
+ Classifier: Intended Audience :: Developers
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.10
10
+ Classifier: Programming Language :: Python :: 3.11
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Requires-Python: >=3.10
13
+ Description-Content-Type: text/markdown
14
+ Requires-Dist: typer>=0.9.0
15
+ Requires-Dist: rich
16
+ Requires-Dist: typing_extensions
17
+ Provides-Extra: dev
18
+ Requires-Dist: pytest>=7.0.0; extra == "dev"
19
+
20
+ # Mana-bar (法力槽)
21
+
22
+ Mana-bar 是一个基于命令行的任务流管理工具,旨在帮助你管理不同项目的“精力”或“疲劳值”。通过设定每个项目的疲劳上限和消耗速率,你可以更好地规划和追踪你的工作状态,避免过度劳累。
23
+
24
+ ## 简介
25
+
26
+ 在日常工作中,我们往往会在多个项目之间切换。Mana-bar 引入了“法力值”(Mana)或“疲劳值”的概念:
27
+ - 每个项目都有一个**疲劳上限**(Total Fatigue)。
28
+ - 当你开始在一个项目上工作时,疲劳值会随着时间积累。
29
+ - 不同的项目可以设置不同的**消耗速率**(Rate)。
30
+ - 当疲劳值达到上限时,意味着你需要休息或切换到其他项目。
31
+ - 疲劳值会随着时间的推移(休息)通过某种机制恢复(当前代码逻辑主要关注记录和显示)。
32
+
33
+ ## 安装
34
+
35
+ 确保你已经安装了 Python 3.10+。
36
+
37
+ ```bash
38
+ git clone <repository_url>
39
+ cd mana_bar
40
+ pip install .
41
+ ```
42
+
43
+ 或者使用 `pip` 安装依赖:
44
+ ```bash
45
+ pip install -e .
46
+ ```
47
+
48
+ ## 使用说明
49
+
50
+ 安装完成后,你可以使用 `mana` 命令来管理你的任务。
51
+
52
+ ### 1. 添加项目 (Add)
53
+
54
+ 开始管理一个新项目之前,你需要先添加它,并设置其疲劳上限和消耗速率。
55
+
56
+ ```bash
57
+ mana add <project_name> --total <total_fatigue> --rate <fatigue_rate>
58
+ ```
59
+
60
+ - `<project_name>`: 项目名称。
61
+ - `--total`, `-t`: 疲劳值上限,默认 156。
62
+ - `--rate`, `-r`: 疲劳消耗速率,默认 1.0。
63
+
64
+ 示例:
65
+ ```bash
66
+ mana add coding -t 200 -r 1.5
67
+ ```
68
+
69
+ ### 2. 开始任务 (Open)
70
+
71
+ 开始在一个项目上工作。
72
+
73
+ ```bash
74
+ mana open <project_name> [--task <task_desc>] [--extra <extra_id>]
75
+ ```
76
+
77
+ - `<project_name>`: 项目名称。
78
+ - `--task`, `-t`: (可选) 任务描述。
79
+ - `--extra`, `-e`: (可选) 额外ID。
80
+
81
+ 示例:
82
+ ```bash
83
+ mana open coding -t "Implement README"
84
+ ```
85
+
86
+ ### 3. 结束任务 (Close)
87
+
88
+ 停止当前项目的计时。
89
+
90
+ ```bash
91
+ mana close <project_name>
92
+ ```
93
+
94
+ 示例:
95
+ ```bash
96
+ mana close coding
97
+ ```
98
+
99
+ ### 4. 查看项目状态 (Check)
100
+
101
+ 查看指定项目的当前疲劳值状态。
102
+
103
+ ```bash
104
+ mana check <project_name>
105
+ ```
106
+
107
+ 输出将显示一个进度条,指示当前的精力和疲劳状态。
108
+
109
+ ### 5. 查看所有项目 (Checkall)
110
+
111
+ 查看所有项目的状态。
112
+
113
+ ```bash
114
+ mana checkall
115
+ ```
116
+
117
+ ## 开发
118
+
119
+ 本项目使用 [Typer](https://typer.tiangolo.com/) 构建 CLI,使用 [Rich](https://github.com/Textualize/rich) 进行终端美化。
120
+
121
+ ### 依赖安装
122
+ ```bash
123
+ pip install -r requirements.txt
124
+ # 或者
125
+ pip install .[dev]
126
+ ```
127
+
128
+ ### 运行测试
129
+ ```bash
130
+ pytest
131
+ ```
132
+
133
+ ## License
134
+
135
+ [MIT License](LICENSE)
@@ -0,0 +1,30 @@
1
+ README.md
2
+ pyproject.toml
3
+ Tonus.egg-info/PKG-INFO
4
+ Tonus.egg-info/SOURCES.txt
5
+ Tonus.egg-info/dependency_links.txt
6
+ Tonus.egg-info/entry_points.txt
7
+ Tonus.egg-info/requires.txt
8
+ Tonus.egg-info/top_level.txt
9
+ package/__init__.py
10
+ package/cli/__init__.py
11
+ package/cli/app.py
12
+ package/cli/main.py
13
+ package/data/__init__.py
14
+ package/data/init.py
15
+ package/data/project.py
16
+ package/data/record.py
17
+ package/entity/__init__.py
18
+ package/entity/project.py
19
+ package/entity/record.py
20
+ package/service/__init__.py
21
+ package/service/project/__init__.py
22
+ package/service/project/add.py
23
+ package/service/project/delete.py
24
+ package/service/project/extra.py
25
+ package/service/record/__init__.py
26
+ package/service/record/complete.py
27
+ package/service/record/show.py
28
+ package/service/record/start.py
29
+ package/service/record/stop.py
30
+ package/utils/utils.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ to = package.cli.main:main
@@ -0,0 +1,6 @@
1
+ typer>=0.9.0
2
+ rich
3
+ typing_extensions
4
+
5
+ [dev]
6
+ pytest>=7.0.0
@@ -0,0 +1 @@
1
+ package
File without changes
File without changes
@@ -0,0 +1,79 @@
1
+ import typer
2
+ from package.service.record.start import start
3
+ from package.service.record.stop import stop
4
+ from package.service.record.show import show, list_all
5
+ from package.service.record.complete import complete_project_name
6
+ from package.service.project.add import add_project
7
+ from package.service.project.delete import delete_project
8
+ from package.service.project.extra import extra_time
9
+
10
+ from typing_extensions import Annotated
11
+
12
+ app = typer.Typer(help="to")
13
+
14
+
15
+ @app.callback(invoke_without_command=True)
16
+ def main(ctx: typer.Context):
17
+ if ctx.invoked_subcommand is None:
18
+ list_all()
19
+
20
+
21
+ @app.command()
22
+ def open(
23
+ project: Annotated[
24
+ str, typer.Argument(help="project", shell_complete=complete_project_name)
25
+ ],
26
+ task_desc: Annotated[str, typer.Option("--task", "-t", help="task")] = "",
27
+ extra_id: Annotated[str, typer.Option("--extra", "-e", help="extra_id")] = "",
28
+ ):
29
+ start(project_name=project, task_desc=task_desc, extra_id=extra_id)
30
+
31
+
32
+ @app.command()
33
+ def close(
34
+ project: Annotated[
35
+ str, typer.Argument(help="project", shell_complete=complete_project_name)
36
+ ],
37
+ ):
38
+ stop(project_name=project)
39
+
40
+
41
+ @app.command()
42
+ def check(
43
+ project: Annotated[
44
+ str, typer.Argument(help="project", shell_complete=complete_project_name)
45
+ ],
46
+ ):
47
+ show(project)
48
+
49
+
50
+ @app.command()
51
+ def checkall():
52
+ list_all()
53
+
54
+
55
+ @app.command()
56
+ def add(
57
+ project: Annotated[str, typer.Argument(help="project")],
58
+ total: Annotated[int, typer.Option("--total", "-t", help="task")] = 156,
59
+ rate: Annotated[float, typer.Option("--extra", "-r", help="extra_id")] = 1.0,
60
+ ):
61
+ add_project(project_name=project, total=total, rate=rate)
62
+
63
+
64
+ @app.command()
65
+ def delete(
66
+ project: Annotated[
67
+ str, typer.Argument(help="project", shell_complete=complete_project_name)
68
+ ],
69
+ ):
70
+ delete_project(project_name=project)
71
+
72
+
73
+ @app.command()
74
+ def extra(
75
+ project: Annotated[str, typer.Argument(help="project")],
76
+ extra_num: Annotated[int,typer.Argument(help="extra minute")] = 0,
77
+ invalid_day: Annotated[int,typer.Argument(help="invalid day")] = 1,
78
+ ):
79
+ extra_time(project_name=project, minutes=extra_num, invalid_day=invalid_day)
@@ -0,0 +1,13 @@
1
+ from package.cli.app import app
2
+ import typer
3
+
4
+
5
+ def main():
6
+ try:
7
+ app()
8
+ except Exception as e:
9
+ typer.secho(f"出现异常: {e}", fg=typer.colors.RED, err=True)
10
+
11
+
12
+ if __name__ == "__main__":
13
+ main()
File without changes
@@ -0,0 +1,76 @@
1
+ import sqlite3
2
+ import functools
3
+ import os
4
+ from pathlib import Path
5
+ import json
6
+
7
+
8
+ _is_initialized = False
9
+
10
+
11
+ DB_DIR = str(Path.home() / ".config" / "mana" / "data")
12
+ DB_PATH = os.path.join(DB_DIR, "fatigue.db")
13
+ CONFIG_PATH = os.path.join(DB_DIR, "config.json")
14
+
15
+
16
+ def _init_db():
17
+ global _is_initialized
18
+
19
+ if _is_initialized:
20
+ return
21
+ conn = get_conn()
22
+ cursor = conn.cursor()
23
+ cursor.executescript(
24
+ """
25
+ CREATE TABLE IF NOT EXISTS records (
26
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
27
+ start INTEGER,
28
+ stop INTEGER,
29
+ duration INTEGER,
30
+ task_desc TEXT,
31
+ extra_id TEXT,
32
+ project_id INTEGER
33
+ );
34
+
35
+ CREATE TABLE IF NOT EXISTS projects (
36
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
37
+ project_name TEXT UNIQUE,
38
+ fatigue_rate REAL DEFAULT 1.0,
39
+ fatigue_total INTEGER DEFAULT 156,
40
+ project_status INTEGER DEFAULT 0,
41
+ extra_num INTEGER
42
+ );
43
+ """
44
+ )
45
+
46
+ conn.commit()
47
+ conn.close()
48
+ _is_initialized = True
49
+
50
+
51
+ def _create_config():
52
+ if os.path.exists(CONFIG_PATH):
53
+ return
54
+ else:
55
+ with open(CONFIG_PATH, "w", encoding="utf-8") as f:
56
+ default_config = {"enable_timew": True}
57
+ json.dump(default_config, f, indent=4)
58
+
59
+
60
+ def get_conn():
61
+ if not os.path.exists(DB_DIR):
62
+ os.makedirs(DB_DIR, exist_ok=True)
63
+
64
+ conn = sqlite3.connect(DB_PATH)
65
+ conn.row_factory = sqlite3.Row
66
+ return conn
67
+
68
+
69
+ def ensure_db_ready(func):
70
+ @functools.wraps(func)
71
+ def wrapper(*args, **kargs):
72
+ _init_db()
73
+ _create_config()
74
+ return func(*args, **kargs)
75
+
76
+ return wrapper
@@ -0,0 +1,105 @@
1
+ from package.data.init import ensure_db_ready, get_conn
2
+ from package.entity.project import Project
3
+
4
+
5
+ @ensure_db_ready
6
+ def add_project(project: Project):
7
+ sql = "insert into projects(project_name,project_status,fatigue_rate,fatigue_total) values(?,?,?,?)"
8
+ with get_conn() as conn:
9
+ cursor = conn.cursor()
10
+ cursor.execute(
11
+ sql,
12
+ (
13
+ project.project_name,
14
+ project.project_status,
15
+ project.fatigue_rate,
16
+ project.fatigue_total,
17
+ ),
18
+ )
19
+
20
+ return cursor.lastrowid
21
+
22
+ @ensure_db_ready
23
+ def delete_project(project: str):
24
+ sql = "delete from projects where project_name = ?"
25
+ with get_conn() as conn:
26
+ cursor = conn.cursor()
27
+ cursor.execute(
28
+ sql,
29
+ (project,),
30
+ )
31
+
32
+
33
+ @ensure_db_ready
34
+ def update_project(project: Project):
35
+ project_dict = vars(project)
36
+ update_parts = {k: v for k,v in project_dict.items() if v is not None and k != "id"}
37
+ if not update_parts:
38
+ return None
39
+ update_keys = ",".join([f"{k} = ?" for k in update_parts.keys()])
40
+ sql = "update projects set " + update_keys + " where id = ?"
41
+ params = list(update_parts.values())
42
+ params.append(project.id)
43
+
44
+ with get_conn() as conn:
45
+ cursor = conn.cursor()
46
+ cursor.execute(sql, params)
47
+ conn.commit()
48
+
49
+ @ensure_db_ready
50
+ def batch_init_extra(project_ids: list[int]):
51
+ sql = "UPDATE projects SET extra_time = 0, invalid_time = 0, extra_start_time = 0 WHERE id = ?"
52
+ params = [(pid,) for pid in project_ids]
53
+ with get_conn() as conn:
54
+ cursor = conn.cursor()
55
+ cursor.executemany(sql, params)
56
+ conn.commit()
57
+
58
+
59
+ @ensure_db_ready
60
+ def get_project_id(id: int):
61
+ sql = "select id,project_name,fatigue_rate,fatigue_total,project_status from projects where id = ?"
62
+ with get_conn() as conn:
63
+ cursor = conn.cursor()
64
+ cursor.execute(
65
+ sql,
66
+ (id,),
67
+ )
68
+
69
+ return Project(**dict(cursor.fetchone()))
70
+
71
+
72
+ @ensure_db_ready
73
+ def get_project_name(project_name):
74
+ sql = "select id,project_name,fatigue_total,fatigue_rate,project_status,extra_time,extra_start_time,invalid_time from projects where project_name = ?"
75
+ with get_conn() as conn:
76
+ cursor = conn.cursor()
77
+ cursor.execute(
78
+ sql,
79
+ (project_name,),
80
+ )
81
+ row = cursor.fetchone()
82
+ if not row:
83
+ return None
84
+
85
+ return Project(**dict(row))
86
+
87
+
88
+ @ensure_db_ready
89
+ def get_all_projects():
90
+ sql = (
91
+ "select id,project_name,fatigue_total,fatigue_rate,project_status,invalid_time,extra_time,extra_start_time from projects"
92
+ )
93
+ with get_conn() as conn:
94
+ cursor = conn.cursor()
95
+ cursor.execute(
96
+ sql,
97
+ )
98
+ rows = cursor.fetchall()
99
+ if not rows:
100
+ return None
101
+
102
+ results = []
103
+ for r in rows:
104
+ results.append(Project(**dict(r)))
105
+ return results
@@ -0,0 +1,162 @@
1
+ from package.data.init import ensure_db_ready, get_conn
2
+ from package.entity.record import Record
3
+ from package.entity.project import Project
4
+ from datetime import datetime, timedelta
5
+ import time
6
+
7
+
8
+ @ensure_db_ready
9
+ def add_record(record: Record):
10
+ sql = "insert into records(start,stop,duration,task_desc,extra_id,project_id) values(?,?,?,?,?,?)"
11
+ with get_conn() as conn:
12
+ cursor = conn.cursor()
13
+ cursor.execute(
14
+ sql,
15
+ (
16
+ record.start,
17
+ record.stop,
18
+ record.duration,
19
+ record.task_desc,
20
+ record.extra_id,
21
+ record.project_id,
22
+ ),
23
+ )
24
+ conn.commit()
25
+
26
+
27
+ @ensure_db_ready
28
+ def update_stop_record(record: Record):
29
+ sql = (
30
+ "update records set stop = ? , duration = ? where project_id = ? and stop = -1"
31
+ )
32
+ with get_conn() as conn:
33
+ cursor = conn.cursor()
34
+ cursor.execute(
35
+ sql,
36
+ (
37
+ record.stop,
38
+ record.duration,
39
+ record.project_id,
40
+ ),
41
+ )
42
+ conn.commit()
43
+
44
+
45
+ @ensure_db_ready
46
+ def get_recent_pending_record_by_project(record: Record):
47
+ sql = (
48
+ "select id,start,stop,task_desc from records where stop = -1 and project_id = ?"
49
+ )
50
+ with get_conn() as conn:
51
+ cursor = conn.cursor()
52
+ cursor.execute(
53
+ sql,
54
+ (record.project_id,),
55
+ )
56
+ row = cursor.fetchone()
57
+
58
+ if not row:
59
+ return None
60
+
61
+ return Record(**dict(row))
62
+
63
+
64
+ @ensure_db_ready
65
+ def get_recent_record_by_project(record: Record):
66
+ sql = "select id,start,stop,task_desc from records where project_id = ? ORDER BY start DESC LIMIT 1"
67
+ with get_conn() as conn:
68
+ cursor = conn.cursor()
69
+ cursor.execute(
70
+ sql,
71
+ (record.project_id,),
72
+ )
73
+ row = cursor.fetchone()
74
+
75
+ if not row:
76
+ return None
77
+
78
+ return Record(**dict(row))
79
+
80
+
81
+ @ensure_db_ready
82
+ def get_projects():
83
+ sql = "select * from projects"
84
+ with get_conn() as conn:
85
+ cursor = conn.cursor()
86
+ cursor.execute(
87
+ sql
88
+ )
89
+ rows = cursor.fetchall()
90
+
91
+ if not rows:
92
+ return None
93
+ return [Project(**dict(row)) for row in rows]
94
+
95
+
96
+ @ensure_db_ready
97
+ def get_today_accumulated_fatigue(project_id=None):
98
+ now = datetime.now()
99
+ # 截止今天0点的时间戳
100
+ # 如果现在还没到凌晨 4 点,说明还在“昨天的那个统计周期”里
101
+ if now.hour < 4:
102
+ start_time = now.replace(hour=4, minute=0, second=0, microsecond=0) - timedelta(
103
+ days=1
104
+ )
105
+ else:
106
+ # 否则,从今天的 4 点开始算
107
+ start_time = now.replace(hour=4, minute=0, second=0, microsecond=0)
108
+
109
+ start_ts = int(start_time.timestamp())
110
+ now_ts = int(time.time())
111
+
112
+ sql = """
113
+ SELECT p.id,
114
+ p.project_name,
115
+ p.fatigue_total,
116
+ SUM(
117
+ CASE
118
+ WHEN r.stop = -1 then (? - start)
119
+ ELSE r.duration
120
+ END * p.fatigue_rate / 60
121
+ ) AS used_fatigue,
122
+ p.project_status,
123
+ p.extra_time,
124
+ p.invalid_time
125
+ FROM projects p
126
+ LEFT JOIN records r ON r.project_id = p.id AND r.start >= ? \
127
+ \
128
+ """
129
+ params = [now_ts, start_ts]
130
+ if project_id:
131
+ sql = sql + " WHERE project_id = ?"
132
+ params.append(project_id)
133
+
134
+ sql += " GROUP BY p.id"
135
+ with get_conn() as conn:
136
+ cursor = conn.cursor()
137
+ cursor.execute(
138
+ sql,
139
+ tuple(params),
140
+ )
141
+
142
+ if project_id:
143
+ row = cursor.fetchone()
144
+ if row is not None:
145
+ total_duration = row[3] if row[3] is not None else 0
146
+ return total_duration
147
+ else:
148
+ return 0
149
+ else:
150
+ rows = cursor.fetchall()
151
+ results = []
152
+ for r in rows:
153
+ results.append(
154
+ {
155
+ "id": r[0],
156
+ "name": r[1],
157
+ "total": r[2] + r[5],
158
+ "used": r[3] if r[3] is not None else 0,
159
+ "status": r[4],
160
+ }
161
+ )
162
+ return results
File without changes
@@ -0,0 +1,14 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+
4
+
5
+ @dataclass
6
+ class Project:
7
+ id: Optional[int]
8
+ project_name: Optional[str] = ""
9
+ fatigue_rate: Optional[float] = 1.0
10
+ fatigue_total: Optional[int] = 156
11
+ project_status: Optional[int] = 0,
12
+ extra_time: Optional[int] = 0,
13
+ invalid_time: Optional[int] = 0,
14
+ extra_start_time: Optional[int] = 0,
@@ -0,0 +1,13 @@
1
+ from dataclasses import dataclass
2
+ from typing import Optional
3
+
4
+
5
+ @dataclass
6
+ class Record:
7
+ id: Optional[int]
8
+ start: int = -1
9
+ stop: int = -1
10
+ duration: int = -1
11
+ task_desc: str = ""
12
+ extra_id: str = ""
13
+ project_id: int = -1
File without changes
File without changes
@@ -0,0 +1,15 @@
1
+ from package.entity.project import Project
2
+ from package.data.project import add_project as add_project_data
3
+ from package.data.init import ensure_db_ready
4
+
5
+
6
+ @ensure_db_ready
7
+ def add_project(project_name, total, rate):
8
+ if project_name == "" or project_name is None:
9
+ raise ValueError("Project name cannot be empty")
10
+
11
+ project = Project(
12
+ None, project_name=project_name, fatigue_rate=rate, fatigue_total=total,project_status=3
13
+ )
14
+
15
+ add_project_data(project)
@@ -0,0 +1,7 @@
1
+ from package.data.project import delete_project as delete_project_data
2
+ from package.data.init import ensure_db_ready
3
+
4
+
5
+ @ensure_db_ready
6
+ def delete_project(project_name):
7
+ delete_project_data(project_name)
@@ -0,0 +1,17 @@
1
+ from datetime import datetime
2
+
3
+ from package.data.project import update_project, get_project_name
4
+ from package.data.init import ensure_db_ready
5
+
6
+
7
+ @ensure_db_ready
8
+ def extra_time(project_name, minutes:int, invalid_day:int):
9
+ invalid_time = invalid_day * 24 * 60 * 60 * 60
10
+ extra_start_time = int(datetime.now().timestamp())
11
+ if project_name == "" or project_name is None:
12
+ raise ValueError("Project name cannot be empty")
13
+ project = get_project_name(project_name=project_name)
14
+ project.extra_time = project.extra_time + minutes
15
+ project.invalid_time = invalid_time
16
+ project.extra_start_time = extra_start_time
17
+ update_project(project)
File without changes
@@ -0,0 +1,14 @@
1
+ from package.data.project import get_all_projects
2
+ import typer
3
+
4
+
5
+ def complete_project_name(ctx: typer.Context, param, incomplete: str):
6
+ text = incomplete
7
+ projects = get_all_projects()
8
+ choices = [
9
+ p.project_name
10
+ for p in projects
11
+ if isinstance(p.project_name, str) and p.project_name.startswith(text)
12
+ ]
13
+
14
+ return choices
@@ -0,0 +1,105 @@
1
+ from datetime import datetime
2
+ from package.data.project import get_project_name,get_all_projects,batch_init_extra
3
+ from package.data.record import (
4
+ get_today_accumulated_fatigue,
5
+ )
6
+ from rich.console import Console
7
+ from rich.table import Table
8
+ from rich.progress_bar import ProgressBar
9
+ from rich.text import Text
10
+ import typer
11
+
12
+ status_map = {
13
+ 0 : ("已暂停", "red"),
14
+ 1 : ("进行中", "green"),
15
+ 2 : ("已结束", "grey"),
16
+ 3 : ("未开始", "yellow")
17
+ }
18
+
19
+ def show(project_name):
20
+ console = Console()
21
+ project = get_project_name(project_name=project_name)
22
+
23
+ if not project:
24
+ typer.secho("找不到此项目", fg=typer.colors.RED, err=True)
25
+ return
26
+
27
+ cost = int(get_today_accumulated_fatigue(project.id))
28
+
29
+ if cost > project.fatigue_total:
30
+ cost = project.fatigue_total
31
+
32
+ rest = (project.fatigue_total + project.extra_time) - cost
33
+ total = project.fatigue_total + project.extra_time
34
+
35
+ if rest < 0:
36
+ rest = 0
37
+
38
+ bar = ProgressBar(
39
+ total=total,
40
+ completed=rest,
41
+ width=40,
42
+ complete_style="yellow",
43
+ finished_style="green",
44
+ )
45
+ status_text, color = status_map.get(project.project_status,("未知","white"))
46
+ status = Text(status_text, style=color)
47
+ grid = Table.grid(padding=1)
48
+ grid.add_row(
49
+ Text(
50
+ f"{project.project_name}: {rest}/{total}:",
51
+ style="bold blue",
52
+ ),
53
+ status,
54
+ bar,
55
+ Text(f"{rest / total:.1%}", style="magenta"),
56
+ )
57
+ console.print(grid)
58
+
59
+
60
+ def list_all():
61
+ p_ids = confirm_extra()
62
+ batch_init_extra(p_ids)
63
+ results = get_today_accumulated_fatigue()
64
+ console = Console()
65
+ if not results:
66
+ typer.secho("没有记录,请确认", fg=typer.colors.RED, err=True)
67
+ grid = Table.grid(padding=1)
68
+ grid.add_column(justify="right")
69
+ grid.add_column(justify="left")
70
+ grid.add_column(justify="right")
71
+ grid = Table.grid(expand=True, padding=(0, 1))
72
+ grid.add_column(no_wrap=True, justify="left")
73
+ grid.add_column(no_wrap=True, justify="right")
74
+ grid.add_column(no_wrap=True, justify="center", width=10)
75
+ grid.add_column(ratio=1)
76
+ grid.add_column(no_wrap=True, justify="right")
77
+
78
+ for r in results:
79
+ rest = max(0, int(r["total"] - r["used"]))
80
+ bar = ProgressBar(
81
+ total=r["total"],
82
+ completed=rest,
83
+ width=None, # 宽度自适应
84
+ complete_style="yellow",
85
+ finished_style="green",
86
+ )
87
+
88
+ status_text, color = status_map.get(r["status"], ("未知", "white"))
89
+ status = Text(status_text, style=color)
90
+ grid.add_row(
91
+ Text(f"{r['id']} {r['name']}:", style="bold blue"),
92
+ Text(f"{rest}/{r['total']}:", style="bold blue"),
93
+ status,
94
+ bar,
95
+ Text(f"{rest / r['total']:.1%}", style="magenta")
96
+ )
97
+ console.print(grid)
98
+
99
+
100
+ def confirm_extra():
101
+ projects = get_all_projects()
102
+ current_stamp = datetime.now().timestamp()
103
+ filtered_projects = filter(lambda p: int(current_stamp) >= p.invalid_time + p.extra_start_time, projects)
104
+ project_ids = list(map(lambda p: p.id, filtered_projects))
105
+ return project_ids
@@ -0,0 +1,34 @@
1
+ from package.data.project import get_project_name, add_project, update_project
2
+ from package.data.record import add_record, get_recent_pending_record_by_project
3
+ from package.entity.project import Project
4
+ from package.entity.record import Record
5
+ from package.utils.utils import get_config
6
+ from datetime import datetime
7
+ import typer
8
+ import subprocess
9
+
10
+
11
+ def start(task_desc, project_name, extra_id):
12
+ p = get_project_name(project_name)
13
+ if not p:
14
+ p = Project(None, project_name=project_name, project_status=1)
15
+ p_id = add_project(p)
16
+ else:
17
+ p_id = p.id
18
+
19
+ record = get_recent_pending_record_by_project(Record(id=None, project_id=p_id))
20
+ if record:
21
+ typer.secho("项目已经进行,请先停止", fg=typer.colors.RED, err=True)
22
+ return
23
+ start_time = int(datetime.today().timestamp())
24
+ record = Record(
25
+ None, start=start_time, task_desc=task_desc, project_id=p_id, extra_id=extra_id
26
+ )
27
+ add_record(record=record)
28
+ p.project_status = 1
29
+ update_project(project=p)
30
+
31
+ config = get_config()
32
+ if config["enable_timew"]:
33
+ params = ["timew", "start", project_name]
34
+ subprocess.run(params)
@@ -0,0 +1,33 @@
1
+ from package.data.project import get_project_name, update_project
2
+ from package.data.record import get_recent_pending_record_by_project, update_stop_record
3
+ from package.entity.record import Record
4
+ from package.utils.utils import get_config
5
+ from datetime import datetime
6
+ import typer
7
+ import subprocess
8
+
9
+
10
+ def stop(project_name):
11
+ project = get_project_name(project_name=project_name)
12
+ total_second = project.fatigue_total * 60
13
+ used_total_second = project.fatigue_total * total_second
14
+ if not project:
15
+ raise ValueError("找不到项目,请重新输入")
16
+ if not (record_ := get_recent_pending_record_by_project(Record(id=None, project_id=project.id))):
17
+ typer.secho("找不到正在进行的记录")
18
+ return
19
+ start_time = record_.start
20
+ if not start_time:
21
+ raise ValueError("找不到开始时间,请重新输入")
22
+ stop_time = int(datetime.today().timestamp())
23
+ duration_time = stop_time - start_time
24
+ record = Record(
25
+ id=None, stop=stop_time, duration=duration_time, project_id=project.id
26
+ )
27
+ update_stop_record(record)
28
+ project.project_status = 0 if used_total_second >= duration_time else 1
29
+ update_project(project=project)
30
+ config = get_config()
31
+ if config["enable_timew"]:
32
+ params = ["timew", "stop", project_name]
33
+ subprocess.run(params)
@@ -0,0 +1,11 @@
1
+ from pathlib import Path
2
+ import os
3
+ import json
4
+
5
+ CONFIG_DIR = str(Path.home() / ".config" / "mana" / "data")
6
+ CONFIG_PATH = os.path.join(CONFIG_DIR, "config.json")
7
+
8
+
9
+ def get_config():
10
+ with open(CONFIG_PATH, "r", encoding="utf-8") as f:
11
+ return json.load(f)
@@ -0,0 +1,40 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "Tonus"
7
+ version = "1.1.1"
8
+ description = "a energy bar"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ authors = [
12
+ {name = "firefly", email = "your.email@example.com"}
13
+ ]
14
+ classifiers = [
15
+ "Development Status :: 3 - Alpha",
16
+ "Intended Audience :: Developers",
17
+ "Programming Language :: Python :: 3",
18
+ "Programming Language :: Python :: 3.10",
19
+ "Programming Language :: Python :: 3.11",
20
+ "Programming Language :: Python :: 3.12",
21
+ ]
22
+
23
+ dependencies = [
24
+ "typer>=0.9.0",
25
+ "rich",
26
+ "typing_extensions"
27
+ ]
28
+
29
+ [project.optional-dependencies]
30
+ dev = [
31
+ "pytest>=7.0.0",
32
+ ]
33
+
34
+ [project.scripts]
35
+ to = "package.cli.main:main"
36
+
37
+ [tool.setuptools.packages.find]
38
+ where = ["."]
39
+ include = ["package*"]
40
+ exclude = ["test*"]
tonus-1.1.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+