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 +21 -0
- todol-0.3.1/PKG-INFO +188 -0
- todol-0.3.1/README.md +175 -0
- todol-0.3.1/pyproject.toml +29 -0
- todol-0.3.1/setup.cfg +4 -0
- todol-0.3.1/todol/__init__.py +0 -0
- todol-0.3.1/todol/flags/__init__.py +0 -0
- todol-0.3.1/todol/flags/todol_help.py +9 -0
- todol-0.3.1/todol/flags/todol_path.py +11 -0
- todol-0.3.1/todol/flags/todol_upgrade.py +37 -0
- todol-0.3.1/todol/flags/todol_version.py +25 -0
- todol-0.3.1/todol/functions.py +432 -0
- todol-0.3.1/todol/main.py +34 -0
- todol-0.3.1/todol.egg-info/PKG-INFO +188 -0
- todol-0.3.1/todol.egg-info/SOURCES.txt +17 -0
- todol-0.3.1/todol.egg-info/dependency_links.txt +1 -0
- todol-0.3.1/todol.egg-info/entry_points.txt +6 -0
- todol-0.3.1/todol.egg-info/requires.txt +4 -0
- todol-0.3.1/todol.egg-info/top_level.txt +1 -0
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
|
+

|
|
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
|
+

|
|
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
|
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
|
+

|
|
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 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
todol
|