decorators: Use memoize() to implement all factory functions
[git-cola.git] / cola / gitcfg.py
blob962fc5e9543e892908c5ced090e3adabb2f347d5
1 import os
2 import sys
3 import copy
4 import fnmatch
6 from cola import core
7 from cola import gitcmd
8 from cola.decorators import memoize
11 @memoize
12 def instance():
13 """Return a static GitConfig instance."""
14 return GitConfig()
17 def _appendifexists(category, path, result):
18 try:
19 mtime = os.stat(path).st_mtime
20 result.append((category, path, mtime))
21 except OSError:
22 pass
25 def _stat_info():
26 data = []
27 # Try /etc/gitconfig as a fallback for the system config
28 _appendifexists('system', '/etc/gitconfig', data)
29 _appendifexists('user', os.path.expanduser('~/.gitconfig'), data)
30 _appendifexists('repo', gitcmd.instance().git_path('config'), data)
31 return data
34 class GitConfig(object):
35 """Encapsulate access to git-config values."""
37 def __init__(self):
38 self.git = gitcmd.instance()
39 self._system = {}
40 self._user = {}
41 self._repo = {}
42 self._all = {}
43 self._cache_key = None
44 self._configs = []
45 self._config_files = {}
46 self._find_config_files()
48 def reset(self):
49 self._system = {}
50 self._user = {}
51 self._repo = {}
52 self._all = {}
53 self._configs = []
54 self._config_files = {}
55 self._find_config_files()
57 def user(self):
58 return copy.deepcopy(self._user)
60 def repo(self):
61 return copy.deepcopy(self._repo)
63 def all(self):
64 return copy.deepcopy(self._all)
66 def _find_config_files(self):
67 """
68 Classify git config files into 'system', 'user', and 'repo'.
70 Populates self._configs with a list of the files in
71 reverse-precedence order. self._config_files is populated with
72 {category: path} where category is one of 'system', 'user', or 'repo'.
74 """
75 # Try the git config in git's installation prefix
76 statinfo = _stat_info()
77 self._configs = map(lambda x: x[1], statinfo)
78 self._config_files = {}
79 for (cat, path, mtime) in statinfo:
80 self._config_files[cat] = path
82 def update(self):
83 """Read config values from git."""
84 if self._cached():
85 return
86 self._read_configs()
88 def _cached(self):
89 """
90 Return True when the cache matches.
92 Updates the cache and returns False when the cache does not match.
94 """
95 cache_key = _stat_info()
96 if not self._cache_key or cache_key != self._cache_key:
97 self._cache_key = cache_key
98 return False
99 return True
101 def _read_configs(self):
102 """Read git config value into the system, user and repo dicts."""
103 self.reset()
105 if 'system' in self._config_files:
106 self._system = self.read_config(self._config_files['system'])
108 if 'user' in self._config_files:
109 self._user = self.read_config(self._config_files['user'])
111 if 'repo' in self._config_files:
112 self._repo = self.read_config(self._config_files['repo'])
114 self._all = {}
115 for dct in (self._system, self._user, self._repo):
116 self._all.update(dct)
118 def read_config(self, path):
119 """Return git config data from a path as a dictionary."""
120 dest = {}
121 args = ('--null', '--file', path, '--list')
122 config_lines = self.git.config(*args).split('\0')
123 for line in config_lines:
124 try:
125 k, v = line.split('\n')
126 except:
127 # the user has an invalid entry in their git config
128 continue
129 v = core.decode(v)
130 if v == 'yes':
131 v = 'true'
132 elif v == 'no':
133 v = 'false'
134 if v == 'true' or v == 'false':
135 v = bool(eval(v.title()))
136 try:
137 v = int(eval(v))
138 except:
139 pass
140 dest[k] = v
141 return dest
143 def get(self, key, default=None):
144 """Return the string value for a config key."""
145 self.update()
146 return self._all.get(key, default)
148 def find(self, pat):
149 result = {}
150 for key, val in self._all.items():
151 if fnmatch.fnmatch(key, pat):
152 result[key] = val
153 return result
155 def get_encoding(self, default='utf-8'):
156 return self.get('gui.encoding', default=default)
158 guitool_opts = ('cmd', 'needsfile', 'noconsole', 'norescan', 'confirm',
159 'argprompt', 'revprompt', 'revunmerged', 'title', 'prompt')
161 def get_guitool_opts(self, name):
162 """Return the guitool.<name> namespace as a dict"""
163 keyprefix = 'guitool.' + name + '.'
164 opts = {}
165 for cfg in self.guitool_opts:
166 value = self.get(keyprefix + cfg)
167 if value is None:
168 continue
169 opts[cfg] = value
170 return opts
172 def get_guitool_names(self):
173 cmds = []
174 guitools = self.find('guitool.*.cmd')
175 for name, cmd in guitools.items():
176 name = name[len('guitool.'):-len('.cmd')]
177 cmds.append(name)
178 return cmds