Source code for borgini.funcs

"""
borgini.funcs
=============

Package entry point

Parse commandline arguments

Invoke classes, methods and functions
"""
from __future__ import annotations

import os
import re
import shutil
import subprocess
import sys
import typing as t

from . import config
from ._core import HOME, Data, PygmentPrint
from .parser import Catch


[docs]def get_configdir() -> str: """Get path to the config most suitable for active os and privilege. :return: Path to configuration dir for all profiles. """ try: if os.getuid() == 0: return os.path.join("/etc", "xdg", "borgini") return os.path.join(HOME, ".config", "borgini") except AttributeError as err: raise AttributeError("Windows not currently supported") from err
CONFIGDIR = get_configdir()
[docs]def get_file_arg( namespace: t.Dict[str, str], files: t.Dict[str, str] ) -> str | None: """Detect that a file has been selected to edit or view. :param namespace: The ``ArgumentParser`` ``Namespace.__dict__``. :param files: The file's dictionary returned from ``data.Data``. :return: Return an absolute path or ``None``. """ notfile = "editor", "dry", "select", "list", "remove" for key in namespace: if key not in notfile and namespace[key]: try: return files[key] except KeyError: return files[f"{key}.ini"] return None
[docs]def normalize_ntpath(path: str) -> str: """Format NT path to a Unix-like path. :param path: Path to format. :return: Returns a formatted path if running Windows otherwise the same path that came in. """ normalized = "/".join( p[:-1] if re.match("^[a-zA-Z]:", p) else p for p in [p.lower() for p in path.split("\\")] ) return normalized if path[0] == "/" else f"/{normalized}"
[docs]def edit_files( edit: str, file: str, pygments: PygmentPrint, dry: bool ) -> None: """Edit a config file with the editor of choice. :param edit: The editor to edit with. :param file: The path to the file to edit. :param pygments: Instantiated ``print.PygmentPrint`` class configured with user's style option. :param dry: Dry mode for when we do not want to execute the code. """ command = [edit, normalize_ntpath(file)] if dry: pygments.print(" ".join(command), ini=False) else: subprocess.call(command)
[docs]def read_file(filepath: str, pygments: PygmentPrint) -> None: """Read the file and print the output. If reading ``config.ini`` color the text with ini-style syntax, otherwise shell-like syntax. :param filepath: The file to read from. :param pygments: Instantiated ``print.PygmentPrint`` class. configured with user's style option. """ ini = os.path.basename(filepath) == "config.ini" with open(filepath, encoding="utf-8") as file: lines = file.read() pygments.print(lines, ini=ini)
[docs]def edit_file( editor: str, namespace: t.Dict[str, str], files: t.Dict[str, str], pygments: PygmentPrint, dry: bool, ) -> None: """Call the editor to edit a file. If the argument passed does not correspond to an existing file print help. If an editor is not provided go on to simply print the file content. :param editor: The editor to edit the file with. :param namespace: ``argparse.ArgumentParser``'s ``Namespace.__dict__``. :param files: Dictionary of config file paths. :param pygments: Instantiated ``print.PygmentPrint`` class configured with user's style option. :param dry: Dry mode for when we do not want to execute the code. """ filepath = get_file_arg(namespace, files) if editor: edit_files(editor, filepath, pygments, dry) # type: ignore sys.exit(0) elif filepath: read_file(filepath, pygments) sys.exit(0)
[docs]def get_path( borguser: str, hostname: str, port: str, repopath: str, ssh: bool ) -> str: """Get the path to the backup repository location. If ``ssh`` is ``True`` this will return the configured ssh path on the remote system. If ``ssh`` is ``False`` the repository will need exist on the localhost. Note: The ``repopath`` setting in the ``DEFAULT`` section will be the directory the repository is in, not the actual repository i.e. ``/path/to/repopath/reponame`` not ``/path/to/repopath``. :param borguser: The remote's user invoking ``borg`` on that machine. :param hostname: The remote's hostname. :param port: The port that the remote allows ssh through. :param repopath: The path relative to root on the remote or the localhost. :param ssh: Boolean for whether using ssh or not. :return: A path configured based on the ``config.ini`` parameters. """ return f"ssh://{borguser}@{hostname}:{port}{repopath}" if ssh else repopath
[docs]def set_passphrase(keyfile: str, catch: Catch) -> None: """Export the ``BORG_PASSPHRASE`` env var from a keyfile. :param keyfile: The absolute path to the passphrase keyfile. :param catch: ``parser.Catch`` - exit if necessary and inform user why. """ if keyfile: if os.path.isfile(keyfile): with open(keyfile, encoding="utf-8") as file: passphrase = file.read().strip() os.environ["BORG_PASSPHRASE"] = passphrase else: catch.announce_keyfile()
[docs]def initialize_config(configpath: str, catch: Catch) -> config.Config: """If a config file does not exist create a default, else read. :param configpath: Path to config file. :param catch: Instantiated ``Catch`` object. :return: Instantiated ``ConfigParser`` object. """ raw_config = config.RawConfig(configpath) if not os.path.isfile(configpath): raw_config.write_new_config() catch.announce_first_run() raw_config.read() return config.Config(raw_config)
[docs]def remove_profile(remove: t.List[str]) -> None: """Remove selected profile. :param remove: The profile entered to remove via ``argparse.ArgumentParser`` and the commandline. """ profiles = "" if remove: plural = "s" if len(remove) > 1 else "" for profile in remove: profiledir = os.path.join(CONFIGDIR, profile) if os.path.isdir(profiledir): shutil.rmtree(profiledir) profiles += f"\n. {profile}" else: print(f"profile `{profile}' does not exist") sys.exit(1) print(f"removed profile{plural}:{profiles}") sys.exit(0)
[docs]def list_profiles(show_profiles: t.List[str], pygments: PygmentPrint) -> None: """If ``show_profiles`` then display a list of profiles that exist. :param show_profiles: Boolean switch from the commandline. :param pygments: Instantiated ``print.PygmentPrint`` class configured with user's style option. """ if show_profiles: profiles = list(os.listdir(CONFIGDIR)) profiles.insert(0, profiles.pop(profiles.index("default"))) for profile in profiles: data = Data(CONFIGDIR, profile) configpath = data.get_path("config.ini") raw_config = config.RawConfig(configpath) try: raw_config.read() except NotADirectoryError: continue _config = config.Config(raw_config) obj = _config.dict["DEFAULT"] del obj["timestamp"] out = f"[{profile}]\n" for key, val in obj.items(): out += f"{key} = {val}\n" pygments.print(out) sys.exit(0)
[docs]def initialize_datafiles(data: Data) -> None: """Write the default settings to the config files. :param data: ``data.Data``. """ def comment(filetype): return ( f"# --- {filetype} ---\n" "# This is an auto-generated list which should suite most users\n" f"# Remove any entries you do not want to {filetype} and add any " f"that you do\n" "#\n" "# . ensure you always use the absolute path for directories and " "files\n" "# . line and inline comments with `#' are supported\n" "#" ) pathlists = ( ( f"{comment('include')}", "/home", "/root", "/var", "/usr/local", "/srv", ), ( f"{comment('exclude')}", "/home/*/.cache/*", "/var/cache/*", "/var/tmp/*", "/var/run", ), ( "# --- styles ---", "# Uncomment a single line to select style for syntax " "highlighting", "# `pygments' must be installed to use this feature", "#", "# . Install pygments by running:", "# . root: `pip install pygments'", "# . user (recommended): `pip install pygments --user'", "#", '# STYLE="default"', '# STYLE="emacs"', '# STYLE="friendly"', '# STYLE="colorful"', '# STYLE="autumn"', '# STYLE="murphy"', '# STYLE="manni"', 'STYLE="monokai"', '# STYLE="perldoc"', '# STYLE="pastie"', '# STYLE="borland"', '# STYLE="trac"', '# STYLE="native"', '# STYLE="fruity"', '# STYLE="bw"', '# STYLE="vim"', '# STYLE="vs"', '# STYLE="tango"', '# STYLE="rrt"', '# STYLE="xcode"', '# STYLE="igor"', '# STYLE="paraiso-light"', '# STYLE="paraiso-dark"', '# STYLE="lovelace"', '# STYLE="algol"', '# STYLE="algol_nu"', '# STYLE="arduino"', '# STYLE="rainbow_dash"', '# STYLE="abap"', '# STYLE="solarized-dark"', '# STYLE="solarized-light"', '# STYLE="sas"', '# STYLE="stata"', '# STYLE="stata-light"', '# STYLE="stata-dark"', '# STYLE="inkpot"', ), ) data.initialize_data_files(pathlists)