2020-03-29 12:23:44 +00:00
#!/usr/bin/env python
2020-04-29 15:58:14 +00:00
# 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.
2020-03-29 12:23:44 +00:00
2020-04-29 15:58:14 +00:00
# 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.
2020-03-29 12:23:44 +00:00
2020-04-29 15:58:14 +00:00
# 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.
2020-03-29 12:23:44 +00:00
2020-04-29 15:58:14 +00:00
# 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?
2020-03-29 12:23:44 +00:00
import argparse
import json
import logging
import os
import os . path
from pathlib import Path
import subprocess
import sys
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 = None ) :
"""
Creates an instance of PackageDir
: param : package_path - The directory where it should contain a file named ` locations . json ` .
"""
if package_path is None :
package_path = os . getcwd ( )
package_path = Path ( package_path )
self . path = package_path
# Loads the packages
self . packages = { }
try :
self . load_packages ( )
except :
pass
def add_package ( self , package , target ) :
2020-04-29 15:58:14 +00:00
"""
Add the package to the list .
2020-03-29 12:23:44 +00:00
2020-04-29 15:58:14 +00:00
: param : package - the name of the package
2020-03-29 12:23:44 +00:00
: 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 ) :
2020-04-29 15:58:14 +00:00
"""
Remove the package in the list .
Although this function is quite simple , this is only meant as an official API .
2020-03-29 12:23:44 +00:00
: 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 .
2020-04-29 15:58:14 +00:00
: param : commands - A list of strings that ' ll be used as a template.
The template string uses the ` string . format ` syntax .
2020-03-29 12:23:44 +00:00
( https : / / docs . python . org / 3 / library / string . html ? highlight = template #format-string-syntax)
2020-04-29 15:58:14 +00:00
It should contain a binding to the keywords ` package ` and ` location ` ( e . g . , ` stow - - restow { package } - - target { location } ` ) .
2020-03-29 12:23:44 +00:00
"""
for package , location in self . packages . items ( ) :
2020-04-29 15:58:14 +00:00
# Making sure the location is expanded.
2020-03-29 12:23:44 +00:00
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 :
2020-04-29 15:58:14 +00:00
logging . error ( f " { command } : returned with following error \n { process_status . stderr . strip ( ) } " )
2020-03-29 12:23:44 +00:00
@property
def json_location ( self ) :
""" Simply appends the path with the required JSON file. """
return self . path / PACKAGE_DATA_FILE
def setup_logging ( ) :
2020-04-29 15:58:14 +00:00
"""
Setup the logger instance .
2020-03-29 12:23:44 +00:00
"""
logging . basicConfig ( format = " [ %(levelname)s ] %(module)s : %(message)s " , level = logging . INFO , stream = sys . stdout )
def setup_args ( ) :
"""
2020-04-29 15:58:14 +00:00
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. """
2020-03-29 12:23:44 +00:00
argparser = argparse . ArgumentParser ( description = description )
2020-04-29 15:58:14 +00:00
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} " ] )
2020-03-29 12:23:44 +00:00
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 ) :
2020-04-29 15:58:14 +00:00
"""
Parse the arguments .
2020-03-29 12:23:44 +00:00
2020-04-29 15:58:14 +00:00
This is also the main function to pay attention to .
2020-03-29 12:23:44 +00:00
2020-04-29 15:58:14 +00:00
: param : parser - An instance of the argument parser .
: param : argv - A list of arguments to be parsed .
2020-03-29 12:23:44 +00:00
"""
args = parser . parse_args ( argv )
try :
package_dir = PackageDir ( args . directory )
2020-04-29 15:58:14 +00:00
# Include the following packages.
2020-03-29 12:23:44 +00:00
for package , target in args . include :
try :
package_dir . add_package ( package , target )
except Exception as e :
logging . error ( e )
2020-04-29 15:58:14 +00:00
# Exclude the following packages.
# We don't need the value here so we'll let it pass.
2020-03-29 12:23:44 +00:00
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
2020-04-29 15:58:14 +00:00
# Execute the commands with the packages.
2020-03-29 12:23:44 +00:00
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 : ] )