├── .gitignore ├── INSTALL.md ├── LICENSE ├── README.md ├── example.csv ├── install.sh ├── src ├── LICENSE ├── __init__.py ├── cli.py ├── csv_import.py ├── data.py ├── database.py ├── dolist.py └── utils.py └── tests └── mockup.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | #mysql 7 | *.db 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | pip-wheel-metadata/ 27 | share/python-wheels/ 28 | *.egg-info/ 29 | .installed.cfg 30 | *.egg 31 | MANIFEST 32 | 33 | # PyInstaller 34 | # Usually these files are written by a python script from a template 35 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 36 | *.manifest 37 | *.spec 38 | 39 | # Installer logs 40 | pip-log.txt 41 | pip-delete-this-directory.txt 42 | 43 | # Unit test / coverage reports 44 | htmlcov/ 45 | .tox/ 46 | .nox/ 47 | .coverage 48 | .coverage.* 49 | .cache 50 | nosetests.xml 51 | coverage.xml 52 | *.cover 53 | *.py,cover 54 | .hypothesis/ 55 | .pytest_cache/ 56 | 57 | # Translations 58 | *.mo 59 | *.pot 60 | 61 | # Django stuff: 62 | *.log 63 | local_settings.py 64 | db.sqlite3 65 | db.sqlite3-journal 66 | 67 | # Flask stuff: 68 | instance/ 69 | .webassets-cache 70 | 71 | # Scrapy stuff: 72 | .scrapy 73 | 74 | # Sphinx documentation 75 | docs/_build/ 76 | 77 | # PyBuilder 78 | target/ 79 | 80 | # Jupyter Notebook 81 | .ipynb_checkpoints 82 | 83 | # IPython 84 | profile_default/ 85 | ipython_config.py 86 | 87 | # pyenv 88 | .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 98 | __pypackages__/ 99 | 100 | # Celery stuff 101 | celerybeat-schedule 102 | celerybeat.pid 103 | 104 | # SageMath parsed files 105 | *.sage.py 106 | 107 | # Environments 108 | .env 109 | .venv 110 | env/ 111 | venv/ 112 | ENV/ 113 | env.bak/ 114 | venv.bak/ 115 | 116 | # Spyder project settings 117 | .spyderproject 118 | .spyproject 119 | 120 | # Rope project settings 121 | .ropeproject 122 | 123 | # mkdocs documentation 124 | /site 125 | 126 | # mypy 127 | .mypy_cache/ 128 | .dmypy.json 129 | dmypy.json 130 | 131 | # Pyre type checker 132 | .pyre/ 133 | .DS_Store 134 | examples.csv 135 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Installation 2 | 3 | ### 1. 4 | **Download and extract** the zip file: [https://github.com/CedricFauth/dolist-client/archive/main.zip](https://github.com/CedricFauth/dolist-client/archive/main.zip) 5 | 6 | **Or clone** the repo: 7 | ``` 8 | git clone https://github.com/CedricFauth/dolist-client.git 9 | cd dolist-client/ 10 | ``` 11 | 12 | ### 2. 13 | Make the ```install.sh``` file executable: 14 | ``` 15 | chmod 755 install.sh 16 | ``` 17 | 18 | ### 3. 19 | Run the installation file: 20 | ``` 21 | ./install.sh 22 | ``` 23 | This script will create a .dolist/ directory in your $HOME directory and adds an alias to you shell's config file. 24 | 25 | ### 4. 26 | After successful installing all files you are free to remove the download or the repo. \ 27 | **In order to run dolist please restart your console or source your shell's config file.** 28 | Now you should be able to run the program: 29 | ``` 30 | dl 31 | ``` 32 | ![Screenshot 2020-11-07 at 16 34 19](https://user-images.githubusercontent.com/25117793/98445363-b20b9500-2117-11eb-954e-38941174532b.png) 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Cedric Fauth 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. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dolist client 2 | 3 | A lecture, event, and assignment manager. 4 | 5 | ![Screenshot 2020-11-07 at 16 38 14](https://user-images.githubusercontent.com/25117793/98445362-b172fe80-2117-11eb-8a59-952643a5c2f4.png) 6 | 7 | ## Run the program: 8 | 9 | **You need Python 3 or higher in order to run the program.** \ 10 | Check out the [Installation Guide](/INSTALL.md). 11 | 12 | ## Commands: 13 | ### Import events and tasks from a .csv file 14 | The program comes with a tool called dlimport that you can use to import data from CSV files. The CSV file needs to be semicolon separated. This repo contains an ```example.csv``` file that you can modify by your needs. After setting up the data you can run this command to import the data: 15 | ``` 16 | dlimport example.csv 17 | ``` 18 | (replace example.csv with the path to your csv file if you need) 19 | ### Show today's overview 20 | This shows all events of today and all tasks. 21 | ``` 22 | dl 23 | ``` 24 | ### Add a new event: 25 | ``` 26 | dl event TITLE -f daily|weekly|once [-d weekday|date] -t HH:MM-HH:MM 27 | ``` 28 | Examples: 29 | ``` 30 | dl event 'CS Lecture 1' -f w -d mon -t 13:37-14:42 31 | dl event 'CS Lecture 2' -f o -d 2020-12-24 -t 12:34-13:57 32 | dl event 'CS Lecture 3' -f d -t 00:00-01:05 33 | ``` 34 | ### Add a new task: 35 | ``` 36 | dl task TITLE -f daily|weekly|once [-d weekday|date] -t HH:MM 37 | ``` 38 | Examples: 39 | ``` 40 | dl task 'Assignment 1' -f w -d mon -t 13:37 41 | dl task 'Assignment 2' -f o -d 2020-12-24 -t 12:34 42 | dl task 'Assignment 3' -f d -t 00:00 43 | ``` 44 | ### List all events and tasks 45 | This shows all events, tasks, and their corresponding IDs (You need the IDs in order to delete events/tasks or mark tasks as done). 46 | ``` 47 | dl ls 48 | ``` 49 | ### Mark a task as done 50 | ID: integer number of a task (see dl ls) 51 | ``` 52 | dl done ID 53 | ``` 54 | ### Delete an event 55 | ``` 56 | dl rm -e ID 57 | ``` 58 | ### Delete a task 59 | ``` 60 | dl rm -t ID 61 | ``` 62 | -------------------------------------------------------------------------------- /example.csv: -------------------------------------------------------------------------------- 1 | events;frequency;day_or_date;start_time;end_time 2 | ;;;; 3 | Test Lecture 1;weekly;mon;00:00;00:40 4 | Test Lecture 1;weekly;tue;13:37;14:14 5 | Lecture 3;once;2020-12-24;22:24;23:00 6 | Lecture 4;once;2020-12-31;06:30;08:00 7 | Lecture 5;daily;;14:42;15:52 8 | Lecture 6;daily;;09:07;11:05 9 | ;;;; 10 | tasks;frequency;day_or_date;deadline; 11 | ;;;; 12 | Assignment 1;weekly;tue;23:59; 13 | Assignment 2;daily;;09:05; 14 | Task 3;once;2020-10-31;10:25; -------------------------------------------------------------------------------- /install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | GRE="\033[32m" 4 | RES="\033[0m" 5 | MAG="\033[95m" 6 | RED="\033[31m" 7 | YEL="\033[33m" 8 | BRED="\033[31;1m" 9 | 10 | name=$SHELL 11 | 12 | if [ $name = "/bin/bash" ]; then 13 | name=".bashrc" 14 | elif [ $name = "/bin/zsh" ]; then 15 | name=".zshrc" 16 | elif [ $name = "o" ]; then 17 | printf "$YEL[INSTALL]$RES Current shell is not supported...\n" 18 | printf "Please name your shell's cnonfig file (has to be located in $HOME)?" 19 | read name 20 | else 21 | echo "$RED[Error]$RESET Unknown argument: $name" 22 | exit 1 23 | fi 24 | 25 | # make folder 26 | mkdir $HOME/.dolist 27 | # copy files 28 | cp -R src/ $HOME/.dolist/bin 29 | # make alias 30 | echo "alias dl=\"python3 $HOME/.dolist/bin/dolist.py\"" >> $HOME/$name 31 | echo "alias dlimport=\"python3 $HOME/.dolist/bin/csv_import.py\"" >> $HOME/$name 32 | 33 | echo "$GRE[DONE]$RES Installation successful." 34 | echo "$GRE[DONE]$RES Please run '${MAG}source $HOME/$name$RES' to finish the installation or ${BRED}close and reopen your terminal.$RES" 35 | 36 | exit 0 37 | -------------------------------------------------------------------------------- /src/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Cedric Fauth 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. 22 | -------------------------------------------------------------------------------- /src/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CedricFauth/dolist-client/67ab15388e732c61639c9abeec5c42d6b07e19ff/src/__init__.py -------------------------------------------------------------------------------- /src/cli.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MIT License 3 | 4 | Copyright (c) 2020 Cedric Fauth 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | ''' 24 | 25 | import argparse 26 | import logging 27 | from utils import Symbol as sym 28 | 29 | logger = logging.getLogger(__name__) 30 | logging.basicConfig(level=logging.ERROR) 31 | 32 | class CLI_Parser: 33 | 34 | def __init__(self): 35 | # init parser 36 | self.parser = argparse.ArgumentParser('dolist client') 37 | self.subparsers = self.parser.add_subparsers(dest="cmd") 38 | # event args 39 | self.e_parser = self.subparsers.add_parser('event', help='add an event (event -h)') 40 | self.e_parser.add_argument('title', type=str, action='store', help='event title') 41 | self.e_parser.add_argument('-d', type=str, action='store', help='weekday or date') 42 | self.e_parser.add_argument('-t', type=str, action='store', required=True, help='time period HH:MM-HH:MM') 43 | self.e_parser.add_argument('-f', type=str, action='store', required=True, help='frequency') 44 | # task args 45 | self.t_parser = self.subparsers.add_parser('task', help='add a task (task -h)') 46 | self.t_parser.add_argument('title', type=str, action='store', help='task title') 47 | self.t_parser.add_argument('-d', type=str, action='store', help='weekday') 48 | self.t_parser.add_argument('-t', type=str, action='store', required=True, help='time HH:MM') 49 | self.t_parser.add_argument('-f', type=str, action='store', required=True, help='frequency') 50 | # remove 51 | self.rm_parser = self.subparsers.add_parser('rm', help='remove event or task') 52 | self.rm_group = self.rm_parser.add_mutually_exclusive_group(required=True) 53 | self.rm_group.add_argument('-e', action='store_true', help='remove event') 54 | self.rm_group.add_argument('-t', action='store_true', help='remove task') 55 | self.rm_parser.add_argument('id', type=int, action='store', help='id of event/task') 56 | # list 57 | self.ls_parser = self.subparsers.add_parser('ls', help='list all id\'s') 58 | # done 59 | self.done_parser = self.subparsers.add_parser('done', help='mark a task as done\'s') 60 | self.done_parser.add_argument('task_id', type=int, action='store', help='task ID') 61 | # parse 62 | self.args = self.parser.parse_args() 63 | logger.debug(self.args) 64 | 65 | class Output: 66 | 67 | int_to_days = {None: '', 0: "mon", 1: "tue", 2: "wed", 3: "thu", 68 | 4: "fri", 5: "sat", 6: "sun"} 69 | 70 | char_to_freq = {'w': 'weekly', 'd': 'daily', 'o': 'once'} 71 | 72 | @staticmethod 73 | def open(): 74 | # \033[2J\033[H clear screen 75 | print(f'{sym.default()}', end='', flush=True) 76 | 77 | @staticmethod 78 | def close(): 79 | print(sym.RESET, end='', flush=True) 80 | 81 | @staticmethod 82 | def info(msg): 83 | """ 84 | prints an info text 85 | """ 86 | print(f' {sym.MAGENTA}{sym.DONE}{sym.default()} {msg}') 87 | 88 | @staticmethod 89 | def error(msg): 90 | """ 91 | prints an error text 92 | """ 93 | print(f' {sym.RED}{sym.ERR}{sym.default()} {msg}') 94 | 95 | @staticmethod 96 | def list_all(events, tasks): 97 | le = len(events) - 1 98 | lt = len(tasks) - 1 99 | event_head = f'{sym.CYAN} event [ID] [TITLE]{"".join(" " for _ in range(25))}' + \ 100 | f'[FREQ] [DAY] [DATE] [FROM - TO]{sym.default()}' 101 | task_head = f'\n{sym.MAGENTA} task [ID] [TITLE]{"".join(" " for _ in range(31))}' + \ 102 | f'[FREQ] [DAY] [DATE] [DUE]{sym.default()}' 103 | out = event_head 104 | for i,e in enumerate(events): 105 | title = Output.align_text_left(e[1], 31) 106 | freq = Output.align_text_left(Output.char_to_freq[e[5]], 6) 107 | day = Output.align_text_left(Output.int_to_days[e[2]], 5) 108 | date = (e[6] if e[6] != None else ' ') 109 | if i == le: 110 | out += f'\n {sym.BOX2}{sym.BOX3*4} ' 111 | else: 112 | out += f'\n {sym.BOX1}{sym.BOX3*4} ' 113 | out += f'{Output.align_text_left(str(e[0]), 4)} {title} {freq} {day} {date} {e[3]}-{e[4]}' 114 | out += task_head 115 | for i,t in enumerate(tasks): 116 | title = Output.align_text_left(t[1], 37) 117 | freq = Output.align_text_left(Output.char_to_freq[t[4]], 6) 118 | day = Output.align_text_left(Output.int_to_days[t[2]], 5) 119 | date = (t[5] if t[5] != None else ' ') 120 | if i == lt: 121 | out += f'\n {sym.BOX2}{sym.BOX3*4} ' 122 | else: 123 | out += f'\n {sym.BOX1}{sym.BOX3*4} ' 124 | out += f'{Output.align_text_left(str(t[0]), 4)} {title} {freq} {day} {date} {t[3]}' 125 | out += sym.default() 126 | print(out, flush=True) 127 | 128 | @staticmethod 129 | def align_text_left(title, max_len): 130 | """ 131 | shortens or extends a title to max_len 132 | """ 133 | if len(title) > max_len: 134 | return title[:max_len - 3] + '...' 135 | else: 136 | return title + ''.join(' ' for _ in range(max_len-len(title))) 137 | 138 | @staticmethod 139 | def align_text_right(title, max_len): 140 | """ 141 | shortens or extends a title to max_len 142 | """ 143 | if len(title) > max_len: 144 | return '...' + title[-(max_len-3):] 145 | else: 146 | return ''.join(' ' for _ in range(max_len-len(title))) + title 147 | 148 | @staticmethod 149 | def format_time(days, hours, minutes, now=False): 150 | if days < 0: 151 | if now: 152 | return f'(now)' 153 | return '(missed)' 154 | elif days == 0 and hours == 0: 155 | return f'({minutes}m)' 156 | elif days == 0: 157 | return f'({hours}h{minutes:02}m)' 158 | else: 159 | return f'({days}d{hours:02}h{minutes:02}m)' 160 | 161 | @staticmethod 162 | def color_time(days, hours, minutes, time_string): 163 | """ 164 | colors a time string 165 | """ 166 | if days < 1: # missed 167 | return f'{sym.RED}{time_string}' 168 | elif days == 0 and hours < 3: # less than 2h left 169 | return f'{sym.BRED}{time_string}' 170 | elif days < 3: # less than one day daft 171 | return f'{sym.YELLOW}{time_string}' 172 | else: 173 | return f'{sym.default()}{time_string}' 174 | 175 | @staticmethod 176 | def overview(events, tasks, tasks_done): 177 | 178 | # TODO sym class \u2500... 179 | # TODO more functions for 'left' + colors 180 | # TODO less code per line 181 | 182 | out = f'{sym.default()}{sym.CYAN}{sym.HLINE*3}[ \u001b[1mToday\'s Events{sym.default()}{sym.CYAN} ]' \ 183 | + f'{sym.HLINE*59}{sym.default()}' 184 | 185 | if len(events) == 0: 186 | out += "\n No events found for today. Use 'dl event -h' and add new events :)" 187 | i = 0 188 | for e in events: 189 | if e[7] < 0 and e[10] == False: 190 | continue 191 | title = Output.align_text_left(e[1], 50) 192 | if i == 0 or (e[7] < 0 and e[10] == True): 193 | out += f'\n{sym.BLUE} {sym.ARROW} {sym.default()}' 194 | time_left = Output.align_text_right(f'{Output.format_time(*e[7:11])}',9) 195 | out += f'{title} [{e[5]}] {e[3]}-{e[4]} {sym.CYAN}{time_left}{sym.default()}' 196 | else: 197 | out += f'\n{sym.BLUE} {sym.ARROW} {sym.default()}{sym.DIM}' 198 | time_left = Output.align_text_right(f'{Output.format_time(*e[7:10])}',9) 199 | out += f'{title} [{e[5]}] {e[3]}-{e[4]} {time_left}{sym.default()}' 200 | i += 1 201 | 202 | out += f'\n{sym.MAGENTA}{sym.HLINE*3}[ \u001b[1mYour Tasks{sym.default()}{sym.MAGENTA} ]' \ 203 | + "\u2500"*63 + sym.default() 204 | 205 | if len(tasks) == 0 and len(tasks_done) == 0: 206 | out += "\n No tasks found. Use 'dl task -h' and add new tasks ;)" 207 | 208 | 209 | for i,t in enumerate(tasks): 210 | title = Output.align_text_left(t[1], 48) 211 | weekday = Output.align_text_left(Output.int_to_days[t[2]], 3).upper() 212 | time_left = Output.align_text_right(f'{Output.format_time(*t[8:11])}',11) 213 | time_left = Output.color_time(*t[8:11], time_left) 214 | mark = (' ', sym.WHITE, ) 215 | if t[8] < 0: 216 | mark = (sym.MISSED, sym.RED) 217 | out += f'\n{sym.RED} [{mark[0]}]{mark[1]} {title} [{t[4]}] ' \ 218 | + f'{weekday} {t[3]} {time_left}{sym.default()}' 219 | 220 | for i,t in enumerate(tasks_done): 221 | title = Output.align_text_left(t[1], 48) 222 | weekday = Output.align_text_left(Output.int_to_days[t[2]], 3).upper() 223 | time_left = Output.align_text_right(f'{Output.format_time(*t[8:11])}',11) 224 | out += f'\n{sym.GREEN} [{sym.DONE}] {title} {sym.default()}[{t[4]}] ' \ 225 | + sym.striken(f'{weekday} {t[3]} {time_left}') + sym.default() 226 | out += sym.default() 227 | print(out, flush=True) 228 | -------------------------------------------------------------------------------- /src/csv_import.py: -------------------------------------------------------------------------------- 1 | import csv 2 | from dolist import Controller 3 | import sys 4 | from cli import Output 5 | 6 | def main(): 7 | c = Controller() 8 | if len(sys.argv) != 2: 9 | Output.error("usage: dlimport [filename]") 10 | c.exit() 11 | return 0 12 | try: 13 | csvfile = open(sys.argv[1], newline='', encoding='utf-8-sig') 14 | reader = csv.reader(csvfile, delimiter=';') 15 | category = '' 16 | for row in reader: 17 | if row[0] == 'events': 18 | category = 'e' 19 | continue 20 | elif row[0] == 'tasks': 21 | category = 't' 22 | continue 23 | elif row[0] == '': 24 | continue 25 | 26 | if category == 'e': 27 | pass 28 | print(row[0], row[2], f'{row[3]}-{row[4]}', row[1]) 29 | c.add_event(row[0], row[2], f'{row[3]}-{row[4]}', row[1]) 30 | elif category == 't': 31 | print(row[0], row[2], row[3], row[1]) 32 | c.add_task(row[0], row[2], row[3], row[1]) 33 | csvfile.close() 34 | except FileNotFoundError: 35 | Output.error("file not found") 36 | finally: 37 | c.exit() 38 | 39 | if __name__ == '__main__': 40 | main() 41 | -------------------------------------------------------------------------------- /src/data.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MIT License 3 | 4 | Copyright (c) 2020 Cedric Fauth 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | ''' 24 | 25 | import re 26 | import logging 27 | from cli import Output as O 28 | from datetime import datetime, date, timedelta 29 | 30 | logger = logging.getLogger(__name__) 31 | logging.basicConfig(level=logging.ERROR) 32 | 33 | 34 | class Dataparser(): 35 | 36 | days_to_int = {"mon" : 0, "tue" : 1, "wed" : 2, "thu" : 3, 37 | "fri" : 4, "sat" : 5, "sun" : 6 } 38 | 39 | @staticmethod 40 | def validate(args): 41 | ''' 42 | validates input values of the user 43 | ''' 44 | # only need to validate if cmd is event or task 45 | if args.cmd == 'event' or args.cmd == 'task': 46 | # -t -d -f are available but -d is optional 47 | 48 | # try to match -f w/o/d 49 | if not re.match('^(w|weekly|d|daily|o|once)$', args.f): 50 | O.error(f'wrong frequency format: {args.f}') 51 | return False 52 | 53 | # if daily: no date/day set 54 | if args.f[0] == 'd': 55 | if args.d != None: 56 | O.error(f'you cannot use -d here because the {args.cmd} is daily.') 57 | return False 58 | # if once: date YYYY-MM-DD needs to be set 59 | elif args.f[0] == 'o': 60 | if not args.d or not re.match('^((\d\d\d\d)-(0[1-9]|1[0-2])-(0[1-9]|(1|2)[0-9]|3[0-1]))$', args.d): 61 | O.error(f'wrong date format: {args.d}') 62 | return False 63 | # if weekly: day needs to be set 64 | else: 65 | if not args.d or not re.match('^(mon|tue|wed|thu|fri|sat|sun)$', args.d): 66 | O.error(f'wrong day format: {args.d}') 67 | return False 68 | 69 | # if event try to match HH:MM-HH:MM 70 | if args.cmd == 'event': 71 | if not re.match('^([0-1][0-9]|2[0-3]):[0-5][0-9]-([0-1][0-9]|2[0-3]):[0-5][0-9]$', args.t): 72 | O.error(f'wrong time format: {args.t}') 73 | return False 74 | # if event try to match HH:MM 75 | else: 76 | if not re.match('^([0-1][0-9]|2[0-3]):[0-5][0-9]$', args.t): 77 | O.error(f'wrong time format: {args.t}') 78 | return False 79 | return True 80 | 81 | @staticmethod 82 | def parse(c, title, day_date, time, freq): 83 | ''' 84 | weekly event data gets prepared for database 85 | ''' 86 | f = freq[0] 87 | day = None 88 | date = None 89 | 90 | if f == 'o': 91 | date = day_date 92 | elif f == 'w': 93 | day = Dataparser.days_to_int[day_date] 94 | if c =='e': 95 | t = time.split('-') 96 | return (title, day ,t[0], t[1], f, date) 97 | else: 98 | tmp = (0, title, day, time, f, date, ) 99 | previous_date = Dataparser.last_deadline(tmp) 100 | if previous_date: 101 | return (title, day, time, f, date, previous_date.strftime('%Y-%m-%d %H:%M')) 102 | else: 103 | return (title, day, time, f, date) 104 | 105 | @staticmethod 106 | def nearest_deadline(task): 107 | freq = task[4] 108 | # date now 109 | dt = datetime.now() 110 | date_str = dt.date().isoformat() 111 | # time now 112 | current_time_str = dt.strftime("%H:%M") 113 | current_datetime = datetime.fromisoformat(f'{date_str} {current_time_str}') 114 | deadline_datetime = datetime.fromisoformat(f'{date_str} {task[3]}') 115 | 116 | if freq == 'w': 117 | while 1: 118 | if deadline_datetime.weekday() == task[2]: 119 | if deadline_datetime > current_datetime: 120 | break 121 | deadline_datetime += timedelta(1) 122 | # check if last deadline was missed 123 | if task[7]: 124 | last_done = datetime.fromisoformat(task[7]) 125 | if deadline_datetime - last_done > timedelta(7): 126 | deadline_datetime -= timedelta(7) 127 | elif freq == 'd': 128 | if deadline_datetime <= current_datetime: 129 | deadline_datetime += timedelta(1) 130 | # check if last deadline was missed 131 | if task[7]: 132 | last_done = datetime.fromisoformat(task[7]) 133 | if deadline_datetime - last_done > timedelta(1): 134 | deadline_datetime -= timedelta(1) 135 | else: 136 | deadline_datetime = datetime.fromisoformat(f'{task[5]} {task[3]}') 137 | 138 | logger.debug(f'nearest_deadline {deadline_datetime} for {task}') 139 | 140 | return deadline_datetime 141 | 142 | @staticmethod 143 | def last_deadline(task): 144 | freq = task[4] 145 | # date now 146 | dt = datetime.now() 147 | date_str = dt.date().isoformat() 148 | # time now 149 | current_time_str = dt.strftime("%H:%M") 150 | current_datetime = datetime.fromisoformat(f'{date_str} {current_time_str}') 151 | deadline_datetime = datetime.fromisoformat(f'{date_str} {task[3]}') 152 | 153 | if freq == 'w': 154 | while 1: 155 | if deadline_datetime.weekday() == task[2]: 156 | if deadline_datetime < current_datetime: 157 | break 158 | deadline_datetime -= timedelta(1) 159 | elif freq == 'd': 160 | if deadline_datetime > current_datetime: 161 | deadline_datetime -= timedelta(1) 162 | else: 163 | deadline_datetime = None 164 | 165 | logger.debug(f'last_deadline {deadline_datetime} for {task}') 166 | 167 | return deadline_datetime 168 | 169 | @staticmethod 170 | def delta_to_tupel(tdelta): 171 | hours, rem = divmod(tdelta.seconds, 3600) 172 | minutes = rem // 60 # + 1 173 | return (tdelta.days, hours, minutes, ) 174 | 175 | @staticmethod 176 | def prepare_out_events(events): 177 | """ 178 | creates list of events with additional attibs like 'time left' 179 | """ 180 | event_list = [] 181 | daytime = datetime.today() 182 | day = date.today() 183 | for e in events: 184 | start_time = datetime.fromisoformat(f'{day.isoformat()} {e[3]}') 185 | end_time = datetime.fromisoformat(f'{day.isoformat()} {e[4]}') 186 | dstart = start_time - daytime 187 | dend = end_time - daytime 188 | dstart = Dataparser.delta_to_tupel(dstart) 189 | if dend.days >= 0: 190 | event_list.append(e+dstart+(True, )) 191 | else: 192 | event_list.append(e+dstart+(False, )) 193 | return event_list 194 | 195 | 196 | @staticmethod 197 | def prepare_out_tasks(tasks): 198 | """ 199 | creates list of tasks with additional attibs like 'time left' 200 | """ 201 | task_list = [] 202 | daytime = datetime.today() 203 | day = date.today() 204 | for t in tasks: 205 | left = None 206 | # info: stays missed until new day begins 207 | 208 | deadline_datetime = Dataparser.nearest_deadline(t) 209 | logger.debug(deadline_datetime) 210 | left = deadline_datetime - daytime 211 | logger.debug(f'left{left}') 212 | 213 | task_list.append(t + Dataparser.delta_to_tupel(left)) 214 | 215 | def time_left_to_str(x): 216 | #if x[8] < 0: 217 | # y = int(f'{x[8]}{((x[9]*(-1) + 24) % 24):02}{x[10]:02}') 218 | #else: 219 | y = int(f'{x[8]}{x[9]:02}{x[10]:02}') 220 | return y 221 | 222 | return sorted(task_list, key=time_left_to_str) 223 | 224 | @staticmethod 225 | def get_reset_ids(tasks_done): 226 | # TODO maybe wrong because of many changes 227 | task_ids = [] 228 | daytime = datetime.today() 229 | day = date.today() 230 | for t in tasks_done: 231 | left = None 232 | if t[7] != None: 233 | task_time = datetime.fromisoformat(t[7]) 234 | left = task_time - daytime 235 | if left.days < 0: 236 | task_ids.append((t[0],t[4], )) 237 | #elif t[4] == 'o' and t[7] != None: 238 | # task_ids.append((t[0],t[4], )) 239 | # check if last-done is before last due: reset 240 | #if datetime.fromisoformat(t[7]) == task_time: 241 | # logger.info(f'{t[1]} can be deleted') 242 | # task_ids.append(t[0]) 243 | return task_ids 244 | -------------------------------------------------------------------------------- /src/database.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MIT License 3 | 4 | Copyright (c) 2020 Cedric Fauth 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | ''' 24 | 25 | import sqlite3 26 | from os import path 27 | from datetime import datetime, date 28 | import logging 29 | import os 30 | 31 | 32 | logger = logging.getLogger(__name__) 33 | logging.basicConfig(level=logging.ERROR) 34 | 35 | class Database: 36 | def __init__(self): 37 | self.dirname = os.path.dirname(__file__) 38 | self.path = os.path.join(self.dirname[:-3], 'data.db') 39 | #self.path = "./data.db" 40 | self.conn = None 41 | self.reset = False 42 | if not path.exists(self.path): 43 | self.reset = True 44 | logger.info("no database detected") 45 | 46 | try: 47 | self.conn = sqlite3.connect(self.path) 48 | if self.reset: 49 | self.create_new_db() 50 | self.reset = False 51 | except Exception as e: 52 | logging.ERROR(e) 53 | 54 | def create_new_db(self): 55 | logger.info("creating database...") 56 | create_event_table = """ CREATE TABLE events ( 57 | id integer PRIMARY KEY autoincrement, 58 | title text NOT NULL, 59 | day integer, 60 | start_time text NOT NULL, 61 | end_time text NOT NULL, 62 | freq text NOT NULL, 63 | date text 64 | ); """ 65 | create_task_table = """ CREATE TABLE tasks ( 66 | id integer PRIMARY KEY autoincrement, 67 | title text NOT NULL, 68 | day integer, 69 | time text NOT NULL, 70 | freq text NOT NULL, 71 | date text, 72 | done integer, 73 | done_on text 74 | ); """ 75 | try: 76 | c = self.conn.cursor() 77 | c.execute(create_event_table) 78 | c.execute(create_task_table) 79 | except Exception as e: 80 | logging.ERROR(e) 81 | 82 | def new_event(self,title,day,s_time,e_time,freq,date): 83 | sql = ''' INSERT INTO events 84 | (title,day,start_time,end_time,freq,date) 85 | VALUES(?,?,?,?,?,?); ''' 86 | cur = self.conn.cursor() 87 | cur.execute(sql, (title,day,s_time,e_time,freq,date,)) 88 | self.conn.commit() 89 | logger.info(f'inserted: {cur.lastrowid}') 90 | return cur.lastrowid 91 | 92 | def new_task(self,title,day,time,freq,date,done_on=None,done=0): 93 | sql = ''' INSERT INTO tasks 94 | (title,day,time,freq,date,done,done_on) 95 | VALUES(?,?,?,?,?,?,?); ''' 96 | cur = self.conn.cursor() 97 | cur.execute(sql, (title,day,time,freq,date,done,done_on,)) 98 | self.conn.commit() 99 | logger.info(f'inserted: {cur.lastrowid}') 100 | return cur.lastrowid 101 | 102 | def get_overview_data(self): 103 | 104 | today = date.today() 105 | weekday = today.weekday() 106 | 107 | sql1 = '''SELECT * FROM events 108 | WHERE (day = ? AND freq = 'w') OR (date = ?) OR (freq = 'd') 109 | ORDER BY start_time ASC;''' 110 | sql2 = '''SELECT * FROM tasks 111 | WHERE done = ?;''' 112 | 113 | cur = self.conn.cursor() 114 | cur.execute(sql1, (weekday, today.isoformat())) 115 | event_rows = cur.fetchall() 116 | cur.execute(sql2, (0, )) 117 | task_rows_todo = cur.fetchall() 118 | cur.execute(sql2, (1, )) 119 | task_rows_done = cur.fetchall() 120 | 121 | return (event_rows, task_rows_todo, task_rows_done) 122 | 123 | def get_done_tasks(self): 124 | sql1 = '''SELECT * FROM tasks 125 | WHERE done = 1;''' 126 | cur = self.conn.cursor() 127 | cur.execute(sql1) 128 | task_rows_done = cur.fetchall() 129 | return task_rows_done 130 | 131 | def get_id_list(self): 132 | sql1 = '''SELECT * FROM events;''' 133 | sql2 = '''SELECT * FROM tasks;''' 134 | 135 | cur = self.conn.cursor() 136 | cur.execute(sql1) 137 | event_rows = cur.fetchall() 138 | cur.execute(sql2) 139 | task_rows = cur.fetchall() 140 | 141 | return (event_rows, task_rows, ) 142 | 143 | def delete_data(self, id, typ): 144 | sql1 = 'DELETE FROM events WHERE id=? ;' 145 | sql2 = 'DELETE FROM tasks WHERE id=? ;' 146 | cur = self.conn.cursor() 147 | if typ == 'e': 148 | cur.execute(sql1, (id, )) 149 | else: 150 | cur.execute(sql2, (id, )) 151 | self.conn.commit() 152 | 153 | def get_task_by(self, id): 154 | sql1 = '''SELECT * FROM tasks WHERE id = ?;''' 155 | cur = self.conn.cursor() 156 | cur.execute(sql1, (id, )) 157 | return cur.fetchone() 158 | 159 | def set_done(self, task_id, date): 160 | sql1 = '''update tasks set done_on = ?, 161 | done=1 where id = ?;''' 162 | cur = self.conn.cursor() 163 | cur.execute(sql1, (date, task_id, )) 164 | self.conn.commit() 165 | logger.info(f'updated: done=1 and done-date="{date}" at id {task_id}') 166 | 167 | def reset_done(self, reset_id_list): 168 | sql1 = '''UPDATE tasks SET done = 0 WHERE id = ?;''' 169 | sql2 = '''DELETE FROM tasks WHERE id = ?;''' 170 | cur = self.conn.cursor() 171 | logger.info(reset_id_list) 172 | for id,freq in reset_id_list: 173 | if freq == 'o': 174 | cur.execute(sql2, (id, )) 175 | else: 176 | cur.execute(sql1, (id, )) 177 | logger.info(f'reset: done=0 at id {id}') 178 | self.conn.commit() 179 | 180 | def close(self): 181 | if self.conn: 182 | self.conn.close() 183 | logger.info("database closed") 184 | -------------------------------------------------------------------------------- /src/dolist.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MIT License 3 | 4 | Copyright (c) 2020 Cedric Fauth 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | ''' 24 | 25 | from cli import CLI_Parser, Output as Out 26 | from database import Database 27 | from data import Dataparser 28 | import logging 29 | from datetime import date, datetime 30 | 31 | logger = logging.getLogger(__name__) 32 | logging.basicConfig(level=logging.ERROR) 33 | 34 | class Controller: 35 | def __init__(self): 36 | Out.open() 37 | self.db = Database() 38 | 39 | def reset_done_tasks(self): 40 | data = self.db.get_done_tasks() 41 | reset_id_list = Dataparser.get_reset_ids(data) 42 | self.db.reset_done(reset_id_list) 43 | 44 | def show_overview(self): 45 | logger.info('cmd: show overview') 46 | data = self.db.get_overview_data() 47 | e = Dataparser.prepare_out_events(data[0]) 48 | t = Dataparser.prepare_out_tasks(data[1]) 49 | td = Dataparser.prepare_out_tasks(data[2]) 50 | 51 | Out.overview(e, t, td) 52 | 53 | def add_event(self, title, day, timeFromTo, freq): 54 | logger.info('cmd: event') 55 | 56 | args = Dataparser.parse('e', title, day, timeFromTo, freq) 57 | self.db.new_event(*args) # tupel to parameters (*) 58 | 59 | Out.info("event added") 60 | 61 | def add_task(self, title, day, time, freq): 62 | logger.info('cmd: task') 63 | args = Dataparser.parse('t', title, day, time, freq) 64 | self.db.new_task(*args) # tupel to parameters (*) 65 | Out.info("task added") 66 | 67 | def list_ids(self): 68 | logger.info(f'cmd: list_ids') 69 | data = self.db.get_id_list() 70 | Out.list_all(data[0], data[1]) 71 | 72 | def remove_by_id(self, id, typ): 73 | logger.info(f'cmd: remove_by_id -{typ} {id}') 74 | self.db.delete_data(id, typ) 75 | if typ == 't': 76 | Out.info(f"task {id} deleted") 77 | elif typ == 'e': 78 | Out.info(f"event {id} deleted") 79 | 80 | def done(self, id): 81 | t = self.db.get_task_by(id) 82 | if not t: 83 | Out.error(f'no task with id {id} found') 84 | return 0 85 | 86 | done_time = Dataparser.nearest_deadline(t).strftime('%Y-%m-%d %H:%M') 87 | Out.info(f'task {id} done until {done_time}') 88 | self.db.set_done(id, done_time) 89 | 90 | def exit(self): 91 | self.db.close() 92 | Out.close() 93 | 94 | 95 | def main(): 96 | # parse cli 97 | p = CLI_Parser() 98 | 99 | if not Dataparser.validate(p.args): 100 | return 0 101 | 102 | c = Controller() 103 | c.reset_done_tasks() 104 | 105 | if p.args.cmd == None: 106 | c.show_overview() 107 | elif p.args.cmd == 'event': 108 | c.add_event(p.args.title, p.args.d, p.args.t, p.args.f) 109 | elif p.args.cmd == 'task': 110 | c.add_task(p.args.title, p.args.d, p.args.t, p.args.f) 111 | elif p.args.cmd == 'rm' and p.args.t: 112 | c.remove_by_id(p.args.id, 't') 113 | elif p.args.cmd == 'rm' and p.args.e: 114 | c.remove_by_id(p.args.id, 'e') 115 | elif p.args.cmd == 'ls': 116 | c.list_ids() 117 | elif p.args.cmd == 'done': 118 | c.done(p.args.task_id) 119 | 120 | c.exit() 121 | 122 | return 0 123 | 124 | 125 | if __name__ == '__main__': 126 | main() 127 | -------------------------------------------------------------------------------- /src/utils.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MIT License 3 | 4 | Copyright (c) 2020 Cedric Fauth 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | ''' 24 | 25 | class Symbol: 26 | BLACK = "\u001b[30m" 27 | RED = "\u001b[31m" 28 | GREEN = "\u001b[32m" 29 | YELLOW = "\u001b[33m" 30 | BLUE = "\u001b[34m" 31 | MAGENTA = "\u001b[35m" 32 | CYAN = "\u001b[36m" 33 | WHITE = "\u001b[37m" 34 | 35 | BRED = "\u001b[31;1m" 36 | BYELLOW = "\u001b[33;1m" 37 | BBLUE = "\u001b[34;1m" 38 | BACKBLUE = "\u001b[44m" 39 | 40 | DIM = "\u001b[2m" 41 | 42 | RESET = "\u001b[0m" 43 | 44 | HLINE = "\u2500" 45 | 46 | ARROW = "\u25B6" 47 | ERR = "\u2717" 48 | DONE = "\u2714" 49 | MISSED = "\u2717" 50 | BOX1 = "\u251C" 51 | BOX2 = "\u2514" 52 | BOX3 = "\u2500" 53 | 54 | @staticmethod 55 | def default(): 56 | return "\u001b[0m\u001b[37m" 57 | 58 | @staticmethod 59 | def striken(text): 60 | return ''.join(t+chr(822) for t in text) 61 | -------------------------------------------------------------------------------- /tests/mockup.py: -------------------------------------------------------------------------------- 1 | ''' 2 | MIT License 3 | 4 | Copyright (c) 2020 Cedric Fauth 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in all 14 | copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 22 | SOFTWARE. 23 | ''' 24 | 25 | def striken(text): 26 | return ''.join(t+chr(822) for t in text) 27 | 28 | class Color: 29 | BLACK = "\u001b[30m" 30 | RED = "\u001b[31m" 31 | GREEN = "\u001b[32m" 32 | YELLOW = "\u001b[33m" 33 | BLUE = "\u001b[34m" 34 | MAGENTA = "\u001b[35m" 35 | CYAN = "\u001b[36m" 36 | WHITE = "\u001b[37m" 37 | 38 | BRED = "\u001b[31;1m" 39 | BYELLOW = "\u001b[33;1m" 40 | BBLUE = "\u001b[34;1m" 41 | 42 | BACKBLUE = "\u001b[44m" 43 | 44 | DIM = "\u001b[2m" 45 | 46 | RESET = "\u001b[0m" 47 | 48 | @staticmethod 49 | def default(): 50 | return "\u001b[0m\u001b[37m" 51 | 52 | #print(f"{Color.default()}\n1:\n") 53 | 54 | #print(Color.default() + f"\u256D\u2500\u2500( \u001b[1mToday's events{Color.default()} )"+"".join("\u2500" for _ in range(0,58))+"\u256E") 55 | #print(f"\u2502{Color.BLUE} \u25B6{Color.default()} Lecture Human Computer Interaction\t\t\tTUE 10:00 {Color.CYAN}(1h 13m){Color.default()} \u2502") 56 | #print(f"\u2502{Color.WHITE} \u25B6 Exercise Human Computer Interaction\t\t\tTUE 11:30 (2h 43m){Color.default()} \u2502") 57 | #print(f"\u251C\u2500\u2500( \u001b[1mThis week's assignments{Color.default()} )" + "".join("\u2500" for _ in range(0,49))+"\u2524") 58 | #print(f"\u2502{Color.RED}[ ]{Color.WHITE} IT Security Assignment 01\t\t\t\tMON 23:55 {Color.RED}(22h 40m){Color.default()} \u2502") 59 | #print(f"\u2502{Color.RED}[ ]{Color.WHITE} Human Computer Interaction Assignment 02\t\tMON 23:55 {Color.BYELLOW}(1d 22h 40m){Color.default()} \u2502") 60 | #print(f"\u2502{Color.GREEN}[\u2714]{Color.WHITE} {striken('Proseminar Preperation')}\t\t\t\t\u001b[42m{Color.default()}{striken('MON 23:55 (1d 22h 40m)')}{Color.default()} \u2502") 61 | #print("\u2570" + "".join("\u2500" for _ in range(0,78)) + "\u256F") 62 | 63 | print(f"{Color.default()}\033[2J\033[H\nLayout:\n") 64 | 65 | print(Color.default() + f"{Color.CYAN}\u2500\u2500\u2500[ \u001b[1mToday's events{Color.default()}{Color.CYAN} ]"+"".join("\u2500" for _ in range(0,59))) 66 | print(f"{Color.BLUE} \u25B6{Color.default()} Lecture Human Computer Interaction\t\t [w] 10:00-11:30 (1h13m){Color.default()}") 67 | print(f"{Color.BLUE} \u25B6{Color.default()} {Color.DIM}Exercise Human Computer Interaction\t\t [w] 11:30-13:00 (2h43m){Color.default()}") 68 | print(f"{Color.BLUE} \u25B6{Color.default()} {Color.DIM}Foundations of Information Retrieval\t\t [w] 14:00-15:00 (13h13m){Color.default()}") 69 | 70 | print(Color.default() + f"{Color.MAGENTA}\u2500\u2500\u2500[ \u001b[1mAssignments{Color.default()}{Color.MAGENTA} ]"+"".join("\u2500" for _ in range(0,62))) 71 | print(f"{Color.RED} [ ]{Color.WHITE} IT Security Assignment 01\t\t\t [w] MON 08:15{Color.BRED} (9h40m){Color.default()}") 72 | print(f"{Color.RED} [ ]{Color.WHITE} Human Computer Interaction Assignment 02\t [w] MON 23:55{Color.BYELLOW} (1d22h40m){Color.default()}") 73 | print(f"{Color.GREEN} [\u2714]{Color.WHITE} {striken('Proseminar Preperation')}\t\t\t [w] \u001b[42m{Color.default()}{striken('MON 23:55 (1d22h40m)')}{Color.default()}") 74 | 75 | print("\nInput format:\n") 76 | print("Show today's events and all assignments:") 77 | print(f"{Color.MAGENTA}$ >{Color.default()} dl") 78 | print("Add an event (-e):") 79 | print(f"{Color.MAGENTA}$ >{Color.default()} dl task 'VL Human Computer Interaction' -f w -t 15:00-16:30 -d MON") 80 | print(f"{Color.MAGENTA}$ >{Color.default()} dl task 'VL Human Computer Interaction' -f d -t 15:00-16:30") 81 | print(f"{Color.MAGENTA}$ >{Color.default()} dl task 'VL Human Computer Interaction' -f o -t 15:00-16:30 -d 2020-12-24") 82 | print("Add an assignment (-a):") 83 | print(f"{Color.MAGENTA}$ >{Color.default()} dl -a 'Homework MCI' -d FRI -t 00:00 -f once") 84 | print("List all events and IDs:") 85 | print(f"{Color.MAGENTA}$ >{Color.default()} dl -le") 86 | print("List all assignments and IDs:") 87 | print(f"{Color.MAGENTA}$ >{Color.default()} dl -la") 88 | print("Remove event:") 89 | print(f"{Color.MAGENTA}$ >{Color.default()} dl -re ID") 90 | print("Remove event:") 91 | print(f"{Color.MAGENTA}$ >{Color.default()} dl -ra ID") 92 | print() 93 | --------------------------------------------------------------------------------