summaryrefslogtreecommitdiff
path: root/git/config.py
diff options
context:
space:
mode:
Diffstat (limited to 'git/config.py')
-rw-r--r--git/config.py204
1 files changed, 103 insertions, 101 deletions
diff --git a/git/config.py b/git/config.py
index 285ade6b..15aa76f0 100644
--- a/git/config.py
+++ b/git/config.py
@@ -17,7 +17,9 @@ from git.util import LockFile
__all__ = ('GitConfigParser', 'SectionConstraint')
+
class MetaParserBuilder(type):
+
"""Utlity class wrapping base-class methods into decorators that assure read-only properties"""
def __new__(metacls, name, bases, clsdict):
"""
@@ -27,7 +29,7 @@ class MetaParserBuilder(type):
if kmm in clsdict:
mutating_methods = clsdict[kmm]
for base in bases:
- methods = ( t for t in inspect.getmembers(base, inspect.ismethod) if not t[0].startswith("_") )
+ methods = (t for t in inspect.getmembers(base, inspect.ismethod) if not t[0].startswith("_"))
for name, method in methods:
if name in clsdict:
continue
@@ -35,30 +37,32 @@ class MetaParserBuilder(type):
if name in mutating_methods:
method_with_values = set_dirty_and_flush_changes(method_with_values)
# END mutating methods handling
-
+
clsdict[name] = method_with_values
# END for each name/method pair
# END for each base
# END if mutating methods configuration is set
-
+
new_type = super(MetaParserBuilder, metacls).__new__(metacls, name, bases, clsdict)
return new_type
-
-
+
def needs_values(func):
"""Returns method assuring we read values (on demand) before we try to access them"""
+
def assure_data_present(self, *args, **kwargs):
self.read()
return func(self, *args, **kwargs)
# END wrapper method
assure_data_present.__name__ = func.__name__
return assure_data_present
-
+
+
def set_dirty_and_flush_changes(non_const_func):
"""Return method that checks whether given non constant function may be called.
If so, the instance will be set dirty.
Additionally, we flush the changes right to disk"""
+
def flush_changes(self, *args, **kwargs):
rval = non_const_func(self, *args, **kwargs)
self.write()
@@ -66,64 +70,65 @@ def set_dirty_and_flush_changes(non_const_func):
# END wrapper method
flush_changes.__name__ = non_const_func.__name__
return flush_changes
-
+
class SectionConstraint(object):
- """Constrains a ConfigParser to only option commands which are constrained to
+
+ """Constrains a ConfigParser to only option commands which are constrained to
always use the section we have been initialized with.
-
+
It supports all ConfigParser methods that operate on an option"""
__slots__ = ("_config", "_section_name")
- _valid_attrs_ = ("get_value", "set_value", "get", "set", "getint", "getfloat", "getboolean", "has_option",
+ _valid_attrs_ = ("get_value", "set_value", "get", "set", "getint", "getfloat", "getboolean", "has_option",
"remove_section", "remove_option", "options")
-
+
def __init__(self, config, section):
self._config = config
self._section_name = section
-
+
def __getattr__(self, attr):
if attr in self._valid_attrs_:
return lambda *args, **kwargs: self._call_config(attr, *args, **kwargs)
- return super(SectionConstraint,self).__getattribute__(attr)
-
+ return super(SectionConstraint, self).__getattribute__(attr)
+
def _call_config(self, method, *args, **kwargs):
- """Call the configuration at the given method which must take a section name
+ """Call the configuration at the given method which must take a section name
as first argument"""
return getattr(self._config, method)(self._section_name, *args, **kwargs)
-
+
@property
def config(self):
"""return: Configparser instance we constrain"""
return self._config
-
+
class GitConfigParser(cp.RawConfigParser, object):
+
"""Implements specifics required to read git style configuration files.
-
+
This variation behaves much like the git.config command such that the configuration
will be read on demand based on the filepath given during initialization.
-
- The changes will automatically be written once the instance goes out of scope, but
+
+ The changes will automatically be written once the instance goes out of scope, but
can be triggered manually as well.
-
- The configuration file will be locked if you intend to change values preventing other
+
+ The configuration file will be locked if you intend to change values preventing other
instances to write concurrently.
-
+
:note:
The config is case-sensitive even when queried, hence section and option names
must match perfectly."""
__metaclass__ = MetaParserBuilder
-
-
+
#{ Configuration
# The lock type determines the type of lock to use in new configuration readers.
# They must be compatible to the LockFile interface.
# A suitable alternative would be the BlockingLockFile
t_lock = LockFile
re_comment = re.compile('^\s*[#;]')
-
- #} END configuration
-
+
+ #} END configuration
+
OPTCRE = re.compile(
r'\s*(?P<option>[^:=\s][^:=]*)' # very permissive, incuding leading whitespace
r'\s*(?P<vi>[:=])\s*' # any number of space/tab,
@@ -132,75 +137,74 @@ class GitConfigParser(cp.RawConfigParser, object):
# by any # space/tab
r'(?P<value>.*)$' # everything up to eol
)
-
+
# list of RawConfigParser methods able to change the instance
_mutating_methods_ = ("add_section", "remove_section", "remove_option", "set")
- __slots__ = ("_sections", "_defaults", "_file_or_files", "_read_only","_is_initialized", '_lock')
-
+ __slots__ = ("_sections", "_defaults", "_file_or_files", "_read_only", "_is_initialized", '_lock')
+
def __init__(self, file_or_files, read_only=True):
- """Initialize a configuration reader to read the given file_or_files and to
+ """Initialize a configuration reader to read the given file_or_files and to
possibly allow changes to it by setting read_only False
-
+
:param file_or_files:
A single file path or file objects or multiple of these
-
+
:param read_only:
If True, the ConfigParser may only read the data , but not change it.
If False, only a single file path or file object may be given."""
super(GitConfigParser, self).__init__()
- # initialize base with ordered dictionaries to be sure we write the same
- # file back
+ # initialize base with ordered dictionaries to be sure we write the same
+ # file back
self._sections = OrderedDict()
self._defaults = OrderedDict()
-
+
self._file_or_files = file_or_files
self._read_only = read_only
self._is_initialized = False
self._lock = None
-
+
if not read_only:
if isinstance(file_or_files, (tuple, list)):
raise ValueError("Write-ConfigParsers can operate on a single file only, multiple files have been passed")
# END single file check
-
+
if not isinstance(file_or_files, basestring):
file_or_files = file_or_files.name
# END get filename from handle/stream
# initialize lock base - we want to write
self._lock = self.t_lock(file_or_files)
-
+
self._lock._obtain_lock()
# END read-only check
-
-
+
def __del__(self):
"""Write pending changes if required and release locks"""
# checking for the lock here makes sure we do not raise during write()
# in case an invalid parser was created who could not get a lock
if self.read_only or not self._lock._has_lock():
return
-
+
try:
try:
self.write()
- except IOError,e:
+ except IOError, e:
print "Exception during destruction of GitConfigParser: %s" % str(e)
finally:
self._lock._release_lock()
-
+
def optionxform(self, optionstr):
"""Do not transform options in any way when writing"""
return optionstr
-
+
def _read(self, fp, fpname):
"""A direct copy of the py2.4 version of the super class's _read method
to assure it uses ordered dicts. Had to change one line to make it work.
-
- Future versions have this fixed, but in fact its quite embarassing for the
+
+ Future versions have this fixed, but in fact its quite embarassing for the
guys not to have done it right in the first place !
-
+
Removed big comments to make it more compact.
-
+
Made sure it ignores initial whitespace as git uses tabs"""
cursect = None # None, or a dictionary
optname = None
@@ -242,7 +246,7 @@ class GitConfigParser(cp.RawConfigParser, object):
optname, vi, optval = mo.group('option', 'vi', 'value')
if vi in ('=', ':') and ';' in optval:
pos = optval.find(';')
- if pos != -1 and optval[pos-1].isspace():
+ if pos != -1 and optval[pos - 1].isspace():
optval = optval[:pos]
optval = optval.strip()
if optval == '""':
@@ -253,28 +257,27 @@ class GitConfigParser(cp.RawConfigParser, object):
if not e:
e = cp.ParsingError(fpname)
e.append(lineno, repr(line))
- # END
- # END ?
+ # END
+ # END ?
# END ?
- # END while reading
+ # END while reading
# if any parsing errors occurred, raise an exception
if e:
raise e
-
-
+
def read(self):
- """Reads the data stored in the files we have been initialized with. It will
+ """Reads the data stored in the files we have been initialized with. It will
ignore files that cannot be read, possibly leaving an empty configuration
-
+
:return: Nothing
:raise IOError: if a file cannot be handled"""
if self._is_initialized:
return
-
+
files_to_read = self._file_or_files
if not isinstance(files_to_read, (tuple, list)):
- files_to_read = [ files_to_read ]
-
+ files_to_read = [files_to_read]
+
for file_object in files_to_read:
fp = file_object
close_fp = False
@@ -283,10 +286,10 @@ class GitConfigParser(cp.RawConfigParser, object):
try:
fp = open(file_object)
close_fp = True
- except IOError,e:
+ except IOError, e:
continue
# END fp handling
-
+
try:
self._read(fp, fp.name)
finally:
@@ -295,9 +298,9 @@ class GitConfigParser(cp.RawConfigParser, object):
# END read-handling
# END for each file object to read
self._is_initialized = True
-
+
def _write(self, fp):
- """Write an .ini-format representation of the configuration state in
+ """Write an .ini-format representation of the configuration state in
git compatible format"""
def write_section(name, section_dict):
fp.write("[%s]\n" % name)
@@ -305,29 +308,28 @@ class GitConfigParser(cp.RawConfigParser, object):
if key != "__name__":
fp.write("\t%s = %s\n" % (key, str(value).replace('\n', '\n\t')))
# END if key is not __name__
- # END section writing
-
+ # END section writing
+
if self._defaults:
write_section(cp.DEFAULTSECT, self._defaults)
- map(lambda t: write_section(t[0],t[1]), self._sections.items())
+ map(lambda t: write_section(t[0], t[1]), self._sections.items())
-
@needs_values
def write(self):
"""Write changes to our file, if there are changes at all
-
- :raise IOError: if this is a read-only writer instance or if we could not obtain
+
+ :raise IOError: if this is a read-only writer instance or if we could not obtain
a file lock"""
self._assure_writable("write")
-
+
fp = self._file_or_files
close_fp = False
-
+
# we have a physical file on disk, so get a lock
if isinstance(fp, (basestring, file)):
self._lock._obtain_lock()
# END get lock for physical files
-
+
if not hasattr(fp, "seek"):
fp = open(self._file_or_files, "w")
close_fp = True
@@ -336,9 +338,9 @@ class GitConfigParser(cp.RawConfigParser, object):
# make sure we do not overwrite into an existing file
if hasattr(fp, 'truncate'):
fp.truncate()
- #END
+ #END
# END handle stream or file
-
+
# WRITE DATA
try:
self._write(fp)
@@ -346,33 +348,33 @@ class GitConfigParser(cp.RawConfigParser, object):
if close_fp:
fp.close()
# END data writing
-
- # we do not release the lock - it will be done automatically once the
+
+ # we do not release the lock - it will be done automatically once the
# instance vanishes
-
+
def _assure_writable(self, method_name):
if self.read_only:
raise IOError("Cannot execute non-constant method %s.%s" % (self, method_name))
-
+
@needs_values
@set_dirty_and_flush_changes
def add_section(self, section):
"""Assures added options will stay in order"""
super(GitConfigParser, self).add_section(section)
self._sections[section] = OrderedDict()
-
+
@property
def read_only(self):
""":return: True if this instance may change the configuration file"""
return self._read_only
-
- def get_value(self, section, option, default = None):
+
+ def get_value(self, section, option, default=None):
"""
:param default:
- If not None, the given default value will be returned in case
+ If not None, the given default value will be returned in case
the option did not exist
:return: a properly typed value, either int, float or string
-
+
:raise TypeError: in case the value could not be understood
Otherwise the exceptions known to the ConfigParser will be raised."""
try:
@@ -381,44 +383,44 @@ class GitConfigParser(cp.RawConfigParser, object):
if default is not None:
return default
raise
-
- types = ( long, float )
+
+ types = (long, float)
for numtype in types:
try:
- val = numtype( valuestr )
+ val = numtype(valuestr)
# truncated value ?
- if val != float( valuestr ):
+ if val != float(valuestr):
continue
return val
- except (ValueError,TypeError):
+ except (ValueError, TypeError):
continue
# END for each numeric type
-
+
# try boolean values as git uses them
- vl = valuestr.lower()
+ vl = valuestr.lower()
if vl == 'false':
return False
if vl == 'true':
return True
-
- if not isinstance( valuestr, basestring ):
- raise TypeError( "Invalid value type: only int, long, float and str are allowed", valuestr )
-
+
+ if not isinstance(valuestr, basestring):
+ raise TypeError("Invalid value type: only int, long, float and str are allowed", valuestr)
+
return valuestr
-
+
@needs_values
@set_dirty_and_flush_changes
def set_value(self, section, option, value):
"""Sets the given option in section to the given value.
- It will create the section if required, and will not throw as opposed to the default
+ It will create the section if required, and will not throw as opposed to the default
ConfigParser 'set' method.
-
+
:param section: Name of the section in which the option resides or should reside
:param option: Name of the options whose value to set
-
- :param value: Value to set the option to. It must be a string or convertible
+
+ :param value: Value to set the option to. It must be a string or convertible
to a string"""
if not self.has_section(section):
self.add_section(section)