Add a mechanism for building a cache of "system parameters"

- Add a system_parameters module to eryntools that implements some basic types and can write a cache to a file.
- Add a system-parameters Python script that builds the cache.
- Add init_system_parameters to zsh that exports an environment variable that points to the parameters file
- Add an eryn.system-parameters.plist LaunchAgent file for running the script at regular intervals
This commit is contained in:
Eryn Wells 2024-03-05 11:05:11 -08:00
parent 05b5fef583
commit 661e5516e7
5 changed files with 253 additions and 0 deletions

View file

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>Label</key>
<string>eryn.system-parameters</string>
<key>ProgramArguments</key>
<array>
<string>/Users/eryn/bin/system-parameters</string>
<string>--output</string>
<string>/Users/eryn/.config/eryn/system_parameters.json</string>
</array>
<key>WorkingDirectory</key>
<string>/Users/eryn</string>
<key>StandardOutPath</key>
<string>/Users/eryn/.config/eryn/system_parameters.output.log</string>
<key>StandardErrorPath</key>
<string>/Users/eryn/.config/eryn/system_parameters.error.log</string>
<key>EnvironmentVariables</key>
<dict>
<key>PATH</key>
<string>/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
</dict>
<key>RunAtLoad</key>
<true/>
</dict>
</plist>

View file

@ -0,0 +1,169 @@
# Eryn Wells <eryn@erynwells.me>
import json
import os.path
import subprocess
import sys
from typing import Any, Optional, TextIO, Union
ParameterValue = Union[str, int, float]
ParametersObject = dict[str, Any]
_PARAMETERS_FILE = "~/.config/eryn/system_parameters.json"
class SystemParameters:
class _Keys:
DATA = 'data'
PARAMETERS = 'parameters'
VALUE = 'value'
def __init__(self):
self._object: ParametersObject = {}
@property
def parameters(self) -> dict:
if not self._object:
return {}
return self._object[SystemParameters._Keys.PARAMETERS]
def load_from_file(self, input_file: TextIO):
self._object = json.load(input_file)
def write_to_file(self, file):
json.dump(self._object, file, indent=4, cls=_JSONEncoder)
def __dir__(self):
return dir(self._object)
def __getattr__(self, name: str) -> Optional[ParameterValue]:
if not self._object:
return None
return self._object[SystemParameters._Keys.PARAMETERS][name][SystemParameters._Keys.VALUE]
def __setattr__(self, name: str, value: 'Parameter'):
if not isinstance(value, Parameter):
super().__setattr__(name, value)
return
parameter_value: ParametersObject = {
SystemParameters._Keys.VALUE: value.value,
}
additional_data = value.additional_data
if additional_data:
parameter_value[SystemParameters._Keys.DATA] = additional_data
self._object[name] = parameter_value
class Parameter:
@property
def value(self) -> str:
'''The value of this parameter.'''
raise NotImplementedError()
@property
def additional_data(self) -> Optional[dict[str, ParametersObject]]:
'''Optional additional data to include in the parameter structure.'''
return None
class CommandParameter(Parameter):
class _Keys:
CODE = 'code'
COMMAND = 'command'
RESULT = 'result'
STDOUT = 'stdout'
STDERR = 'stderr'
def __init__(self, command_arguments: list):
self.command = command_arguments
self.result: Optional[subprocess.CompletedProcess] = None
@property
def value(self) -> str:
self()
return self.standard_output.strip()
@property
def standard_output(self) -> str:
if not self.result or not self.result.stdout:
return ''
return self.result.stdout.decode('utf-8')
@property
def standard_error(self) -> str:
if not self.result or not self.result.stderr:
return ''
return self.result.stderr.decode('utf-8')
@property
def additional_data(self) -> Optional[dict]:
self()
assert self.result is not None
obj = {
CommandParameter._Keys.COMMAND: self.result.args,
CommandParameter._Keys.RESULT: {
CommandParameter._Keys.CODE: self.result.returncode,
}
}
stdout = self.standard_output
if stdout:
obj[CommandParameter._Keys.RESULT][CommandParameter._Keys.STDOUT] = stdout.strip()
stderr = self.standard_error
if stderr:
obj[CommandParameter._Keys.RESULT][CommandParameter._Keys.STDERR] = stderr.strip()
return obj
def __call__(self) -> 'CommandParameter':
if not self.result:
self.result = subprocess.run(
self.command,
stdout=subprocess.PIPE,
stderr=subprocess.STDOUT
)
return self
class _JSONEncoder(json.JSONEncoder):
def default(self, o: Any) -> Any:
if isinstance(o, SystemParameters):
return super().default(o._object)
return super().default(o)
def main(argv) -> Optional[int]:
import argparse
parser = argparse.ArgumentParser(prog=argv[0])
parser.add_argument(
'parameters_file',
default=os.path.abspath(os.path.expanduser(_PARAMETERS_FILE)),
nargs='?',
)
parser.add_argument('name')
arguments = parser.parse_args(argv[1:])
parameters = SystemParameters()
with open(arguments.parameters_file) as f:
parameters.load_from_file(f)
print(getattr(parameters, arguments.name))
if __name__ == '__main__':
import sys
sys.exit(main(sys.argv) or 0)

41
bin/system-parameters Executable file
View file

@ -0,0 +1,41 @@
#!/usr/bin/env python3
import argparse
from eryntools.dotfiles.system_parameters import CommandParameter, SystemParameters
class HomebrewPrefix(CommandParameter):
def __init__(self):
super().__init__(['brew', '--prefix'])
class XcodeSelectPrintPath(CommandParameter):
def __init__(self):
super().__init__(['xcode-select', '-p'])
def main(argv):
parser = argparse.ArgumentParser(prog=argv[0])
parser.add_argument('-f', '--file', type=argparse.FileType('r'))
parser.add_argument('-o', '--output', type=argparse.FileType('w'), default=sys.stdout)
arguments = parser.parse_args(argv[1:])
output_parameters = SystemParameters()
if arguments.file:
output_parameters.load_from_file(arguments.file)
parameters = {
'homebrew_prefix': HomebrewPrefix(),
'xcode_path': XcodeSelectPrintPath(),
}
for name, parameter in parameters.items():
setattr(output_parameters, name, parameter)
output_parameters.write_to_file(arguments.output)
if __name__ == "__main__":
import sys
sys.exit(main(sys.argv) or 0)

View file

@ -0,0 +1,15 @@
#!/usr/bin/env zsh
# Eryn Wells <eryn@erynwells.me>
function init_system_parameters
{
local PARAMETERS_FILE="$HOME/.config/eryn/system_parameters"
if [[ ! -e "$PARAMETERS_FILE" ]]; then
return
fi
export SYSTEM_PARAMETERS_FILE="$PARAMETERS_FILE"
}
init_system_parameters "$@"

1
zshenv
View file

@ -22,6 +22,7 @@ autoload -Uz do_init_functions
typeset -a zsh_init_env_functions=( \
init_path \
init_system_parameters \
init_env \
init_env_aliases \
init_env_python \