dotfiles/vtsm
Gabriel Arazas 0681d1fd7c Structure the project with NixOS-styled distros
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.
2020-09-11 03:12:26 +08:00

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:])