Last active
April 24, 2024 08:28
-
-
Save rindeal/bde5809996239815ed09f33da859e803 to your computer and use it in GitHub Desktop.
GSettings array manipulation made easy from CLI
This file contains 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
#!/usr/bin/env python3 | |
# SPDX-FileCopyrightText: ANNO DOMINI 2024 Jan Chren (rindeal) <dev.rindeal(a)gmail.com> | |
# SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only | |
import sys | |
assert sys.version_info >= (3, 11) | |
import argparse | |
import dataclasses | |
import enum | |
from typing import List | |
from gi.repository import Gio, GLib | |
PROG = "gsettings-array" | |
class ArgDest(enum.StrEnum): | |
CMD = 'COMMAND' | |
SCHEMA = 'SCHEMA' | |
KEY = 'KEY' | |
ITEMS = 'ARRAY_ITEM' | |
CMD_LS = 'ls' | |
CMD_ADD = 'add' | |
CMD_RM = 'rm' | |
CMD_SORT = 'sort' | |
CMD_DEDUP = 'dedup' | |
OPT_DEDUP = '--dedup' | |
OPT_SORT = '--sort' | |
@dataclasses.dataclass(init=False) | |
class ParsedArgs: | |
cmd: str | |
schema: str | |
key: str | |
items: List[str] | |
opt_sort: bool | |
opt_dedup: bool | |
def __init__(self, args_ns: argparse.Namespace): | |
self.cmd = getattr(args_ns, ArgDest.CMD) | |
self.schema = getattr(args_ns, ArgDest.SCHEMA) | |
self.key = getattr(args_ns, ArgDest.KEY) | |
self.items = getattr(args_ns, ArgDest.ITEMS, list()) | |
self.opt_sort = getattr(args_ns, ArgDest.OPT_SORT, bool(False)) | |
self.opt_dedup = getattr(args_ns, ArgDest.OPT_DEDUP, bool(False)) | |
class App: | |
@staticmethod | |
def _parse_args(args: List[str] | None = None) -> ParsedArgs: | |
par = argparse.ArgumentParser( | |
PROG, | |
description='Manipulate GSettings arrays.', | |
epilog=f"\n {PROG} ".join([ | |
'''examples:''', | |
'''add --dedup "org.gnome.desktop.input-sources" "sources" "('xkb', 'us+cz_sk_de')"''', | |
]), | |
formatter_class=argparse.RawDescriptionHelpFormatter | |
) | |
subpar = par.add_subparsers(dest=ArgDest.CMD, metavar=ArgDest.CMD, help="Action to perform on the array", required=True) | |
class cmd_p: | |
ls = subpar.add_parser(ArgDest.CMD_LS, help="List array items one per line") | |
add = subpar.add_parser(ArgDest.CMD_ADD, help="Add one or more array items") | |
rm = subpar.add_parser(ArgDest.CMD_RM, help="Remove one or more array items") | |
dedup = subpar.add_parser(ArgDest.CMD_DEDUP, help="Remove duplicated array items") | |
sort = subpar.add_parser(ArgDest.CMD_SORT, help="Sort array items") | |
for p in (cmd_p.ls, cmd_p.add, cmd_p.rm, cmd_p.dedup, cmd_p.sort): | |
p.add_argument(ArgDest.SCHEMA, help="GSettings schema, eg. `org.gnome.desktop`") | |
p.add_argument(ArgDest.KEY, help="GSettings key, eg. `foo-bar`") | |
for p in (cmd_p.add, cmd_p.rm): | |
p.add_argument(ArgDest.OPT_DEDUP, help="Also run `dedup` command thereafter", dest=ArgDest.OPT_DEDUP, action='store_true') | |
p.add_argument(ArgDest.OPT_SORT, help="Also run `sort` command thereafter", dest=ArgDest.OPT_SORT, action='store_true') | |
p.add_argument(metavar=ArgDest.ITEMS, help="Value formatted according to the array's inner type", dest=ArgDest.ITEMS, nargs=argparse.ONE_OR_MORE) | |
return ParsedArgs(par.parse_args(args)) | |
@staticmethod | |
def _maybe_get_schema(schema_str: str) -> Gio.SettingsSchema | None: | |
source = Gio.SettingsSchemaSource.get_default() | |
schema = Gio.SettingsSchemaSource.lookup(source, schema_str, True) | |
return schema | |
@classmethod | |
def _main(cls, args: ParsedArgs) -> int | str: | |
schema = cls._maybe_get_schema(args.schema) | |
if schema is None: | |
return f"Error: Schema not found: schema=`{args.schema}`\n{args}" | |
if not schema.has_key(args.key): | |
return f"Error: Key not found: key=`{args.key}`\n{args}" | |
array_schema = schema.get_key(args.key) | |
array_type = array_schema.get_value_type() | |
if not array_type.is_array(): | |
return f"Error: Key not an array: type=`{array_type.dup_string()}`\n{args}" | |
# all checks passed, now act | |
gsettings = Gio.Settings.new(args.schema) | |
cmd_mutates_value = args.cmd not in (ArgDest.CMD_LS, ) | |
if cmd_mutates_value: | |
print("Old value:", gsettings[args.key]) | |
parsed_array = GLib.Variant.parse(array_type, "[%s]" % ",".join(args.items)) | |
if ArgDest.CMD_LS == args.cmd: | |
for item in gsettings[args.key]: | |
print(item) | |
if ArgDest.CMD_ADD == args.cmd: | |
gsettings[args.key] += parsed_array | |
if ArgDest.CMD_RM == args.cmd: | |
gsettings[args.key] = [x for x in gsettings[args.key] if x not in parsed_array] | |
if ArgDest.CMD_DEDUP == args.cmd or args.opt_dedup: | |
# use dict.fromkeys() since set() doesn't preserve order | |
gsettings[args.key] = list(dict.fromkeys(gsettings[args.key]).keys()) | |
if ArgDest.CMD_SORT == args.cmd or args.opt_sort: | |
gsettings[args.key] = sorted(gsettings[args.key]) | |
# Writes made to a GSettings are handled asynchronously. | |
# Without sync(), new changes won't take effect at all! | |
gsettings.sync() | |
if cmd_mutates_value: | |
print("New value:", gsettings[args.key]) | |
return 0 | |
@classmethod | |
def main(cls, args: List[str] | None = None) -> int | str: | |
return cls._main(cls._parse_args(args)) | |
if __name__ == '__main__': | |
app = App() | |
exit(app.main()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment