From 661e5516e7377c479f117f58e39af3e533620c57 Mon Sep 17 00:00:00 2001 From: Eryn Wells Date: Tue, 5 Mar 2024 11:05:11 -0800 Subject: [PATCH] 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 --- LaunchAgents/eryn.system-parameters.plist | 27 +++ .../eryntools/dotfiles/system_parameters.py | 169 ++++++++++++++++++ bin/system-parameters | 41 +++++ zsh/func/init_system_parameters | 15 ++ zshenv | 1 + 5 files changed, 253 insertions(+) create mode 100644 LaunchAgents/eryn.system-parameters.plist create mode 100644 Python/eryntools/src/eryntools/dotfiles/system_parameters.py create mode 100755 bin/system-parameters create mode 100644 zsh/func/init_system_parameters diff --git a/LaunchAgents/eryn.system-parameters.plist b/LaunchAgents/eryn.system-parameters.plist new file mode 100644 index 0000000..6326350 --- /dev/null +++ b/LaunchAgents/eryn.system-parameters.plist @@ -0,0 +1,27 @@ + + + + + Label + eryn.system-parameters + ProgramArguments + + /Users/eryn/bin/system-parameters + --output + /Users/eryn/.config/eryn/system_parameters.json + + WorkingDirectory + /Users/eryn + StandardOutPath + /Users/eryn/.config/eryn/system_parameters.output.log + StandardErrorPath + /Users/eryn/.config/eryn/system_parameters.error.log + EnvironmentVariables + + PATH + /opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin + + RunAtLoad + + + diff --git a/Python/eryntools/src/eryntools/dotfiles/system_parameters.py b/Python/eryntools/src/eryntools/dotfiles/system_parameters.py new file mode 100644 index 0000000..9fa543d --- /dev/null +++ b/Python/eryntools/src/eryntools/dotfiles/system_parameters.py @@ -0,0 +1,169 @@ +# Eryn Wells + +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) diff --git a/bin/system-parameters b/bin/system-parameters new file mode 100755 index 0000000..be4214d --- /dev/null +++ b/bin/system-parameters @@ -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) diff --git a/zsh/func/init_system_parameters b/zsh/func/init_system_parameters new file mode 100644 index 0000000..ced1b9c --- /dev/null +++ b/zsh/func/init_system_parameters @@ -0,0 +1,15 @@ +#!/usr/bin/env zsh +# Eryn Wells + +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 "$@" diff --git a/zshenv b/zshenv index 1b45e80..572807d 100644 --- a/zshenv +++ b/zshenv @@ -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 \