feat(rewrite): Config preparations
This commit is contained in:
+2
-2
@@ -4,6 +4,6 @@
|
||||
# TODO: Re-export the config stuff
|
||||
|
||||
if __name__ == "__main__":
|
||||
import app as _app
|
||||
from app import run as _run
|
||||
|
||||
_app.run()
|
||||
_run()
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
# Declare a file explicitly if it has different permissions
|
||||
@@ -0,0 +1,2 @@
|
||||
def clone_git_repo(repo_url: str, clone_path: str, branch: str = "DEFAULT"):
|
||||
pass
|
||||
@@ -0,0 +1,6 @@
|
||||
def add_packages(pkgs: list[str]):
|
||||
pass
|
||||
|
||||
|
||||
def add_package_bundles(pkgs: list[str]):
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
def enable_reflector():
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
def enable_repo(name: str):
|
||||
pass
|
||||
@@ -0,0 +1,36 @@
|
||||
def add_template_data(name: str, data: str):
|
||||
"""Replace all occurrences of variable specified by `name` by
|
||||
the specified data
|
||||
|
||||
Args:
|
||||
name: The variable to replace
|
||||
data: The data to replace it with
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def add_template_array(name: str, template: str, data: list[str]):
|
||||
"""Replace all occurrences of variable specified by `name` in the files with
|
||||
the specified template, where {{ data }} is replaced with the each element
|
||||
of the data argument
|
||||
|
||||
Args:
|
||||
name: The name of the variable to replace
|
||||
template:
|
||||
Template syntax, with {{ data }} as variable, see example below or docs.
|
||||
Recursive replacement is supported up to one layer deep
|
||||
data: A list of data that is substituted into the {{ data }} variable in the template
|
||||
|
||||
Example:
|
||||
```python
|
||||
add_template_array("test", "- Hello World {{ data }}\\n", [0, 1])
|
||||
```
|
||||
for example replaces {{ test }} in a file with
|
||||
```
|
||||
- Hello World 0
|
||||
|
||||
- Hello World 1
|
||||
```
|
||||
"""
|
||||
# TODO: Recursive replacement
|
||||
pass
|
||||
@@ -0,0 +1,14 @@
|
||||
from typing import Literal
|
||||
|
||||
|
||||
def add_custom_theme():
|
||||
pass
|
||||
|
||||
|
||||
def custom_theme_color_source(src: Literal["wallpaper"] | Literal["default"]):
|
||||
pass
|
||||
|
||||
|
||||
# TODO: For desktop environments, what command??
|
||||
def set_wallpaper(path: str):
|
||||
pass
|
||||
@@ -0,0 +1,2 @@
|
||||
def set_gtk_theme():
|
||||
pass
|
||||
@@ -0,0 +1,34 @@
|
||||
from typing import List
|
||||
|
||||
|
||||
def pkg_diff(target: List[str], actual: List[str]) -> tuple[List[str], List[str]]:
|
||||
"""Compute a diff between target packages and installed packages
|
||||
|
||||
Args:
|
||||
target: The target packages
|
||||
actual: The actually installed packages
|
||||
|
||||
Returns:
|
||||
A tuple with first the missing (not installed) packages
|
||||
and second the extraneous (to be uninstalled) packages
|
||||
"""
|
||||
removed = []
|
||||
diffed_out = []
|
||||
for pkg in actual:
|
||||
try:
|
||||
diffed_out.append(target.index(pkg))
|
||||
except Exception:
|
||||
removed.append(pkg)
|
||||
|
||||
diffed_out.reverse()
|
||||
new_pkgs = []
|
||||
if len(diffed_out) > 0:
|
||||
curr = diffed_out.pop()
|
||||
for i, pkg in enumerate(target):
|
||||
if i != curr:
|
||||
new_pkgs.append(pkg)
|
||||
else:
|
||||
if len(diffed_out) > 0:
|
||||
curr = diffed_out.pop()
|
||||
|
||||
return (new_pkgs, removed)
|
||||
@@ -0,0 +1,105 @@
|
||||
import os
|
||||
import subprocess
|
||||
|
||||
|
||||
def init(dirname: str) -> bool:
|
||||
"""Initialize a new git repo in the directory
|
||||
|
||||
Args:
|
||||
dirname: The directory to create it in
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
return subprocess.call("git init", cwd=dirname) == 0
|
||||
|
||||
|
||||
def commit(msg: str):
|
||||
"""Create a new commit
|
||||
|
||||
Args:
|
||||
msg: The commit message
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call('git commit -am "' + msg + '"', cwd=dir) == 0
|
||||
|
||||
|
||||
def branch_add(name: str):
|
||||
"""Create a new git branch
|
||||
|
||||
Args:
|
||||
name: The name of the branch to create
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call("git branch " + name, cwd=dir) == 0
|
||||
|
||||
|
||||
def branch_switch(name: str):
|
||||
"""Switch to a git branch
|
||||
|
||||
Args:
|
||||
name: The name of the branch to switch to
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call("git checkout " + name, cwd=dir) == 0
|
||||
|
||||
|
||||
def branch_show():
|
||||
"""Create a new git branch
|
||||
|
||||
Args:
|
||||
name: The name of the branch to create
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call("git branch", cwd=dir) == 0
|
||||
|
||||
|
||||
def branch_delete(name: str):
|
||||
"""Delete a git branch
|
||||
|
||||
Args:
|
||||
name: The name of the branch to delete
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call("git branch -d " + name, cwd=dir) == 0
|
||||
|
||||
|
||||
def pull(rebase: bool = False):
|
||||
"""Pull git changes
|
||||
|
||||
Args:
|
||||
rebase: Whether to rebase or not
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call("git pull" + (" --rebase" if rebase else ""), cwd=dir) == 0
|
||||
|
||||
|
||||
def push(force: bool = False):
|
||||
"""Push git changes
|
||||
|
||||
Args:
|
||||
force: Whether to force push
|
||||
|
||||
Returns:
|
||||
True on success, False on error
|
||||
"""
|
||||
dir = os.getcwd()
|
||||
return subprocess.call("git push" + (" --force" if force else ""), cwd=dir) == 0
|
||||
@@ -0,0 +1,44 @@
|
||||
from typing import Optional
|
||||
|
||||
from password_mgr import PasswordManager
|
||||
|
||||
|
||||
def choice(default: str, options: str, msg: str) -> str:
|
||||
default = default.lower()
|
||||
formatted_options = "".join(
|
||||
[(opt + "/" if opt != default else default.upper() + "/") for opt in options]
|
||||
)[:-1]
|
||||
choice = input(msg + " [" + formatted_options + "] ")
|
||||
if choice == "":
|
||||
return default
|
||||
else:
|
||||
return choice.lower()
|
||||
|
||||
|
||||
def confirm(is_true_default: bool, msg: Optional[str] = ""):
|
||||
return (
|
||||
choice(
|
||||
"y" if is_true_default else "n",
|
||||
"yn",
|
||||
(msg if msg != "" and msg != None else "Do you really want to continue?"),
|
||||
)
|
||||
== "y"
|
||||
)
|
||||
|
||||
|
||||
def confirm_overwrite(msg: Optional[str] = ""):
|
||||
return confirm(
|
||||
False, (msg if msg != "" else "Do you really want to overwrite your changes?")
|
||||
)
|
||||
|
||||
|
||||
# Use class to store the password
|
||||
pw_manager = PasswordManager()
|
||||
|
||||
|
||||
def password():
|
||||
return pw_manager.get()
|
||||
|
||||
|
||||
def unlock_sudo():
|
||||
return pw_manager.validate()
|
||||
@@ -0,0 +1,70 @@
|
||||
import getpass
|
||||
import time
|
||||
import colorama
|
||||
import subprocess as sp
|
||||
|
||||
|
||||
class PasswordManager:
|
||||
_pw = ""
|
||||
_wrong_cnt = 0
|
||||
_valid = False
|
||||
|
||||
def get(self, msg: str = ""):
|
||||
"""Get the user's password (uses cached password if PW is valid)
|
||||
Otherwise prompts user
|
||||
|
||||
Args:
|
||||
msg: The message to use for the password prompt
|
||||
|
||||
Returns:
|
||||
The user's password
|
||||
"""
|
||||
if self._valid or self.validate():
|
||||
return self._pw
|
||||
|
||||
if msg != "":
|
||||
self._pw = getpass.getpass(msg)
|
||||
else:
|
||||
self._pw = getpass.getpass()
|
||||
if self._pw != "":
|
||||
if not self.validate():
|
||||
print(
|
||||
colorama.Fore.RED + "Error:",
|
||||
colorama.Style.RESET_ALL + "Invalid Password. Please try again",
|
||||
)
|
||||
return self.get(msg)
|
||||
return self._pw
|
||||
else:
|
||||
time.sleep(1)
|
||||
print(
|
||||
colorama.Fore.RED + "Error:",
|
||||
colorama.Style.RESET_ALL + "Password cannot be empty",
|
||||
)
|
||||
return self.get(msg)
|
||||
|
||||
def validate(self) -> bool:
|
||||
"""Validate that the password is correct by running sudo command
|
||||
|
||||
Returns:
|
||||
True if password is valid, False otherwise
|
||||
"""
|
||||
|
||||
def helper():
|
||||
if self._pw == "":
|
||||
return False
|
||||
try:
|
||||
sp.run(
|
||||
["sudo", "-k"], capture_output=True, text=True
|
||||
).check_returncode()
|
||||
sp.run(
|
||||
["sudo", "-S", "echo"],
|
||||
capture_output=True,
|
||||
input=self._pw,
|
||||
text=True,
|
||||
).check_returncode()
|
||||
except Exception:
|
||||
return False
|
||||
return True
|
||||
|
||||
self._valid = helper()
|
||||
return self._valid
|
||||
@@ -0,0 +1,64 @@
|
||||
import subprocess as sp
|
||||
from typing import List
|
||||
|
||||
from util.input import password
|
||||
|
||||
pkg_manager = ["yay", "--noconfirm"]
|
||||
|
||||
|
||||
def list_explicitly_installed() -> List[str]:
|
||||
"""List all packages explicitly installed
|
||||
|
||||
Returns:
|
||||
The package list
|
||||
"""
|
||||
return run_pkg_manager_cmd(["-Qeq"]).stdout.split()
|
||||
|
||||
|
||||
def remove_orphans() -> bool:
|
||||
"""Removes all orphan packages
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
return (
|
||||
sp.run(
|
||||
["pacman", "-Qdtq", "|", "sudo", "pacman", "-Rns", "--noconfirm", "-"],
|
||||
capture_output=True,
|
||||
).returncode
|
||||
== 0
|
||||
)
|
||||
|
||||
|
||||
def uninstall_package_list(pkgs: List[str]) -> bool:
|
||||
"""Uninstall all packages in the list
|
||||
|
||||
Args:
|
||||
pkgs: A list of packages to uninstall
|
||||
|
||||
Returns:
|
||||
True if successful, False otherwise
|
||||
"""
|
||||
# TODO: Add guard to protect against uninstalling archmgr and confirm uninstalling crucial pkgs
|
||||
# pkgs.index("archmgr")
|
||||
# TODO: Consider if we want to print out stdout and stderr on err
|
||||
return run_pkg_manager_cmd(["-Rs", *pkgs], True).returncode == 0
|
||||
|
||||
|
||||
def install_package_list(pkgs: List[str]) -> bool:
|
||||
"""Install all packages in the list
|
||||
|
||||
Args:
|
||||
pkgs: A list of packages to install
|
||||
"""
|
||||
return run_pkg_manager_cmd(["-S", *pkgs], True).returncode == 0
|
||||
|
||||
|
||||
def run_pkg_manager_cmd(
|
||||
args: List[str], use_sudo: bool = False
|
||||
) -> sp.CompletedProcess[str]:
|
||||
if use_sudo:
|
||||
pw = password()
|
||||
return sp.run([*pkg_manager, *args], capture_output=True, text=True, input=pw)
|
||||
else:
|
||||
return sp.run([*pkg_manager, *args], capture_output=True, text=True)
|
||||
@@ -0,0 +1,2 @@
|
||||
import list
|
||||
import diff
|
||||
@@ -0,0 +1,64 @@
|
||||
from typing import List
|
||||
import colorama as cl
|
||||
|
||||
from list import print_list
|
||||
|
||||
|
||||
def print_diff(add: List[str], remove: List[str]):
|
||||
if len(add) == 0 and len(remove) == 0:
|
||||
print(
|
||||
cl.Fore.BLUE
|
||||
+ "-->"
|
||||
+ cl.Style.DIM
|
||||
+ cl.Fore.GREEN
|
||||
+ " No packages changes"
|
||||
+ cl.Style.RESET_ALL,
|
||||
)
|
||||
# Packages to be installed
|
||||
if len(add) == 0:
|
||||
print(
|
||||
cl.Fore.BLUE
|
||||
+ "-->"
|
||||
+ cl.Style.DIM
|
||||
+ cl.Fore.GREEN
|
||||
+ " No packages to be installed"
|
||||
+ cl.Style.RESET_ALL,
|
||||
)
|
||||
else:
|
||||
print(
|
||||
cl.Fore.GREEN + "==>",
|
||||
cl.Style.RESET_ALL + "Packages that will be",
|
||||
cl.Fore.GREEN + "installed" + cl.Style.RESET_ALL,
|
||||
)
|
||||
print_list(add)
|
||||
print()
|
||||
|
||||
# Packages to be removed
|
||||
if len(remove) == 0:
|
||||
print(
|
||||
cl.Fore.BLUE
|
||||
+ "-->"
|
||||
+ cl.Style.DIM
|
||||
+ cl.Fore.GREEN
|
||||
+ " No packages to be uninstalled"
|
||||
+ cl.Style.RESET_ALL,
|
||||
)
|
||||
else:
|
||||
print(
|
||||
cl.Fore.GREEN + "==>",
|
||||
cl.Style.RESET_ALL + "Packages that will be",
|
||||
cl.Fore.RED + "uninstalled" + cl.Style.RESET_ALL,
|
||||
)
|
||||
print_list(remove)
|
||||
print()
|
||||
|
||||
# Ask user to confirm
|
||||
print(
|
||||
cl.Fore.GREEN + "==>",
|
||||
cl.Style.RESET_ALL
|
||||
+ f"Transaction (packages): {cl.Fore.BLUE + str(len(add)) + cl.Style.RESET_ALL}",
|
||||
cl.Fore.GREEN + "installed",
|
||||
cl.Style.RESET_ALL
|
||||
+ f"and {cl.Fore.BLUE + str(len(remove)) + cl.Style.RESET_ALL}",
|
||||
cl.Fore.RED + "uninstalled" + cl.Style.RESET_ALL,
|
||||
)
|
||||
@@ -0,0 +1,20 @@
|
||||
from typing import Any, List
|
||||
import colorama as cl
|
||||
|
||||
|
||||
def count_digits(number: int):
|
||||
return len(str(number))
|
||||
|
||||
|
||||
def print_list(list: List[Any]):
|
||||
digit_count = count_digits(len(list))
|
||||
for i, pkg in enumerate(list):
|
||||
print(
|
||||
" "
|
||||
+ cl.Fore.BLUE
|
||||
+ cl.Style.DIM
|
||||
+ (" " * (digit_count - count_digits(i + 1)))
|
||||
+ str(1 + i)
|
||||
+ cl.Style.RESET_ALL + " ",
|
||||
pkg,
|
||||
)
|
||||
Reference in New Issue
Block a user