class XDGConfig(UserConfig):
"Base class with XDG var support"
meta__xdg_file_fmt = [
"yml",
"yaml",
"json",
"toml",
# "ini",
]
XDG_CONFIG_HOME = Field(default="$HOME/.config", cast=cast_env) # Like: /etc
XDG_DATA_HOME = Field(
default="$HOME/.local/share", cast=cast_env
) # Like: /usr/share
XDG_CACHE_HOME = Field(default="$HOME/.cache", cast=cast_env) # Like: /var/cache
XDG_STATE_HOME = Field(
default="$HOME/.local/state", cast=cast_env
) # Like: /var/lib
XDG_RUNTIME_DIR = Field(
default="/run/user/$UID", cast=cast_env
) # Like: /run/user/$UID for pam_systemd
XDG_CONFIG_DIRS = Field(default="/etc/xdg", cast=cast_env_colon)
XDG_DATA_DIRS = Field(default="/usr/local/share:/usr/share", cast=cast_env_colon)
# Legacy commands
def get_config_file(self, name=None):
"""Get config file path."""
return self.get_file("XDG_CONFIG_HOME", name)
def get_config_dir(self, name=None):
"""Get config directory path."""
return self.get_dir("XDG_CONFIG_HOME", name)
# Internal API
def _parse_path(self, values, name=None, prefix=None, extensions=None):
"""Parse and expand path."""
found = False
extensions = extensions or []
ret = []
for path in values:
path = os.path.expandvars(path)
if isinstance(prefix, str):
path = os.path.join(path, prefix)
path = path.rstrip("/")
if extensions:
# File lookup only
if name:
# Validate name doesn't already end with any extension
name, file_extension = os.path.splitext(name)
if file_extension:
file_extension = file_extension.lstrip(".")
if file_extension not in extensions:
name = f"{name}.{file_extension}"
else:
extensions = [file_extension]
default_path = os.path.join(path, f"{name}.{extensions[0]}")
found = False
for ext in extensions:
if found is False:
fname = f"{name}.{ext}"
fpath = os.path.join(path, fname)
if os.path.isfile(fpath):
path = fpath
found = True
else:
default_path = f"{path}.{extensions[0]}"
found = False
for ext in extensions:
if found is False:
fpath = f"{path}.{ext}"
if os.path.isfile(fpath):
path = fpath
found = True
path = path if found else default_path
else:
# Only for directory lookup
if name:
path = os.path.join(path, f"{name}")
else:
path = f"{path}"
path = Path(path)
ret.append(path)
# print ("MATCHED", found, ret)
return ret
def read_file(self, item, name=None, missing_ok=True):
"Read a file content"
# Fetch best file
files = self.get_file(item, name=name)
if not isinstance(files, list):
files = [files]
# Return error if no files
if not files:
if name:
raise XDGException(f"Could not find any file in {item} called {name}")
raise XDGException(f"Could not find any file in {item}")
# Return first file
out = None
found = False
for file in files:
file = str(file)
if not os.path.isfile(file):
continue
found = True
logger.info("Read file %s", file)
fcontent = read_file(file)
if file.endswith("yaml") or file.endswith("yml"):
out = from_yaml(fcontent)
elif file.endswith("json"):
out = from_json(fcontent)
elif file.endswith("toml"):
raise NotImplementedError("Toml support not implemented yet")
elif file.endswith("ini"):
raise NotImplementedError("Ini support is not implemented yet")
else:
raise NotImplementedError(f"Format not supported: {file}")
break
if not found:
if not missing_ok:
raise XDGException(f"Could not find any file in {str(files[0])}")
return out
def write_file(self, item, data, name=None):
"Write content to file"
# Fetch best file
files = self.get_file(item, name=name)
if not isinstance(files, list):
files = [files]
# Return error if no files
if not files:
if name:
raise XDGException(f"Could not find any file in {item} called {name}")
raise XDGException(f"Could not find any file in {item}")
# Write only on the first file
files = [files[0]]
for file in files:
file = str(file)
# fcontent = read_file(file)
if file.endswith("yaml") or file.endswith("yml"):
out = to_yaml(data)
elif file.endswith("json"):
out = to_json(data)
elif file.endswith("toml"):
raise NotImplementedError("Toml support not implemented yet")
elif file.endswith("ini"):
raise NotImplementedError("Ini support is not implemented yet")
else:
raise NotImplementedError(f"Format not supported: {file}")
logger.info("Write data in file %s", file)
write_file(file, out)
break
def get_file(self, item, name=None):
"""Get file path for given XDG item."""
# Fetch current value
if not hasattr(self, item.upper()):
choices = list(self.get_values().keys())
raise XDGException(f"Unknown item: {item}, choose one of: {choices}")
values = self[item]
# Convert to list
return_one = True
if isinstance(values, list):
return_one = False
else:
values = [values]
assert isinstance(values, list), f"Got: {values}"
# Prepare extensions
extensions = list(self.meta__xdg_file_fmt)
if extensions: # Only append first extension if list is not empty
extensions.append(extensions[0])
# Parse paths
app_name = self.query_cfg("app_name", default=None)
if not isinstance(app_name, str):
root = self.get_hierarchy()[-1]
app_name = root.__class__.__name__
assert app_name
# print ("=== PARSE PATH", values, name, app_name, extensions)
ret = self._parse_path(
values, name=name, prefix=app_name, extensions=extensions
)
if return_one is True:
return ret[0] if len(ret) > 0 else None
return ret
def get_dir(self, item, name=None):
"""Get directory path for given XDG item."""
# Fetch current value
if not hasattr(self, item.upper()):
choices = list(self.get_values().keys())
raise XDGException(f"Unknown item: {item}, choose one of: {choices}")
values = self[item]
# Convert to list
return_one = True
if isinstance(values, list):
return_one = False
else:
values = [values]
assert isinstance(values, list), f"Got: {values}"
# Prepare extensions
extensions = []
# Parse paths
app_name = self.query_cfg("app_name", default=None)
if not app_name or not isinstance(app_name, str):
root = self.get_hierarchy()[-1]
app_name = root.__class__.__name__
ret = self._parse_path(
values, name=name, prefix=app_name, extensions=extensions
)
if return_one is True:
return ret[0] if len(ret) > 0 else None
return ret