mirror of
https://github.com/foo-dogsquared/dotfiles.git
synced 2025-02-07 06:18:59 +00:00
![Gabriel Arazas](/assets/img/avatar_default.png)
It's been a while but I've been using NixOS (or anything styled like it like GuixSD, for example) and distro-hopped from Arch Linux. I think it's high noon for making the structure of this setup to be truer to one of the big objectives which is how easy to transfer this between different setups. Which means I removed some things such as the package lists, systemd config files, and package manager-specific configs. While the solution is easy (which is to simply ignore the system-specific files) but I'm not going with the pragmatic solution not because I'm a dumbass but because I'm so smart that I want to create a challenge for myself to solve a puzzle on figuring out a way on how to structure my dotfiles. :) Such a productive use of my time, that's for sure.
194 lines
7.0 KiB
Python
Executable File
194 lines
7.0 KiB
Python
Executable File
#!/usr/bin/env python
|
|
|
|
# A simple setup script for the packages.
|
|
# There should be a file named `locations.json` in this setup where it contains a top-level associative array with the packages as the key and their target path as the value.
|
|
# Feel free to modify it accordingly.
|
|
|
|
# This script is tailored to my specific needs.
|
|
# It also strives to only rely on the standard library so further no installation needed.
|
|
# Feel free to modify this script as well.
|
|
|
|
# For future references, the Python version when creating for this script is v3.8.2.
|
|
# If there's any reason why stuff is not working, it might be because it is different on the older versions or just my bad code lol.
|
|
|
|
# Anyway, I feel like this script should be only up to half the size.
|
|
# Hell, I think this should be simpler but no... I pushed for a more complex setup or something.
|
|
# What am I doing?
|
|
# Is this what ricing is all about?
|
|
# Why are you reading this?
|
|
|
|
import argparse
|
|
import json
|
|
import logging
|
|
import os
|
|
import os.path
|
|
from pathlib import Path
|
|
import subprocess
|
|
import sys
|
|
|
|
DEFAULT_PACKAGE_DATA_FILE="locations.json"
|
|
|
|
|
|
class PackageDir:
|
|
""" A package directory should have a file named `locations.json` where it contains a top-level object of the stow packages with their usual target path. """
|
|
|
|
def __init__(self, package_path = os.getcwd(), package_data_path = DEFAULT_PACKAGE_DATA_FILE):
|
|
"""
|
|
Creates an instance of PackageDir
|
|
|
|
:param: package_path - The directory where it should contain a file named `locations.json`.
|
|
"""
|
|
self.path = Path(package_path)
|
|
self.data_path = Path(package_data_path)
|
|
|
|
# Loads the packages
|
|
self.packages = {}
|
|
try:
|
|
self.load_packages()
|
|
except:
|
|
pass
|
|
|
|
|
|
def add_package(self, package, target):
|
|
"""
|
|
Add the package to the list.
|
|
|
|
:param: package - the name of the package
|
|
:param: target - the target path of the package
|
|
"""
|
|
package_path = self.path / package
|
|
assert package_path.is_dir(), f"The given package '{package}' does not exist in the package directory."
|
|
self.packages[package] = target
|
|
|
|
|
|
def remove_package(self, package):
|
|
"""
|
|
Remove the package in the list.
|
|
Although this function is quite simple, this is only meant as an official API.
|
|
|
|
:param: package - the package to be removed
|
|
"""
|
|
return self.packages.pop(package, None)
|
|
|
|
|
|
def load_packages(self):
|
|
"""
|
|
Loads the packages from the data file.
|
|
"""
|
|
assert self.json_location.is_file(), "There is no 'package.json` in the given directory."
|
|
|
|
with open(self.json_location) as f:
|
|
package_map = json.load(f)
|
|
for package, target in package_map.items():
|
|
try:
|
|
self.add_package(package, target)
|
|
except Exception as e:
|
|
logging.error(e)
|
|
|
|
|
|
def execute_packages(self, commands):
|
|
"""
|
|
Execute a set of commands with the packages.
|
|
|
|
:param: commands - A list of strings that'll be used as a template.
|
|
The template string uses the `string.format` syntax.
|
|
(https://docs.python.org/3/library/string.html?highlight=template#format-string-syntax)
|
|
It should contain a binding to the keywords `package` and `location` (e.g., `stow --restow {package} --target {location}`).
|
|
"""
|
|
for package, location in self.packages.items():
|
|
# Making sure the location is expanded.
|
|
location = os.path.expanduser(location)
|
|
target_cwd = os.path.realpath(self.path)
|
|
|
|
for command in commands:
|
|
command = command.format(package=package, location=location)
|
|
|
|
process_status = subprocess.run(command, cwd=target_cwd, capture_output=True, shell=True, encoding='utf-8')
|
|
if process_status.returncode == 0:
|
|
logging.info(f"{command}: successfully ran")
|
|
else:
|
|
logging.error(f"{command}: returned with following error\n{process_status.stderr.strip()}")
|
|
|
|
|
|
@property
|
|
def json_location(self):
|
|
""" Simply appends the path with the required JSON file. """
|
|
return self.path / self.data_path
|
|
|
|
|
|
def setup_logging():
|
|
"""
|
|
Setup the logger instance.
|
|
"""
|
|
logging.basicConfig(format="[%(levelname)s] %(module)s: %(message)s", level=logging.INFO, stream=sys.stdout)
|
|
|
|
|
|
def setup_args():
|
|
"""
|
|
Setup the argument parser.
|
|
|
|
:returns: An ArgumentParser object.
|
|
"""
|
|
description = """A quick installation script for this setup. Take note this is tailored to my specific needs but I tried to make this script generic."""
|
|
argparser = argparse.ArgumentParser(description=description)
|
|
|
|
argparser.add_argument("-c", "--commands", metavar = "command", help = "Executing the specified commands. All of the commands are treated as they were entered in the shell.", nargs = "*", default = ["echo {package} is set at {location}"])
|
|
argparser.add_argument("-d", "--directory", metavar = "path", help = "Set the directory of the package data file.", type = Path, nargs = "?", default = Path(os.getcwd()))
|
|
argparser.add_argument("--exclude", metavar = "package", help = "Exclude the given packages.", type = str, nargs = "+", default = [])
|
|
argparser.add_argument("--include", metavar = ("package", "location"), help = "Include with the following packages.", type = str, nargs = 2, action = "append", default = [])
|
|
argparser.add_argument("--only", metavar = "package", help = "Only execute with the given packages.", type = str, nargs = "+", default = [])
|
|
|
|
return argparser
|
|
|
|
|
|
def parse_args(parser, argv):
|
|
"""
|
|
Parse the arguments.
|
|
|
|
This is also the main function to pay attention to.
|
|
|
|
:param: parser - An instance of the argument parser.
|
|
:param: argv - A list of arguments to be parsed.
|
|
"""
|
|
args = parser.parse_args(argv)
|
|
|
|
try:
|
|
package_dir = PackageDir(args.directory)
|
|
|
|
# Include the following packages.
|
|
for package, target in args.include:
|
|
try:
|
|
package_dir.add_package(package, target)
|
|
except Exception as e:
|
|
logging.error(e)
|
|
|
|
# Exclude the following packages.
|
|
# We don't need the value here so we'll let it pass.
|
|
for package in args.exclude:
|
|
package_dir.remove_package(package)
|
|
|
|
if len(args.only) >= 1:
|
|
items = {}
|
|
for package in args.only:
|
|
value = package_dir.remove_package(package)
|
|
if value is None:
|
|
continue
|
|
items[package] = value
|
|
|
|
package_dir.packages.clear()
|
|
package_dir.packages = items
|
|
|
|
# Execute the commands with the packages.
|
|
package_dir.execute_packages(args.commands)
|
|
except Exception as e:
|
|
logging.error(e)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
setup_logging()
|
|
argparser = setup_args()
|
|
|
|
parse_args(argparser, sys.argv[1:])
|
|
|
|
|