2019-08-30 11:32:14 +02:00
|
|
|
import json
|
|
|
|
import os
|
|
|
|
from pathlib import Path
|
2022-02-01 10:14:24 +01:00
|
|
|
from typing import Any, Dict, Union
|
2012-09-17 00:37:36 +02:00
|
|
|
|
2021-05-05 14:13:39 +02:00
|
|
|
from . import __version__
|
|
|
|
from .compat import is_windows
|
2021-10-06 17:27:07 +02:00
|
|
|
from .encoding import UTF8
|
2012-08-17 23:23:02 +02:00
|
|
|
|
|
|
|
|
2020-05-23 12:12:15 +02:00
|
|
|
ENV_XDG_CONFIG_HOME = 'XDG_CONFIG_HOME'
|
|
|
|
ENV_HTTPIE_CONFIG_DIR = 'HTTPIE_CONFIG_DIR'
|
|
|
|
DEFAULT_CONFIG_DIRNAME = 'httpie'
|
|
|
|
DEFAULT_RELATIVE_XDG_CONFIG_HOME = Path('.config')
|
|
|
|
DEFAULT_RELATIVE_LEGACY_CONFIG_DIR = Path('.httpie')
|
|
|
|
DEFAULT_WINDOWS_CONFIG_DIR = Path(
|
|
|
|
os.path.expandvars('%APPDATA%')) / DEFAULT_CONFIG_DIRNAME
|
|
|
|
|
|
|
|
|
2020-05-21 15:50:00 +02:00
|
|
|
def get_default_config_dir() -> Path:
|
2020-05-23 12:12:15 +02:00
|
|
|
"""
|
|
|
|
Return the path to the httpie configuration directory.
|
2020-05-21 15:50:00 +02:00
|
|
|
|
|
|
|
This directory isn't guaranteed to exist, and nor are any of its
|
2020-05-23 12:12:15 +02:00
|
|
|
ancestors (only the legacy ~/.httpie, if returned, is guaranteed to exist).
|
|
|
|
|
|
|
|
XDG Base Directory Specification support:
|
|
|
|
|
|
|
|
<https://wiki.archlinux.org/index.php/XDG_Base_Directory>
|
|
|
|
|
|
|
|
$XDG_CONFIG_HOME is supported; $XDG_CONFIG_DIRS is not
|
|
|
|
|
2020-05-21 15:50:00 +02:00
|
|
|
"""
|
2020-05-23 12:12:15 +02:00
|
|
|
# 1. explicitly set through env
|
|
|
|
env_config_dir = os.environ.get(ENV_HTTPIE_CONFIG_DIR)
|
2020-05-21 15:50:00 +02:00
|
|
|
if env_config_dir:
|
|
|
|
return Path(env_config_dir)
|
2020-05-23 12:12:15 +02:00
|
|
|
|
|
|
|
# 2. Windows
|
2020-05-21 15:50:00 +02:00
|
|
|
if is_windows:
|
2020-05-23 12:12:15 +02:00
|
|
|
return DEFAULT_WINDOWS_CONFIG_DIR
|
|
|
|
|
|
|
|
home_dir = Path.home()
|
|
|
|
|
|
|
|
# 3. legacy ~/.httpie
|
|
|
|
legacy_config_dir = home_dir / DEFAULT_RELATIVE_LEGACY_CONFIG_DIR
|
|
|
|
if legacy_config_dir.exists():
|
|
|
|
return legacy_config_dir
|
|
|
|
|
|
|
|
# 4. XDG
|
|
|
|
xdg_config_home_dir = os.environ.get(
|
2020-07-07 13:25:36 +02:00
|
|
|
ENV_XDG_CONFIG_HOME, # 4.1. explicit
|
2020-05-23 12:12:15 +02:00
|
|
|
home_dir / DEFAULT_RELATIVE_XDG_CONFIG_HOME # 4.2. default
|
|
|
|
)
|
|
|
|
return Path(xdg_config_home_dir) / DEFAULT_CONFIG_DIRNAME
|
2020-05-21 15:50:00 +02:00
|
|
|
|
|
|
|
|
|
|
|
DEFAULT_CONFIG_DIR = get_default_config_dir()
|
2012-09-17 00:37:36 +02:00
|
|
|
|
|
|
|
|
2019-12-02 17:43:16 +01:00
|
|
|
class ConfigFileError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
2022-02-01 10:14:24 +01:00
|
|
|
def read_raw_config(config_type: str, path: Path) -> Dict[str, Any]:
|
|
|
|
try:
|
|
|
|
with path.open(encoding=UTF8) as f:
|
|
|
|
try:
|
|
|
|
return json.load(f)
|
|
|
|
except ValueError as e:
|
|
|
|
raise ConfigFileError(
|
|
|
|
f'invalid {config_type} file: {e} [{path}]'
|
|
|
|
)
|
|
|
|
except FileNotFoundError:
|
|
|
|
pass
|
|
|
|
except OSError as e:
|
|
|
|
raise ConfigFileError(f'cannot read {config_type} file: {e}')
|
|
|
|
|
|
|
|
|
2012-09-17 00:37:36 +02:00
|
|
|
class BaseConfigDict(dict):
|
|
|
|
name = None
|
2013-05-13 14:47:44 +02:00
|
|
|
helpurl = None
|
2012-12-01 18:16:00 +01:00
|
|
|
about = None
|
2012-09-17 00:37:36 +02:00
|
|
|
|
2019-12-02 17:43:16 +01:00
|
|
|
def __init__(self, path: Path):
|
|
|
|
super().__init__()
|
|
|
|
self.path = path
|
2013-05-13 14:47:44 +02:00
|
|
|
|
2019-12-02 17:43:16 +01:00
|
|
|
def ensure_directory(self):
|
2021-05-31 10:10:41 +02:00
|
|
|
self.path.parent.mkdir(mode=0o700, parents=True, exist_ok=True)
|
2012-09-17 00:37:36 +02:00
|
|
|
|
2019-08-30 11:32:14 +02:00
|
|
|
def is_new(self) -> bool:
|
2019-12-02 17:43:16 +01:00
|
|
|
return not self.path.exists()
|
2012-09-17 00:37:36 +02:00
|
|
|
|
2022-02-01 10:14:24 +01:00
|
|
|
def pre_process_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
|
"""Hook for processing the incoming config data."""
|
|
|
|
return data
|
|
|
|
|
|
|
|
def post_process_data(self, data: Dict[str, Any]) -> Dict[str, Any]:
|
|
|
|
"""Hook for processing the outgoing config data."""
|
|
|
|
return data
|
|
|
|
|
2012-09-17 00:37:36 +02:00
|
|
|
def load(self):
|
2019-12-02 17:43:16 +01:00
|
|
|
config_type = type(self).__name__.lower()
|
2022-02-01 10:14:24 +01:00
|
|
|
data = read_raw_config(config_type, self.path)
|
|
|
|
if data is not None:
|
|
|
|
data = self.pre_process_data(data)
|
|
|
|
self.update(data)
|
|
|
|
|
|
|
|
def save(self, *, bump_version: bool = False):
|
|
|
|
self.setdefault('__meta__', {})
|
|
|
|
if bump_version or 'httpie' not in self['__meta__']:
|
|
|
|
self['__meta__']['httpie'] = __version__
|
2013-05-13 14:47:44 +02:00
|
|
|
if self.helpurl:
|
|
|
|
self['__meta__']['help'] = self.helpurl
|
2012-12-01 18:16:00 +01:00
|
|
|
|
|
|
|
if self.about:
|
|
|
|
self['__meta__']['about'] = self.about
|
|
|
|
|
2019-12-02 17:43:16 +01:00
|
|
|
self.ensure_directory()
|
|
|
|
|
2020-07-07 13:25:36 +02:00
|
|
|
json_string = json.dumps(
|
2022-02-01 10:14:24 +01:00
|
|
|
obj=self.post_process_data(self),
|
2020-07-07 13:25:36 +02:00
|
|
|
indent=4,
|
|
|
|
sort_keys=True,
|
|
|
|
ensure_ascii=True,
|
|
|
|
)
|
2021-08-06 18:04:08 +02:00
|
|
|
self.path.write_text(json_string + '\n', encoding=UTF8)
|
2012-09-17 00:37:36 +02:00
|
|
|
|
2022-02-01 10:14:24 +01:00
|
|
|
@property
|
|
|
|
def version(self):
|
|
|
|
return self.get(
|
|
|
|
'__meta__', {}
|
|
|
|
).get('httpie', __version__)
|
|
|
|
|
2012-09-17 00:37:36 +02:00
|
|
|
|
|
|
|
class Config(BaseConfigDict):
|
2019-12-02 17:43:16 +01:00
|
|
|
FILENAME = 'config.json'
|
2012-09-17 00:37:36 +02:00
|
|
|
DEFAULTS = {
|
2012-09-17 02:15:00 +02:00
|
|
|
'default_options': []
|
2012-09-17 00:37:36 +02:00
|
|
|
}
|
2012-08-17 23:23:02 +02:00
|
|
|
|
2019-08-30 11:32:14 +02:00
|
|
|
def __init__(self, directory: Union[str, Path] = DEFAULT_CONFIG_DIR):
|
|
|
|
self.directory = Path(directory)
|
2019-12-02 17:43:16 +01:00
|
|
|
super().__init__(path=self.directory / self.FILENAME)
|
|
|
|
self.update(self.DEFAULTS)
|
2019-08-30 11:32:14 +02:00
|
|
|
|
|
|
|
@property
|
|
|
|
def default_options(self) -> list:
|
|
|
|
return self['default_options']
|
2021-11-30 09:12:51 +01:00
|
|
|
|
|
|
|
@property
|
|
|
|
def plugins_dir(self) -> Path:
|
|
|
|
return Path(self.get('plugins_dir', self.directory / 'plugins')).resolve()
|