pytodo-qt 0.2.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.

Potentially problematic release.


This version of pytodo-qt might be problematic. Click here for more details.

@@ -0,0 +1,62 @@
1
+ Metadata-Version: 2.1
2
+ Name: pytodo-qt
3
+ Version: 0.2.0
4
+ Summary: A simple To-Do list app written in PyQt5
5
+ Author-email: Michael Berry <trismegustis@gmail.com>
6
+ License: GPLv3
7
+ Project-URL: Homepage, https://github.com/berrym/todo
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.8
12
+ Description-Content-Type: text/markdown
13
+ License-File: COPYING
14
+ Requires-Dist: PyQt5
15
+ Requires-Dist: pycryptodomex
16
+
17
+ # To-Do
18
+
19
+ A simple to-do list program.
20
+
21
+ ## Description
22
+
23
+ A small application to manage multiple to-do lists written in Python 3 and Qt5
24
+
25
+ ## Getting started
26
+
27
+ * Clone the git repository from https://github.com/berrym/todo.git
28
+
29
+ * Install a recent version of Python, needs version 3.8+.
30
+
31
+ * A requirements.txt is provided in the repository so required packages can be installed using pip.
32
+ * pip install -r requirements.txt --user
33
+
34
+ ### Executing program
35
+
36
+ The main script can be executed from the command line, e.g.
37
+
38
+ * python3 todo.py
39
+
40
+ ## Help
41
+
42
+ python3 todo.py --help
43
+
44
+ ## Authors
45
+
46
+ Copyright 2020
47
+ Michael Berry <trismegustis@gmail.com>
48
+
49
+ ## Version History
50
+ * 0.1.1
51
+ * Needs Python 3.8 for walrus operator.
52
+ * Improved dialogs for syncing.
53
+ * Serialize network I/O with json.
54
+
55
+ * 0.1.0
56
+ * Initial Release
57
+
58
+ ## License
59
+
60
+ This project is licensed under the GNU General Public License version 3, or at your option any later version.
61
+
62
+ See the COPYING file included in the git repository.
@@ -0,0 +1,26 @@
1
+ todo/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
2
+ todo/__main__.py,sha256=71QnTJRRMg-gx4xjTyhoXkYM5idgnb2WnlsVRLn_fqk,3190
3
+ todo/core/Logger.py,sha256=xx9V4Dq54hDg7TmkEY-FvkUl4cTITUn8NHe1wTCX72w,1261
4
+ todo/core/TodoDataBase.py,sha256=y1etUExcdHZ85GhYQWQvTWGBRh3b-7oLet6IcRVjDd4,8323
5
+ todo/core/__init__.py,sha256=FUqE5ViWxy7Sn_n3pxfJrpKRJY21ry2FuJwD7T16AZY,900
6
+ todo/core/json_helpers.py,sha256=h3CXVbbQ4KrMvrpEcsekZxQNqBi7JUSk6IZpbG9C9co,2879
7
+ todo/core/settings.py,sha256=c_eiX9bf9tCpZAsz_2mq4JejgcJ_Z71R15_bVLPn-Po,627
8
+ todo/crypto/AESCipher.py,sha256=cf7YBbxN-K8czTIrXuLVeEGpo5quVn3TNArol1o9JRU,1768
9
+ todo/crypto/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
10
+ todo/gui/AddTodoDialog.py,sha256=kUYbMOqpkGLVSYcClXfu1HO6E4SuGMtVCjTleNPZQjw,2624
11
+ todo/gui/MainWindow.py,sha256=bLyX105tUqOJ9f5Uad0A4JsPUc1Vmkt3jF_CqUhuxIc,33511
12
+ todo/gui/SyncDialog.py,sha256=nqW_khAFQ6PcEveHgDDrSVGsjpdCUaI7z4MDagoALv8,3206
13
+ todo/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
14
+ todo/gui/icons/minus.png,sha256=PoaZl2RAW8wrkOBvz7bY03xEAdaBNd6HNKaodHbgdMc,217
15
+ todo/gui/icons/plus.png,sha256=VywdidKhN2IdJLPkBVQB3O2GmFXzfSDMV1rLkpKDoGg,287
16
+ todo/gui/icons/todo.png,sha256=kBRWlulLIqDxB5-XtlCz-OYhzrjyj5eEWN36nXWMYzI,659
17
+ todo/net/__init__.py,sha256=-PEFQDpZYUD-aHhMYpJE1NMh92J831ifUCfs10rP5gc,538
18
+ todo/net/sync_operations.py,sha256=TSa7p-tgdd6jiwcQ1Cy3PNJw26eqRlVRrYFqLc9LNz8,279
19
+ todo/net/tcp_client_lib.py,sha256=wcMwfSuZ2EFfF6AaAjYnvOR4xyjheZUqf_hwCALRGiw,4478
20
+ todo/net/tcp_server_lib.py,sha256=tEvDlc6eZTfZ-iCQEhzhWhu94Hdaw_snkeUcMKsXcsE,4638
21
+ pytodo_qt-0.2.0.dist-info/COPYING,sha256=jOtLnuWt7d5Hsx6XXB2QxzrSe2sWWh3NgMfFRetluQM,35147
22
+ pytodo_qt-0.2.0.dist-info/METADATA,sha256=S_kYzdnr2X8G7iES0e8dZLNA5gDt1-XFUEXEWIkamYc,1528
23
+ pytodo_qt-0.2.0.dist-info/WHEEL,sha256=oiQVh_5PnQM0E3gPdiz09WCNmwiHDMaGer_elqB3coM,92
24
+ pytodo_qt-0.2.0.dist-info/entry_points.txt,sha256=ywI6kRYcR3cVBDg62_kvFicV4FPmk9ItG6jEKndNXqg,44
25
+ pytodo_qt-0.2.0.dist-info/top_level.txt,sha256=c1x0MAVpTPy2QFoNZ9fz48_PoX9pcGKJOwNu8vee_hs,5
26
+ pytodo_qt-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.42.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ todo = todo.__main__:main
@@ -0,0 +1 @@
1
+ todo
todo/__init__.py ADDED
File without changes
todo/__main__.py ADDED
@@ -0,0 +1,120 @@
1
+ #!/usr/bin/env python3
2
+
3
+ """__main__.py
4
+
5
+ A to-do list program written in Python using Qt5
6
+
7
+ Copyright (C) 2024 Michael Berry <trismegustis@gmail.com>
8
+
9
+ This program is free software: you can redistribute it and/or modify
10
+ it under the terms of the GNU General Public License as published by
11
+ the Free Software Foundation, either version 3 of the License, or
12
+ (at your option) any later version.
13
+
14
+ This program is distributed in the hope that it will be useful,
15
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
16
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17
+ GNU General Public License for more details.
18
+
19
+ You should have received a copy of the GNU General Public License
20
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
21
+ """
22
+
23
+ import argparse
24
+ import os
25
+ import sys
26
+ from PyQt5 import QtWidgets
27
+
28
+ from todo.core.Logger import Logger
29
+ from todo.core import settings, TodoDataBase
30
+ from todo.gui import MainWindow
31
+
32
+
33
+ logger = Logger(__name__)
34
+
35
+
36
+ # Main function
37
+ def main():
38
+ location = ""
39
+ for i in range(0, len(sys.path)):
40
+ if sys.path[i].__contains__("site-packages"):
41
+ location = sys.path[i]
42
+ location += "/todo"
43
+ os.chdir(location)
44
+ break
45
+
46
+ if location == "":
47
+ logger.log.exception("Unknown installation or invocation, exiting")
48
+ sys.exit(0)
49
+
50
+ # create a command line arg_parser
51
+ arg_parser = argparse.ArgumentParser(
52
+ prog="To-Do",
53
+ description="To-Do List Program",
54
+ epilog="Copyright Michael Berry 2020",
55
+ )
56
+
57
+ # add network server command group
58
+ server_group = arg_parser.add_argument_group("Server Commands")
59
+
60
+ # add options
61
+ server_group.add_argument(
62
+ "-s",
63
+ "--run-server",
64
+ action="store",
65
+ type=str,
66
+ choices=["yes", "no"],
67
+ help="run a simple to-do list network server",
68
+ )
69
+
70
+ server_group.add_argument(
71
+ "-a",
72
+ "--allow-pull",
73
+ action="store",
74
+ type=str,
75
+ choices=["yes", "no"],
76
+ help="allow remote users to grab your lists",
77
+ )
78
+
79
+ server_group.add_argument(
80
+ "-A",
81
+ "--allow-push",
82
+ action="store",
83
+ type=str,
84
+ choices=["yes", "no"],
85
+ help="allow remote users to send you their lists",
86
+ )
87
+
88
+ server_group.add_argument(
89
+ "-i", "--ip", type=str, help="set the servers ip addresses."
90
+ )
91
+
92
+ server_group.add_argument(
93
+ "-p",
94
+ "--port",
95
+ type=int,
96
+ help="specify which port the network server will bind to",
97
+ )
98
+
99
+ arg_parser.add_argument(
100
+ "-V", "--version", action="version", version=f"%(prog)s v{settings.__version__}"
101
+ )
102
+
103
+ # parse args then convert to dict format
104
+ args = arg_parser.parse_args()
105
+ for k, v in vars(args).items():
106
+ if v is not None:
107
+ settings.options[k] = v
108
+
109
+ # create a to-do database
110
+ settings.db = TodoDataBase.CreateTodoDataBase()
111
+
112
+ # create a QApplication, the main window, then hand over control to Qt
113
+ app = QtWidgets.QApplication(sys.argv)
114
+ _ = MainWindow.CreateMainWindow()
115
+ sys.exit(app.exec())
116
+
117
+
118
+ # Main? - Program entry point
119
+ if __name__ == "__main__":
120
+ main()
todo/core/Logger.py ADDED
@@ -0,0 +1,47 @@
1
+ """Logger.py
2
+
3
+ A Generic logging class.
4
+ """
5
+
6
+ import logging
7
+ import os
8
+ import sys
9
+
10
+ home_dir = os.getenv("HOME")
11
+ if home_dir is not None:
12
+ todo_dir = os.path.join(home_dir, ".todo")
13
+ else:
14
+ print("Error: unable to write log file, exiting")
15
+ sys.exit(1)
16
+
17
+ log_fn = os.path.join(todo_dir, "todo.log")
18
+
19
+
20
+ class Logger:
21
+ """A flexible logger."""
22
+
23
+ def __init__(
24
+ self,
25
+ module_name,
26
+ file_name=log_fn,
27
+ file_mode="w",
28
+ file_handler_level=logging.DEBUG,
29
+ file_handler_format="%(asctime)-15s [%(threadName)-12s][%(levelname)-8s] %(message)s",
30
+ console_handler=logging.StreamHandler(),
31
+ console_handler_level=logging.DEBUG,
32
+ console_handler_format=logging.Formatter(
33
+ "%(asctime)-15s [%(threadName)-12s][%(levelname)-8s] %(message)s"
34
+ ),
35
+ ):
36
+ """Initialize the logger."""
37
+ logging.basicConfig(
38
+ level=file_handler_level,
39
+ format=file_handler_format,
40
+ filename=file_name,
41
+ filemode=file_mode,
42
+ )
43
+
44
+ self.log = logging.getLogger(module_name)
45
+ console_handler.setLevel(console_handler_level)
46
+ console_handler.setFormatter(console_handler_format)
47
+ self.log.addHandler(console_handler)
@@ -0,0 +1,234 @@
1
+ """TodoDataBase.py
2
+
3
+ This module implements the to-do database.
4
+ """
5
+
6
+ import configparser
7
+ import os
8
+ import sys
9
+ import threading
10
+
11
+ from todo.core import settings
12
+ from todo.core.Logger import Logger
13
+ from todo.net import tcp_server_lib, tcp_client_lib
14
+
15
+
16
+ logger = Logger(__name__)
17
+
18
+
19
+ class CreateTodoDataBase:
20
+ """Maintains a database of to-do lists."""
21
+
22
+ def __init__(self):
23
+ """Create a working database."""
24
+ logger.log.info("Building the to-do database")
25
+ self.initialized = False
26
+ self.server_up = False
27
+
28
+ # dictionary of to-do lists, which are lists of dictionaries
29
+ self.todo_lists = {}
30
+ self.todo_total = 0
31
+ self.list_count = 0
32
+
33
+ # keep statistics on 'active' to-do list
34
+ self.active_list = ""
35
+ self.todo_count = 0
36
+
37
+ # create an ini config parser
38
+ self.config = configparser.ConfigParser()
39
+ if not os.path.exists(settings.ini_fn):
40
+ self.write_default_config()
41
+ self.parse_config()
42
+
43
+ # buffer size for sending/receiving data
44
+ self.buf_size = 4096
45
+
46
+ # create a server, and start it in a new thread
47
+ # the server will create a new thread for each connection established
48
+ self.net_server = None
49
+ if settings.options["run"]:
50
+ self.start_server()
51
+
52
+ # create a client
53
+ self.net_client = tcp_client_lib.DataBaseClient()
54
+
55
+ def write_default_config(self):
56
+ """Write the default configuration for To-Do."""
57
+ logger.log.info("Writing default configuration to %s", settings.ini_fn)
58
+ self.config["database"] = {}
59
+ self.config["database"]["active_list"] = ""
60
+ self.config["database"]["sort_key"] = "priority"
61
+ self.config["database"]["reverse_sort"] = "no"
62
+ self.config["server"] = {}
63
+ self.config["server"]["key"] = "BewareTheBlackGuardian"
64
+ self.config["server"]["run"] = "yes"
65
+ self.config["server"]["address"] = "127.0.0.1"
66
+ self.config["server"]["port"] = "5364"
67
+ self.config["server"]["pull"] = "yes"
68
+ self.config["server"]["push"] = "yes"
69
+
70
+ try:
71
+ with open(settings.ini_fn, "w", encoding="utf-8") as f:
72
+ self.config.write(f)
73
+ except IOError as e:
74
+ logger.log.exception("Unable to write default config: %s", e)
75
+ sys.exit(1)
76
+
77
+ def write_config(self):
78
+ """Write the current configuration options to config file."""
79
+ logger.log.info("Writing configuration file %s", settings.ini_fn)
80
+ self.config["database"]["active_list"] = settings.options["active_list"]
81
+ self.config["database"]["sort_key"] = settings.options["sort_key"]
82
+ if settings.options["reverse_sort"]:
83
+ self.config["database"]["reverse_sort"] = "yes"
84
+ else:
85
+ self.config["database"]["reverse_sort"] = "no"
86
+ if settings.options["run"]:
87
+ self.config["server"]["run"] = "yes"
88
+ else:
89
+ self.config["server"]["run"] = "no"
90
+ self.config["server"]["address"] = settings.options["address"]
91
+ self.config["server"]["port"] = str(settings.options["port"])
92
+ self.config["server"]["pull"] = settings.options["pull"]
93
+ self.config["server"]["push"] = settings.options["push"]
94
+
95
+ try:
96
+ with open(settings.ini_fn, "w", encoding="utf-8") as f:
97
+ self.config.write(f)
98
+ msg = f"Successfully wrote configuration file {settings.ini_fn}"
99
+ logger.log.info(msg)
100
+ return True, msg
101
+ except IOError as e:
102
+ msg = f"Unable to write default configuration file {settings.ini_fn}: {e}"
103
+ logger.log.exception(msg)
104
+ return False, msg
105
+
106
+ def parse_config(self):
107
+ """Parse configuration file.
108
+
109
+ If a configuration file exists read it in and set relevant data.
110
+ """
111
+ logger.log.info("Parsing configuration file %s", settings.ini_fn)
112
+ self.config.read(settings.ini_fn)
113
+
114
+ for k, v in self.config["database"].items():
115
+ if k not in settings.options:
116
+ logger.log.info(f"%r = %r", k, v)
117
+ settings.options[k] = v
118
+
119
+ for k, v in self.config["server"].items():
120
+ if k not in settings.options:
121
+ logger.log.info(f"%r = %r", k, v)
122
+ settings.options[k] = v
123
+
124
+ # fix some option types
125
+ if settings.options["reverse_sort"] == "yes":
126
+ settings.options["reverse_sort"] = True
127
+ elif settings.options["reverse_sort"] == "no":
128
+ settings.options["reverse_sort"] = False
129
+ else:
130
+ logger.log.warning("Reverse sort option invalid, defaulting to no")
131
+ settings.options["reverse_sort"] = False
132
+
133
+ if settings.options["run"] == "yes":
134
+ settings.options["run"] = True
135
+ elif settings.options["run"] == "no":
136
+ settings.options["run"] = False
137
+ else:
138
+ logger.log.warning("Run network server option invalid, defaulting to yes")
139
+ settings.options["run"] = True
140
+
141
+ try:
142
+ settings.options["port"] = int(settings.options["port"])
143
+ except ValueError as e:
144
+ logger.log.exception("Port must be a number: %s", e)
145
+ settings.options["port"] = 5364
146
+
147
+ def write_text_file(self, fn="todo_list.txt"):
148
+ """Write active list to plain text file."""
149
+ if self.todo_count == 0:
150
+ return
151
+
152
+ logger.log.info("Writing todo lists to file %s", fn)
153
+ try:
154
+ with open(fn, "w", encoding="utf-8") as f:
155
+ f.write(f"{self.active_list:*^60}\n\n")
156
+ for todo in self.todo_lists[self.active_list]:
157
+ f.write(f'{todo["reminder"]}\n')
158
+ except IOError as e:
159
+ logger.log.exception("Unable to write todo list to %s: %s", fn, e)
160
+
161
+ def server_running(self):
162
+ """Determine if the server is running"""
163
+ if self.server_up and self.net_server is not None:
164
+ return True
165
+ else:
166
+ return False
167
+
168
+ def start_server(self):
169
+ """Create and start the server."""
170
+ if self.server_running():
171
+ return
172
+
173
+ logger.log.info("Starting the server")
174
+ self.net_server = tcp_server_lib.DataBaseServer(
175
+ (settings.options["address"], settings.options["port"]),
176
+ tcp_server_lib.TCPRequestHandler,
177
+ )
178
+
179
+ # start network server thread
180
+ st = threading.Thread(target=self.net_server.serve_forever)
181
+ st.daemon = True
182
+ st.start()
183
+ self.server_up = True
184
+
185
+ logger.log.info(
186
+ "Server up at %s:%d", settings.options["address"], settings.options["port"]
187
+ )
188
+
189
+ def stop_server(self):
190
+ """Stop and destroy the server."""
191
+ if self.net_server is not None and self.server_running():
192
+ logger.log.info("Shutting down the server")
193
+ self.net_server.shutdown()
194
+ self.net_server = None
195
+ self.server_up = False
196
+ logger.log.info("Server shut down")
197
+ else:
198
+ logger.log.exception(
199
+ "network server does not exist, or is not running, exiting"
200
+ )
201
+ sys.exit(1)
202
+
203
+ def restart_server(self):
204
+ """Stop and then restart the server."""
205
+ if self.server_running():
206
+ self.stop_server()
207
+ self.start_server()
208
+ else:
209
+ self.start_server()
210
+
211
+ def sync_pull(self, host):
212
+ """Perform a client pull."""
213
+ return self.net_client.sync_pull(host)
214
+
215
+ def sync_push(self, host):
216
+ """Perform a client push."""
217
+ return self.net_client.sync_push(host)
218
+
219
+ def sort_active_list(self):
220
+ """Sort the active to-do list."""
221
+ logger.log.info("Sorting list %s", self.active_list)
222
+ self.todo_lists[self.active_list].sort(
223
+ key=lambda todo_list: todo_list[settings.options["sort_key"]],
224
+ reverse=settings.options["reverse_sort"],
225
+ )
226
+
227
+ def todo_index(self, reminder):
228
+ """Return the index location of the to-do matching reminder."""
229
+ i = 0
230
+ for todo in self.todo_lists[self.active_list]:
231
+ if todo["reminder"] == reminder:
232
+ return i
233
+ else:
234
+ i += 1
todo/core/__init__.py ADDED
@@ -0,0 +1,35 @@
1
+ """__init__.py
2
+
3
+ todo.core: A decorator to make security checks on the to-do database.
4
+ """
5
+
6
+ import sys
7
+
8
+ from PyQt5 import QtWidgets
9
+
10
+ from todo.core import settings
11
+ from todo.core.Logger import Logger
12
+
13
+
14
+ def error_on_none_db(func):
15
+ """Check if settings.db is valid and run func or error out if it is None."""
16
+
17
+ logger = Logger(func.__name__)
18
+
19
+ def wrapper(*args, **kwargs):
20
+ """Wrap around func and check settings.db"""
21
+
22
+ if settings.db is not None:
23
+ try:
24
+ result = func(*args, **kwargs)
25
+ return result
26
+ except OSError as e:
27
+ logger.log.exception(f"To-Do error: {e}")
28
+ return
29
+ else:
30
+ msg = "Database does not exist, exiting"
31
+ QtWidgets.QMessageBox.critical(None, "Database Error", msg)
32
+ logger.log.exception(msg)
33
+ sys.exit(1)
34
+
35
+ return wrapper
@@ -0,0 +1,89 @@
1
+ import json
2
+ import os
3
+ import sys
4
+
5
+ from todo.core import error_on_none_db, settings
6
+ from todo.core.Logger import Logger
7
+
8
+
9
+ logger = Logger(__name__)
10
+
11
+
12
+ def merge_todo_lists(*todo_lists):
13
+ """Merge to-do lists keeping only unique entries."""
14
+ logger.log.info("Merging to-do lists")
15
+ new_lists = {}
16
+ for list_entry in todo_lists:
17
+ for k in list_entry:
18
+ new_lists[k] = list_entry[k]
19
+
20
+ return new_lists
21
+
22
+
23
+ @error_on_none_db
24
+ def read_json_data(fn=settings.lists_fn):
25
+ """Read in to-do lists from a JSON file."""
26
+ if not os.path.exists(fn):
27
+ msg = f"JSON file {fn} does not exist"
28
+ logger.log.warning(msg)
29
+ return False, msg
30
+
31
+ logger.log.info("Reading JSON file %s", fn)
32
+ try:
33
+ with open(fn, "r", encoding="utf-8") as f:
34
+ # Merge lists
35
+ if len(settings.db.todo_lists) > 0:
36
+ todo_lists = json.load(f)
37
+ new_lists = merge_todo_lists(settings.db.todo_lists, todo_lists)
38
+ settings.db.todo_lists = new_lists
39
+ else:
40
+ settings.db.todo_lists = json.load(f)
41
+ except IOError as e:
42
+ logger.log.exception("Error reading JSON file %s: %s", fn, e)
43
+ return False, e
44
+
45
+ # set active list
46
+ if settings.options is not None and "active_list" in settings.options:
47
+ if settings.options["active_list"]:
48
+ settings.db.active_list = settings.options["active_list"]
49
+ elif len(settings.db.todo_lists) == 0:
50
+ msg = "No JSON data to read"
51
+ logger.log.warning(f"{msg}")
52
+ return False, msg
53
+ else:
54
+ for list_entry in settings.db.todo_lists:
55
+ settings.db.active_list = list_entry
56
+ logger.log.info("%s set as active_list", list_entry)
57
+ else:
58
+ logger.log.exception("settings.options does not exist, exiting")
59
+ sys.exit(1)
60
+
61
+ settings.db.list_count = len(settings.db.todo_lists.keys())
62
+ settings.db.todo_total = 0
63
+ for list_entry in settings.db.todo_lists.values():
64
+ settings.db.todo_total += len(list_entry)
65
+
66
+ msg = f"Successfully read JSON file {fn}"
67
+ logger.log.info(f"{msg}")
68
+ return True, msg
69
+
70
+
71
+ @error_on_none_db
72
+ def write_json_data(fn=settings.lists_fn):
73
+ """Write to-do lists as a JSON file."""
74
+ logger.log.info("Writing JSON file %s", fn)
75
+ try:
76
+ with open(fn, "w+", encoding="utf-8") as f:
77
+ if settings.db.todo_lists is not None:
78
+ json.dump(settings.db.todo_lists, f, indent=2)
79
+ else:
80
+ logger.log.exception("settings.db.todo_lists does not exist, exiting")
81
+ sys.exit(1)
82
+ except IOError as e:
83
+ msg = f"Error writing JSON file {fn}: {e}"
84
+ logger.log.exception(msg)
85
+ return False, msg
86
+
87
+ msg = f"Successfully wrote JSON file {fn}"
88
+ logger.log.info(msg)
89
+ return True, msg
todo/core/settings.py ADDED
@@ -0,0 +1,30 @@
1
+ """settings.py
2
+
3
+ This module creates To-Do core global variables and functions.
4
+ """
5
+
6
+ import sys
7
+ import os
8
+
9
+ __version__ = "0.2.0"
10
+ options = {}
11
+ db = None
12
+
13
+ home_dir = os.getenv("HOME")
14
+ if home_dir is not None:
15
+ todo_dir = os.path.join(home_dir, ".todo")
16
+ else:
17
+ print("Error: unable to write log file, exiting")
18
+ sys.exit(1)
19
+
20
+ if not os.path.exists(todo_dir):
21
+ try:
22
+ os.mkdir(todo_dir)
23
+ except OSError as e:
24
+ print(f"Error creating To-Do configuration directory: {e}")
25
+ sys.exit(1)
26
+
27
+
28
+ # private files
29
+ ini_fn = os.path.join(todo_dir, "todo.ini")
30
+ lists_fn = os.path.join(todo_dir, "todo_lists.json")