improve-cli 0.1.0__py3-none-any.whl
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.
- improve.py +129 -0
- improve_cli-0.1.0.dist-info/METADATA +164 -0
- improve_cli-0.1.0.dist-info/RECORD +13 -0
- improve_cli-0.1.0.dist-info/WHEEL +5 -0
- improve_cli-0.1.0.dist-info/entry_points.txt +2 -0
- improve_cli-0.1.0.dist-info/licenses/LICENSE +21 -0
- improve_cli-0.1.0.dist-info/top_level.txt +2 -0
- src/__init__.py +1 -0
- src/export.py +23 -0
- src/help.py +19 -0
- src/helpers.py +8 -0
- src/project.py +94 -0
- src/setup.py +152 -0
improve.py
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"""Improve CLI"""
|
|
2
|
+
"""Developed by: harshit-malik25813"""
|
|
3
|
+
"""Open Sourced on GitHub"""
|
|
4
|
+
"""https://github.com/harshit-malik25813/Improve"""
|
|
5
|
+
import argparse # To include the functionality of parsing the arguements
|
|
6
|
+
import json # To Read JSON file containing the user data
|
|
7
|
+
from pathlib import Path # To locate the path of essential tracking data
|
|
8
|
+
# Essential helper functions imported
|
|
9
|
+
from src import helpers, setup as setup_mod, project as project_mod
|
|
10
|
+
from src.help import show_help, commands
|
|
11
|
+
|
|
12
|
+
# User tracking path
|
|
13
|
+
USER_FILE = Path("tracking") / "user_info.json"
|
|
14
|
+
|
|
15
|
+
# Internal function to load user data and return it
|
|
16
|
+
def _load_user():
|
|
17
|
+
if USER_FILE.exists():
|
|
18
|
+
with USER_FILE.open("r", encoding="utf-8") as f:
|
|
19
|
+
return json.load(f)
|
|
20
|
+
return {"user_exists": False, "user_name": "", "password": ""}
|
|
21
|
+
|
|
22
|
+
# Main program
|
|
23
|
+
def main():
|
|
24
|
+
# Adding argument parsers
|
|
25
|
+
parser = argparse.ArgumentParser(prog="improve", description="Improve CLI") # Initiate parsing of arguements
|
|
26
|
+
# Allow subparsers in the program
|
|
27
|
+
subparsers = parser.add_subparsers(dest="command")
|
|
28
|
+
|
|
29
|
+
# setup arguments
|
|
30
|
+
setup_parser = subparsers.add_parser("setup", help="Manage your local user account")
|
|
31
|
+
setup_sub = setup_parser.add_subparsers(dest="setup_cmd", required=True)
|
|
32
|
+
setup_sub.add_parser("login", help="Create or sign in to your local account")
|
|
33
|
+
setup_sub.add_parser("logout", help="Sign out and clear local user data")
|
|
34
|
+
update_parser = setup_sub.add_parser("update", help="Update username or password")
|
|
35
|
+
update_group = update_parser.add_mutually_exclusive_group(required=True)
|
|
36
|
+
update_group.add_argument("--username", action="store_true", help="Change username")
|
|
37
|
+
update_group.add_argument("--password", action="store_true", help="Change password")
|
|
38
|
+
|
|
39
|
+
# help (use "help" not "--help"; argparse reserves --help for usage)
|
|
40
|
+
help_parser = subparsers.add_parser("help", help="Show help or list commands")
|
|
41
|
+
help_parser.add_argument("--commands", action="store_true", help="List all commands")
|
|
42
|
+
|
|
43
|
+
# add-project (legacy) - kept for backward compatibility as the feature has been integrated with project command
|
|
44
|
+
addp = subparsers.add_parser("add-project") # add-project argument
|
|
45
|
+
addp.add_argument("project_name") # Project name
|
|
46
|
+
|
|
47
|
+
# project management group(improved)
|
|
48
|
+
project_parser = subparsers.add_parser("project")
|
|
49
|
+
project_sub = project_parser.add_subparsers(dest="proj_cmd")
|
|
50
|
+
project_sub.add_parser("list")
|
|
51
|
+
create = project_sub.add_parser("create")
|
|
52
|
+
create.add_argument("project_name")
|
|
53
|
+
add_entry = project_sub.add_parser("add-entry")
|
|
54
|
+
add_entry.add_argument("project_name")
|
|
55
|
+
add_entry.add_argument("--day", default="")
|
|
56
|
+
add_entry.add_argument("--date", default="")
|
|
57
|
+
add_entry.add_argument("--progress", default="")
|
|
58
|
+
add_entry.add_argument("--productivity", default="")
|
|
59
|
+
add_entry.add_argument("--feedback", default="")
|
|
60
|
+
feedback = project_sub.add_parser("feedback")
|
|
61
|
+
feedback.add_argument("project_name")
|
|
62
|
+
feedback.add_argument("--last", type=int, default=5)
|
|
63
|
+
show = project_sub.add_parser("show")
|
|
64
|
+
show.add_argument("project_name")
|
|
65
|
+
show.add_argument("--last", type=int, default=10)
|
|
66
|
+
delete = project_sub.add_parser("delete")
|
|
67
|
+
delete.add_argument("project_name")
|
|
68
|
+
|
|
69
|
+
# export
|
|
70
|
+
subparsers.add_parser("export")
|
|
71
|
+
|
|
72
|
+
args = parser.parse_args()
|
|
73
|
+
|
|
74
|
+
user_info = _load_user() # Load user data
|
|
75
|
+
if not user_info.get("user_exists"): # If the user data doesn't exist
|
|
76
|
+
helpers.first_time() # Initiate the script to invite user for the first time
|
|
77
|
+
else:
|
|
78
|
+
print(f"Welcome back!, {user_info.get('user_name')}")
|
|
79
|
+
|
|
80
|
+
if args.command == "setup":
|
|
81
|
+
if args.setup_cmd == "login":
|
|
82
|
+
setup_mod.login()
|
|
83
|
+
elif args.setup_cmd == "logout":
|
|
84
|
+
setup_mod.logout()
|
|
85
|
+
elif args.setup_cmd == "update":
|
|
86
|
+
if args.username:
|
|
87
|
+
setup_mod.update_user("username")
|
|
88
|
+
else:
|
|
89
|
+
setup_mod.update_user("password")
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
if args.command == "help":
|
|
93
|
+
if args.commands:
|
|
94
|
+
commands()
|
|
95
|
+
else:
|
|
96
|
+
show_help()
|
|
97
|
+
return
|
|
98
|
+
|
|
99
|
+
if args.command == "add-project": # Legacy function to create the project
|
|
100
|
+
# legacy behavior: create project CSV
|
|
101
|
+
project_mod.create_project(args.project_name) # Use the create project function in place of add_project.py
|
|
102
|
+
return
|
|
103
|
+
# Project functions
|
|
104
|
+
if args.command == "project":
|
|
105
|
+
if args.proj_cmd == "list":
|
|
106
|
+
project_mod.list_projects()
|
|
107
|
+
elif args.proj_cmd == "create":
|
|
108
|
+
project_mod.create_project(args.project_name)
|
|
109
|
+
elif args.proj_cmd == "add-entry":
|
|
110
|
+
project_mod.add_entry(args.project_name, day=args.day, date=args.date, progress=args.progress, productivity=args.productivity, feedback=args.feedback)
|
|
111
|
+
elif args.proj_cmd == "feedback":
|
|
112
|
+
project_mod.get_feedback(args.project_name, last=args.last)
|
|
113
|
+
elif args.proj_cmd == "show":
|
|
114
|
+
project_mod.show_project(args.project_name, last=args.last)
|
|
115
|
+
elif args.proj_cmd == "delete":
|
|
116
|
+
project_mod.delete_project(args.project_name)
|
|
117
|
+
else:
|
|
118
|
+
show_help()
|
|
119
|
+
return
|
|
120
|
+
# Export functions
|
|
121
|
+
if args.command == "export":
|
|
122
|
+
from src.export import export_data
|
|
123
|
+
export_data()
|
|
124
|
+
return
|
|
125
|
+
# if invalid
|
|
126
|
+
parser.print_help()
|
|
127
|
+
# Initialise the function
|
|
128
|
+
if __name__ == "__main__":
|
|
129
|
+
main()
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: improve-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: CLI project tracker with CSV-backed logs
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Dynamic: license-file
|
|
9
|
+
|
|
10
|
+
# **Improve — CLI Project Tracker**
|
|
11
|
+
|
|
12
|
+
Improve is a small, single-file CLI application (with supporting modules under `src/`) to help you track progress on projects using CSV-backed project logs. It provides simple user setup, per-project CSV tracking, quick reporting and export functionality.
|
|
13
|
+
|
|
14
|
+
## **Features**
|
|
15
|
+
- **User setup:** create, update, and logout a local user account (`setup` commands).
|
|
16
|
+
- **Project management:** create, list, delete projects and add daily entries (`project` commands).
|
|
17
|
+
- **Entry tracking:** each project is stored as a CSV under `tracking/` with columns Day, Date, Progress, Productivity Level, Feedback.
|
|
18
|
+
- **Feedback & view:** fetch recent feedback entries or show recent rows for a project.
|
|
19
|
+
- **Export:** copy all tracking CSV files to a `backup/` folder.
|
|
20
|
+
|
|
21
|
+
### **Quick Links**
|
|
22
|
+
- Main CLI entry: [improve.py](improve.py)
|
|
23
|
+
- Core project logic: [src/project.py](src/project.py)
|
|
24
|
+
- User/setup utilities: [src/setup.py](src/setup.py)
|
|
25
|
+
- Export helper: [src/export.py](src/export.py)
|
|
26
|
+
- First-run helper text: [src/helpers.py](src/helpers.py)
|
|
27
|
+
- Legacy project helper: [src/add_project.py](src/add_project.py)
|
|
28
|
+
|
|
29
|
+
## **Requirements**
|
|
30
|
+
- Python 3.9+ (`requires-python` in [pyproject.toml](pyproject.toml); uses standard library features such as `dataclasses` and `pathlib`).
|
|
31
|
+
- No third-party runtime dependencies. The package is defined in [pyproject.toml](pyproject.toml); [requirements.txt](requirements.txt) only lists standard-library modules for reference and is not used by pip.
|
|
32
|
+
|
|
33
|
+
## **Install with pip**
|
|
34
|
+
|
|
35
|
+
From a clone of this repository (recommended: use a virtual environment):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
python3 -m venv .venv
|
|
39
|
+
source .venv/bin/activate # Windows: .venv\Scripts\activate
|
|
40
|
+
pip install .
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
After install, the `improve` console script is on your `PATH` (same commands as `python improve.py` below).
|
|
44
|
+
|
|
45
|
+
For local development (changes to `improve.py` or `src/` apply without reinstalling):
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
pip install -e .
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Install a built wheel without cloning:
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install dist/improve_cli-0.1.0-py3-none-any.whl
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## **Build from source**
|
|
58
|
+
|
|
59
|
+
Build sdist and wheel artifacts into `dist/` (requires [build](https://pypi.org/project/build/) once per environment):
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python3 -m venv .venv
|
|
63
|
+
source .venv/bin/activate
|
|
64
|
+
pip install build
|
|
65
|
+
python -m build
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Outputs:
|
|
69
|
+
- `dist/improve_cli-0.1.0.tar.gz` — source distribution
|
|
70
|
+
- `dist/improve_cli-0.1.0-py3-none-any.whl` — installable wheel
|
|
71
|
+
|
|
72
|
+
Install the wheel locally:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
pip install dist/improve_cli-0.1.0-py3-none-any.whl
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## **Run without installing**
|
|
79
|
+
|
|
80
|
+
Clone the repo, create a venv if you like, and invoke the entry module directly:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
python improve.py --help
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## **Running the CLI**
|
|
87
|
+
- Basic help:
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
improve --help
|
|
91
|
+
# or: python improve.py --help
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- Setup a user account (interactive):
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
improve setup login
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
- Sign out and clear local user data:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
improve setup logout
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
- Update username or password:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
improve setup update --username
|
|
110
|
+
improve setup update --password
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
- Create a project:
|
|
114
|
+
|
|
115
|
+
```bash
|
|
116
|
+
improve project create MyProject
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
- Add an entry to a project:
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
improve project add-entry MyProject --day "Day 1" --date "2026-05-28" --progress "Started" --productivity "7" --feedback "Good start"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
- List projects:
|
|
126
|
+
|
|
127
|
+
```bash
|
|
128
|
+
improve project list
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
- Show recent entries for a project:
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
improve project show MyProject --last 10
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- Get recent feedback entries:
|
|
138
|
+
|
|
139
|
+
```bash
|
|
140
|
+
improve project feedback MyProject --last 5
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
- Export all tracking CSVs to `backup/`:
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
improve export
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
**Storage & File Layout**
|
|
150
|
+
- Tracking CSVs and user info are stored in the `tracking/` directory. Example files:
|
|
151
|
+
- `tracking/MyProject.csv` — per-project CSV with header `Day,Date,Progress,Productivity Level,Feedback`
|
|
152
|
+
- `tracking/user_info.json` — stores a small JSON object with `user_exists`, `user_name`, and `password`.
|
|
153
|
+
|
|
154
|
+
**Notes & Behavior**
|
|
155
|
+
- The CLI is implemented in `improve.py` and delegates commands to modules in `src/`.
|
|
156
|
+
- Password validation enforces a minimum length and requires digits, alphabetic, uppercase, lowercase and a special character (see `src/setup.py`).
|
|
157
|
+
- The `add-project` command is retained for backward compatibility but `project create` is preferred.
|
|
158
|
+
|
|
159
|
+
**Development & Contribution**
|
|
160
|
+
- Use `pip install -e .` and run `improve`, or run `python improve.py` without installing.
|
|
161
|
+
- Rebuild distributables with `python -m build` after packaging changes.
|
|
162
|
+
- The codebase is small and intended as a learning/demo project. Contributions or issues can be opened against the original upstream repository referenced in the code comments.
|
|
163
|
+
|
|
164
|
+
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
improve.py,sha256=URtBOBz0DZdxPUha_9X9KyOUSvjFSd8FRetnlx6unqk,4976
|
|
2
|
+
improve_cli-0.1.0.dist-info/licenses/LICENSE,sha256=Gx6gQSILjb0n_mNNc0n_t9PepS9513UsjlMLRojgF3E,1075
|
|
3
|
+
src/__init__.py,sha256=QQ453nXqN0jo3CcLjOviklQO-appJP-FCgo7Vm6wVp0,97
|
|
4
|
+
src/export.py,sha256=Tc7ZcJLjC4PN0E1eVt3mZeCGYNf11DmdlFq4NAmgAGs,886
|
|
5
|
+
src/help.py,sha256=gAzemecKifrDiNzm0TPCgWz9Xxr7Po48IE0nvlqm_Do,719
|
|
6
|
+
src/helpers.py,sha256=kILSXx7-1qCYidiqhDvVMvslL3AgECavavLHqkD0rlI,523
|
|
7
|
+
src/project.py,sha256=3KZBLPphUvvHjEWkKf7_HMRG0m9oFomb8H0xkKsm-3Y,3350
|
|
8
|
+
src/setup.py,sha256=B_pg9njQsCouq9LUr01el1RjNJH33lPxuqst24p3v0c,5640
|
|
9
|
+
improve_cli-0.1.0.dist-info/METADATA,sha256=HMcnNQgHD6sDPWkHqPbPG9oseYDkxoNwzbiHokiENQE,4836
|
|
10
|
+
improve_cli-0.1.0.dist-info/WHEEL,sha256=aeYiig01lYGDzBgS8HxWXOg3uV61G9ijOsup-k9o1sk,91
|
|
11
|
+
improve_cli-0.1.0.dist-info/entry_points.txt,sha256=szXk0Yhe_HCKwjlJtqiPask90N0uhGlDhs1DBmzZSXU,41
|
|
12
|
+
improve_cli-0.1.0.dist-info/top_level.txt,sha256=1evx0noKIWBgNokQL2NSKjYonTD91I9OlfOYkJkc6x8,12
|
|
13
|
+
improve_cli-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 harshit-malik25813
|
|
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.
|
src/__init__.py
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Initialising the directory to allow for importing functions from the files in the directory"""
|
src/export.py
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import shutil
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
# To export user tracking data to a CSV file
|
|
5
|
+
def export_data():
|
|
6
|
+
tracking_folder = Path("tracking")
|
|
7
|
+
if not tracking_folder.exists(): # If user exports data but the folder doesnt contain any
|
|
8
|
+
print("No tracking data found.")
|
|
9
|
+
return
|
|
10
|
+
|
|
11
|
+
csv_files = list(tracking_folder.glob("*.csv")) # Creating a list of files in the folder
|
|
12
|
+
|
|
13
|
+
destination_folder = Path("backup") # Creating a folder called backup to copy the tracked CSV files
|
|
14
|
+
destination_folder.mkdir(parents=True, exist_ok=True)
|
|
15
|
+
|
|
16
|
+
for csv_file in csv_files:
|
|
17
|
+
shutil.copy2(csv_file, destination_folder / csv_file.name)
|
|
18
|
+
|
|
19
|
+
print("Export was successful!")
|
|
20
|
+
print("Find your exported progress in CSV format in:\n" \
|
|
21
|
+
f"{destination_folder.resolve()}")
|
|
22
|
+
print("You can still continue with your progress as it hasnt been deleted")
|
|
23
|
+
return
|
src/help.py
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
def show_help():
|
|
2
|
+
print("Improve")
|
|
3
|
+
print("You might have entered a wrong command to get here")
|
|
4
|
+
print("To see the list of commands, run: improve help --commands")
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def commands():
|
|
8
|
+
print("Available commands:")
|
|
9
|
+
print(" setup login")
|
|
10
|
+
print(" setup logout")
|
|
11
|
+
print(" setup update --username | --password")
|
|
12
|
+
print(" project list")
|
|
13
|
+
print(" project create <name>")
|
|
14
|
+
print(" project add-entry <name> [--day] [--date] [--progress] [--productivity] [--feedback]")
|
|
15
|
+
print(" project show <name> [--last N]")
|
|
16
|
+
print(" project feedback <name> [--last N]")
|
|
17
|
+
print(" project delete <name>")
|
|
18
|
+
print(" add-project <name> (legacy; prefer project create)")
|
|
19
|
+
print(" export")
|
src/helpers.py
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
def first_time(): # When user opens the program for the first time
|
|
2
|
+
print("Welcome to Improve!")
|
|
3
|
+
print("Improve is a CLI-tool designed to help people manage and organize their \n " \
|
|
4
|
+
"ongoing projects and track their progress in them")
|
|
5
|
+
print("This project has been developed and open-sourced by harshit-malik25813 on GitHub")
|
|
6
|
+
print("For all those nerds, find this project on https://github.com/harshit-malik25813/Improve")
|
|
7
|
+
print("To start, setup your account by running 'improve setup login' in your terminal")
|
|
8
|
+
return
|
src/project.py
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import csv
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List
|
|
4
|
+
# Project functions, most important part of the program
|
|
5
|
+
# Defining constants
|
|
6
|
+
TRACKING = Path("tracking")
|
|
7
|
+
HEADER = ["Day", "Date", "Progress", "Productivity Level", "Feedback"]
|
|
8
|
+
|
|
9
|
+
# Function to make the project file
|
|
10
|
+
def _project_file(name: str) -> Path:
|
|
11
|
+
TRACKING.mkdir(parents=True, exist_ok=True)
|
|
12
|
+
return TRACKING / f"{name}.csv"
|
|
13
|
+
# Function to list the projects the user has created thus far
|
|
14
|
+
|
|
15
|
+
def list_projects() -> List[str]:
|
|
16
|
+
if not TRACKING.exists():
|
|
17
|
+
print("No projects found.")
|
|
18
|
+
return []
|
|
19
|
+
projects = [p.stem for p in TRACKING.glob("*.csv")]
|
|
20
|
+
if not projects:
|
|
21
|
+
print("No projects found.")
|
|
22
|
+
return []
|
|
23
|
+
print("Projects:")
|
|
24
|
+
for p in projects:
|
|
25
|
+
print(f" - {p}")
|
|
26
|
+
return projects
|
|
27
|
+
# Function to create a project
|
|
28
|
+
|
|
29
|
+
def create_project(name: str) -> Path:
|
|
30
|
+
# Path to project file
|
|
31
|
+
path = _project_file(name)
|
|
32
|
+
# If the user has already created a project with this name before
|
|
33
|
+
if path.exists():
|
|
34
|
+
print(f"Project '{name}' already exists at {path}")
|
|
35
|
+
return path
|
|
36
|
+
# Create the basic structure of the project in the
|
|
37
|
+
with path.open("w", newline="", encoding="utf-8") as f:
|
|
38
|
+
writer = csv.writer(f)
|
|
39
|
+
writer.writerow(HEADER)
|
|
40
|
+
print(f"Project '{name}' created at {path}")
|
|
41
|
+
return path
|
|
42
|
+
# Function to add an entry to the project
|
|
43
|
+
|
|
44
|
+
def add_entry(name: str, day: str = "", date: str = "", progress: str = "", productivity: str = "", feedback: str = ""):
|
|
45
|
+
path = _project_file(name)
|
|
46
|
+
if not path.exists():
|
|
47
|
+
print(f"Project '{name}' does not exist. Creating it now.")
|
|
48
|
+
create_project(name)
|
|
49
|
+
row = [day, date, progress, productivity, feedback]
|
|
50
|
+
with path.open("a", newline="", encoding="utf-8") as f:
|
|
51
|
+
writer = csv.writer(f)
|
|
52
|
+
writer.writerow(row)
|
|
53
|
+
print(f"Added entry to project '{name}'.")
|
|
54
|
+
# Function to retrieve feedbacks
|
|
55
|
+
|
|
56
|
+
def get_feedback(name: str, last: int = 5):
|
|
57
|
+
path = _project_file(name)
|
|
58
|
+
if not path.exists():
|
|
59
|
+
print(f"Project '{name}' not found.")
|
|
60
|
+
return
|
|
61
|
+
with path.open("r", encoding="utf-8") as f:
|
|
62
|
+
reader = list(csv.DictReader(f))
|
|
63
|
+
feedbacks = [r.get("Feedback", "") for r in reader if r.get("Feedback")]
|
|
64
|
+
if not feedbacks:
|
|
65
|
+
print("No feedback entries found.")
|
|
66
|
+
return
|
|
67
|
+
print(f"Last {last} feedback entries for '{name}':")
|
|
68
|
+
for fb in feedbacks[-last:]:
|
|
69
|
+
print(f" - {fb}")
|
|
70
|
+
# Function to show the project progress as the user asks
|
|
71
|
+
|
|
72
|
+
def show_project(name: str, last: int = 10):
|
|
73
|
+
path = _project_file(name)
|
|
74
|
+
if not path.exists():
|
|
75
|
+
print(f"Project '{name}' not found.")
|
|
76
|
+
return
|
|
77
|
+
with path.open("r", encoding="utf-8") as f:
|
|
78
|
+
reader = list(csv.DictReader(f))
|
|
79
|
+
if not reader:
|
|
80
|
+
print("No entries yet.")
|
|
81
|
+
return
|
|
82
|
+
rows = reader[-last:]
|
|
83
|
+
print(f"Showing last {len(rows)} entries for '{name}':")
|
|
84
|
+
for r in rows:
|
|
85
|
+
print(f"Day: {r.get('Day','')}, Date: {r.get('Date','')}, Progress: {r.get('Progress','')}, Productivity: {r.get('Productivity Level','')}, Feedback: {r.get('Feedback','')}")
|
|
86
|
+
|
|
87
|
+
# Function to delete the project
|
|
88
|
+
def delete_project(name: str):
|
|
89
|
+
path = _project_file(name)
|
|
90
|
+
if not path.exists():
|
|
91
|
+
print(f"Project '{name}' not found.")
|
|
92
|
+
return
|
|
93
|
+
path.unlink()
|
|
94
|
+
print(f"Project '{name}' deleted.")
|
src/setup.py
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""User setup utilities for Improve CLI"""
|
|
2
|
+
from dataclasses import dataclass # to create dataclass for user
|
|
3
|
+
import json # to access user data in JSON file
|
|
4
|
+
from pathlib import Path # To manipulate the folder and files
|
|
5
|
+
import shutil # To make directories
|
|
6
|
+
from .export import export_data # Export data
|
|
7
|
+
from .help import show_help # Printing help
|
|
8
|
+
|
|
9
|
+
USER_FILE = Path("tracking") / "user_info.json" # The path of user file
|
|
10
|
+
# Declaring essential functions
|
|
11
|
+
|
|
12
|
+
def _load_user_info():
|
|
13
|
+
# Do not create the file by default. Return defaults if file missing.
|
|
14
|
+
if not USER_FILE.exists():
|
|
15
|
+
return {"user_exists": False, "user_name": "", "password": ""}
|
|
16
|
+
with USER_FILE.open("r", encoding="utf-8") as f:
|
|
17
|
+
return json.load(f)
|
|
18
|
+
|
|
19
|
+
# To write user_info
|
|
20
|
+
def _write_user_info(data: dict):
|
|
21
|
+
# To write user info in a JSON file for the first time
|
|
22
|
+
USER_FILE.parent.mkdir(parents=True, exist_ok=True)
|
|
23
|
+
with USER_FILE.open("w", encoding="utf-8") as f:
|
|
24
|
+
json.dump(data, f, indent=2)
|
|
25
|
+
|
|
26
|
+
# To validate password
|
|
27
|
+
def _validate_password(pw: str) -> tuple[bool, str]:
|
|
28
|
+
if len(pw) < 8:
|
|
29
|
+
return False, "Password must be at least 8 characters"
|
|
30
|
+
if not any(c.isdigit() for c in pw):
|
|
31
|
+
return False, "Password must contain a digit"
|
|
32
|
+
special_char = "!@#$%^&*()"
|
|
33
|
+
if not any(c in special_char for c in pw):
|
|
34
|
+
return False, "Password must contain a special character"
|
|
35
|
+
if not any(c.isalpha() for c in pw):
|
|
36
|
+
return False, "Password must contain alphabetic characters"
|
|
37
|
+
if not any(c.islower() for c in pw):
|
|
38
|
+
return False, "Password must contain a lowercase character"
|
|
39
|
+
if not any(c.isupper() for c in pw):
|
|
40
|
+
return False, "Password must contain an uppercase character"
|
|
41
|
+
return True, ""
|
|
42
|
+
|
|
43
|
+
# Logging user in
|
|
44
|
+
def login():
|
|
45
|
+
data = _load_user_info()
|
|
46
|
+
# If user exists, no need to do anything
|
|
47
|
+
if data.get("user_exists"):
|
|
48
|
+
print(f"A user already exists. You are logged in as {data.get('user_name')}")
|
|
49
|
+
return
|
|
50
|
+
|
|
51
|
+
print("User Account not set")
|
|
52
|
+
# Get user confirmation
|
|
53
|
+
ans = input("Do you want to set a user? (y/n): ").strip().lower()
|
|
54
|
+
if ans not in ("y", "n"):
|
|
55
|
+
print("Invalid input. Please enter y or n.")
|
|
56
|
+
return
|
|
57
|
+
if ans == "n":
|
|
58
|
+
print("Aborting user setup")
|
|
59
|
+
return
|
|
60
|
+
# Setting up a dataclass to help with easily accessing username and password
|
|
61
|
+
@dataclass
|
|
62
|
+
class User:
|
|
63
|
+
user_name: str
|
|
64
|
+
password: str
|
|
65
|
+
# Setting up the user
|
|
66
|
+
# Only let user leave if he enters the username and password correctly
|
|
67
|
+
while True:
|
|
68
|
+
# Validating username and password
|
|
69
|
+
user_name = input("Username: ").strip()
|
|
70
|
+
if len(user_name) < 4:
|
|
71
|
+
|
|
72
|
+
print("The length of user name should be at least 4 characters!")
|
|
73
|
+
continue
|
|
74
|
+
|
|
75
|
+
password = input("Password: ")
|
|
76
|
+
valid, msg = _validate_password(password)
|
|
77
|
+
if not valid:
|
|
78
|
+
print(msg)
|
|
79
|
+
continue
|
|
80
|
+
user = User(user_name, password)
|
|
81
|
+
break
|
|
82
|
+
|
|
83
|
+
new_data = {"user_exists": True, "user_name": user.user_name, "password": user.password}
|
|
84
|
+
_write_user_info(new_data)
|
|
85
|
+
print("User has been set successfully!")
|
|
86
|
+
print("Log in to start your improvement journey!")
|
|
87
|
+
|
|
88
|
+
# Logging user out
|
|
89
|
+
def logout():
|
|
90
|
+
data = _load_user_info()
|
|
91
|
+
# If user tries to log out but user doesn't exist
|
|
92
|
+
if not data.get("user_exists"):
|
|
93
|
+
print("No user is currently logged in.")
|
|
94
|
+
return
|
|
95
|
+
print("Logout")
|
|
96
|
+
# Asking user for confirmation
|
|
97
|
+
confirmation = input("Are you sure you want to logout? Your data will be deleted unless exported (y/n): ").strip().lower()
|
|
98
|
+
if confirmation not in ("y", "n"):
|
|
99
|
+
print("Please respond with y or n")
|
|
100
|
+
return
|
|
101
|
+
if confirmation == "n":
|
|
102
|
+
return
|
|
103
|
+
# Ask user if he wishes to export his data
|
|
104
|
+
ans = input("Do you wish to export your progress report to CSV before logout? (y/n): ").strip().lower()
|
|
105
|
+
if ans == "y":
|
|
106
|
+
export_data()
|
|
107
|
+
# After successful export, offer to delete the tracking directory
|
|
108
|
+
try:
|
|
109
|
+
tracking_dir = USER_FILE.parent
|
|
110
|
+
if tracking_dir.exists():
|
|
111
|
+
delete_confirm = input("Export complete. Do you want to delete the tracking directory? (y/n): ").strip().lower()
|
|
112
|
+
if delete_confirm == "y":
|
|
113
|
+
shutil.rmtree(tracking_dir)
|
|
114
|
+
print("Tracking directory deleted.")
|
|
115
|
+
except Exception as e:
|
|
116
|
+
print(f"Warning: failed to delete tracking directory: {e}")
|
|
117
|
+
# Update JSON file
|
|
118
|
+
no_user = {"user_exists": False, "user_name": "", "password": ""}
|
|
119
|
+
_write_user_info(no_user)
|
|
120
|
+
print("Logged out and user data cleared.")
|
|
121
|
+
|
|
122
|
+
# To update user data
|
|
123
|
+
def update_user(field: str):
|
|
124
|
+
if field not in ("username", "password"):
|
|
125
|
+
print("Invalid input!")
|
|
126
|
+
show_help()
|
|
127
|
+
return
|
|
128
|
+
data = _load_user_info()
|
|
129
|
+
if not data.get("user_exists"):
|
|
130
|
+
print("No user set. Run 'improve setup login' to create an account.")
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
if field == "username":
|
|
134
|
+
new_username = input("Enter new username: ").strip()
|
|
135
|
+
if len(new_username) < 4:
|
|
136
|
+
print("Username must be at least 4 characters long")
|
|
137
|
+
return
|
|
138
|
+
data["user_name"] = new_username
|
|
139
|
+
_write_user_info(data)
|
|
140
|
+
print("Username updated.")
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
if field == "password":
|
|
144
|
+
new_password = input("Enter new password: ")
|
|
145
|
+
valid, msg = _validate_password(new_password)
|
|
146
|
+
if not valid:
|
|
147
|
+
print(msg)
|
|
148
|
+
return
|
|
149
|
+
data["password"] = new_password
|
|
150
|
+
_write_user_info(data)
|
|
151
|
+
print("Password updated.")
|
|
152
|
+
return
|