-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathvtsm
executable file
·194 lines (145 loc) · 7.18 KB
/
vtsm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
#!/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:])