Compare commits

...

6 Commits
master ... main

Author SHA1 Message Date
Riley Housden 44555fc84e
add basic backup alias 2022-07-04 01:38:57 -04:00
Riley Housden 067ab48de0
Add lib
Adds backup and storage libraries to ease backing up of files on command.
2022-07-04 01:36:36 -04:00
Riley Housden eedecb34d5
fix home access for JDS 2022-06-30 18:15:25 -04:00
Riley Housden cc6bbe937f
add error handling to jds 2022-06-30 00:24:59 -04:00
Riley Housden 67fd1b3cf4
push alacritty changes 2022-06-29 23:32:56 -04:00
Riley Housden 0f5bbd78ad
update 2022-06-25 00:09:01 -04:00
8 changed files with 492 additions and 160 deletions

View File

@ -1,5 +1,3 @@
import:
- /home/riley/.config/alacritty/dracula.yml
# Configuration for Alacritty, the GPU enhanced terminal emulator.
# Import additional configuration files
@ -16,16 +14,11 @@ import:
# Any items in the `env` entry below will be added as
# environment variables. Some entries may override variables
# set by alacritty itself.
#env:
# TERM variable
#
# This value is used to set the `$TERM` environment variable for
# each instance of Alacritty. If it is not present, alacritty will
# check the local terminfo database and use `alacritty` if it is
# available, otherwise `xterm-256color` is used.
#TERM: alacritty
env:
WINIT_X11_SCALE_FACTOR: '1.0'
window:
opacity: 0.95
# Window dimensions (changes require restart)
#
# Number of lines/columns (not pixels) in the terminal. The number of columns
@ -178,142 +171,120 @@ window:
#draw_bold_text_with_bright_colors: false
# Colors (Tomorrow Night)
#colors:
# Default colors
#primary:
# background: '#1d1f21'
# foreground: '#c5c8c6'
schemes:
dracula: &dracula
colors:
primary:
background: '0x282a36'
foreground: '0xf8f8f2'
cursor:
text: CellBackground
cursor: CellForeground
vi_mode_cursor:
text: CellBackground
cursor: CellForeground
search:
matches:
foreground: '0x44475a'
background: '0x50fa7b'
focused_match:
foreground: '0x44475a'
background: '0xffb86c'
bar:
background: '0x282a36'
foreground: '0xf8f8f2'
line_indicator:
foreground: None
background: None
selection:
text: CellForeground
background: '0x44475a'
normal:
black: '0x000000'
red: '0xff5555'
green: '0x50fa7b'
yellow: '0xf1fa8c'
blue: '0xbd93f9'
magenta: '0xff79c6'
cyan: '0x8be9fd'
white: '0xbfbfbf'
bright:
black: '0x4d4d4d'
red: '0xff6e67'
green: '0x5af78e'
yellow: '0xf4f99d'
blue: '0xcaa9fa'
magenta: '0xff92d0'
cyan: '0x9aedfe'
white: '0xe6e6e6'
dim:
black: '0x14151b'
red: '0xff2222'
green: '0x1ef956'
yellow: '0xebf85b'
blue: '0x4d5b86'
magenta: '0xff46b0'
cyan: '0x59dffc'
white: '0xe6e6d1'
# Tokyo Night theme, based on both:
# https://github.com/ghifarit53/tokyonight-vim
# https://github.com/enkia/tokyo-night-vscode-theme
tokyo-night: &tokyo-night
# Default colors
primary:
background: '0x1a1b26'
foreground: '0xa9b1d6'
# Bright and dim foreground colors
#
# The dimmed foreground color is calculated automatically if it is not
# present. If the bright foreground color is not set, or
# `draw_bold_text_with_bright_colors` is `false`, the normal foreground
# color will be used.
#dim_foreground: '#828482'
#bright_foreground: '#eaeaea'
# Normal colors
normal:
black: '0x32344a'
red: '0xf7768e'
green: '0x9ece6a'
yellow: '0xe0af68'
blue: '0x7aa2f7'
magenta: '0xad8ee6'
cyan: '0x449dab'
white: '0x787c99'
# Cursor colors
#
# Colors which should be used to draw the terminal cursor.
#
# Allowed values are CellForeground/CellBackground, which reference the
# affected cell, or hexadecimal colors like #ff00ff.
#cursor:
# text: CellBackground
# cursor: CellForeground
# Bright colors
bright:
black: '0x444b6a'
red: '0xff7a93'
green: '0xb9f27c'
yellow: '0xff9e64'
blue: '0x7da6ff'
magenta: '0xbb9af7'
cyan: '0x0db9d7'
white: '0xacb0d0'
# Vi mode cursor colors
#
# Colors for the cursor when the vi mode is active.
#
# Allowed values are CellForeground/CellBackground, which reference the
# affected cell, or hexadecimal colors like #ff00ff.
#vi_mode_cursor:
# text: CellBackground
# cursor: CellForeground
tokyo-night-storm: &tokyo-night-storm
# Default colors
primary:
background: '0x24283b'
foreground: '0xa9b1d6'
# Search colors
#
# Colors used for the search bar and match highlighting.
#search:
# Allowed values are CellForeground/CellBackground, which reference the
# affected cell, or hexadecimal colors like #ff00ff.
#matches:
# foreground: '#000000'
# background: '#ffffff'
#focused_match:
# foreground: '#ffffff'
# background: '#000000'
#bar:
# background: '#c5c8c6'
# foreground: '#1d1f21'
# Keyboard regex hints
#hints:
# First character in the hint label
#
# Allowed values are CellForeground/CellBackground, which reference the
# affected cell, or hexadecimal colors like #ff00ff.
#start:
# foreground: '#1d1f21'
# background: '#e9ff5e'
# All characters after the first one in the hint label
#
# Allowed values are CellForeground/CellBackground, which reference the
# affected cell, or hexadecimal colors like #ff00ff.
#end:
# foreground: '#e9ff5e'
# background: '#1d1f21'
# Line indicator
#
# Color used for the indicator displaying the position in history during
# search and vi mode.
#
# By default, these will use the opposing primary color.
#line_indicator:
# foreground: None
# background: None
# Selection colors
#
# Colors which should be used to draw the selection area.
#
# Allowed values are CellForeground/CellBackground, which reference the
# affected cell, or hexadecimal colors like #ff00ff.
#selection:
# text: CellBackground
# background: CellForeground
# Normal colors
#normal:
# black: '#1d1f21'
# red: '#cc6666'
# green: '#b5bd68'
# yellow: '#f0c674'
# blue: '#81a2be'
# magenta: '#b294bb'
# cyan: '#8abeb7'
# white: '#c5c8c6'
# Bright colors
#bright:
# black: '#666666'
# red: '#d54e53'
# green: '#b9ca4a'
# yellow: '#e7c547'
# blue: '#7aa6da'
# magenta: '#c397d8'
# cyan: '#70c0b1'
# white: '#eaeaea'
# Dim colors
#
# If the dim colors are not set, they will be calculated automatically based
# on the `normal` colors.
#dim:
# black: '#131415'
# red: '#864343'
# green: '#777c44'
# yellow: '#9e824c'
# blue: '#556a7d'
# magenta: '#75617b'
# cyan: '#5b7d78'
# white: '#828482'
# Indexed Colors
#
# The indexed colors include all colors from 16 to 256.
# When these are not set, they're filled with sensible defaults.
#
# Example:
# `- { index: 16, color: '#ff00ff' }`
#
#indexed_colors: []
# Normal colors
normal:
black: '0x32344a'
red: '0xf7768e'
green: '0x9ece6a'
yellow: '0xe0af68'
blue: '0x7aa2f7'
magenta: '0xad8ee6'
cyan: '0x449dab'
white: '0x9699a8'
# Bright colors
bright:
black: '0x444b6a'
red: '0xff7a93'
green: '0xb9f27c'
yellow: '0xff9e64'
blue: '0x7da6ff'
magenta: '0xbb9af7'
cyan: '0x0db9d7'
white: '0xacb0d0'
colors: *tokyo-night-storm
# Bell
#
# The bell is rung every time the BEL control character is received.

