dotfiles/vtsm

195 lines
7.2 KiB
Plaintext
Raw Normal View History

#!/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
2020-05-21 15:16:15 +00:00
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. """
2020-05-21 15:16:15 +00:00
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`.
"""
2020-05-21 15:16:15 +00:00
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. """
2020-05-21 15:16:15 +00:00
return self.path / self.data_path
def setup_logging():
"""
Setup the logger instance.
"""
logging.basicConfig(format="[%(levelname)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("-m", "--manifest", metavar = "manifest", help = "Specify what metadata file to be used (e.g., locations.json).", type = Path, nargs = "?", default = DEFAULT_PACKAGE_DATA_FILE)
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, args.manifest)
# 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:])