todol 0.3.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.
todol-0.3.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Wattox
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
todol-0.3.1/PKG-INFO ADDED
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: todol
3
+ Version: 0.3.1
4
+ Summary: A python TUI todo app
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: prompt_toolkit>=3.0.52
9
+ Requires-Dist: platformdirs>=4.5.1
10
+ Requires-Dist: rich>=14.2.0
11
+ Requires-Dist: requests>=2.32.5
12
+ Dynamic: license-file
13
+
14
+ # Todol - Python TUI ToDo app
15
+
16
+ ## Installation
17
+
18
+ ```
19
+ pip install todol
20
+ ```
21
+
22
+ `todol` is a terminal application. I recommend installing it with `pipx`.
23
+
24
+ More Info
25
+
26
+ - Check out the project page on PyPi: [https://pypi.org/project/todol/](https://pypi.org/project/todol/)
27
+ - and on Github: [https://github.com/WattoX00/todol](https://github.com/WattoX00/todol)
28
+
29
+ ![Demo](assets/demo.png)
30
+
31
+ ## Running
32
+
33
+ ### Run from anywhere in your terminal
34
+
35
+ ```
36
+ todol
37
+ ```
38
+
39
+ ### Additional flags
40
+
41
+ View all flags (for more options):
42
+
43
+ ```
44
+ todol-help
45
+ ```
46
+
47
+ Check the current version:
48
+
49
+ ```
50
+ todol-version
51
+ ```
52
+
53
+ See where todo files are saved:
54
+
55
+ ```
56
+ todol-path
57
+ ```
58
+
59
+ Update todol with a single command
60
+
61
+ This runs `pipx upgrade todol` under the hood.
62
+
63
+ ```
64
+ todol-upgrade
65
+ ```
66
+
67
+ ## COMMAND GUIDE
68
+
69
+ ```
70
+ Command Alias Action Usage
71
+
72
+ add a Add new task add [task]
73
+ done d Mark task done done [id]
74
+ list l Show todo list list
75
+ remove rm Remove task rm [id]
76
+ edit e Edit task edit [id]
77
+ clear c Clear done tasks clear
78
+ help h Show help help
79
+ reload reset Reload the app reload
80
+ exit 0 Exit app exit
81
+ ```
82
+ ### Pro Tips:
83
+ - You can use Tab for autocomplete.
84
+ - Navigate the terminal efficiently: arrow keys, backspace, and delete all work.
85
+ - You can execute multiple commands at once:
86
+ - all - apply the command to all items
87
+
88
+ - id-id – apply the command to a range of IDs
89
+
90
+ - id1 id2 id3 – apply the command to specific IDs
91
+
92
+ ### examples:
93
+
94
+ ```
95
+ done all # marks all tasks as done
96
+ remove 4-7 # removes tasks with IDs 4 through 7
97
+ rm 3 5 8 # removes tasks 3, 5, and 8
98
+ ```
99
+
100
+ ## FAQ
101
+
102
+ ### Where are the saved todo files stored?
103
+
104
+ #### You can simply check it by running `todol-path`
105
+
106
+ `todol` stores its data using `platformdirs.user_data_dir`, which means files are written to the standard user data directory for each operating system.
107
+
108
+ #### Default locations
109
+
110
+ - **Linux**
111
+ `~/.local/share/todol/todoFiles/`
112
+
113
+ - **macOS**
114
+ `~/Library/Application Support/todol/todoFiles/`
115
+
116
+ - **Windows**
117
+ `%APPDATA%\todol\todoFiles\`
118
+
119
+ ## Hotkeys are available!
120
+
121
+ ### Cursor navigation
122
+
123
+ | Key | Action |
124
+ | -------- | -------------------------------- |
125
+ | `Ctrl‑a` | Move cursor to beginning of line |
126
+ | `Ctrl‑e` | Move cursor to end of line |
127
+ | `Ctrl‑f` | Move cursor forward (right) |
128
+ | `Ctrl‑b` | Move cursor backward (left) |
129
+ | `Alt‑f` | Move forward one word |
130
+ | `Alt‑b` | Move backward one word |
131
+ | `Home` | Go to start of line |
132
+ | `End` | Go to end of line |
133
+
134
+ ### Editing
135
+
136
+ | Key | Action |
137
+ | ---------------------- | ------------------------------ |
138
+ | `Ctrl‑d` | Delete character under cursor |
139
+ | `Ctrl‑h` / `Backspace` | Delete character before cursor |
140
+ | `Alt‑d` | Delete word forward |
141
+ | `Ctrl‑k` | Kill (cut) text to end of line |
142
+ | `Ctrl‑y` | Yank (paste) killed text |
143
+ | `Ctrl‑t` | Transpose characters |
144
+
145
+ ### History
146
+
147
+ | Key | Action |
148
+ | -------- | --------------------- |
149
+ | `Ctrl‑p` | Previous history item |
150
+ | `Ctrl‑n` | Next history item |
151
+
152
+ ### Searching
153
+
154
+ | Key | Action |
155
+ | -------- | ---------------------------------------------------------------------- |
156
+ | `Ctrl‑r` | Reverse search history |
157
+ | `Ctrl‑s` | Forward search history *(may be intercepted by terminal flow control)* |
158
+
159
+ ### Completion & Accept
160
+
161
+ | Key | Action |
162
+ | ------------ | ------------------------ |
163
+ | `Tab` | Trigger completion |
164
+ | `Ctrl‑Space` | Start/advance completion |
165
+ | `Enter` | Accept input |
166
+
167
+ ### Misc
168
+
169
+ | Key | Action |
170
+ | ---------- | ------------------------------------ |
171
+ | `Ctrl‑c` | Cancel / raise KeyboardInterrupt |
172
+ | `Ctrl‑z` | Suspend (depends on shell) |
173
+ | `Escape` | Escape/Meta prefix for `Alt‑` combos |
174
+ | Arrow keys | Move cursor up/down/left/right |
175
+
176
+ For the full official key binding documentation, check the prompt_toolkit docs: [prompt_toolkit GITHUB](https://github.com/prompt-toolkit/python-prompt-toolkit)
177
+
178
+ ## Support
179
+
180
+ If you find this project helpful and would like to support its development, you can make a donation via the following method:
181
+
182
+ - [PayPal](https://www.paypal.com/paypalme/wattox)
183
+
184
+ Your contribution helps in maintaining and improving the app. Thank you for your support!
185
+
186
+ ## License
187
+
188
+ This project is licensed under the [MIT License](LICENSE) - see the [LICENSE](LICENSE) file for details.
todol-0.3.1/README.md ADDED
@@ -0,0 +1,175 @@
1
+ # Todol - Python TUI ToDo app
2
+
3
+ ## Installation
4
+
5
+ ```
6
+ pip install todol
7
+ ```
8
+
9
+ `todol` is a terminal application. I recommend installing it with `pipx`.
10
+
11
+ More Info
12
+
13
+ - Check out the project page on PyPi: [https://pypi.org/project/todol/](https://pypi.org/project/todol/)
14
+ - and on Github: [https://github.com/WattoX00/todol](https://github.com/WattoX00/todol)
15
+
16
+ ![Demo](assets/demo.png)
17
+
18
+ ## Running
19
+
20
+ ### Run from anywhere in your terminal
21
+
22
+ ```
23
+ todol
24
+ ```
25
+
26
+ ### Additional flags
27
+
28
+ View all flags (for more options):
29
+
30
+ ```
31
+ todol-help
32
+ ```
33
+
34
+ Check the current version:
35
+
36
+ ```
37
+ todol-version
38
+ ```
39
+
40
+ See where todo files are saved:
41
+
42
+ ```
43
+ todol-path
44
+ ```
45
+
46
+ Update todol with a single command
47
+
48
+ This runs `pipx upgrade todol` under the hood.
49
+
50
+ ```
51
+ todol-upgrade
52
+ ```
53
+
54
+ ## COMMAND GUIDE
55
+
56
+ ```
57
+ Command Alias Action Usage
58
+
59
+ add a Add new task add [task]
60
+ done d Mark task done done [id]
61
+ list l Show todo list list
62
+ remove rm Remove task rm [id]
63
+ edit e Edit task edit [id]
64
+ clear c Clear done tasks clear
65
+ help h Show help help
66
+ reload reset Reload the app reload
67
+ exit 0 Exit app exit
68
+ ```
69
+ ### Pro Tips:
70
+ - You can use Tab for autocomplete.
71
+ - Navigate the terminal efficiently: arrow keys, backspace, and delete all work.
72
+ - You can execute multiple commands at once:
73
+ - all - apply the command to all items
74
+
75
+ - id-id – apply the command to a range of IDs
76
+
77
+ - id1 id2 id3 – apply the command to specific IDs
78
+
79
+ ### examples:
80
+
81
+ ```
82
+ done all # marks all tasks as done
83
+ remove 4-7 # removes tasks with IDs 4 through 7
84
+ rm 3 5 8 # removes tasks 3, 5, and 8
85
+ ```
86
+
87
+ ## FAQ
88
+
89
+ ### Where are the saved todo files stored?
90
+
91
+ #### You can simply check it by running `todol-path`
92
+
93
+ `todol` stores its data using `platformdirs.user_data_dir`, which means files are written to the standard user data directory for each operating system.
94
+
95
+ #### Default locations
96
+
97
+ - **Linux**
98
+ `~/.local/share/todol/todoFiles/`
99
+
100
+ - **macOS**
101
+ `~/Library/Application Support/todol/todoFiles/`
102
+
103
+ - **Windows**
104
+ `%APPDATA%\todol\todoFiles\`
105
+
106
+ ## Hotkeys are available!
107
+
108
+ ### Cursor navigation
109
+
110
+ | Key | Action |
111
+ | -------- | -------------------------------- |
112
+ | `Ctrl‑a` | Move cursor to beginning of line |
113
+ | `Ctrl‑e` | Move cursor to end of line |
114
+ | `Ctrl‑f` | Move cursor forward (right) |
115
+ | `Ctrl‑b` | Move cursor backward (left) |
116
+ | `Alt‑f` | Move forward one word |
117
+ | `Alt‑b` | Move backward one word |
118
+ | `Home` | Go to start of line |
119
+ | `End` | Go to end of line |
120
+
121
+ ### Editing
122
+
123
+ | Key | Action |
124
+ | ---------------------- | ------------------------------ |
125
+ | `Ctrl‑d` | Delete character under cursor |
126
+ | `Ctrl‑h` / `Backspace` | Delete character before cursor |
127
+ | `Alt‑d` | Delete word forward |
128
+ | `Ctrl‑k` | Kill (cut) text to end of line |
129
+ | `Ctrl‑y` | Yank (paste) killed text |
130
+ | `Ctrl‑t` | Transpose characters |
131
+
132
+ ### History
133
+
134
+ | Key | Action |
135
+ | -------- | --------------------- |
136
+ | `Ctrl‑p` | Previous history item |
137
+ | `Ctrl‑n` | Next history item |
138
+
139
+ ### Searching
140
+
141
+ | Key | Action |
142
+ | -------- | ---------------------------------------------------------------------- |
143
+ | `Ctrl‑r` | Reverse search history |
144
+ | `Ctrl‑s` | Forward search history *(may be intercepted by terminal flow control)* |
145
+
146
+ ### Completion & Accept
147
+
148
+ | Key | Action |
149
+ | ------------ | ------------------------ |
150
+ | `Tab` | Trigger completion |
151
+ | `Ctrl‑Space` | Start/advance completion |
152
+ | `Enter` | Accept input |
153
+
154
+ ### Misc
155
+
156
+ | Key | Action |
157
+ | ---------- | ------------------------------------ |
158
+ | `Ctrl‑c` | Cancel / raise KeyboardInterrupt |
159
+ | `Ctrl‑z` | Suspend (depends on shell) |
160
+ | `Escape` | Escape/Meta prefix for `Alt‑` combos |
161
+ | Arrow keys | Move cursor up/down/left/right |
162
+
163
+ For the full official key binding documentation, check the prompt_toolkit docs: [prompt_toolkit GITHUB](https://github.com/prompt-toolkit/python-prompt-toolkit)
164
+
165
+ ## Support
166
+
167
+ If you find this project helpful and would like to support its development, you can make a donation via the following method:
168
+
169
+ - [PayPal](https://www.paypal.com/paypalme/wattox)
170
+
171
+ Your contribution helps in maintaining and improving the app. Thank you for your support!
172
+
173
+ ## License
174
+
175
+ This project is licensed under the [MIT License](LICENSE) - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,29 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77.0.3"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "todol"
7
+ version = "0.3.1"
8
+ description = "A python TUI todo app"
9
+ readme = "README.md"
10
+ license-files = ["LICENSE"]
11
+ requires-python = ">=3.9"
12
+
13
+ dependencies = [
14
+ "prompt_toolkit>=3.0.52",
15
+ "platformdirs>=4.5.1",
16
+ "rich>=14.2.0",
17
+ "requests>=2.32.5"
18
+ ]
19
+
20
+ [project.scripts]
21
+ todol = "todol.main:main"
22
+ todol-help = "todol.flags.todol_help:main"
23
+ todol-path = "todol.flags.todol_path:main"
24
+ todol-version = "todol.flags.todol_version:main"
25
+ todol-upgrade = "todol.flags.todol_upgrade:main"
26
+
27
+ [tool.setuptools.packages.find]
28
+ where = ["."]
29
+ include = ["todol*"]
todol-0.3.1/setup.cfg ADDED
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
File without changes
File without changes
@@ -0,0 +1,9 @@
1
+ def main():
2
+ print("todol - A simple terminal todo list app")
3
+ print("Type 'h' inside the app to see available commands.")
4
+ print("\nAdditional flags:")
5
+ print(" todol-help Show this help message")
6
+ print(" todol-version Show current version")
7
+ print(" todol-path Show where todo files are stored")
8
+ print(" todol-upgrade Update todol (runs 'pipx upgrade todol')")
9
+ print("\nDocumentation: https://github.com/wattox00/todol")
@@ -0,0 +1,11 @@
1
+ from platformdirs import user_data_dir
2
+ from pathlib import Path
3
+
4
+ def main():
5
+ DATA_DIR = Path(user_data_dir('todol', 'todol'))
6
+ TODO_DIR = DATA_DIR / 'todoFiles'
7
+
8
+ if TODO_DIR.exists():
9
+ print(f"Todol files:\n{TODO_DIR}")
10
+ else:
11
+ print("No data directory found. Run the app first: todol")
@@ -0,0 +1,37 @@
1
+ import sys
2
+ import requests
3
+ import subprocess
4
+ from importlib.metadata import version, PackageNotFoundError
5
+
6
+ PACKAGE_NAME = "todol"
7
+
8
+ def main():
9
+ try:
10
+ installed_version = version(PACKAGE_NAME)
11
+ except PackageNotFoundError:
12
+ installed_version = "not installed"
13
+
14
+ try:
15
+ resp = requests.get(f"https://pypi.org/pypi/{PACKAGE_NAME}/json", timeout=3)
16
+ resp.raise_for_status()
17
+ latest_version = resp.json()["info"]["version"]
18
+ except Exception:
19
+ latest_version = "unknown"
20
+
21
+ if installed_version == latest_version:
22
+ print(f"{PACKAGE_NAME} {installed_version} Your packages are up to date")
23
+ else:
24
+ print(f"{PACKAGE_NAME} {installed_version} (newer version available: {latest_version})")
25
+
26
+ update_question = input("Do you want to update it now? [Y/n]: ").strip().lower()
27
+ if update_question in ("", "y", "yes"):
28
+ try:
29
+ print("Running: pipx upgrade todol")
30
+ subprocess.run(["pipx", "upgrade", PACKAGE_NAME], check=True)
31
+ print("Update completed!")
32
+ except FileNotFoundError:
33
+ print("pipx not found. Please install it first: https://pipx.pypa.io/latest/installation/")
34
+ except subprocess.CalledProcessError:
35
+ print("Update failed. Try running the command manually: pipx upgrade todol")
36
+
37
+ sys.exit(0)
@@ -0,0 +1,25 @@
1
+ import sys
2
+ import requests
3
+ from importlib.metadata import version, PackageNotFoundError
4
+
5
+ PACKAGE_NAME = "todol"
6
+
7
+ def main():
8
+ try:
9
+ installed_version = version(PACKAGE_NAME)
10
+ except PackageNotFoundError:
11
+ installed_version = "not installed"
12
+
13
+ try:
14
+ resp = requests.get(f"https://pypi.org/pypi/{PACKAGE_NAME}/json", timeout=3)
15
+ resp.raise_for_status()
16
+ latest_version = resp.json()["info"]["version"]
17
+ except Exception:
18
+ latest_version = "unknown"
19
+
20
+ if installed_version == latest_version:
21
+ print(f"{PACKAGE_NAME} {installed_version} LTS")
22
+ else:
23
+ print(f"{PACKAGE_NAME} {installed_version} newer version available: {latest_version}")
24
+
25
+ sys.exit(0)
@@ -0,0 +1,432 @@
1
+ import json
2
+
3
+ from prompt_toolkit.completion import Completer, Completion
4
+ from prompt_toolkit.history import FileHistory
5
+ from prompt_toolkit.formatted_text import HTML
6
+ from prompt_toolkit import PromptSession
7
+ from prompt_toolkit.shortcuts import clear
8
+
9
+ from prompt_toolkit.key_binding import KeyBindings
10
+ from prompt_toolkit.filters import Condition
11
+ from prompt_toolkit.application.current import get_app
12
+
13
+ from rich.console import Console
14
+ from rich.table import Table
15
+ from rich.text import Text
16
+ from rich import print
17
+
18
+ from platformdirs import user_data_dir
19
+ from pathlib import Path
20
+
21
+ # app start
22
+
23
+ DATA_DIR = Path(user_data_dir('todol', 'todol'))
24
+ TODO_DIR = DATA_DIR / 'todoFiles'
25
+ TODO_JSON = TODO_DIR / 'main.json'
26
+ HISTORY_FILE = TODO_DIR / 'history'
27
+
28
+ TODO_DIR.mkdir(parents = True, exist_ok = True)
29
+
30
+ if not TODO_JSON.exists():
31
+ TODO_JSON.write_text('{"tasks": {}}')
32
+
33
+ HISTORY_FILE.touch()
34
+ HISTORY_FILE.write_text('')
35
+
36
+ class Functions():
37
+
38
+ # greeting
39
+ # reload view
40
+
41
+ def greetingAppStart():
42
+
43
+ clear()
44
+
45
+ print(r"""
46
+ ████████ ██████ █████ ██████ ██
47
+ ██ ██ ██ ██ ██ ██ ██ ██
48
+ ██ ██ ██ ██ ██ ██ ██ ██
49
+ ██ ██ ██ ██ ██ ██ ██ ██
50
+ ██ ██████ █████ ██████ ███████
51
+ """)
52
+
53
+ print('[bold yellow]Type h or help to see the available commands and what they do![/bold yellow]\n')
54
+
55
+ Functions.openJson()
56
+
57
+ # open Json (write on start)
58
+
59
+ def openJson():
60
+ console = Console()
61
+ data = Functions.load_todos()
62
+ tasks = data.get("tasks", {})
63
+
64
+ pending = []
65
+ completed = []
66
+
67
+ for task_id, task in tasks.items():
68
+ if task.get("completed"):
69
+ completed.append((task_id, task))
70
+ else:
71
+ pending.append((task_id, task))
72
+
73
+ table = Table(
74
+ show_header=True,
75
+ header_style="bold magenta",
76
+ title="Todo List",
77
+ caption=f"Pending: {len(pending)} | Completed: {len(completed)}"
78
+ )
79
+
80
+ table.add_column("ID", style="cyan", width=3, no_wrap=True)
81
+ table.add_column("Task", style="bold white", min_width=20)
82
+ table.add_column("Description", style="dim", overflow="fold")
83
+ table.add_column("Time", style="yellow", width=10)
84
+ table.add_column("Status", justify="center", width=10)
85
+
86
+ def render_row(task_id, task, completed=False):
87
+ status = Text("DONE", style="bold green") if completed else Text("TODO", style="bold red")
88
+ name = Text(task["name"])
89
+
90
+ if completed:
91
+ name.stylize("strike dim")
92
+
93
+ return [
94
+ task_id,
95
+ name,
96
+ task.get("desc", ""),
97
+ task.get("time", "-"),
98
+ status
99
+ ]
100
+
101
+ for task_id, task in pending:
102
+ table.add_row(*render_row(task_id, task))
103
+
104
+ if completed:
105
+ table.add_section()
106
+ for task_id, task in completed:
107
+ table.add_row(*render_row(task_id, task, completed=True))
108
+
109
+ console.print((table))
110
+
111
+ # add task to json
112
+
113
+ def addTaskJson(task):
114
+ data: dict = Functions.load_todos()
115
+
116
+ if data['tasks']:
117
+ new_id: str = str(max(map(int, data['tasks'].keys())) + 1)
118
+ else:
119
+ new_id: str = '1'
120
+
121
+ data['tasks'][new_id] = task
122
+
123
+ Functions.save_todos(data)
124
+ print(f'\n[bold yellow]Task {new_id} Added![/bold yellow]\n')
125
+
126
+ def addTask(full_cmd):
127
+ title: str = " ".join(full_cmd)
128
+ description: str = Prompts.session.prompt(HTML('\n<ansiblue>[todol ~] description : </ansiblue>\n'+ Prompts.line_prefix(1))).strip()
129
+ time: str = Prompts.session.prompt('\n[todol ~] time : ').strip()
130
+ return {'name': title, 'desc': description, 'time': time, 'completed': False}
131
+
132
+ # remove task from json
133
+
134
+ def removeTaskJson(index):
135
+
136
+ data: dict = Functions.load_todos()
137
+
138
+ try:
139
+ if index[0] == "all":
140
+ data['tasks'].clear()
141
+
142
+ print(f'\n[bold yellow]All Tasks been removed![/bold yellow]\n')
143
+ else:
144
+ for arg in index:
145
+
146
+ if "-" in arg:
147
+ min_i, max_i = arg.split("-")
148
+
149
+ for task in range(int(min_i), int(max_i) + 1):
150
+ task = str(task)
151
+ if task in data['tasks']:
152
+ del data['tasks'][task]
153
+
154
+ print(f'\n[bold yellow]Tasks {index[0]} been removed![/bold yellow]\n')
155
+
156
+ else:
157
+ del data['tasks'][str(arg)]
158
+
159
+ print(f'\n[bold yellow]Task(s) {index} been removed![/bold yellow]\n')
160
+ Functions.save_todos(data)
161
+
162
+ except ValueError:
163
+ print('Invalid input. Please enter a valid number.')
164
+ except KeyError:
165
+ print('Invalid input. Please enter a valid number.')
166
+
167
+ # edit task
168
+
169
+ def editTask(editIndex):
170
+
171
+ data: dict = Functions.load_todos()
172
+
173
+ try:
174
+ title: str = data['tasks'][editIndex]['name']
175
+ desc: str = data['tasks'][editIndex]['desc']
176
+ time: str = data['tasks'][editIndex]['time']
177
+
178
+ editTittle = Prompts.session.prompt('[todol ~] title (edit) : ', default=title)
179
+
180
+ editDesc = Prompts.session.prompt(HTML('\n<ansiblue>[todol ~] description (edit) : </ansiblue>\n'+Prompts.line_prefix(1)), default=desc)
181
+
182
+ editTime = Prompts.session.prompt('\n[todol ~] time (edit) : ', default=time)
183
+
184
+ data['tasks'][editIndex] = {'name': editTittle, 'desc': editDesc, 'time': editTime, 'completed': False}
185
+
186
+ Functions.save_todos(data)
187
+
188
+ print(f'\n[bold yellow]Task {editIndex} Edited![/bold yellow]\n')
189
+
190
+ except ValueError:
191
+ print('Invalid input. Please enter a valid number.')
192
+ except KeyError:
193
+ print('Invalid input. Please enter a valid number.')
194
+
195
+ # mark task as done in json
196
+
197
+ def doneTaskJson(doneIndex):
198
+
199
+ data: dict = Functions.load_todos()
200
+
201
+ try:
202
+ if doneIndex[0] == "all":
203
+ for key in data['tasks']:
204
+ data['tasks'][key]['completed'] = True
205
+
206
+ else:
207
+ for arg in doneIndex:
208
+
209
+ if "-" in arg:
210
+ min_i, max_i = arg.split("-")
211
+
212
+ for task in range(int(min_i), int(max_i) + 1):
213
+ task = str(task)
214
+ if task in data['tasks']:
215
+ data['tasks'][task]['completed'] = True
216
+
217
+ else:
218
+ data['tasks'][str(arg)]['completed'] = True
219
+
220
+ Functions.save_todos(data)
221
+
222
+ print(f'\n[bold yellow]Task(s) {doneIndex} marked Done![/bold yellow]\n')
223
+
224
+ except ValueError:
225
+ print('Invalid input. Please enter a valid number.')
226
+ except KeyError:
227
+ print('Invalid input. Please enter a valid number.')
228
+
229
+ # remove tasks that are completed
230
+
231
+ def clearTaskJson():
232
+
233
+ data: dict = Functions.load_todos()
234
+
235
+ for count in list(data['tasks']):
236
+ if data['tasks'][count]['completed']:
237
+ del data['tasks'][count]
238
+
239
+ Functions.save_todos(data)
240
+
241
+ print('\n[bold yellow]TODO list CLEARED![/bold yellow]\n')
242
+
243
+ # print help commands
244
+
245
+ def helpText():
246
+ console = Console()
247
+
248
+ table = Table(show_header=True, header_style="bold")
249
+
250
+ table.add_column("Command", style="cyan", width=10)
251
+ table.add_column("Alias", style="green", width=6)
252
+ table.add_column("Action", style="bold")
253
+ table.add_column("Usage", style="dim")
254
+
255
+ table.add_row("add", "a", "Add new task", "add [task]")
256
+ table.add_row("done", "d", "Mark task done", "done [id]")
257
+ table.add_row("list", "l", "Show todo list", "list")
258
+ table.add_row("remove", "rm", "Remove task", "rm [id]")
259
+ table.add_row("edit", "e", "Edit task", "edit [id]")
260
+ table.add_row("clear", "c", "Clear done tasks", "clear")
261
+ table.add_row("help", "h", "Show help", "help")
262
+ table.add_row("reload", "reset", "Reload the app", "reload")
263
+ table.add_row("exit", "0", "Exit app", "exit")
264
+
265
+ console.print(table)
266
+ print(
267
+ "\nBatch Operations:\n"
268
+ "You can apply commands to multiple tasks at once:\n"
269
+ " - Use 'all' to target all tasks\n"
270
+ " - Specify a range with 'start-end', e.g., 2-5\n"
271
+ " - List multiple IDs separated by spaces, e.g., 1 3 7\n"
272
+ "Examples:\n"
273
+ " done all # mark all tasks done\n"
274
+ " rm 2-4 # remove tasks 2, 3, 4\n"
275
+ " done 1 5 7 # mark tasks 1, 5, and 7 done"
276
+ )
277
+
278
+ # load json file
279
+
280
+ def load_todos():
281
+ with open(TODO_JSON, 'r') as f:
282
+ return json.load(f)
283
+
284
+ # save to the json file
285
+
286
+ def save_todos(data):
287
+ with open(TODO_JSON, 'w') as f:
288
+ json.dump(data, f, indent=4)
289
+
290
+ class Commands():
291
+ def cmd_add(args):
292
+ data = Functions.addTask(args)
293
+ Functions.addTaskJson(data)
294
+
295
+ def cmd_done(args):
296
+ Functions.doneTaskJson(args)
297
+
298
+ def cmd_remove(args):
299
+ Functions.removeTaskJson(args)
300
+
301
+ def cmd_edit(args):
302
+ Functions.editTask(args[0])
303
+
304
+ def cmd_help(args):
305
+ Functions.helpText()
306
+
307
+ def cmd_list(args):
308
+ Functions.openJson()
309
+
310
+ def cmd_clear(args):
311
+ Functions.clearTaskJson()
312
+
313
+ def cmd_reload(args):
314
+ Functions.greetingAppStart()
315
+
316
+ def cmd_exit(args):
317
+ raise SystemExit
318
+
319
+ def aliases(func, *names):
320
+ return {name: func for name in names}
321
+
322
+ COMMANDS = {
323
+ **aliases(cmd_add, "add", "a"),
324
+ **aliases(cmd_done, "done", "d"),
325
+ **aliases(cmd_remove, "remove", "rm"),
326
+ **aliases(cmd_edit, "edit", "e"),
327
+ **aliases(cmd_help, "help", "h"),
328
+ **aliases(cmd_list, "list", "ll", "ls", "l"),
329
+ **aliases(cmd_clear, "clear", "clean", "c"),
330
+ **aliases(cmd_reload, "reload", "reset"),
331
+ **aliases(cmd_exit, "exit", "0", "q"),
332
+ }
333
+
334
+ class ShellCompleter(Completer):
335
+ def get_completions(self, document, complete_event):
336
+ if not complete_event.completion_requested:
337
+ return
338
+
339
+ text = document.text_before_cursor
340
+ words = text.split()
341
+
342
+ if not words:
343
+ for cmd in Commands.COMMANDS:
344
+ yield Completion(cmd, start_position=0)
345
+ return
346
+
347
+ if len(words) == 1 and not text.endswith(" "):
348
+ current = words[0]
349
+ for cmd in Commands.COMMANDS:
350
+ if cmd.startswith(current):
351
+ yield Completion(cmd, start_position=-len(current))
352
+ return
353
+
354
+ cmd = words[0]
355
+ args = Commands.COMMANDS.get(cmd, [])
356
+
357
+ if args:
358
+ current = words[-1] if not text.endswith(" ") else ""
359
+ for arg in args:
360
+ if arg.startswith(current):
361
+ yield Completion(arg, start_position=-len(current))
362
+
363
+
364
+ class Prompts:
365
+ kb = KeyBindings()
366
+
367
+ @staticmethod
368
+ def line_prefix(n: int) -> str:
369
+ return f"{n:>3} | "
370
+
371
+ @staticmethod
372
+ def prompt_continuation(width, line_number, is_soft_wrap):
373
+ return Prompts.line_prefix(line_number + 1)
374
+
375
+ @staticmethod
376
+ def editing_bottom_toolbar():
377
+ text = (
378
+ "[MULTILINE MODE] "
379
+ "Switch mode: Ctrl+D | "
380
+ "Save: Esc+Enter | "
381
+ "New line: Enter | "
382
+ "Move: ↑/↓ | "
383
+ "Clear line: Ctrl+U"
384
+ )
385
+ app = get_app()
386
+ width = app.output.get_size().columns
387
+ padded = text.ljust(width)
388
+ return HTML(f"<style fg='ansiblack' bg='ansiwhite'>{padded}</style>")
389
+
390
+ @staticmethod
391
+ def normal_bottom_toolbar():
392
+ text = (
393
+ "[NORMAL MODE] "
394
+ "Switch mode: Ctrl+D | "
395
+ "Execute: Enter"
396
+ )
397
+ app = get_app()
398
+ width = app.output.get_size().columns
399
+ padded = text.ljust(width)
400
+ return HTML(f"<style fg='ansiblack' bg='ansiwhite'>{padded}</style>")
401
+ @Condition
402
+ def desc_mode():
403
+ return getattr(Prompts.session, "_desc_mode", False)
404
+
405
+ @staticmethod
406
+ def dynamic_multiline():
407
+ return Prompts._desc_mode()
408
+
409
+ def dynamic_prompt_continuation(width, line_number, is_soft_wrap):
410
+ if Prompts.desc_mode():
411
+ return Prompts.prompt_continuation(width, line_number, is_soft_wrap)
412
+ return ""
413
+
414
+ def dynamic_toolbar():
415
+ if Prompts.desc_mode():
416
+ return Prompts.editing_bottom_toolbar()
417
+ return Prompts.normal_bottom_toolbar()
418
+
419
+ @kb.add("c-d")
420
+ def toggle_desc_mode(event):
421
+ Prompts.session._desc_mode = not getattr(Prompts.session, "_desc_mode", False)
422
+ event.app.invalidate()
423
+
424
+ session = PromptSession(
425
+ completer=ShellCompleter(),
426
+ complete_while_typing=False,
427
+ history=FileHistory(HISTORY_FILE),
428
+ multiline=desc_mode,
429
+ prompt_continuation=dynamic_prompt_continuation,
430
+ bottom_toolbar=dynamic_toolbar,
431
+ key_bindings=kb,
432
+ )
@@ -0,0 +1,34 @@
1
+ from .functions import Functions, Commands, Prompts
2
+
3
+ def main():
4
+ Functions.greetingAppStart()
5
+
6
+ # main loop
7
+
8
+ while True:
9
+ try:
10
+ raw = Prompts.session.prompt('[todol ~]$ ').strip()
11
+
12
+ except KeyboardInterrupt:
13
+ break
14
+
15
+ if not raw:
16
+ continue
17
+
18
+ parts = raw.split()
19
+ command, *args = parts
20
+
21
+ func = Commands.COMMANDS.get(command)
22
+
23
+ if not func:
24
+ print(f'{command}: command not found')
25
+ continue
26
+
27
+ try:
28
+ func(args)
29
+ except IndexError:
30
+ print('Missing argument')
31
+ except SystemExit:
32
+ break
33
+ except KeyboardInterrupt:
34
+ break
@@ -0,0 +1,188 @@
1
+ Metadata-Version: 2.4
2
+ Name: todol
3
+ Version: 0.3.1
4
+ Summary: A python TUI todo app
5
+ Requires-Python: >=3.9
6
+ Description-Content-Type: text/markdown
7
+ License-File: LICENSE
8
+ Requires-Dist: prompt_toolkit>=3.0.52
9
+ Requires-Dist: platformdirs>=4.5.1
10
+ Requires-Dist: rich>=14.2.0
11
+ Requires-Dist: requests>=2.32.5
12
+ Dynamic: license-file
13
+
14
+ # Todol - Python TUI ToDo app
15
+
16
+ ## Installation
17
+
18
+ ```
19
+ pip install todol
20
+ ```
21
+
22
+ `todol` is a terminal application. I recommend installing it with `pipx`.
23
+
24
+ More Info
25
+
26
+ - Check out the project page on PyPi: [https://pypi.org/project/todol/](https://pypi.org/project/todol/)
27
+ - and on Github: [https://github.com/WattoX00/todol](https://github.com/WattoX00/todol)
28
+
29
+ ![Demo](assets/demo.png)
30
+
31
+ ## Running
32
+
33
+ ### Run from anywhere in your terminal
34
+
35
+ ```
36
+ todol
37
+ ```
38
+
39
+ ### Additional flags
40
+
41
+ View all flags (for more options):
42
+
43
+ ```
44
+ todol-help
45
+ ```
46
+
47
+ Check the current version:
48
+
49
+ ```
50
+ todol-version
51
+ ```
52
+
53
+ See where todo files are saved:
54
+
55
+ ```
56
+ todol-path
57
+ ```
58
+
59
+ Update todol with a single command
60
+
61
+ This runs `pipx upgrade todol` under the hood.
62
+
63
+ ```
64
+ todol-upgrade
65
+ ```
66
+
67
+ ## COMMAND GUIDE
68
+
69
+ ```
70
+ Command Alias Action Usage
71
+
72
+ add a Add new task add [task]
73
+ done d Mark task done done [id]
74
+ list l Show todo list list
75
+ remove rm Remove task rm [id]
76
+ edit e Edit task edit [id]
77
+ clear c Clear done tasks clear
78
+ help h Show help help
79
+ reload reset Reload the app reload
80
+ exit 0 Exit app exit
81
+ ```
82
+ ### Pro Tips:
83
+ - You can use Tab for autocomplete.
84
+ - Navigate the terminal efficiently: arrow keys, backspace, and delete all work.
85
+ - You can execute multiple commands at once:
86
+ - all - apply the command to all items
87
+
88
+ - id-id – apply the command to a range of IDs
89
+
90
+ - id1 id2 id3 – apply the command to specific IDs
91
+
92
+ ### examples:
93
+
94
+ ```
95
+ done all # marks all tasks as done
96
+ remove 4-7 # removes tasks with IDs 4 through 7
97
+ rm 3 5 8 # removes tasks 3, 5, and 8
98
+ ```
99
+
100
+ ## FAQ
101
+
102
+ ### Where are the saved todo files stored?
103
+
104
+ #### You can simply check it by running `todol-path`
105
+
106
+ `todol` stores its data using `platformdirs.user_data_dir`, which means files are written to the standard user data directory for each operating system.
107
+
108
+ #### Default locations
109
+
110
+ - **Linux**
111
+ `~/.local/share/todol/todoFiles/`
112
+
113
+ - **macOS**
114
+ `~/Library/Application Support/todol/todoFiles/`
115
+
116
+ - **Windows**
117
+ `%APPDATA%\todol\todoFiles\`
118
+
119
+ ## Hotkeys are available!
120
+
121
+ ### Cursor navigation
122
+
123
+ | Key | Action |
124
+ | -------- | -------------------------------- |
125
+ | `Ctrl‑a` | Move cursor to beginning of line |
126
+ | `Ctrl‑e` | Move cursor to end of line |
127
+ | `Ctrl‑f` | Move cursor forward (right) |
128
+ | `Ctrl‑b` | Move cursor backward (left) |
129
+ | `Alt‑f` | Move forward one word |
130
+ | `Alt‑b` | Move backward one word |
131
+ | `Home` | Go to start of line |
132
+ | `End` | Go to end of line |
133
+
134
+ ### Editing
135
+
136
+ | Key | Action |
137
+ | ---------------------- | ------------------------------ |
138
+ | `Ctrl‑d` | Delete character under cursor |
139
+ | `Ctrl‑h` / `Backspace` | Delete character before cursor |
140
+ | `Alt‑d` | Delete word forward |
141
+ | `Ctrl‑k` | Kill (cut) text to end of line |
142
+ | `Ctrl‑y` | Yank (paste) killed text |
143
+ | `Ctrl‑t` | Transpose characters |
144
+
145
+ ### History
146
+
147
+ | Key | Action |
148
+ | -------- | --------------------- |
149
+ | `Ctrl‑p` | Previous history item |
150
+ | `Ctrl‑n` | Next history item |
151
+
152
+ ### Searching
153
+
154
+ | Key | Action |
155
+ | -------- | ---------------------------------------------------------------------- |
156
+ | `Ctrl‑r` | Reverse search history |
157
+ | `Ctrl‑s` | Forward search history *(may be intercepted by terminal flow control)* |
158
+
159
+ ### Completion & Accept
160
+
161
+ | Key | Action |
162
+ | ------------ | ------------------------ |
163
+ | `Tab` | Trigger completion |
164
+ | `Ctrl‑Space` | Start/advance completion |
165
+ | `Enter` | Accept input |
166
+
167
+ ### Misc
168
+
169
+ | Key | Action |
170
+ | ---------- | ------------------------------------ |
171
+ | `Ctrl‑c` | Cancel / raise KeyboardInterrupt |
172
+ | `Ctrl‑z` | Suspend (depends on shell) |
173
+ | `Escape` | Escape/Meta prefix for `Alt‑` combos |
174
+ | Arrow keys | Move cursor up/down/left/right |
175
+
176
+ For the full official key binding documentation, check the prompt_toolkit docs: [prompt_toolkit GITHUB](https://github.com/prompt-toolkit/python-prompt-toolkit)
177
+
178
+ ## Support
179
+
180
+ If you find this project helpful and would like to support its development, you can make a donation via the following method:
181
+
182
+ - [PayPal](https://www.paypal.com/paypalme/wattox)
183
+
184
+ Your contribution helps in maintaining and improving the app. Thank you for your support!
185
+
186
+ ## License
187
+
188
+ This project is licensed under the [MIT License](LICENSE) - see the [LICENSE](LICENSE) file for details.
@@ -0,0 +1,17 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ todol/__init__.py
5
+ todol/functions.py
6
+ todol/main.py
7
+ todol.egg-info/PKG-INFO
8
+ todol.egg-info/SOURCES.txt
9
+ todol.egg-info/dependency_links.txt
10
+ todol.egg-info/entry_points.txt
11
+ todol.egg-info/requires.txt
12
+ todol.egg-info/top_level.txt
13
+ todol/flags/__init__.py
14
+ todol/flags/todol_help.py
15
+ todol/flags/todol_path.py
16
+ todol/flags/todol_upgrade.py
17
+ todol/flags/todol_version.py
@@ -0,0 +1,6 @@
1
+ [console_scripts]
2
+ todol = todol.main:main
3
+ todol-help = todol.flags.todol_help:main
4
+ todol-path = todol.flags.todol_path:main
5
+ todol-upgrade = todol.flags.todol_upgrade:main
6
+ todol-version = todol.flags.todol_version:main
@@ -0,0 +1,4 @@
1
+ prompt_toolkit>=3.0.52
2
+ platformdirs>=4.5.1
3
+ rich>=14.2.0
4
+ requests>=2.32.5
@@ -0,0 +1 @@
1
+ todol