3
.vimrc
View File

@ -12,7 +12,6 @@ if $TERM == 'tmux-256color'
let &t_8b = "\033[48;2;%lu;%lu;%lum"
endif
set termguicolors
set background=dark
syntax enable
@ -66,5 +65,7 @@ Plug 'preservim/nerdtree'
Plug 'chiel92/vim-autoformat'
Plug 'natebosch/vim-lsc'
Plug 'https://tildegit.org/sloum/gemini-vim-syntax'
Plug 'meatballs/vim-xonsh'
Plug 'davidhalter/jedi-vim'
call plug#end()

View File

@ -6,15 +6,12 @@ elif $HOSTNAME == "riley-server":
elif $HOSTNAME == "riley-desktop":
$PROMPT = '{RED} >< {RESET}| {BOLD_GREEN}{cwd_base}{RESET} ) '
$XONSH_COLOR_STYLE = 'default'
xontrib load argcomplete autovox jedi z bashisms
xontrib load argcomplete autovox jedi z bashisms jds
source "~/xsh/functions.xsh"
source "~/xsh/exceptions.xsh"
source "~/xsh/aliases.xsh"
# TODO: Fix with Arch-based distros
# import addins # my extra stuffs :p
# Windows Subsystem for Linux stuff
if in_wsl():
$GPG_TTY=$(tty) # allows gpg to work inside WSL

