config: Add preprocessor for preserving comments when editing configs.
Adds markers to configs files that ensures configparser won't mangle comments in the files. Will be combined with a postprocessor that undoes these changes.
This commit is contained in:
parent
add79d90dd
commit
d5cb4282d9
@ -2,8 +2,10 @@ from __future__ import unicode_literals
|
|||||||
|
|
||||||
import ConfigParser as configparser
|
import ConfigParser as configparser
|
||||||
import io
|
import io
|
||||||
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
import os.path
|
import os.path
|
||||||
|
import re
|
||||||
|
|
||||||
from mopidy.config import keyring
|
from mopidy.config import keyring
|
||||||
from mopidy.config.schemas import * # noqa
|
from mopidy.config.schemas import * # noqa
|
||||||
@ -145,6 +147,41 @@ def _format(config, comments, schemas, display):
|
|||||||
return b'\n'.join(output)
|
return b'\n'.join(output)
|
||||||
|
|
||||||
|
|
||||||
|
def _preprocess(string):
|
||||||
|
"""Convert a raw config into a form that preserves comments etc."""
|
||||||
|
results = ['[__COMMENTS__]']
|
||||||
|
counter = itertools.count(0)
|
||||||
|
|
||||||
|
section_re = re.compile(r'^(\[[^\]]+\])\s*(.+)$')
|
||||||
|
blank_line_re = re.compile(r'^\s*$')
|
||||||
|
comment_re = re.compile(r'^(#|;)')
|
||||||
|
inline_comment_re = re.compile(r' ;')
|
||||||
|
|
||||||
|
def newlines(match):
|
||||||
|
return '__BLANK%d__ =' % next(counter)
|
||||||
|
|
||||||
|
def comments(match):
|
||||||
|
if match.group(1) == '#':
|
||||||
|
return '__HASH%d__ =' % next(counter)
|
||||||
|
elif match.group(1) == ';':
|
||||||
|
return '__SEMICOLON%d__ =' % next(counter)
|
||||||
|
|
||||||
|
def inlinecomments(match):
|
||||||
|
return '\n__INLINE%d__ =' % next(counter)
|
||||||
|
|
||||||
|
def sections(match):
|
||||||
|
return '%s\n__SECTION%d__ = %s' % (
|
||||||
|
match.group(1), next(counter), match.group(2))
|
||||||
|
|
||||||
|
for line in string.splitlines():
|
||||||
|
line = blank_line_re.sub(newlines, line)
|
||||||
|
line = section_re.sub(sections, line)
|
||||||
|
line = comment_re.sub(comments, line)
|
||||||
|
line = inline_comment_re.sub(inlinecomments, line)
|
||||||
|
results.append(line)
|
||||||
|
return '\n'.join(results)
|
||||||
|
|
||||||
|
|
||||||
class Proxy(collections.Mapping):
|
class Proxy(collections.Mapping):
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self._data = data
|
self._data = data
|
||||||
|
|||||||
@ -106,3 +106,99 @@ class ValidateTest(unittest.TestCase):
|
|||||||
self.assertEqual({'foo': {'bar': 'bad'}}, errors)
|
self.assertEqual({'foo': {'bar': 'bad'}}, errors)
|
||||||
|
|
||||||
# TODO: add more tests
|
# TODO: add more tests
|
||||||
|
|
||||||
|
|
||||||
|
INPUT_CONFIG = """# comments before first section should work
|
||||||
|
|
||||||
|
[section] anything goes ; after the [] block it seems.
|
||||||
|
; this is a valid comment
|
||||||
|
this-should-equal-baz = baz ; as this is a comment
|
||||||
|
this-should-equal-everything = baz # as this is not a comment
|
||||||
|
|
||||||
|
# this is also a comment ; and the next line should be a blank comment.
|
||||||
|
;
|
||||||
|
# foo # = should all be treated as a comment.
|
||||||
|
"""
|
||||||
|
|
||||||
|
PROCESSED_CONFIG = """[__COMMENTS__]
|
||||||
|
__HASH0__ = comments before first section should work
|
||||||
|
__BLANK1__ =
|
||||||
|
[section]
|
||||||
|
__SECTION2__ = anything goes
|
||||||
|
__INLINE3__ = after the [] block it seems.
|
||||||
|
__SEMICOLON4__ = this is a valid comment
|
||||||
|
this-should-equal-baz = baz
|
||||||
|
__INLINE5__ = as this is a comment
|
||||||
|
this-should-equal-everything = baz # as this is not a comment
|
||||||
|
__BLANK6__ =
|
||||||
|
__HASH7__ = this is also a comment
|
||||||
|
__INLINE8__ = and the next line should be a blank comment.
|
||||||
|
__SEMICOLON9__ =
|
||||||
|
__HASH10__ = foo # = should all be treated as a comment."""
|
||||||
|
|
||||||
|
|
||||||
|
class ProcessorTest(unittest.TestCase):
|
||||||
|
maxDiff = None # Show entire diff.
|
||||||
|
|
||||||
|
def test_preprocessor_empty_config(self):
|
||||||
|
result = config._preprocess('')
|
||||||
|
self.assertEqual(result, '[__COMMENTS__]')
|
||||||
|
|
||||||
|
def test_preprocessor_plain_section(self):
|
||||||
|
result = config._preprocess('[section]\nfoo = bar')
|
||||||
|
self.assertEqual(result, '[__COMMENTS__]\n'
|
||||||
|
'[section]\n'
|
||||||
|
'foo = bar')
|
||||||
|
|
||||||
|
def test_preprocessor_initial_comments(self):
|
||||||
|
result = config._preprocess('; foobar')
|
||||||
|
self.assertEqual(result, '[__COMMENTS__]\n'
|
||||||
|
'__SEMICOLON0__ = foobar')
|
||||||
|
|
||||||
|
result = config._preprocess('# foobar')
|
||||||
|
self.assertEqual(result, '[__COMMENTS__]\n'
|
||||||
|
'__HASH0__ = foobar')
|
||||||
|
|
||||||
|
result = config._preprocess('; foo\n# bar')
|
||||||
|
self.assertEqual(result, '[__COMMENTS__]\n'
|
||||||
|
'__SEMICOLON0__ = foo\n'
|
||||||
|
'__HASH1__ = bar')
|
||||||
|
|
||||||
|
def test_preprocessor_initial_comment_inline_handling(self):
|
||||||
|
result = config._preprocess('; foo ; bar ; baz')
|
||||||
|
self.assertEqual(result, '[__COMMENTS__]\n'
|
||||||
|
'__SEMICOLON0__ = foo\n'
|
||||||
|
'__INLINE1__ = bar\n'
|
||||||
|
'__INLINE2__ = baz')
|
||||||
|
|
||||||
|
def test_preprocessor_inline_semicolon_comment(self):
|
||||||
|
result = config._preprocess('[section]\nfoo = bar ; baz')
|
||||||
|
self.assertEqual(result, '[__COMMENTS__]\n'
|
||||||
|
'[section]\n'
|
||||||
|
'foo = bar\n'
|
||||||
|
'__INLINE0__ = baz')
|
||||||
|
|
||||||
|
def test_preprocessor_no_inline_hash_comment(self):
|
||||||
|
result = config._preprocess('[section]\nfoo = bar # baz')
|
||||||
|
self.assertEqual(result, '[__COMMENTS__]\n'
|
||||||
|
'[section]\n'
|
||||||
|
'foo = bar # baz')
|
||||||
|
|
||||||
|
def test_preprocessor_section_extra_text(self):
|
||||||
|
result = config._preprocess('[section] foobar')
|
||||||
|
self.assertEqual(result, '[__COMMENTS__]\n'
|
||||||
|
'[section]\n'
|
||||||
|
'__SECTION0__ = foobar')
|
||||||
|
|
||||||
|
def test_preprocessor_section_extra_text_inline_semicolon(self):
|
||||||
|
result = config._preprocess('[section] foobar ; baz')
|
||||||
|
self.assertEqual(result, '[__COMMENTS__]\n'
|
||||||
|
'[section]\n'
|
||||||
|
'__SECTION0__ = foobar\n'
|
||||||
|
'__INLINE1__ = baz')
|
||||||
|
|
||||||
|
def test_preprocessor_conversion(self):
|
||||||
|
"""Tests all of the above cases at once."""
|
||||||
|
result = config._preprocess(INPUT_CONFIG)
|
||||||
|
self.assertEqual(result, PROCESSED_CONFIG)
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user