mirror of
https://github.com/eblot/pybootd.git
synced 2024-07-14 17:20:30 +03:00
Start porting to Py3 only and fix old syntax
This commit is contained in:
parent
efe3ff3b40
commit
d1231d55c0
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2010-2016 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||
# Copyright (c) 2010-2019 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||
# Copyright (c) 2010-2011 Neotion
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
|
@ -21,7 +21,7 @@ import os
|
||||
import sys
|
||||
|
||||
|
||||
def _get_package_name(default='', version='1.5.0'):
|
||||
def _get_package_name(default='', version='1.6.0'):
|
||||
try:
|
||||
from pkg_resources import WorkingSet
|
||||
except ImportError:
|
||||
|
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2010-2016 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||
# Copyright (c) 2010-2019 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||
# Copyright (c) 2010-2011 Neotion
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
@ -21,14 +21,13 @@
|
||||
"""Boot up server, a tiny BOOTP/DHCP/TFTP/PXE server"""
|
||||
|
||||
|
||||
import os
|
||||
import sys
|
||||
from pxed import BootpServer
|
||||
from pybootd import pybootd_path, PRODUCT_NAME, __version__ as VERSION
|
||||
from six import print_
|
||||
from tftpd import TftpServer
|
||||
from util import logger_factory, EasyConfigParser
|
||||
from os.path import isfile
|
||||
from threading import Thread
|
||||
from sys import exit as sysexit, modules, stderr
|
||||
from . import pybootd_path, PRODUCT_NAME, __version__ as VERSION
|
||||
from .pxed import BootpServer
|
||||
from .tftpd import TftpServer
|
||||
from .util import logger_factory, EasyConfigParser
|
||||
|
||||
|
||||
class BootpDaemon(Thread):
|
||||
@ -65,7 +64,7 @@ def main():
|
||||
debug = False
|
||||
try:
|
||||
from argparse import ArgumentParser
|
||||
argparser = ArgumentParser(description=sys.modules[__name__].__doc__)
|
||||
argparser = ArgumentParser(description=modules[__name__].__doc__)
|
||||
argparser.add_argument('-c', '--config', dest='config',
|
||||
default='pybootd/etc/pybootd.ini',
|
||||
help='configuration file')
|
||||
@ -80,7 +79,7 @@ def main():
|
||||
args = argparser.parse_args()
|
||||
debug = args.debug
|
||||
|
||||
if not os.path.isfile(args.config):
|
||||
if not isfile(args.config):
|
||||
argparser.error('Invalid configuration file')
|
||||
|
||||
if args.pxe and args.tftp:
|
||||
@ -110,10 +109,10 @@ def main():
|
||||
if not daemon.is_alive():
|
||||
break
|
||||
except Exception as e:
|
||||
print_('\nError: %s' % e, file=sys.stderr)
|
||||
print('\nError: %s' % e, file=stderr)
|
||||
if debug:
|
||||
import traceback
|
||||
print_(traceback.format_exc(), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
print(traceback.format_exc(), file=stderr)
|
||||
sysexit(1)
|
||||
except KeyboardInterrupt:
|
||||
print_("Aborting...")
|
||||
print("Aborting...")
|
||||
|
@ -14,7 +14,6 @@ lease_time = 86400
|
||||
access = mac
|
||||
allow_simple_dhcp = enable
|
||||
dns = 10.130.0.2
|
||||
boot_file = pxelinux.0
|
||||
set_gateway = true
|
||||
; use "nc -l -u 127.0.0.1 -p 12345" to debug
|
||||
; notify = 192.168.26.201:12345;192.168.26.200:12345
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2010-2016 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||
# Copyright (c) 2010-2019 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||
# Copyright (c) 2010-2011 Neotion
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
@ -25,7 +25,7 @@ import struct
|
||||
import sys
|
||||
import time
|
||||
from binascii import hexlify
|
||||
from pybootd import PRODUCT_NAME
|
||||
from . import PRODUCT_NAME
|
||||
from .util import hexline, to_bool, iptoint, inttoip, get_iface_config
|
||||
|
||||
BOOTP_PORT_REQUEST = 67
|
||||
@ -34,10 +34,10 @@ BOOTP_PORT_REPLY = 68
|
||||
BOOTREQUEST = 1
|
||||
BOOTREPLY = 2
|
||||
|
||||
BOOTPFormat = '!4bIHH4s4s4s4s16s64s128s64s'
|
||||
BOOTPFormatSize = struct.calcsize(BOOTPFormat)
|
||||
DHCPFormat = '!4bIHH4s4s4s4s16s64s128s4s'
|
||||
DHCPFormatSize = struct.calcsize(DHCPFormat)
|
||||
BOOTPFORMAT = '!4bIHH4s4s4s4s16s64s128s64s'
|
||||
BOOTPFORMATSIZE = struct.calcsize(BOOTPFORMAT)
|
||||
DHCPFORMAT = '!4bIHH4s4s4s4s16s64s128s4s'
|
||||
DHCPFORMATSIZE = struct.calcsize(DHCPFORMAT)
|
||||
|
||||
(BOOTP_OP, BOOTP_HTYPE, BOOTP_HLEN, BOOTP_HOPS, BOOTP_XID, BOOTP_SECS,
|
||||
BOOTP_FLAGS, BOOTP_CIADDR, BOOTP_YIADDR, BOOTP_SIADDR, BOOTP_GIADDR,
|
||||
@ -46,7 +46,7 @@ DHCPFormatSize = struct.calcsize(DHCPFormat)
|
||||
BOOTP_FLAGS_NONE = 0
|
||||
BOOTP_FLAGS_BROADCAST = 1<<15
|
||||
|
||||
COOKIE='\0x63\0x82\0x53\0x63'
|
||||
COOKIE = r'\0x63\0x82\0x53\0x63'
|
||||
|
||||
DHCP_OPTIONS = {0: 'Byte padding',
|
||||
1: 'Subnet mask',
|
||||
@ -191,7 +191,8 @@ class BootpServer:
|
||||
host = self.config.get(self.bootp_section, 'address', '0.0.0.0')
|
||||
self.netconfig = get_iface_config(host)
|
||||
if not self.netconfig:
|
||||
raise BootpError('Unable to detect network configuration')
|
||||
# the available networks on the host may not match the config...
|
||||
raise BootpError('Unable to detect a matching network config')
|
||||
|
||||
keys = sorted(self.netconfig.keys())
|
||||
self.log.info('Using %s' % ', '.join(map(
|
||||
@ -204,8 +205,8 @@ class BootpServer:
|
||||
for n in nlist:
|
||||
n = n.strip().split(':')
|
||||
self.notify.append((n[0], int(n[1])))
|
||||
except Exception, e:
|
||||
raise BootpError('Invalid notification URL: %s' % str(e))
|
||||
except Exception as exc:
|
||||
raise BootpError('Invalid notification URL: %s' % exc)
|
||||
access = self.config.get(self.bootp_section, 'access')
|
||||
if not access:
|
||||
self.acl = None
|
||||
@ -263,9 +264,9 @@ class BootpServer:
|
||||
for sock in r:
|
||||
data, addr = sock.recvfrom(556)
|
||||
self.handle(sock, addr, data)
|
||||
except Exception, e:
|
||||
except Exception as exc:
|
||||
import traceback
|
||||
self.log.critical('%s\n%s' % (str(e), traceback.format_exc()))
|
||||
self.log.critical('%s\n%s' % (exc, traceback.format_exc()))
|
||||
time.sleep(1)
|
||||
|
||||
def parse_options(self, tail):
|
||||
@ -315,8 +316,8 @@ class BootpServer:
|
||||
len(vendor), vendor)
|
||||
buf += struct.pack('!BBB', 255, 0, 0)
|
||||
return buf
|
||||
except KeyError, e:
|
||||
self.log.error('Missing options, cancelling: ' + str(e))
|
||||
except KeyError as exc:
|
||||
self.log.error('Missing options, cancelling: %s' % exc)
|
||||
return None
|
||||
|
||||
def build_dhcp_options(self, clientname):
|
||||
@ -329,10 +330,10 @@ class BootpServer:
|
||||
|
||||
def handle(self, sock, addr, data):
|
||||
self.log.info('Sender: %s on socket %s' % (addr, sock.getsockname()))
|
||||
if len(data) < DHCPFormatSize:
|
||||
if len(data) < DHCPFORMATSIZE:
|
||||
self.log.error('Cannot be a DHCP or BOOTP request - too small!')
|
||||
tail = data[DHCPFormatSize:]
|
||||
buf = list(struct.unpack(DHCPFormat, data[:DHCPFormatSize]))
|
||||
tail = data[DHCPFORMATSIZE:]
|
||||
buf = list(struct.unpack(DHCPFORMAT, data[:DHCPFORMATSIZE]))
|
||||
if buf[BOOTP_OP] != BOOTREQUEST:
|
||||
self.log.warn('Not a BOOTREQUEST')
|
||||
return
|
||||
@ -360,9 +361,9 @@ class BootpServer:
|
||||
uuid = self.uuidpool.get(mac_addr, None)
|
||||
pxe = False
|
||||
self.log.info('PXE UUID not present in request')
|
||||
uuid_str = uuid and ('%s-%s-%s-%s-%s' % tuple(
|
||||
[hexlify(x) for x in uuid[0:4], uuid[4:6], uuid[6:8],
|
||||
uuid[8:10], uuid[10:16]])).upper()
|
||||
uuid_str = uuid and ('%s-%s-%s-%s-%s' % tuple([hexlify(x)
|
||||
for x in (uuid[0:4], uuid[4:6], uuid[6:8], uuid[8:10], uuid[10:16])
|
||||
])).upper()
|
||||
if uuid_str:
|
||||
self.log.info('UUID is %s for MAC %s' % (uuid_str, mac_str))
|
||||
|
||||
@ -441,16 +442,16 @@ class BootpServer:
|
||||
filename = v
|
||||
except ValueError:
|
||||
pass
|
||||
except urllib2.HTTPError, e:
|
||||
self.log.error('HTTP Error: %s' % str(e))
|
||||
except urllib2.HTTPError as exc:
|
||||
self.log.error('HTTP Error: %s' % exc)
|
||||
self.states[mac_str] = self.ST_IDLE
|
||||
return
|
||||
except urllib2.URLError, e:
|
||||
self.log.critical('Internal error: %s' % str(e))
|
||||
except urllib2.URLError as exc:
|
||||
self.log.critical('Internal error: %s' % exc)
|
||||
self.states[mac_str] = self.ST_IDLE
|
||||
return
|
||||
except httplib.HTTPException, e:
|
||||
self.log.error('Server error: %s' % type(e))
|
||||
except httplib.HTTPException as exc:
|
||||
self.log.error('Server error: %s' % type(exc))
|
||||
self.states[mac_str] = self.ST_IDLE
|
||||
return
|
||||
# local access is only validated if mac address is not yet known
|
||||
@ -566,7 +567,7 @@ class BootpServer:
|
||||
else:
|
||||
self.log.debug('No filename defined for IP %s' % ip)
|
||||
|
||||
pkt = struct.pack(DHCPFormat, *buf)
|
||||
pkt = struct.pack(DHCPFORMAT, *buf)
|
||||
pkt += struct.pack('!BBB', DHCP_MSG, 1, dhcp_reply)
|
||||
server = socket.inet_aton(server_addr)
|
||||
pkt += struct.pack('!BB4s', DHCP_SERVER, 4, server)
|
||||
@ -622,7 +623,7 @@ class BootpServer:
|
||||
self.states[mac_str] = newstate
|
||||
|
||||
def get_dns_servers(self):
|
||||
nscre = re.compile('nameserver\s+(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\s')
|
||||
nscre = re.compile(r'nameserver\s+(\d{1,3}.\d{1,3}.\d{1,3}.\d{1,3})\s')
|
||||
result = []
|
||||
try:
|
||||
with open('/etc/resolv.conf', 'r') as resolv:
|
||||
@ -632,7 +633,7 @@ class BootpServer:
|
||||
dns = mo.group(1)
|
||||
self.log.info('Found nameserver: %s' % dns)
|
||||
result.append(dns)
|
||||
except Exception, e:
|
||||
except Exception:
|
||||
pass
|
||||
if not result:
|
||||
self.log.info('No nameserver found')
|
||||
|
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2010-2016 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||
# Copyright (c) 2010-2019 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||
# Copyright (c) 2010-2011 Neotion
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
@ -25,13 +25,13 @@ import string
|
||||
import struct
|
||||
import sys
|
||||
import time
|
||||
import thread
|
||||
import urllib2
|
||||
import urlparse
|
||||
from ConfigParser import NoSectionError
|
||||
from cStringIO import StringIO
|
||||
from pybootd import pybootd_path
|
||||
from util import hexline
|
||||
from configparser import NoSectionError
|
||||
from io import StringIO
|
||||
from threading import Thread
|
||||
from urllib.parse import urlparse
|
||||
from urllib.request import urlopen
|
||||
from . import pybootd_path
|
||||
from .util import hexline
|
||||
|
||||
__all__ = ['TftpServer']
|
||||
|
||||
@ -217,8 +217,8 @@ class TftpConnection(object):
|
||||
else:
|
||||
raise TftpError(5, 'Invalid opcode')
|
||||
self.log.debug('End of active: %s:%s' % addr)
|
||||
except TftpError, detail:
|
||||
self.send_error(detail[0], detail[1])
|
||||
except TftpError as exc:
|
||||
self.send_error(exc[0], exc[1])
|
||||
except:
|
||||
import traceback
|
||||
self.log.error(traceback.format_exc())
|
||||
@ -310,7 +310,7 @@ class TftpConnection(object):
|
||||
else:
|
||||
try:
|
||||
if self.is_url(resource):
|
||||
rp = urllib2.urlopen(resource)
|
||||
rp = urlopen(resource)
|
||||
meta = rp.info()
|
||||
filesize = int(meta.getheaders('Content-Length')[0])
|
||||
else:
|
||||
@ -333,7 +333,7 @@ class TftpConnection(object):
|
||||
try:
|
||||
if self.is_url(resource):
|
||||
self.log.info("Sending resource '%s'" % resource)
|
||||
self.file = urllib2.urlopen(resource)
|
||||
self.file = urlopen(resource)
|
||||
else:
|
||||
resource = os.path.realpath(resource)
|
||||
self.log.info("Sending file '%s'" % resource)
|
||||
@ -420,8 +420,9 @@ class TftpServer:
|
||||
r, w, e = select.select(self.sock, [], self.sock)
|
||||
for sock in r:
|
||||
data, addr = sock.recvfrom(516)
|
||||
t = TftpConnection(self)
|
||||
thread.start_new_thread(t.connect, (addr, data))
|
||||
tc = TftpConnection(self)
|
||||
thread = Thread(tc.connect, (addr, data))
|
||||
thread.start()
|
||||
|
||||
def filter_file(self, connexion, mo):
|
||||
# extract the position of the matching pattern, then extract the
|
||||
@ -442,12 +443,12 @@ class TftpServer:
|
||||
for pos, pattern in enumerate(self.config.options('filters'), 1):
|
||||
value = self.config.get('filters', pattern).strip()
|
||||
pattern = pattern.strip('\r\n \t')
|
||||
pattern = pattern.replace('.', '\.')
|
||||
pattern = pattern.replace('*', '.*').replace('?', '.')
|
||||
pattern = pattern.replace(r'.', r'\.')
|
||||
pattern = pattern.replace(r'*', r'.*').replace(r'?', r'.')
|
||||
pname = 'p%d' % pos
|
||||
replacements[pname] = value
|
||||
patterns.append('(?P<%s>%s)' % (pname, pattern))
|
||||
xre = '^(?:\./)?(?:%s)$' % '|'.join(patterns)
|
||||
patterns.append(r'(?P<%s>%s)' % (pname, pattern))
|
||||
xre = r'^(?:\./)?(?:%s)$' % r'|'.join(patterns)
|
||||
except NoSectionError:
|
||||
xre = '^$'
|
||||
xre = r'^$'
|
||||
return (re.compile(xre), replacements)
|
||||
|
100
pybootd/util.py
100
pybootd/util.py
@ -1,6 +1,6 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2010-2016 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||
# Copyright (c) 2010-2019 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||
# Copyright (c) 2010-2011 Neotion
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
@ -18,14 +18,17 @@
|
||||
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
||||
|
||||
from array import array
|
||||
import commands
|
||||
import logging
|
||||
import re
|
||||
import socket
|
||||
import struct
|
||||
import sys
|
||||
from ConfigParser import SafeConfigParser
|
||||
from six import PY3, integer_types, binary_type
|
||||
from configparser import SafeConfigParser, InterpolationSyntaxError
|
||||
from logging import (DEBUG, INFO, ERROR, CRITICAL, WARNING,
|
||||
Formatter, FileHandler, StreamHandler, getLogger)
|
||||
from logging.handlers import (BufferingHandler, NTEventLogHandler,
|
||||
SysLogHandler)
|
||||
from re import match
|
||||
from socket import inet_aton, inet_ntoa
|
||||
from subprocess import run
|
||||
from struct import pack as spack, unpack as sunpack
|
||||
from sys import stderr
|
||||
|
||||
|
||||
try:
|
||||
import netifaces
|
||||
@ -35,6 +38,7 @@ except ImportError:
|
||||
raise ImportError('netifaces package is not installed')
|
||||
netifaces = None
|
||||
|
||||
|
||||
# String values evaluated as true boolean values
|
||||
TRUE_BOOLEANS = ['on', 'high', 'true', 'enable', 'enabled', 'yes', '1']
|
||||
# String values evaluated as false boolean values
|
||||
@ -56,9 +60,9 @@ def to_int(value):
|
||||
"""
|
||||
if not value:
|
||||
return 0
|
||||
if isinstance(value, integer_types):
|
||||
if isinstance(value, int):
|
||||
return int(value)
|
||||
mo = re.match('^\s*(\d+)\s*(?:([KMkm]i?)?B?)?\s*$', value)
|
||||
mo = match(r'^\s*(\d+)\s*(?:([KMkm]i?)?B?)?\s*$', value)
|
||||
if mo:
|
||||
mult = {'K': (1000),
|
||||
'KI': (1 << 10),
|
||||
@ -108,7 +112,7 @@ def hexline(data, sep=' '):
|
||||
of the buffer data
|
||||
"""
|
||||
try:
|
||||
if isinstance(data, (binary_type, array)):
|
||||
if isinstance(data, (bytes, array)):
|
||||
src = bytearray(data)
|
||||
elif isinstance(data, bytearray):
|
||||
src = data
|
||||
@ -128,20 +132,20 @@ def hexline(data, sep=' '):
|
||||
def logger_factory(logtype='syslog', logfile=None, level='WARNING',
|
||||
logid='PXEd', format=None):
|
||||
# this code has been copied from Trac (MIT modified license)
|
||||
logger = logging.getLogger(logid)
|
||||
logger = getLogger(logid)
|
||||
logtype = logtype.lower()
|
||||
if logtype == 'file':
|
||||
hdlr = logging.FileHandler(logfile)
|
||||
hdlr = FileHandler(logfile)
|
||||
elif logtype in ('winlog', 'eventlog', 'nteventlog'):
|
||||
# Requires win32 extensions
|
||||
hdlr = logging.handlers.NTEventLogHandler(logid,
|
||||
hdlr = NTEventLogHandler(logid,
|
||||
logtype='Application')
|
||||
elif logtype in ('syslog', 'unix'):
|
||||
hdlr = logging.handlers.SysLogHandler('/dev/log')
|
||||
hdlr = SysLogHandler('/dev/log')
|
||||
elif logtype in ('stderr'):
|
||||
hdlr = logging.StreamHandler(sys.stderr)
|
||||
hdlr = StreamHandler(stderr)
|
||||
else:
|
||||
hdlr = logging.handlers.BufferingHandler(0)
|
||||
hdlr = BufferingHandler(0)
|
||||
|
||||
if not format:
|
||||
format = 'PXEd[%(module)s] %(levelname)s: %(message)s'
|
||||
@ -152,23 +156,23 @@ def logger_factory(logtype='syslog', logfile=None, level='WARNING',
|
||||
datefmt = '%X'
|
||||
level = level.upper()
|
||||
if level in ('DEBUG', 'ALL'):
|
||||
logger.setLevel(logging.DEBUG)
|
||||
logger.setLevel(DEBUG)
|
||||
elif level == 'INFO':
|
||||
logger.setLevel(logging.INFO)
|
||||
logger.setLevel(INFO)
|
||||
elif level == 'ERROR':
|
||||
logger.setLevel(logging.ERROR)
|
||||
logger.setLevel(ERROR)
|
||||
elif level == 'CRITICAL':
|
||||
logger.setLevel(logging.CRITICAL)
|
||||
logger.setLevel(CRITICAL)
|
||||
else:
|
||||
logger.setLevel(logging.WARNING)
|
||||
formatter = logging.Formatter(format, datefmt)
|
||||
logger.setLevel(WARNING)
|
||||
formatter = Formatter(format, datefmt)
|
||||
hdlr.setFormatter(formatter)
|
||||
logger.addHandler(hdlr)
|
||||
|
||||
def logerror(record):
|
||||
import traceback
|
||||
print_(record.msg)
|
||||
print_(record.args)
|
||||
print(record.msg)
|
||||
print(record.args)
|
||||
traceback.print_exc()
|
||||
# uncomment the following line to show logger formatting error
|
||||
#hdlr.handleError = logerror
|
||||
@ -177,11 +181,11 @@ def logger_factory(logtype='syslog', logfile=None, level='WARNING',
|
||||
|
||||
|
||||
def iptoint(ipstr):
|
||||
return struct.unpack('!I', socket.inet_aton(ipstr))[0]
|
||||
return sunpack('!I', inet_aton(ipstr))[0]
|
||||
|
||||
|
||||
def inttoip(ipval):
|
||||
return socket.inet_ntoa(struct.pack('!I', ipval))
|
||||
return inet_ntoa(spack('!I', ipval))
|
||||
|
||||
|
||||
def _netifaces_get_iface_config(address):
|
||||
@ -213,7 +217,7 @@ def _netifaces_get_iface_config(address):
|
||||
def _iproute_get_iface_config(address):
|
||||
pool = iptoint(address)
|
||||
iplines = (line.strip()
|
||||
for line in commands.getoutput("ip address show").split('\n'))
|
||||
for line in run("ip address show").stdout.split('\n'))
|
||||
iface = None
|
||||
for l in iplines:
|
||||
items = l.split()
|
||||
@ -246,11 +250,43 @@ def get_iface_config(address):
|
||||
|
||||
|
||||
class EasyConfigParser(SafeConfigParser):
|
||||
"ConfigParser extension to support default config values"
|
||||
"""ConfigParser extension to support default config values and do not
|
||||
mess with multi-line option strings"""
|
||||
|
||||
def get(self, section, option, default=None):
|
||||
INDENT_SIZE = 8
|
||||
|
||||
InterpolationSyntaxError = InterpolationSyntaxError
|
||||
|
||||
def get(self, section, option, default=None, raw=True, vars=None,
|
||||
fallback=None):
|
||||
"""Return the section:option value if it exists, or the default value
|
||||
if either the section or the option is missing"""
|
||||
if not self.has_section(section):
|
||||
return default
|
||||
if not self.has_option(section, option):
|
||||
return default
|
||||
return SafeConfigParser.get(self, section, option)
|
||||
return SafeConfigParser.get(self, section, option, raw=raw, vars=vars,
|
||||
fallback=fallback)
|
||||
|
||||
def write(self, filep):
|
||||
"""Write an .ini-format representation of the configuration state,
|
||||
with automatic line wrapping, using improved multi-line
|
||||
representation.
|
||||
"""
|
||||
for section in self._sections:
|
||||
filep.write("[%s]\n" % section)
|
||||
for (key, value) in self._sections[section].items():
|
||||
if key != "__name__":
|
||||
filep.write("%s = %s\n" %
|
||||
(key, str(value).replace('\n', '\n' +
|
||||
' ' * self.INDENT_SIZE)))
|
||||
filep.write("\n")
|
||||
|
||||
def _interpolate(self, section, option, rawval, vars):
|
||||
# special overloading of SafeConfigParser._interpolate:
|
||||
# do not attempt to interpolate if the string is (double-)quoted
|
||||
if is_quoted(rawval):
|
||||
return rawval
|
||||
# cannot use 'super' here as ConfigParser is outdated
|
||||
return SafeConfigParser._interpolate(self, section, option,
|
||||
rawval, vars)
|
||||
|
6
setup.py
6
setup.py
@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
#
|
||||
# Copyright (c) 2010-2016 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||
# Copyright (c) 2010-2019 Emmanuel Blot <emmanuel.blot@free.fr>
|
||||
# Copyright (c) 2010-2011 Neotion
|
||||
#
|
||||
# This library is free software; you can redistribute it and/or
|
||||
@ -26,7 +26,7 @@ def _read(fname):
|
||||
return open(os.path.join(os.path.dirname(__file__), fname)).read()
|
||||
|
||||
|
||||
requirements = ['six']
|
||||
requirements = []
|
||||
if os.uname()[0].lower() == 'darwin':
|
||||
requirements.append('netifaces (>= 0.5)')
|
||||
|
||||
@ -52,7 +52,7 @@ setup(
|
||||
'Lesser General Public License (LGPL)',
|
||||
'Operating System :: MacOS :: MacOS X',
|
||||
'Operating System :: POSIX',
|
||||
'Programming Language :: Python :: 2.7',
|
||||
'Programming Language :: Python :: 3.5',
|
||||
'Topic :: Internet',
|
||||
'Topic :: System :: Installation/Setup',
|
||||
'Topic :: System :: Networking',
|
||||
|
Loading…
Reference in New Issue
Block a user