129
lib/backup.py Normal file
View File

@ -0,0 +1,129 @@
"""
Backup Module:
Classes:
- BackupManager
"""
from datetime import datetime
from pathlib import Path
from zipfile import ZipFile
from . import Storage
__all__ = ["BackupManager"]
class BackupManager:
"""
Facilitates backup creation. Stores all backups in ~/.storage/backups
Methods:
- create_backup()
- get_backups()
- get_delete_candidates()
- delete_excess_backups()
"""
def __init__(self, target_path: Path, backup_storage: Storage = Storage("backups"),
date_format: str = "%d_%m_%y__%H%M%S", separator: str = "-") -> None:
"""
Test
"""
self.target_path = target_path
self.backup_storage = backup_storage
self.date_format = date_format
self.separator = separator
@property
def target_path(self) -> Path:
"""
Target to create backups of. This could be a file or folder.
"""
return self._target_path
@target_path.setter
def target_path(self, new_path: Path):
if isinstance(new_path, Path):
if new_path.exists() and (new_path.is_dir() or new_path.is_file()):
self._target_path = new_path
else:
raise ValueError(new_path)
else:
raise TypeError(new_path)
@property
def backup_storage(self) -> Storage:
"""
Storage object to store created backups in.
"""
return self._backup_storage
@backup_storage.setter
def backup_storage(self, new_storage: Storage):
if isinstance(new_storage, Storage):
self._backup_storage = new_storage
else:
raise TypeError(new_storage)
@property
def date_format(self) -> str:
"""The datetime format string used to date the backups."""
return self._date_format
@date_format.setter
def date_format(self, new_format):
if isinstance(new_format, str):
self._date_format = new_format
else:
raise TypeError(new_format)
@property
def separator(self) -> str:
"""The separator that separates the archive name from the date"""
return self._separator
@separator.setter
def separator(self, new_separator):
if isinstance(new_separator, str):
if new_separator not in self.target_path.name and new_separator not in self.date_format:
self._separator = new_separator
else:
raise ValueError(new_separator)
else:
raise TypeError(new_separator)
def create_backup(self):
"""
Create a backup of the target path, stored in the backup storage folder.
"""
date_string = datetime.now().strftime(self.date_format)
backup_name = f"{self.target_path.name}{self.separator}{date_string}.zip"
backup_path = self.backup_storage.get_file(backup_name)
with ZipFile(backup_path, mode="w") as zip_file:
for item in self.target_path.glob("**/*"):
zip_file.write(item, item.relative_to(self.target_path))
def get_backups(self):
"""
Get all backups found in the given folder.
"""
return self.backup_storage.list_files(f"{self.target_path.stem}{self.separator}*.zip")
def get_delete_candidates(self, max_backup_count) -> list[Path]:
"""
Get all candidates for deletion with the given max_backup_count.
If none are available for deletion, returns None.
"""
def get_date(file: Path) -> datetime:
"""
Turns the datetime string in the file name into a datetime object.
"""
date_string = file.name.split(self.separator)[1].replace(file.suffix, "")
return datetime.strptime(date_string, self.date_format)
backups = self.get_backups()
backups.sort(key=get_date)
return backups[:(len(backups)-max_backup_count)] # returns the oldest excess backups
def delete_excess_backups(self, max_backup_count: int):
"""Delete all excess backups"""
for file in self.get_delete_candidates(max_backup_count):
file.unlink()

137
lib/storage.py Normal file
View File

