mirror of
https://github.com/openSUSE/osc.git
synced 2025-01-14 01:26:23 +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,17 +76,14 @@ class Osc(cmdln.Cmdln):
|
|||||||
config['user'] = raw_input('Username: ')
|
config['user'] = raw_input('Username: ')
|
||||||
config['pass'] = getpass.getpass()
|
config['pass'] = getpass.getpass()
|
||||||
|
|
||||||
if conf.write_config(e.file, config):
|
conf.write_initial_config(e.file, config, True)
|
||||||
print >>sys.stderr, 'done'
|
print >>sys.stderr, 'done'
|
||||||
conf.get_config(override_conffile = self.options.conffile,
|
conf.get_config(override_conffile = self.options.conffile,
|
||||||
override_apisrv = self.options.apisrv,
|
override_apisrv = self.options.apisrv,
|
||||||
override_debug = self.options.debug,
|
override_debug = self.options.debug,
|
||||||
override_http_debug = self.options.http_debug,
|
override_http_debug = self.options.http_debug,
|
||||||
override_traceback = self.options.traceback,
|
override_traceback = self.options.traceback,
|
||||||
override_post_mortem = self.options.post_mortem)
|
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
|
self.conf = conf
|
||||||
|
|
||||||
|
|
||||||
|
66
osc/conf.py
66
osc/conf.py
@ -32,7 +32,7 @@ The configuration dictionary could look like this:
|
|||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import ConfigParser
|
import OscConfigParser
|
||||||
from osc import oscerr
|
from osc import oscerr
|
||||||
|
|
||||||
# being global to this module, this dict can be accessed from outside
|
# being global to this module, this dict can be accessed from outside
|
||||||
@ -213,39 +213,38 @@ def get_configParser(conffile=None, force_read=False):
|
|||||||
conffile = conffile or os.environ.get('OSC_CONFIG', '~/.oscrc')
|
conffile = conffile or os.environ.get('OSC_CONFIG', '~/.oscrc')
|
||||||
conffile = os.path.expanduser(conffile)
|
conffile = os.path.expanduser(conffile)
|
||||||
if force_read or not get_configParser.__dict__.has_key('cp'):
|
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)
|
get_configParser.cp.read(conffile)
|
||||||
return get_configParser.cp
|
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' } ).
|
for the config file (e.g. { 'user' : 'username', 'pass' : 'password' } ).
|
||||||
custom_template is an optional configuration template. Use force=True if you
|
custom_template is an optional configuration template.
|
||||||
want to overwrite an existing configuration file.
|
|
||||||
"""
|
"""
|
||||||
import os
|
import os, StringIO, sys
|
||||||
conf_template = custom_template or new_conf_template
|
conf_template = custom_template or new_conf_template
|
||||||
config = DEFAULTS.copy()
|
config = DEFAULTS.copy()
|
||||||
config.update(entries)
|
config.update(entries)
|
||||||
if force or not os.path.exists(conffile):
|
sio = StringIO.StringIO(conf_template.strip() % config)
|
||||||
file = None
|
cp = OscConfigParser.OscConfigParser(DEFAULTS)
|
||||||
|
cp.readfp(sio)
|
||||||
|
|
||||||
|
file = None
|
||||||
|
try:
|
||||||
|
file = open(conffile, 'w')
|
||||||
|
except IOError, e:
|
||||||
|
raise oscerr.OscIOError(e, 'cannot open configfile \'%s\'' % conffile)
|
||||||
|
try:
|
||||||
try:
|
try:
|
||||||
file = open(conffile, 'w')
|
os.chmod(conffile, 0600)
|
||||||
|
cp.write(file)
|
||||||
except IOError, e:
|
except IOError, e:
|
||||||
raise oscerr.OscIOError(e, 'cannot open configfile \'%s\'' % conffile)
|
raise oscerr.OscIOError(e, 'cannot write configfile \'s\'' % conffile)
|
||||||
try:
|
finally:
|
||||||
try:
|
if file: file.close()
|
||||||
os.chmod(conffile, 0600)
|
|
||||||
file.write(conf_template % config)
|
|
||||||
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,
|
def get_config(override_conffile = None,
|
||||||
@ -264,27 +263,8 @@ def get_config(override_conffile = None,
|
|||||||
conffile = os.path.expanduser(conffile)
|
conffile = os.path.expanduser(conffile)
|
||||||
|
|
||||||
if not os.path.exists(conffile):
|
if not os.path.exists(conffile):
|
||||||
|
raise oscerr.NoConfigfile(conffile, \
|
||||||
# okay, let's create a fresh config file
|
account_not_configured_text % conffile)
|
||||||
# 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)
|
|
||||||
|
|
||||||
# okay, we made sure that .oscrc exists
|
# okay, we made sure that .oscrc exists
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user