Hide keyboard shortcuts

Hot-keys on this page

r m x p   toggle line displays

j k   next/prev highlighted chunk

0   (zero) top of page

1   (one) first highlighted chunk

1#!/usr/bin/env python3 

2# -*- coding: utf-8; mode: python; -*- 

3# Copyright © 2021 Pradyumna Paranjape 

4# 

5# This file is part of xdgpspconf. 

6# 

7# xdgpspconf is free software: you can redistribute it and/or modify 

8# it under the terms of the GNU Lesser General Public License as published by 

9# the Free Software Foundation, either version 3 of the License, or 

10# (at your option) any later version. 

11# 

12# xdgpspconf is distributed in the hope that it will be useful, 

13# but WITHOUT ANY WARRANTY; without even the implied warranty of 

14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

15# GNU Lesser General Public License for more details. 

16# 

17# You should have received a copy of the GNU Lesser General Public License 

18# along with xdgpspconf. If not, see <https://www.gnu.org/licenses/>. 

19# 

20""" 

21Read/Write configurations. 

22 

23""" 

24 

25import configparser 

26from pathlib import Path 

27from typing import Any, Dict 

28 

29import toml 

30import yaml 

31 

32from xdgpspconf.errors import BadConf 

33 

34 

35def parse_yaml(config: Path) -> Dict[str, Any]: 

36 """ 

37 Read configuration. 

38 

39 Args: 

40 config: path to yaml config file 

41 

42 Returns: 

43 parsed configuration 

44 """ 

45 with open(config, 'r') as rcfile: 

46 conf: Dict[str, Any] = yaml.safe_load(rcfile) 

47 if conf is None: # pragma: no cover 

48 raise yaml.YAMLError 

49 return conf 

50 

51 

52def parse_toml(config: Path, section: str = None) -> Dict[str, Any]: 

53 """ 

54 Read configuration. 

55 

56 Args: 

57 config: path to yaml config file 

58 section: section in ``pyproject.toml`` corresponding to project 

59 

60 Returns: 

61 parsed configuration 

62 """ 

63 if section is not None: 

64 with open(config, 'r') as rcfile: 

65 conf: Dict[str, Any] = toml.load(rcfile).get(section, {}) 

66 return conf 

67 with open(config, 'r') as rcfile: 

68 conf = dict(toml.load(rcfile)) 

69 if conf is None: # pragma: no cover 

70 raise toml.TomlDecodeError 

71 return conf 

72 

73 

74def parse_ini(config: Path, section: str = None) -> Dict[str, Any]: 

75 """ 

76 Read configuration. 

77 

78 

79 Args: 

80 config: path to yaml config file 

81 section: section in ``pyproject.toml`` corresponding to project 

82 

83 Returns: 

84 parsed configuration 

85 """ 

86 parser = configparser.ConfigParser() 

87 parser.read(config) 

88 if section is not None: 

89 return { 

90 pspcfg.replace(f'{section}.', ''): dict(parser.items(pspcfg)) 

91 for pspcfg in parser.sections() if f'{section}.' in pspcfg 

92 } 

93 return { 

94 pspcfg: dict(parser.items(pspcfg)) 

95 for pspcfg in parser.sections() 

96 } # pragma: no cover 

97 

98 

99def parse_rc(config: Path, project: str = None) -> Dict[str, Any]: 

100 """ 

101 Parse rc file. 

102 

103 Args: 

104 config: path to configuration file 

105 project: name of project (to locate subsection from pyptoject.toml) 

106 

107 Returns: 

108 configuration sections 

109 

110 Raises: 

111 BadConf: Bad configuration 

112 

113 """ 

114 if config.name == 'setup.cfg': 

115 # declared inside setup.cfg 

116 return parse_ini(config, section=project) 

117 if config.name == 'pyproject.toml': 

118 # declared inside pyproject.toml 

119 return parse_toml(config, section=project) 

120 try: 

121 # yaml configuration format 

122 return parse_yaml(config) 

123 except yaml.YAMLError: 

124 try: 

125 # toml configuration format 

126 return parse_toml(config) 

127 except toml.TomlDecodeError: 

128 try: 

129 # try generic config-parser 

130 return parse_ini(config) 

131 except configparser.Error: 

132 raise BadConf(config_file=config) from None 

133 

134 

135def write_yaml(data: Dict[str, Any], 

136 config: Path, 

137 force: str = 'fail') -> bool: 

138 """ 

139 Write data to configuration file. 

140 

141 Args: 

142 data: serial data to save 

143 config: configuration file path 

144 force: force overwrite {'overwrite','update','fail'} 

145 

146 Returns: 

147 write success 

148 

149 """ 

150 old_data: Dict[str, Any] = {} 

151 if config.is_file(): 

152 # file already exists 

153 if force == 'fail': 

154 return False 

155 if force == 'update': 

156 old_data = parse_yaml(config) 

157 data = {**old_data, **data} 

158 with open(config, 'w') as rcfile: 

159 yaml.dump(data, rcfile) 

160 return True 

161 

162 

163def write_toml(data: Dict[str, Any], 

164 config: Path, 

165 force: str = 'fail') -> bool: 

166 """ 

167 Write data to configuration file. 

168 

169 Args: 

170 data: serial data to save 

171 config: configuration file path 

172 force: force overwrite {'overwrite', 'update', 'fail'} 

173 

174 Returns: 

175 write success 

176 

177 """ 

178 old_data: Dict[str, Any] = {} 

179 if config.is_file(): 

180 # file already exists 

181 if force == 'fail': 

182 return False 

183 if force == 'update': 

184 old_data = parse_toml(config) 

185 data = {**old_data, **data} 

186 with open(config, 'w') as rcfile: 

187 toml.dump(data, rcfile) 

188 return True 

189 

190 

191def write_ini(data: Dict[str, Any], config: Path, force: str = 'fail') -> bool: 

192 """ 

193 Write data to configuration file. 

194 

195 Args: 

196 data: serial data to save 

197 config: configuration file path 

198 force: force overwrite {'overwrite', 'update', 'fail'} 

199 

200 Returns: 

201 write success 

202 

203 """ 

204 old_data: Dict[str, Any] = {} 

205 if config.is_file(): 

206 # file already exists 

207 if force == 'fail': 

208 return False 

209 if force == 'update': 

210 old_data = parse_ini(config) 

211 data = {**old_data, **data} 

212 parser = configparser.ConfigParser() 

213 parser.update(data) 

214 with open(config, 'w') as rcfile: 

215 parser.write(rcfile) 

216 return True 

217 

218 

219def write_rc(data: Dict[str, Any], config: Path, force: str = 'fail') -> bool: 

220 """ 

221 Write data to configuration file. 

222 

223 Args: 

224 data: serial data to save 

225 config: configuration file path 

226 force: force overwrite {'overwrite', 'update', 'fail'} 

227 

228 Returns: 

229 write success 

230 

231 """ 

232 if config.suffix in ('.conf', '.cfg', '.ini'): 

233 return write_ini(data, config, force) 

234 if config.suffix == '.toml': 

235 return write_toml(data, config, force) 

236 # assume yaml 

237 return write_yaml(data, config, force)