@ -0,0 +1,137 @@
"""
Storage Module:
Classes:
- Storage
"""
from __future__ import annotations
import json
import shutil
from pathlib import Path
__all__ = ['Storage']
class Storage:
"""
Unifies all filesystem access methods under a single class.
Minimizes room for mistakes and improves reusability.
Methods:
- get_file()
- get_folder()
- write_file()
- read_file()
- delete_file()
- rename_file()
- add_file()
- list_files()
"""
def __init__(self, folder: str, root_folder: str = ".storage") -> None:
"""
Create a new storage instance, it automatically creates and manages a folder in the user's home directory, all you have to do is supply a subfolder to store files in.
Args:
folder (str): the name of the folder to use.
"""
self.root = Path.home().joinpath(root_folder)
self.folder = self.root.joinpath(folder)
self.folder.mkdir(exist_ok=True, parents=True)
def get_file(self, path: str) -> Path:
"""Get the fully qualified path of a file in the folder.
Args:
path (str): the name of the file to grab.
Returns:
Path: the path of the file.
"""
return self.folder.joinpath(path)
def get_folder(self, name: str) -> Storage:
"""
Get a folder inside the Storage directory.
Returns a Storage object representing that folder.
"""
return Storage(name, root_folder=self.folder.name)
def write_file(self, name: str, data: dict):
"""Write data to the given file in JSON format.
Args:
name (str): the name of the file.
data (dict): the data to write.
"""
file = self.get_file(name)
file.touch(exist_ok=True)
with file.open("w+", encoding="utf-8") as f:
json.dump(data, f, indent=4)
def read_file(self, name: str) -> dict:
"""Read data from the given file in JSON format.
Args:
name (str): the name of the file.
Returns:
dict: the data from the file.
"""
file = self.get_file(name)
if not file.exists():
return {}
with file.open("r+", encoding="utf-8") as f:
data = json.load(f)
return data
def delete_file(self, name: str):
"""Delete the given file from the folder.
Args:
name (str): the name of the file.
"""
file = self.get_file(name)
file.unlink(missing_ok=True)
def rename_file(self, old_name: str, new_name: str):
"""Rename a file in the folder.
Args:
old_name (str): the current name of the file.
new_name (str): the new name of the file.
"""
file = self.get_file(old_name)
new_file = self.folder.joinpath(new_name)
file.rename(new_file)
def add_file(self, name: str, path: Path | None = None, binary: bytes | None = None):
"""Add a copy of a file to the folder.
If a binary stream is given, it is saved directly to the named location.
Args:
name (str): The name to save it under.
path (Path): The path of the file to copy from.
binary (BinaryIO): The binary stream to copy from.
"""
if path is not None and binary is not None:
raise ValueError(binary, "Cannot supply both a path and a binary stream.")
elif path is not None:
shutil.copy(path, self.folder.joinpath(name))
elif binary is not None:
with self.folder.joinpath(name).open("wb+") as f:
f.write(binary)
def list_files(self, pattern: str | None = None) -> list[Path]:
"""
Return a list of all files in the directory.
"""
files: list[Path] = []
if pattern is None:
for file in self.folder.iterdir():
files.append(file)
else:
for file in self.folder.glob(pattern):
files.append(file)
return files

52
xontrib/jds.xsh Normal file
View File

