mirror of
https://github.com/foo-dogsquared/dotfiles.git
synced 2025-01-30 22:57:54 +00:00
195 lines
7.2 KiB
Python
Executable File
195 lines
7.2 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] %(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:])
|
|
|
|
|