aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md13
-rw-r--r--database.py177
-rw-r--r--docs/adding-repositories.md14
-rw-r--r--docs/creating-repositories.md57
-rw-r--r--logs.py31
-rw-r--r--package.py249
-rw-r--r--paths.py9
-rwxr-xr-xqulay.py126
-rw-r--r--uzbekdb/__init__.py3
-rw-r--r--uzbekdb/database.py56
10 files changed, 735 insertions, 0 deletions
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..2ecc4aa
--- /dev/null
+++ b/README.md
@@ -0,0 +1,13 @@
+# Qulay (Qulay Package Manager) 🕌
+
+Qulay is a minimal, fast, and halal package manager for Uzbek Linux.
+
+- no haram dependencies
+- no unnecessary bloat
+- pure control and barakah
+
+## documentation
+
+documentation is available in the [docs](docs/) directory.
+- [adding repositories](docs/adding-repositories.md)
+- [creating repositories](docs/creating-repositories.md)
diff --git a/database.py b/database.py
new file mode 100644
index 0000000..2ebe97a
--- /dev/null
+++ b/database.py
@@ -0,0 +1,177 @@
+import os
+import sys
+import dbm
+import ssl
+import shutil
+import shelve
+import urllib.request
+import tarfile
+
+import compression.zstd as zstd
+from paths import *
+from uzbekdb import database
+import urllib.request
+import urllib.error
+from urllib.parse import urlsplit, urlunsplit, urlparse
+
+
+TIMEOUT = 10
+_NAME = os.path.basename(sys.argv[0])
+
+
+def get_file_repos():
+ if not os.path.exists(REPOS_FILE):
+ print(f"!! can't get repos database.\n"
+ f"|- please init Qulay PM with '{_NAME} i'\n"
+ f"|- and update repos with '{_NAME} u'")
+ return {}
+
+ try:
+ with shelve.open(REPOS_FILE, flag="r") as db:
+ return db["repos"] or {}
+ except Exception:
+ print(f"!! can't get repos database.\n"
+ f"|- please init Qulay PM with '{_NAME} i'\n"
+ f"|- and update repos with '{_NAME} u'")
+ return {}
+
+
+def get_repos_urls():
+ if not os.path.exists(REPOS_URLS_FILE):
+ return []
+ with open(REPOS_URLS_FILE, "r") as f:
+ try:
+ return f.readlines()
+ except:
+ return []
+
+
+def download_repos(urls, dest_dir="/"):
+ valid_urls = [u.strip() for u in urls if u.strip()]
+ total = len(valid_urls)
+
+ for idx, url in enumerate(valid_urls, 1):
+ file_name = os.path.basename(urlparse(url).path)
+ repo_name = file_name.removesuffix(".tar.zst")
+ try:
+ bar_len = 20
+
+ print(f"\r:: {repo_name:<10} [{'#' * 0}{'-' * bar_len}] {idx-1}/{total}", end="", flush=True)
+
+ with urllib.request.urlopen(url, timeout=10) as response:
+ with open(f"/tmp/{file_name}", "wb") as f:
+ shutil.copyfileobj(response, f)
+
+ print(f"\r:: {repo_name:<10} [{'#' * 10}{'-' * 10}] {idx-1}/{total}", end="", flush=True)
+
+ repos_dir = os.path.join(dest_dir, REPOS_DIR.lstrip(os.sep))
+
+ with zstd.open(f"/tmp/{file_name}", mode='rb') as zf:
+ with tarfile.open(fileobj=zf, mode='r|') as tar:
+ tar.extractall(path=repos_dir)
+ except Exception as e:
+ print(f"\r:: {repo_name:<10} fail [{'x' * 20}] {idx}/{total}")
+ else:
+ print(f"\r:: {repo_name:<10} done [{'#' * 20}] {idx}/{total}")
+ finally:
+ if os.path.exists(f"/tmp/{file_name}"):
+ os.remove(f"/tmp/{file_name}")
+
+
+def _read_installed(dest_dir="/"):
+ installed_full_path = os.path.join(dest_dir, INSTALLED_FILE.lstrip(os.sep))
+ if not os.path.exists(installed_full_path):
+ return {}
+
+ with open(installed_full_path, "r", encoding="utf-8") as f:
+ content = f.read().strip()
+ data = database.loads(content)
+ if isinstance(data, list) and len(data) == 2:
+ data = {data[0]: [data[1]]}
+ return data if data else {}
+
+
+def _write_installed(data: dict, dest_dir="/"):
+ installed_full_path = os.path.join(dest_dir, INSTALLED_FILE.lstrip(os.sep))
+ with open(installed_full_path, "w", encoding="utf-8") as f:
+ f.write(database.dumps(data))
+
+
+def is_installed(name, dest_dir="/"):
+ return name in _read_installed(dest_dir)
+
+
+def install_package(name, version, dest_dir="/"):
+ data = _read_installed(dest_dir)
+
+ action = "added"
+
+ if isinstance(data, dict):
+ if name in data:
+ if data[name][0] == version:
+ action = "already installed"
+ else:
+ action = f"upgraded from {data[name][0]} to {version}"
+ data[name][0] = version
+ else:
+ data[name] = [version]
+ else:
+ data = {name: [version]}
+
+ _write_installed(data, dest_dir)
+ return action
+
+
+def remove_package(name, dest_dir="/"):
+ data = _read_installed(dest_dir)
+
+ if not isinstance(data, dict):
+ raise ValueError("!! installed database is corrupted")
+
+ if name not in data:
+ raise ValueError(f"!! cant remove '{name}': package not installed")
+
+ del data[name]
+ _write_installed(data, dest_dir)
+
+
+def get_version_package(name, dest_dir="/"):
+ return _read_installed(dest_dir).get(name)
+
+
+def ensure_database(verbose, ask, dest_dir="/"):
+ to_create = [INSTALLED_FILE, REPOS_URLS_FILE, LOG_FILE, CACHE_DIR, TMP_DIR, REPOS_DIR]
+
+ if ask:
+ for path in to_create:
+ if path.startswith("/"):
+ path = path.lstrip("/")
+ path = os.path.join(dest_dir, path.lstrip(os.sep))
+ print(f" * {path}")
+ answer = input("-> Do you want to create the following directories and files? [Y/n]: ").strip().lower()
+ if not answer in ("", "y", "yes"):
+ sys.exit(0)
+
+
+ for name in to_create:
+ if name.startswith("/"):
+ name = name.lstrip("/")
+ name_full_path = os.path.join(dest_dir, name)
+ try:
+ if name.endswith("/"):
+ os.makedirs(name_full_path, exist_ok=True)
+ if verbose:
+ print(f"[+] dir {name_full_path}")
+ continue
+
+ os.makedirs(os.path.dirname(name_full_path), exist_ok=True)
+
+ if not os.path.exists(name_full_path):
+ open(name_full_path, "w").close()
+
+ if verbose:
+ print(f"[+] file {name_full_path}")
+
+ except Exception as e:
+ if verbose:
+ print(f"[f] failed to create {name_full_path}: {e}")
diff --git a/docs/adding-repositories.md b/docs/adding-repositories.md
new file mode 100644
index 0000000..3274507
--- /dev/null
+++ b/docs/adding-repositories.md
@@ -0,0 +1,14 @@
+# adding repositories
+
+the file with the list of repository links is located in `/etc/qulay/repositories.uz` (it's not UzbekDB file). to add a repository, you need to open this file and add a line with a link, for example:
+```
+http://127.0.0.1:8000/releases/core.tar.zst
+https://codeberg.org/UzbekLinux/qulay-pkgs/releases/download/latest/halal.tar.zst
+```
+you must provide a link specifically to the release Zstd archive.
+
+update in qulay after adding repositories urls:
+```
+qulay u
+```
+qulay will download tar.zst file and extract it to `/var/lib/qulay/repos/` folder.
diff --git a/docs/creating-repositories.md b/docs/creating-repositories.md
new file mode 100644
index 0000000..f7bbd2f
--- /dev/null
+++ b/docs/creating-repositories.md
@@ -0,0 +1,57 @@
+# creating repositories
+
+your repository should be packed in Zstd archive and your repository should have this structure:
+```
+repo-name.tar.zst/
+| manifest.uz
+| pkgs.uz
+| packages/
+|| example/
+||| install.sh
+||| remove.sh
+||| depends.uz
+|| halal/
+||| ...
+|| ...
+```
+
+*manifest.uz*:
+```
+repo-name | Repository Description | maintainer | not required...
+```
+
+*pkgs.uz*:
+```
+example | Example package for Da | 1.0.0
+halal | most halal package in the world | 78fd004609
+```
+*packages/example/*:
+```
+example/
+| install.sh
+| remove.sh
+| depends.uz
+```
+*packages/example/install.sh*:
+```bash
+#!/bin/sh
+set -e
+
+curl -# -O http://example.org/example/bin/example.sh
+mkdir -p $DESTDIR/usr/bin
+cp -v example.sh $DESTDIR/usr/bin/example
+chmod +x $DESTDIR/usr/bin/example
+```
+*packages/example/remove.sh*:
+```bash
+#!/bin/sh
+set -e
+
+rm -fv $DESTDIR/usr/bin/example
+```
+*packages/example/depends.uz*:
+```
+python-uzbekdb
+example-lib
+procps-ng
+```
diff --git a/logs.py b/logs.py
new file mode 100644
index 0000000..7947cf2
--- /dev/null
+++ b/logs.py
@@ -0,0 +1,31 @@
+# logs.py -- logging "lib" for qulay cli
+
+import time
+from paths import LOG_FILE
+
+def _write_log(level, text, output=False):
+ timestamp = time.strftime("%Y-%m-%d %H:%M:%S")
+ line = f"{timestamp} {level[0]} {text}\n"
+ if output:
+ print(text)
+ with open(LOG_FILE, "a") as f:
+ f.write(line)
+
+def info(text, output=False, write=True):
+ if write:
+ _write_log("info", text, output)
+ else:
+ if output:
+ print(text)
+def warn(text, output=False, write=True):
+ if write:
+ _write_log("warn", text, output)
+ else:
+ if output:
+ print(text)
+def error(text, output=False, write=True):
+ if write:
+ _write_log("error", text, output)
+ else:
+ if output:
+ print(text)
diff --git a/package.py b/package.py
new file mode 100644
index 0000000..2667c7d
--- /dev/null
+++ b/package.py
@@ -0,0 +1,249 @@
+import os
+import sys
+import tarfile
+import shutil
+import tempfile
+import subprocess
+import urllib.request
+from urllib.parse import urlsplit, urlunsplit
+
+from logs import info, warn, error
+from paths import TMP_DIR, CACHE_DIR, REPOS_DIR
+from pathlib import Path
+from uzbekdb import database
+from database import (
+ download_repos,
+ get_repos_urls,
+ install_package,
+ remove_package,
+ is_installed,
+)
+
+
+_NAME = os.path.basename(sys.argv[0])
+
+
+def _random_tmp_dir(tmp_dir=TMP_DIR):
+ return tempfile.mkdtemp(prefix="qulay_", dir=tmp_dir)
+
+
+def _download(url, dest):
+ try:
+ urllib.request.urlretrieve(url, dest)
+ except Exception as e:
+ return e
+ else:
+ return 1
+ return 0
+
+
+def _extract(archive, dest):
+ with tarfile.open(archive) as tar:
+ tar.extractall(dest)
+
+
+def _read_depends(path):
+ depends_file = os.path.join(path, "depends.uz")
+ if not os.path.exists(depends_file):
+ return []
+ with open(depends_file) as f:
+ return [line.strip() for line in f if line.strip()]
+
+
+def _check_pkg_structure(path):
+ required = ["install.sh", "remove.sh"]
+ for file in required:
+ if not os.path.exists(os.path.join(path, file)):
+ error("!! package is corrupted", True)
+
+
+def _get_packages(dest_dir="/"):
+ found = []
+ packages_path = os.path.join(dest_dir, REPOS_DIR.lstrip(os.sep))
+ for repo_name in os.listdir(packages_path):
+ repo_path = os.path.join(packages_path, repo_name)
+ if os.path.isdir(repo_path):
+ pkgs_file = os.path.join(repo_path, "pkgs.uz")
+ if os.path.exists(pkgs_file):
+ try:
+ with open(pkgs_file, "r", encoding="utf-8") as f:
+ pkgs_data = database.loads(f.read())
+ for name, data in pkgs_data.items():
+ found.append({
+ "name": name,
+ "repo": repo_name,
+ "data": data
+ })
+ except Exception:
+ continue
+ return found
+
+
+def _find_package(name, dest_dir="/"):
+ found = []
+ packages_path = os.path.join(dest_dir, REPOS_DIR.lstrip(os.sep))
+
+ if not os.path.exists(packages_path):
+ return "-n"
+
+ for repo_name in os.listdir(packages_path):
+ repo_path = os.path.join(packages_path, repo_name)
+ if os.path.isdir(repo_path):
+ pkgs_file = os.path.join(repo_path, "pkgs.uz")
+ if os.path.exists(pkgs_file):
+ try:
+ with open(pkgs_file, "r", encoding="utf-8") as f:
+ pkgs_data = database.loads(f.read())
+
+ if isinstance(pkgs_data, dict) and name in pkgs_data:
+ found.append({
+ "name": name,
+ "repo": repo_name,
+ "data": pkgs_data[name]
+ })
+ except Exception:
+ continue
+
+ if not found:
+ return None
+ # if len(found) > 1:
+ # return "-r"
+
+ return found
+
+
+def _find_repo_package(result, repo, name):
+ for pkg in result:
+ if pkg["repo"] == repo:
+ return pkg
+ return None
+
+
+def install(name, args=[], dest_dir="/"):
+ # verbose=False, ask=False, reinstall=False, disable_logs=False, disable_deps=False
+ verbose = ":v" in args
+ ask = ":a" in args
+ reinstall = ":r" in args
+ disable_logs = ":nl" in args
+ disable_deps = ":nd" in args
+
+ if "/" in name:
+ parts = name.split("/", 1)
+ repo_name = parts[0]
+ name = parts[1]
+
+ pkg = _find_repo_package(_find_package(name), repo_name, name)
+
+ if not pkg:
+ error(f"!! package {name} not found in {repo_name}", True, disable_logs)
+ return 1
+
+ pkg_data = pkg["data"]
+ version = pkg_data[1]
+ else:
+ result = _find_package(name)
+ if not result:
+ error(f"!! package {name} not found", True, disable_logs)
+ return 1
+
+ if len(result) > 1:
+ error(f"!! {len(result)} same pkgs found in different repos:", True, disable_logs)
+ for pkg in result:
+ print(f" {pkg['repo']}/{pkg['name']} - {pkg['data'][0]} - {pkg['data'][1]}")
+ return 1
+
+ repo_name = result[0]["repo"]
+ pkg_data = result[0]["data"]
+
+ version = pkg_data[1]
+
+ pkg_path = os.path.join(dest_dir, REPOS_DIR.lstrip(os.sep), f"{repo_name}/packages/{name}")
+
+ if ask:
+ download_answer = input(
+ f"-> confirm installing '{name}' with version {version} [Y/n]: "
+ ).strip().lower()
+
+ if download_answer not in ("y", "yes", ""):
+ sys.exit(0)
+
+ # -- install depends --
+ depends = _read_depends(pkg_path)
+ if not disable_deps:
+ for dep in depends:
+ if not is_installed(dep) or reinstall:
+ # -- copy args to dep install --
+ install(dep, args, dest_dir=dest_dir)
+
+ # -- run install.sh --
+ try:
+ subprocess.run(
+ ["sh", "install.sh"],
+ cwd=pkg_path,
+ check=True,
+ env=os.environ
+ )
+ except Exception as e:
+ error(f"!! {e}", True, disable_logs)
+ return 0
+
+ if not disable_logs:
+ install_package(f"{repo_name}/{name}", version, dest_dir=dest_dir)
+
+ info(f":: {name} installed successfully", True, disable_logs)
+
+
+def remove(name, args=[], dest_dir="/"):
+ verbose = ":v" in args
+ ask = ":a" in args
+ disable_logs = ":nl" in args
+ # disable_deps = "/nd" in args
+
+ if "/" in name:
+ parts = name.split("/", 1)
+ repo_name = parts[0]
+ name = parts[1]
+
+ pkg = _find_repo_package(_find_package(name), repo_name, name)
+
+ if not pkg:
+ error(f"!! package {name} not found in {repo_name}", True, disable_logs)
+ return 1
+
+ pkg_data = pkg["data"]
+ version = pkg_data[1]
+ else:
+ result = _find_package(name)
+ if not result:
+ error(f"!! package {name} not found", True, disable_logs)
+ return 1
+
+ if len(result) > 1:
+ error(f"!! {len(result)} same pkgs found in different repos:", True, disable_logs)
+ for pkg in result:
+ print(f" {pkg['repo']}/{pkg['name']} - {pkg['data'][0]} - {pkg['data'][1]}")
+ return 1
+
+ repo_name = result[0]["repo"]
+ pkg_data = result[0]["data"]
+
+ version = pkg_data[1]
+
+ # -- super uzbek package remover da --
+ pkg_path = os.path.join(dest_dir, REPOS_DIR.lstrip(os.sep), f"{repo_name}/packages/{name}")
+
+ try:
+ subprocess.run(
+ ["sh", "remove.sh"],
+ cwd=pkg_path,
+ check=True,
+ env=os.environ
+ )
+ except Exception as e:
+ error(f"!! {e}", True, disable_logs)
+ return 0
+
+ if not disable_logs:
+ remove_package(f"{repo_name}/{name}", dest_dir=dest_dir)
+
+ info(f":: {name} removed successfully", True, disable_logs)
diff --git a/paths.py b/paths.py
new file mode 100644
index 0000000..8d231ef
--- /dev/null
+++ b/paths.py
@@ -0,0 +1,9 @@
+# paths to files, dirs for Qulay PM
+
+REPOS_URLS_FILE = "/etc/qulay/repositories.uz"
+INSTALLED_FILE = "/var/lib/qulay/installed.uz"
+REPOS_FILE = "/var/lib/qulay/shelve_repos.db"
+REPOS_DIR = "/var/lib/qulay/repos/"
+LOG_FILE = "/var/log/qulay/qulay.logs"
+CACHE_DIR = "/var/cache/qulay/"
+TMP_DIR = "/tmp/qulay/"
diff --git a/qulay.py b/qulay.py
new file mode 100755
index 0000000..86ce457
--- /dev/null
+++ b/qulay.py
@@ -0,0 +1,126 @@
+#!/usr/bin/env python3
+
+import package
+import database
+import signal
+import sys
+import os
+
+from pathlib import Path
+from logs import info, warn, error
+
+
+_NAME = os.path.basename(sys.argv[0])
+_VERSION = "0.4"
+_HELP_TEXT = f"""{_NAME}: command-line interface to the Qulay Package Manager
+Usage:
+ {_NAME} h -> help menu
+ {_NAME} + pkg -> download and install package
+ {_NAME} - pkg -> remove package
+ {_NAME} g pkg -> get package version
+ {_NAME} s pkg -> search packages in repos
+ {_NAME} u -> update all repositories
+ {_NAME} i -> init Qulay PM
+ {_NAME} r -> get all repositories URLs
+ {_NAME} q -> get all installed packages
+ {_NAME} v -> get {_NAME} version
+Options:
+ :v -> verbose output
+ :a -> ask before doing
+ :nl -> disable recording in databases and logs
+ :nd -> do not download deps
+ :r -> reinstall already installed deps
+Vars:
+ DESTDIR -> path where the package will be installed/removed"""
+
+all_commands_list = ["h", "+", "-", "g", "u", "w", "i", "r", "q", "v", "s"]
+allowed_args = [":v", ":a", ":nl", ":nd", ":r"]
+def handle_ctrl_c(signum, frame):
+ warn("\nw! canceled by the user", True, ":nl" in sys.argv)
+ sys.exit(130)
+
+signal.signal(signal.SIGINT, handle_ctrl_c)
+
+def parse_args(args, mode):
+ return_args = []
+ for arg in args:
+ if mode == "c":
+ if not arg.startswith(":"):
+ return_args += [arg]
+ elif mode == "a":
+ if arg.startswith(":"):
+ return_args += [arg]
+ elif mode == "o":
+ in_cmds = arg in all_commands_list
+ is_name = arg == sys.argv[0]
+ if not arg.startswith(":") and not in_cmds and not is_name:
+ return_args += [arg]
+ return return_args
+
+if __name__ == "__main__":
+ args = parse_args(sys.argv, "a")
+ cmds = parse_args(sys.argv, "c")
+ oths = parse_args(sys.argv, "o")
+ dest_dir = Path(os.environ.get("DESTDIR") or "/").resolve()
+ root_cmds = ["+", "-", "u", "i", "w"]
+
+ if len(cmds) < 2:
+ print(_HELP_TEXT)
+ sys.exit(1)
+
+ if cmds[1] in root_cmds:
+ if os.geteuid() != 0:
+ error("!! not enough rights. run this with root privileges.", True, ":nl" in args)
+ sys.exit(1)
+
+ if cmds[1] == "+":
+ for pkg in oths:
+ package.install(pkg, args=args, dest_dir=dest_dir)
+ elif cmds[1] == "-":
+ for pkg in oths:
+ package.remove(pkg, args=args, dest_dir=dest_dir)
+ elif cmds[1] == "u":
+ database.download_repos(database.get_repos_urls())
+ elif cmds[1] == "g":
+ if len(cmds) > 2:
+ print(database.get_version_package(cmds[2])[0])
+ else:
+ error("!! package is not specified", True, ":nl" in args)
+ elif cmds[1] == "i":
+ database.ensure_database(verbose=":v" in args, ask=":a" in args, dest_dir=dest_dir)
+ elif cmds[1] == "r":
+ for url in database.get_repos_urls():
+ print(url.strip())
+ elif cmds[1] == "q":
+ for name, data in database._read_installed(dest_dir=dest_dir).items():
+ print(f"{name} - {data[0]}")
+ elif cmds[1] == "s":
+ if len(cmds) > 2:
+ for name in oths:
+ if "/" in name:
+ parts = name.split("/", 1)
+ if parts[0] == "" or parts[1] == "":
+ continue
+ repo_name, name = parts[0], parts[1]
+ pkgs = package._find_package(name, dest_dir=dest_dir)
+ pkgs = [package._find_repo_package(pkgs, repo_name, name)]
+ else:
+ pkgs = package._find_package(name, dest_dir=dest_dir)
+ if not pkgs:
+ continue
+ for pkg in pkgs:
+ if database.is_installed(f"{pkg['repo']}/{pkg['name']}", dest_dir=dest_dir):
+ print(f"[+] {pkg['repo']}/{pkg['name']} - {pkg['data'][0]} - {pkg['data'][1]}")
+ else:
+ print(f" {pkg['repo']}/{pkg['name']} - {pkg['data'][0]} - {pkg['data'][1]}")
+ else:
+ pkgs = package._get_packages(dest_dir=dest_dir)
+ for pkg in pkgs:
+ if database.is_installed(f"{pkg['repo']}/{pkg['name']}", dest_dir=dest_dir):
+ print(f"[+] {pkg['repo']}/{pkg['name']} - {pkg['data'][0]} - {pkg['data'][1]}")
+ else:
+ print(f" {pkg['repo']}/{pkg['name']} - {pkg['data'][0]} - {pkg['data'][1]}")
+ elif cmds[1] == "v":
+ print(f"{_NAME}: {_VERSION}")
+ else:
+ print(_HELP_TEXT)
diff --git a/uzbekdb/__init__.py b/uzbekdb/__init__.py
new file mode 100644
index 0000000..001a104
--- /dev/null
+++ b/uzbekdb/__init__.py
@@ -0,0 +1,3 @@
+"""
+ UzbekDB Python library - made by msh356.
+"""
diff --git a/uzbekdb/database.py b/uzbekdb/database.py
new file mode 100644
index 0000000..f27d107
--- /dev/null
+++ b/uzbekdb/database.py
@@ -0,0 +1,56 @@
+"""
+ UzbekDB database
+"""
+
+def _is_correct(dbstr: str):
+ try:
+ db = [x for x in dbstr.split("\n") if x]
+ except Exception:
+ return False
+ if len(db) == 0:
+ return False
+ else:
+ return True
+
+def _is_multiline(dbstr: str):
+ if _is_correct(dbstr):
+ db = [x for x in dbstr.split("\n") if x]
+ if len(db) == 1:
+ return False
+ else:
+ return True
+
+def _return_correct_type(s):
+ for t in (int, float, complex):
+ try:
+ return t(s)
+ except ValueError:
+ continue
+ return s
+
+def loads(dbstr: str):
+ if _is_correct(dbstr):
+ db = [x for x in dbstr.split("\n") if x]
+ multiline = _is_multiline(dbstr)
+ if multiline:
+ dbobj = {}
+ for i in db:
+ dbel = i.split(" | ")
+ dbelc = []
+ for i in dbel:
+ dbelc.append(_return_correct_type(i))
+ dbobj[dbelc[0]] = dbelc[1:]
+ else:
+ dbobj = []
+ for i in db[0].split(" | "):
+ dbobj.append(_return_correct_type(i))
+ return dbobj
+
+def dumps(dbobj):
+ dbstr = ""
+ if isinstance(dbobj, dict):
+ for k,v in dbobj.items():
+ dbstr = dbstr + f"{k} | {' | '.join(map(str, v))}\n"
+ elif isinstance(dbobj, list):
+ dbstr = " | ".join(map(str, dbobj))
+ return dbstr