Skip to content

Instantly share code, notes, and snippets.

@rindeal
Last active April 24, 2024 08:28
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rindeal/bde5809996239815ed09f33da859e803 to your computer and use it in GitHub Desktop.
Save rindeal/bde5809996239815ed09f33da859e803 to your computer and use it in GitHub Desktop.
GSettings array manipulation made easy from CLI
#!/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