-
-
Save jpramosi/7f81e9dbe3fe54f3645ca35d2192a07d to your computer and use it in GitHub Desktop.
Helper utility for shell scripts to read or modify configuration files like .ini, .toml or .conf
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/bin/python3 | |
""" | |
Config-Util - Helper utility for shell scripts to read or modify | |
configuration files like .ini, .toml or .conf | |
Copyright (C) 2022 Jimmy Pramosi <[email protected]> | |
This program is free software: you can redistribute it and/or modify | |
it under the terms of the GNU General Public License as published by | |
the Free Software Foundation, either version 3 of the License, or | |
(at your option) any later version. | |
This program is distributed in the hope that it will be useful, | |
but WITHOUT ANY WARRANTY; without even the implied warranty of | |
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
GNU General Public License for more details. | |
You should have received a copy of the GNU General Public License | |
along with this program. If not, see <https://www.gnu.org/licenses/>. | |
""" | |
import io | |
import sys | |
from argparse import ArgumentParser, RawTextHelpFormatter | |
from configparser import ConfigParser, _UNSET | |
from pathlib import Path | |
DESCRIPTION = """ | |
Config-Util Copyright (C) 2022 Jimmy Pramosi <[email protected]> | |
This program comes with ABSOLUTELY NO WARRANTY. | |
This is free software, and you are welcome to redistribute it under certain | |
conditions. | |
You should have received a copy of the GNU General Public License along with | |
this program. If not, see <http://www.gnu.org/licenses/>. | |
""" | |
USAGE = """ | |
Get operations: | |
cfg [OPTIONS] <FILE> <VAR-NAME> | |
cfg [OPTIONS] <FILE> <[SECTION-NAME]> <VAR-NAME> | |
Set operations: | |
cfg [OPTIONS] <FILE> <VAR-NAME> <NEW-VALUE> | |
cfg [OPTIONS] <FILE> <[SECTION-NAME]> <VAR-NAME> <NEW-VALUE> | |
""" | |
EPILOG = """ | |
Examples: | |
get variable 'timeout_secs' without section | |
cfg vpn.conf timeout_secs | |
set variable 'timeout_secs' without section | |
cfg vpn.conf timeout_secs myprofile.ovpn | |
get variable 'tun-device' with section | |
cfg vpn.conf [OpenVpn] tun-device | |
set variable 'tun-device' with section | |
cfg vpn.conf [OpenVpn] tun-device tun0 | |
get variable 'select_profile' with nested section | |
cfg vpn.conf [OpenVpn.profile] select_profile | |
set variable 'select_profile' with nested section | |
cfg vpn.conf [OpenVpn.profile] select_profile myprofile.ovpn\n | |
""" | |
def eprint(*args, **kwargs): | |
print(*args, file=sys.stderr, **kwargs) | |
def main(): | |
commands = set() | |
def add_argument(parser: ArgumentParser, *args, **kwargs): | |
nonlocal commands | |
commands.add(args[0]) | |
commands.add(args[1]) | |
parser.add_argument(*args, **kwargs) | |
# parse arguments | |
parser = ArgumentParser(prog="cfg", description=DESCRIPTION, | |
usage=USAGE, epilog=EPILOG, formatter_class=RawTextHelpFormatter) | |
add_argument(parser, "-d", "--dump", action="store_true", | |
help="dump variable and value to stdout") | |
add_argument(parser, "-q", "--quote", action="store_true", | |
help="wrap values with quotes if contains whitespace") | |
add_argument(parser, "-a", "--auto-format", action="store_true", | |
help="format values automatically (quotes)") | |
add_argument(parser, "-n", "--new", action="store_true", | |
help="allow creating new variables with set operation") | |
add_argument(parser, "-p", "--print", action="store_true", | |
help="print file content after successful operation") | |
args, _ = parser.parse_known_args() | |
# clean and check args | |
sys.argv[:] = [x for x in sys.argv if x not in commands] | |
argn = len(sys.argv) | |
if argn < 3: | |
eprint(f"Invalid arguments:\n\n\t{' '.join(sys.argv)}\n\n") | |
parser.print_help() | |
return 1 | |
filepath = Path(sys.argv[1]).resolve() | |
def write_string(config: ConfigParser): | |
output = "" | |
with io.StringIO() as ss: | |
config.write(ss) | |
ss.seek(0) | |
output = ss.read().replace("[_]\n", "") | |
return output | |
def format_value(value, force_quote=False): | |
if isinstance(value, str) and args.quote or force_quote or (args.auto_format and " " in value): | |
value = value.replace("\"", "\\\"") | |
value = f"\"{value}\"" | |
return value | |
def dump_value(config: ConfigParser, name: str, value): | |
if args.dump: | |
print(f"{name}={value}") | |
elif args.print: | |
print(write_config(config)) | |
else: | |
print(value) | |
def read_config(): | |
with open(filepath, "rt", encoding="utf-8") as f: | |
config_string = "[_]\n" + f.read() | |
config = ConfigParser(comment_prefixes="/", allow_no_value=True) | |
config.read_string(config_string) | |
return config | |
def write_config(config: ConfigParser): | |
output = write_string(config) | |
with open(filepath, "wt", encoding="utf-8") as f: | |
f.write(output) | |
# get value without section | |
if argn == 3: | |
try: | |
config = read_config() | |
value = format_value(config.get("_", sys.argv[2])) | |
dump_value(config, sys.argv[2], value) | |
except Exception as ex: | |
eprint(ex) | |
return 1 | |
return | |
# get value with section | |
if argn == 4 and sys.argv[2].startswith("[") and sys.argv[2].endswith("]"): | |
try: | |
config = read_config() | |
section = sys.argv[2].removeprefix("[").removesuffix("]") | |
value = format_value(config.get( | |
section, sys.argv[3])) | |
dump_value(config, sys.argv[2], value) | |
except Exception as ex: | |
eprint(ex) | |
return 1 | |
return | |
# set value without section | |
if argn == 4: | |
try: | |
config = read_config() | |
fallback = type(sys.argv[3])() if args.new else _UNSET | |
rvalue = format_value(config.get( | |
"_", sys.argv[2], fallback=fallback)) | |
value = format_value( | |
sys.argv[3], isinstance(rvalue, str) and rvalue.startswith("\"")) | |
config.set("_", sys.argv[2], value) | |
write_config(config) | |
dump_value(config, sys.argv[2], value) | |
except Exception as ex: | |
eprint(ex) | |
return 1 | |
return 0 | |
# set value with section | |
if argn == 5: | |
try: | |
config = read_config() | |
section = sys.argv[2].removeprefix("[").removesuffix("]") | |
fallback = type(sys.argv[4])() if args.new else _UNSET | |
rvalue = format_value(config.get( | |
section, sys.argv[3], fallback=fallback)) | |
value = format_value( | |
sys.argv[4], isinstance(rvalue, str) and rvalue.startswith("\"")) | |
config.set(section, | |
sys.argv[3], value) | |
write_config(config) | |
dump_value(config, sys.argv[3], value) | |
except Exception as ex: | |
eprint(ex) | |
return 1 | |
return 0 | |
eprint(f"Too many arguments:\n\n\t{' '.join(sys.argv)}\n\n") | |
parser.print_help() | |
return 1 | |
if __name__ == "__main__": | |
main() |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment