first commit
This commit is contained in:
33
README.md
Normal file
33
README.md
Normal file
@@ -0,0 +1,33 @@
|
||||
# Python TUI Checklist
|
||||
|
||||
A minimal terminal checklist app with no dependencies — just the Python standard library.
|
||||
|
||||
## Usage
|
||||
|
||||
Run with the built-in sample tasks:
|
||||
|
||||
```bash
|
||||
python checklist.py
|
||||
```
|
||||
|
||||
Or pass your own items as arguments:
|
||||
|
||||
```bash
|
||||
python checklist.py "Task one" "Task two" "Task three"
|
||||
```
|
||||
|
||||
## Controls
|
||||
|
||||
| Key | Action |
|
||||
|-----|--------|
|
||||
| `↑` / `↓` | Move cursor |
|
||||
| `Space` | Toggle checkbox |
|
||||
| `+` or `i` | Add a new task |
|
||||
| `A` | Check all |
|
||||
| `N` | Uncheck all |
|
||||
| `q` | Quit and show final state |
|
||||
|
||||
## Requirements
|
||||
|
||||
- Python 3.6+
|
||||
- No third-party packages — works out of the box on Windows, macOS, and Linux
|
||||
131
checklist.py
Normal file
131
checklist.py
Normal file
@@ -0,0 +1,131 @@
|
||||
import sys
|
||||
import os
|
||||
|
||||
CHECKED = "[x]"
|
||||
UNCHECKED = "[ ]"
|
||||
|
||||
if os.name == "nt":
|
||||
import msvcrt
|
||||
|
||||
def getch():
|
||||
ch = msvcrt.getwch()
|
||||
if ch in ("\x00", "\xe0"): # special key prefix
|
||||
return "\x1b" + msvcrt.getwch()
|
||||
return ch
|
||||
else:
|
||||
import tty, termios
|
||||
|
||||
def getch():
|
||||
fd = sys.stdin.fileno()
|
||||
old = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
ch = sys.stdin.read(1)
|
||||
if ch == "\x1b":
|
||||
sys.stdin.read(1) # [
|
||||
return "\x1b" + sys.stdin.read(1)
|
||||
return ch
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||
|
||||
|
||||
def clear():
|
||||
os.system("cls" if os.name == "nt" else "clear")
|
||||
|
||||
|
||||
def render(items, checked, cursor):
|
||||
clear()
|
||||
print(" Checklist (↑↓ move, Space toggle, + add, A all, N none, q quit)\n")
|
||||
for i, item in enumerate(items):
|
||||
mark = CHECKED if checked[i] else UNCHECKED
|
||||
pointer = ">" if i == cursor else " "
|
||||
print(f" {pointer} {mark} {item}")
|
||||
done = sum(checked)
|
||||
print(f"\n {done}/{len(items)} done")
|
||||
|
||||
|
||||
def prompt_new_task():
|
||||
print("\n New task (Enter to confirm, Esc to cancel): ", end="", flush=True)
|
||||
buf = []
|
||||
while True:
|
||||
if os.name == "nt":
|
||||
ch = msvcrt.getwch()
|
||||
else:
|
||||
import tty, termios
|
||||
fd = sys.stdin.fileno()
|
||||
old = termios.tcgetattr(fd)
|
||||
try:
|
||||
tty.setraw(fd)
|
||||
ch = sys.stdin.read(1)
|
||||
finally:
|
||||
termios.tcsetattr(fd, termios.TCSADRAIN, old)
|
||||
|
||||
if ch in ("\r", "\n"):
|
||||
print()
|
||||
return "".join(buf).strip() or None
|
||||
elif ch == "\x1b":
|
||||
print()
|
||||
return None
|
||||
elif ch in ("\x08", "\x7f"): # backspace
|
||||
if buf:
|
||||
buf.pop()
|
||||
sys.stdout.write("\b \b")
|
||||
sys.stdout.flush()
|
||||
elif ch == "\x03":
|
||||
raise KeyboardInterrupt
|
||||
elif ch >= " ":
|
||||
buf.append(ch)
|
||||
sys.stdout.write(ch)
|
||||
sys.stdout.flush()
|
||||
|
||||
|
||||
def run(items):
|
||||
items = list(items)
|
||||
checked = [False] * len(items)
|
||||
cursor = 0
|
||||
|
||||
# Windows arrow key codes via msvcrt: up=H, down=P
|
||||
UP = "\x1bH" if os.name == "nt" else "\x1bA"
|
||||
DOWN = "\x1bP" if os.name == "nt" else "\x1bB"
|
||||
|
||||
while True:
|
||||
render(items, checked, cursor)
|
||||
ch = getch()
|
||||
|
||||
if ch == UP:
|
||||
cursor = (cursor - 1) % len(items)
|
||||
elif ch == DOWN:
|
||||
cursor = (cursor + 1) % len(items)
|
||||
elif ch == " ":
|
||||
checked[cursor] = not checked[cursor]
|
||||
elif ch in ("+", "i"):
|
||||
task = prompt_new_task()
|
||||
if task:
|
||||
items.append(task)
|
||||
checked.append(False)
|
||||
cursor = len(items) - 1
|
||||
elif ch == "A":
|
||||
checked = [True] * len(items)
|
||||
elif ch == "N":
|
||||
checked = [False] * len(items)
|
||||
elif ch in ("q", "\x03", "\x1b"):
|
||||
break
|
||||
|
||||
clear()
|
||||
print("Final checklist:")
|
||||
for i, item in enumerate(items):
|
||||
mark = CHECKED if checked[i] else UNCHECKED
|
||||
print(f" {mark} {item}")
|
||||
|
||||
|
||||
DEFAULT_ITEMS = [
|
||||
"Buy groceries",
|
||||
"Write unit tests",
|
||||
"Review pull request",
|
||||
"Update documentation",
|
||||
"Deploy to staging",
|
||||
]
|
||||
|
||||
if __name__ == "__main__":
|
||||
items = sys.argv[1:] if len(sys.argv) > 1 else DEFAULT_ITEMS
|
||||
run(items)
|
||||
Reference in New Issue
Block a user