Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Prep work for adding a mopidy config --set ... type command. #548

Merged
merged 2 commits into from
Oct 27, 2013
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
49 changes: 49 additions & 0 deletions mopidy/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import ConfigParser as configparser
import io
import itertools
import logging
import os.path
import re

from mopidy.config import keyring
from mopidy.config.schemas import * # noqa
Expand Down Expand Up @@ -145,6 +147,53 @@ def _format(config, comments, schemas, display):
return b'\n'.join(output)


def _preprocess(config_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 config_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)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Must the regexps and transformations be applied in a specific order for it to work correctly?

I would like the functions and their use to be sorted in the same order.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Order does matter because say # foo ; bar needs to become __HASH__ = foo\n__INLINE__ = bar or else the second part disappears as a comment.

results.append(line)
return '\n'.join(results)


def _postprocess(config_string):
"""Converts a preprocessed config back to original form."""
flags = re.IGNORECASE | re.MULTILINE
result = re.sub(r'^\[__COMMENTS__\](\n|$)', '', config_string, flags=flags)
result = re.sub(r'\n__INLINE\d+__ =(.*)$', ' ;\g<1>', result, flags=flags)
result = re.sub(r'^__HASH\d+__ =(.*)$', '#\g<1>', result, flags=flags)
result = re.sub(r'^__SEMICOLON\d+__ =(.*)$', ';\g<1>', result, flags=flags)
result = re.sub(r'\n__SECTION\d+__ =(.*)$', '\g<1>', result, flags=flags)
result = re.sub(r'^__BLANK\d+__ =$', '', result, flags=flags)
return result


class Proxy(collections.Mapping):
def __init__(self, data):
self._data = data
Expand Down
159 changes: 159 additions & 0 deletions tests/config/config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,162 @@ def test_config_single_schema_config_error(self):
self.assertEqual({'foo': {'bar': 'bad'}}, errors)

# 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 PreProcessorTest(unittest.TestCase):
maxDiff = None # Show entire diff.

def test_empty_config(self):
result = config._preprocess('')
self.assertEqual(result, '[__COMMENTS__]')

def test_plain_section(self):
result = config._preprocess('[section]\nfoo = bar')
self.assertEqual(result, '[__COMMENTS__]\n'
'[section]\n'
'foo = bar')

def test_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_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_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_no_inline_hash_comment(self):
result = config._preprocess('[section]\nfoo = bar # baz')
self.assertEqual(result, '[__COMMENTS__]\n'
'[section]\n'
'foo = bar # baz')

def test_section_extra_text(self):
result = config._preprocess('[section] foobar')
self.assertEqual(result, '[__COMMENTS__]\n'
'[section]\n'
'__SECTION0__ = foobar')

def test_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_conversion(self):
"""Tests all of the above cases at once."""
result = config._preprocess(INPUT_CONFIG)
self.assertEqual(result, PROCESSED_CONFIG)


class PostProcessorTest(unittest.TestCase):
maxDiff = None # Show entire diff.

def test_empty_config(self):
result = config._postprocess('[__COMMENTS__]')
self.assertEqual(result, '')

def test_plain_section(self):
result = config._postprocess('[__COMMENTS__]\n'
'[section]\n'
'foo = bar')
self.assertEqual(result, '[section]\nfoo = bar')

def test_initial_comments(self):
result = config._postprocess('[__COMMENTS__]\n'
'__SEMICOLON0__ = foobar')
self.assertEqual(result, '; foobar')

result = config._postprocess('[__COMMENTS__]\n'
'__HASH0__ = foobar')
self.assertEqual(result, '# foobar')

result = config._postprocess('[__COMMENTS__]\n'
'__SEMICOLON0__ = foo\n'
'__HASH1__ = bar')
self.assertEqual(result, '; foo\n# bar')

def test_initial_comment_inline_handling(self):
result = config._postprocess('[__COMMENTS__]\n'
'__SEMICOLON0__ = foo\n'
'__INLINE1__ = bar\n'
'__INLINE2__ = baz')
self.assertEqual(result, '; foo ; bar ; baz')

def test_inline_semicolon_comment(self):
result = config._postprocess('[__COMMENTS__]\n'
'[section]\n'
'foo = bar\n'
'__INLINE0__ = baz')
self.assertEqual(result, '[section]\nfoo = bar ; baz')

def test_no_inline_hash_comment(self):
result = config._preprocess('[section]\nfoo = bar # baz')
self.assertEqual(result, '[__COMMENTS__]\n'
'[section]\n'
'foo = bar # baz')

def test_section_extra_text(self):
result = config._postprocess('[__COMMENTS__]\n'
'[section]\n'
'__SECTION0__ = foobar')
self.assertEqual(result, '[section] foobar')

def test_section_extra_text_inline_semicolon(self):
result = config._postprocess('[__COMMENTS__]\n'
'[section]\n'
'__SECTION0__ = foobar\n'
'__INLINE1__ = baz')
self.assertEqual(result, '[section] foobar ; baz')

def test_conversion(self):
result = config._postprocess(PROCESSED_CONFIG)
self.assertEqual(result, INPUT_CONFIG)