@ -0,0 +1,52 @@
"""Integrates Johnny Decimal Notation into Xonsh"""
from pathlib import Path
from xonsh.built_ins import XSH
HOME = Path("/run/media/riley/drive")
def find_category(number: int, path, regex: str):
"""Find matching JDN folder when given notation"""
import re
folders = path.iterdir()
for folder in folders:
if not folder.is_dir():
continue
folder_category = re.match(regex, folder.name)
if folder_category is None:
continue
if len(folder_category.groups()) == 2:
if number >= int(folder_category.group(1)) and number <= int(folder_category.group(2)):
return folder
elif len(folder_category.groups()) == 1:
if number == int(folder_category.group((1))):
return folder
return path
@XSH.builtins.events.on_transform_command
def jds_preproc(cmd: str, **kw):
"""processes command strings starting with "`" to cd to matching folder."""
if cmd.startswith("`") and "." in cmd and not cmd.strip().endswith("`"):
try:
johnny_decimal_notation = cmd.replace("`", "")
bucket = find_category(int(johnny_decimal_notation.split(".")[0]), HOME, r"(^\d0)-(\d9)")
category = find_category(int(johnny_decimal_notation.split(".")[0]), bucket, r"^(\d\d)")
folder = find_category(int(johnny_decimal_notation.split(".")[1]), category, r"^(\d\d)")
cmd = f"cd '{folder.resolve()}'"
except FileNotFoundError:
cmd = f'print("\'{HOME}\' not found.")'
except ValueError:
cmd = f'print("\'{cmd.strip().replace("`", "")}\' is not a valid JDN.")'
elif cmd.startswith("`") and not (len(cmd.strip()) >= 2 and cmd.strip().endswith("`")):
try:
johnny_decimal_notation = cmd.replace("`", "")
if johnny_decimal_notation == "\n":
return f"cd '{HOME.resolve()}'"
bucket = find_category(int(johnny_decimal_notation.split(".")[0]), HOME, r"(^\d0)-(\d9)")
category = find_category(int(johnny_decimal_notation.split(".")[0]), bucket, r"^(\d\d)")
cmd = f"cd '{category.resolve()}'"
except FileNotFoundError:
cmd = f'print("\'{HOME}\' not found.")'
except ValueError:
cmd = f'print("\'{cmd.strip().replace("`", "")}\' is not a valid JDN.")'
return cmd

View File

@ -1,4 +1,5 @@
source xsh/functions.xsh
import lib
source ~/xsh/functions.xsh
def _debug():
if $XONSH_SHOW_TRACEBACK:
@ -41,19 +42,60 @@ def _bwc(args: list):
else:
print(f"copied {i}")
def _mcrcon(args: list):
if p"~/.mcrcon".exists():
source "~/.mcrcon"
else:
raise FileNotFoundError("~/.mcrcon")
$(mcrcon @(" ".join(args)))
def _backup(args: list):
from pathlib import Path
target_path = Path(args[0])
max_backup_count: int = 3
if len(args) == 2 and isinstance(args[1], int):
max_backup_count = int(args[1])
bm = lib.BackupManager(target_path)
bm.create_backup()
bm.delete_excess_backups(max_backup_count)
def _mcterm():
if p"~/.mcrcon".exists():
source "~/.mcrcon"
else:
raise FileNotFoundError("~/.mcrcon")
mcrcon -t
def _mcrcon(args: list):
import json
try:
with p"~/.mcrcon.json".open("r") as fp:
servers_data = json.load(fp)
server_data = servers_data[args[0]]
$MCRCON_HOST = server_data["address"]
$MCRCON_PORT = server_data["port"]
$MCRCON_PASS = server_data["password"]
$(mcrcon @(" ".join(args[1:])))
except IndexError:
print("Please indicate which server to connect to.")
except KeyError:
print("Server configuration not found.")
except FileNotFoundError:
print("Server configuration file '.mcrcon.json' is not found.")
def _mcterm(args: list):
import json
try:
with p"~/.mcrcon.json".open("r") as fp:
servers_data = json.load(fp)
server_data = servers_data[args[0]]
$MCRCON_HOST = server_data["address"]
$MCRCON_PORT = server_data["port"]
$MCRCON_PASS = server_data["password"]
mcrcon -t
except IndexError:
print("Please indicate which server to connect to.")
except KeyError:
print("Server configuration not found.")
except FileNotFoundError:
print("Server configuration file '.mcrcon.json' is not found.")
def _mclist():
import json
try:
with p"~/.mcrcon.json".open("r") as fp:
servers_data = json.load(fp)
print("Servers:")
for key in servers_data:
print("\t-", key)
except FileNotFoundError:
print("Server configuration file '.mcrcon.json' is not found.")
def _alias():
for alias in aliases:
@ -91,12 +133,15 @@ def _ls():
def load_aliases():
aliases.update({
'bu': _backup,
'backup': _backup,
'bwg': _bwc,
'colortest': _colortest,
'debug': _debug,
'ls': 'ls -alhs --color=auto',
'mc': _mcrcon,
'mct': _mcterm,
'mcl': _mclist,
':q': 'exit',
'ensure-tmux': _ensure_tmux,
'aliases': _alias,

View File

@ -1,4 +1,4 @@
source xsh/exceptions.xsh
source ~/xsh/exceptions.xsh
def in_wsl() -> bool:
"""Determines if the system is running inside a WSL environment."""