mirror of
https://github.com/openSUSE/osc.git
synced 2024-12-27 02:16:12 +01:00
- added OscConfigParser module:
OscConfigParser() behaves like a normal ConfigParser() object. The only differences is that it preserves the order+format of configuration entries and that it stores comments. In order to keep the order and the format it makes use of the ConfigLineOrder() class. - removed .netrc cruft from the conf module - other config cleanups
This commit is contained in:
parent
1a3c80423a
commit
8d055f7990
323
osc/OscConfigParser.py
Normal file
323
osc/OscConfigParser.py
Normal file
@ -0,0 +1,323 @@
|
||||
import ConfigParser
|
||||
import re
|
||||
|
||||
# inspired from http://code.google.com/p/iniparse/ - although their implementation is
|
||||
# quite different
|
||||
|
||||
class ConfigLineOrder():
|
||||
"""
|
||||
A ConfigLineOrder() instance task is to preserve the order of a config file.
|
||||
It keeps track of all lines (including comments) in the _lines list. This list
|
||||
either contains SectionLine() instances or CommentLine() instances.
|
||||
"""
|
||||
def __init__(self):
|
||||
self._lines = []
|
||||
|
||||
def _append(self, line_obj):
|
||||
self._lines.append(line_obj)
|
||||
|
||||
def _find_section(self, section):
|
||||
for line in self._lines:
|
||||
if line.type == 'section' and line.name == section:
|
||||
return line
|
||||
return None
|
||||
|
||||
def add_section(self, sectname):
|
||||
self._append(SectionLine(sectname))
|
||||
|
||||
def get_section(self, sectname):
|
||||
section = self._find_section(sectname)
|
||||
if section:
|
||||
return section
|
||||
section = SectionLine(sectname)
|
||||
self._append(section)
|
||||
return section
|
||||
|
||||
def add_other(self, sectname, line):
|
||||
if sectname:
|
||||
self.get_section(sectname).add_other(line)
|
||||
else:
|
||||
self._append(CommentLine(line))
|
||||
|
||||
def keys(self):
|
||||
return [ i.name for i in self._lines if i.type == 'section' ]
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
section = SectionLine(key)
|
||||
#if isinstance(value, dict) and value.has_key('__name__'):
|
||||
# section[key] = value['__name__']
|
||||
self._append(section)
|
||||
|
||||
def __getitem__(self, key):
|
||||
section = self._find_section(key)
|
||||
if not section:
|
||||
raise KeyError()
|
||||
return section
|
||||
|
||||
def __delitem__(self, key):
|
||||
line = self._find(line)
|
||||
if not line:
|
||||
raise KeyError(key)
|
||||
self._lines.remove(line)
|
||||
|
||||
def __iter__(self):
|
||||
#return self._lines.__iter__()
|
||||
for line in self._lines:
|
||||
if line.type == 'section':
|
||||
yield line.name
|
||||
raise StopIteration()
|
||||
|
||||
class Line():
|
||||
"""Base class for all line objects"""
|
||||
def __init__(self, name, type):
|
||||
self.name = name
|
||||
self.type = type
|
||||
|
||||
class SectionLine(Line):
|
||||
"""
|
||||
This class represents a [section]. It stores all lines which belongs to
|
||||
this certain section in the _lines list. The _lines list either contains
|
||||
CommentLine() or OptionLine() instances.
|
||||
"""
|
||||
def __init__(self, sectname, dict = {}):
|
||||
Line.__init__(self, sectname, 'section')
|
||||
self._lines = []
|
||||
self._dict = dict
|
||||
|
||||
def _find(self, name):
|
||||
for line in self._lines:
|
||||
if line.name == name:
|
||||
return line
|
||||
return None
|
||||
|
||||
def _add_option(self, optname, value = None, line = None, sep = '='):
|
||||
if not (value or line):
|
||||
raise Error('Either value or line must be passed in')
|
||||
elif value and line:
|
||||
raise Error('value and line are mutually exclusive')
|
||||
|
||||
if value:
|
||||
line = '%s%s%s' % (optname, sep, value)
|
||||
opt = self._find(optname)
|
||||
if opt:
|
||||
opt.format(line)
|
||||
else:
|
||||
self._lines.append(OptionLine(optname, line))
|
||||
|
||||
def add_other(self, line):
|
||||
self._lines.append(CommentLine(line))
|
||||
|
||||
def copy(self):
|
||||
return dict(self.items())
|
||||
|
||||
def items(self):
|
||||
return [ (i.name, i.value) for i in self._lines if i.type == 'option' ]
|
||||
|
||||
def keys(self):
|
||||
return [ i.name for i in self._lines ]
|
||||
|
||||
def __setitem__(self, key, val):
|
||||
self._add_option(key, val)
|
||||
|
||||
def __getitem__(self, key):
|
||||
line = self._find(key)
|
||||
if not line:
|
||||
print "KEYERROR", key
|
||||
raise KeyError(key)
|
||||
return str(line)
|
||||
|
||||
def __delitem__(self, key):
|
||||
line = self._find(key)
|
||||
if not line:
|
||||
raise KeyError(key)
|
||||
self._lines.remove(line)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
# XXX: needed to support 'x' in cp._sections['sectname']
|
||||
def __iter__(self):
|
||||
#return self._lines.__iter__()
|
||||
for line in self._lines:
|
||||
yield line.name
|
||||
raise StopIteration()
|
||||
|
||||
|
||||
class CommentLine(Line):
|
||||
"""Store a commentline"""
|
||||
def __init__(self, line):
|
||||
Line.__init__(self, line.strip('\n'), 'comment')
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
class OptionLine(Line):
|
||||
"""
|
||||
This class represents an option. The class' "name" attribute is used
|
||||
to store the option's name and the "value" attribute contains the option's
|
||||
value. The "frmt" attribute preserves the format which was used in the configuration
|
||||
file.
|
||||
Example:
|
||||
optionx:<SPACE><SPACE>value
|
||||
=> self.frmt = '%s:<SPACE><SPACE>%s'
|
||||
optiony<SPACE>=<SPACE>value<SPACE>;<SPACE>some_comment
|
||||
=> self.frmt = '%s<SPACE>=<SPACE><SPACE>%s<SPACE>;<SPACE>some_comment
|
||||
"""
|
||||
|
||||
def __init__(self, optname, line):
|
||||
Line.__init__(self, optname, 'option')
|
||||
self.name = optname
|
||||
self.format(line)
|
||||
|
||||
def format(self, line):
|
||||
mo = ConfigParser.ConfigParser.OPTCRE.match(line.strip())
|
||||
key, val = mo.group('option', 'value')
|
||||
self.frmt = line.replace(key.strip(), '%s', 1)
|
||||
pos = val.find(' ;')
|
||||
if pos >= 0:
|
||||
val = val[:pos]
|
||||
self.value = val
|
||||
self.frmt = self.frmt.replace(val.strip(), '%s', 1).rstrip('\n')
|
||||
|
||||
def __str__(self):
|
||||
#return self.frmt % (self.name, self.value)
|
||||
return self.value
|
||||
|
||||
|
||||
class OscConfigParser(ConfigParser.SafeConfigParser):
|
||||
"""
|
||||
OscConfigParser() behaves like a normal ConfigParser() object. The
|
||||
only differences is that it preserves the order+format of configuration entries
|
||||
and that it stores comments.
|
||||
In order to keep the order and the format it makes use of the ConfigLineOrder()
|
||||
class.
|
||||
"""
|
||||
def __init__(self, defaults={}):
|
||||
ConfigParser.SafeConfigParser.__init__(self, defaults)
|
||||
self._sections = ConfigLineOrder()
|
||||
|
||||
# XXX: unfortunately we have to override the _read() method from the ConfigParser()
|
||||
# class because a) we need to store comments b) the original version doesn't use
|
||||
# the its set methods to add and set sections, options etc. instead they use a
|
||||
# dictionary (this makes it hard for subclasses to use their own objects, IMHO
|
||||
# a bug) and c) in case of an option we need the complete line to store the format.
|
||||
# This all sounds complicated but it isn't - we only needed some slight changes
|
||||
def _read(self, fp, fpname):
|
||||
"""Parse a sectioned setup file.
|
||||
|
||||
The sections in setup file contains a title line at the top,
|
||||
indicated by a name in square brackets (`[]'), plus key/value
|
||||
options lines, indicated by `name: value' format lines.
|
||||
Continuations are represented by an embedded newline then
|
||||
leading whitespace. Blank lines, lines beginning with a '#',
|
||||
and just about everything else are ignored.
|
||||
"""
|
||||
cursect = None # None, or a dictionary
|
||||
optname = None
|
||||
lineno = 0
|
||||
e = None # None, or an exception
|
||||
while True:
|
||||
line = fp.readline()
|
||||
if not line:
|
||||
break
|
||||
lineno = lineno + 1
|
||||
# comment or blank line?
|
||||
if line.strip() == '' or line[0] in '#;':
|
||||
self._sections.add_other(cursect, line)
|
||||
continue
|
||||
if line.split(None, 1)[0].lower() == 'rem' and line[0] in "rR":
|
||||
# no leading whitespace
|
||||
continue
|
||||
# continuation line?
|
||||
if line[0].isspace() and cursect is not None and optname:
|
||||
value = line.strip()
|
||||
if value:
|
||||
#cursect[optname] = "%s\n%s" % (cursect[optname], value)
|
||||
#self.set(cursect, optname, "%s\n%s" % (self.get(cursect, optname), value))
|
||||
if cursect == ConfigParser.DEFAULTSECT:
|
||||
self._defaults[optname] = "%s\n%s" % (self._defaults[optname], value)
|
||||
else:
|
||||
self._sections[cursect]._find(optname).value = '%s\n%s' % (self.get(cursect, optname), value)
|
||||
# a section header or option header?
|
||||
else:
|
||||
# is it a section header?
|
||||
mo = self.SECTCRE.match(line)
|
||||
if mo:
|
||||
sectname = mo.group('header')
|
||||
if sectname in self._sections:
|
||||
cursect = self._sections[sectname]
|
||||
elif sectname == ConfigParser.DEFAULTSECT:
|
||||
cursect = self._defaults
|
||||
else:
|
||||
#cursect = {'__name__': sectname}
|
||||
#self._sections[sectname] = cursect
|
||||
self.add_section(sectname)
|
||||
self.set(sectname, '__name__', sectname)
|
||||
# So sections can't start with a continuation line
|
||||
cursect = sectname
|
||||
optname = None
|
||||
# no section header in the file?
|
||||
elif cursect is None:
|
||||
raise ConfigParser.MissingSectionHeaderError(fpname, lineno, line)
|
||||
# an option line?
|
||||
else:
|
||||
mo = self.OPTCRE.match(line)
|
||||
if mo:
|
||||
optname, vi, optval = mo.group('option', 'vi', 'value')
|
||||
if vi in ('=', ':') and ';' in optval:
|
||||
# ';' is a comment delimiter only if it follows
|
||||
# a spacing character
|
||||
pos = optval.find(';')
|
||||
if pos != -1 and optval[pos-1].isspace():
|
||||
optval = optval[:pos]
|
||||
optval = optval.strip()
|
||||
# allow empty values
|
||||
if optval == '""':
|
||||
optval = ''
|
||||
optname = self.optionxform(optname.rstrip())
|
||||
#print optname
|
||||
#print optval
|
||||
if cursect == ConfigParser.DEFAULTSECT:
|
||||
self._defaults[optname] = optval
|
||||
else:
|
||||
self._sections[cursect]._add_option(optname, line=line)
|
||||
else:
|
||||
# a non-fatal parsing error occurred. set up the
|
||||
# exception but keep going. the exception will be
|
||||
# raised at the end of the file and will contain a
|
||||
# list of all bogus lines
|
||||
if not e:
|
||||
e = ConfigParser.ParsingError(fpname)
|
||||
e.append(lineno, repr(line))
|
||||
# if any parsing errors occurred, raise an exception
|
||||
if e:
|
||||
raise e
|
||||
|
||||
def write(self, fp, comments = False):
|
||||
"""
|
||||
write the configuration file. If comments is True all comments etc.
|
||||
will be written to fp otherwise the ConfigParsers' default write method
|
||||
will be called.
|
||||
"""
|
||||
if comments:
|
||||
fp.write(str(self))
|
||||
fp.write('\n')
|
||||
else:
|
||||
ConfigParser.SafeConfigParser.write(self, fp)
|
||||
|
||||
# XXX: simplify!
|
||||
def __str__(self):
|
||||
ret = []
|
||||
for line in self._sections._lines:
|
||||
if line.type == 'section':
|
||||
ret.append('[%s]' % line.name)
|
||||
for sline in line._lines:
|
||||
if sline.name == '__name__':
|
||||
continue
|
||||
if sline.type == 'option':
|
||||
ret.append(sline.frmt % (sline.name, sline.value))
|
||||
else:
|
||||
ret.append(str(sline))
|
||||
else:
|
||||
ret.append(str(line))
|
||||
return '\n'.join(ret)
|
@ -76,7 +76,7 @@ class Osc(cmdln.Cmdln):
|
||||
config['user'] = raw_input('Username: ')
|
||||
config['pass'] = getpass.getpass()
|
||||
|
||||
if conf.write_config(e.file, config):
|
||||
conf.write_initial_config(e.file, config, True)
|
||||
print >>sys.stderr, 'done'
|
||||
conf.get_config(override_conffile = self.options.conffile,
|
||||
override_apisrv = self.options.apisrv,
|
||||
@ -84,9 +84,6 @@ class Osc(cmdln.Cmdln):
|
||||
override_http_debug = self.options.http_debug,
|
||||
override_traceback = self.options.traceback,
|
||||
override_post_mortem = self.options.post_mortem)
|
||||
else:
|
||||
raise NoConfigfile(e.file, 'Unable to create osc\'s configuration file \
|
||||
\'%s\'' % e.file)
|
||||
self.conf = conf
|
||||
|
||||
|
||||
|
42
osc/conf.py
42
osc/conf.py
@ -32,7 +32,7 @@ The configuration dictionary could look like this:
|
||||
|
||||
"""
|
||||
|
||||
import ConfigParser
|
||||
import OscConfigParser
|
||||
from osc import oscerr
|
||||
|
||||
# being global to this module, this dict can be accessed from outside
|
||||
@ -213,23 +213,25 @@ def get_configParser(conffile=None, force_read=False):
|
||||
conffile = conffile or os.environ.get('OSC_CONFIG', '~/.oscrc')
|
||||
conffile = os.path.expanduser(conffile)
|
||||
if force_read or not get_configParser.__dict__.has_key('cp'):
|
||||
get_configParser.cp = ConfigParser.SafeConfigParser(DEFAULTS)
|
||||
get_configParser.cp = OscConfigParser.OscConfigParser(DEFAULTS)
|
||||
get_configParser.cp.read(conffile)
|
||||
return get_configParser.cp
|
||||
|
||||
|
||||
def write_config(conffile, entries, custom_template = None, force = False):
|
||||
def write_initial_config(conffile, entries, custom_template = ''):
|
||||
"""
|
||||
write osc's configuration file. entries is a dict which contains values
|
||||
write osc's intial configuration file. entries is a dict which contains values
|
||||
for the config file (e.g. { 'user' : 'username', 'pass' : 'password' } ).
|
||||
custom_template is an optional configuration template. Use force=True if you
|
||||
want to overwrite an existing configuration file.
|
||||
custom_template is an optional configuration template.
|
||||
"""
|
||||
import os
|
||||
import os, StringIO, sys
|
||||
conf_template = custom_template or new_conf_template
|
||||
config = DEFAULTS.copy()
|
||||
config.update(entries)
|
||||
if force or not os.path.exists(conffile):
|
||||
sio = StringIO.StringIO(conf_template.strip() % config)
|
||||
cp = OscConfigParser.OscConfigParser(DEFAULTS)
|
||||
cp.readfp(sio)
|
||||
|
||||
file = None
|
||||
try:
|
||||
file = open(conffile, 'w')
|
||||
@ -238,14 +240,11 @@ def write_config(conffile, entries, custom_template = None, force = False):
|
||||
try:
|
||||
try:
|
||||
os.chmod(conffile, 0600)
|
||||
file.write(conf_template % config)
|
||||
cp.write(file)
|
||||
except IOError, e:
|
||||
raise oscerr.OscIOError(e, 'cannot write configfile \'s\'' % conffile)
|
||||
finally:
|
||||
if file: file.close()
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
|
||||
def get_config(override_conffile = None,
|
||||
@ -264,25 +263,6 @@ def get_config(override_conffile = None,
|
||||
conffile = os.path.expanduser(conffile)
|
||||
|
||||
if not os.path.exists(conffile):
|
||||
|
||||
# okay, let's create a fresh config file
|
||||
# if credentials are found in .netrc, use those
|
||||
# otherwise ask
|
||||
|
||||
config = DEFAULTS.copy()
|
||||
|
||||
# try .netrc
|
||||
# the needed entry needs to look like this:
|
||||
# machine api.opensuse.org login your_login password your_pass
|
||||
# note that it is not suited for credentials containing spaces
|
||||
import netrc
|
||||
try:
|
||||
# XXX: apisrv is a URL now, thus requiring the "scheme" setting if https is to be used
|
||||
netrc_host = parse_apisrv_url(None, DEFAULTS['apisrv'])[1]
|
||||
config['user'], account, config['pass'] = \
|
||||
netrc.netrc().authenticators(netrc_host)
|
||||
print >>sys.stderr, 'Read credentials from %s.' % os.path.expanduser('~/.netrc')
|
||||
except (IOError, TypeError, netrc.NetrcParseError):
|
||||
raise oscerr.NoConfigfile(conffile, \
|
||||
account_not_configured_text % conffile)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user