Skip to content

Commit

Permalink
Eval list- and dict-typed option values, instead of JSON-parsing them.
Browse files Browse the repository at this point in the history
This is to support list concatenation syntax in config files, e.g.,

dev_opts: %(basic_opts)s + %(big_mem_opts)s + ['-ldebug']

This is a legitimate use case that we actually have, but that I overlooked
when I made the JSON requirement in 8c3b5b3.

Testing Done:
Ran options unit tests.

CI baking.

Reviewed at https://rbcommons.com/s/twitter/r/1443/
  • Loading branch information
benjyw authored and Benjy committed Dec 9, 2014
1 parent 217a051 commit 676c0e8
Show file tree
Hide file tree
Showing 2 changed files with 25 additions and 24 deletions.
38 changes: 17 additions & 21 deletions src/python/pants/option/custom_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
from __future__ import (nested_scopes, generators, division, absolute_import, with_statement,
print_function, unicode_literals)

import json

from pants.option.errors import ParseError


Expand All @@ -22,30 +20,28 @@ def _parse_error(s, msg):
def dict_type(s):
"""An option of type 'dict'.
The value (on the command-line, in an env var or in the config file) must be a JSON object.
The value (on the command-line, in an env var or in the config file) must be eval'able to a dict.
"""
if isinstance(s, dict):
return s
try:
ret = json.loads(s)
except ValueError as e:
raise _parse_error(s, e.message)
if not isinstance(ret, dict):
raise _parse_error(s, 'Value is not dict')
return ret
return _convert(s, (dict,))


def list_type(s):
"""An option of type 'list'.
The value (on the command-line, in an env var or in the config file) must be a JSON list.
The value (on the command-line, in an env var or in the config file) must be eval'able to a
list or tuple.
"""
if isinstance(s, (list, tuple)):
return s
return _convert(s, (list, tuple))


def _convert(val, acceptable_types):
"""Ensure that val is one of the acceptable types, converting it if needed."""
if isinstance(val, acceptable_types):
return val
try:
ret = json.loads(s)
except ValueError as e:
raise _parse_error(s, e.message)
if not isinstance(ret, list):
raise _parse_error(s, 'Value is not list')
return ret
parsed_value = eval(val, {}, {})
except Exception as e:
raise _parse_error(val, 'Value cannot be evaluated: {0}'.format(e.message))
if not isinstance(parsed_value, acceptable_types):
raise _parse_error(val, 'Value is not of the acceptable types: {0}'.format(acceptable_types))
return parsed_value
11 changes: 8 additions & 3 deletions tests/python/pants_test/option/test_custom_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class CustomTypesTest(unittest.TestCase):
def _do_test(self, expected_val, s):
if isinstance(expected_val, dict):
val = dict_type(s)
elif isinstance(expected_val, list):
elif isinstance(expected_val, (list, tuple)):
val = list_type(s)
else:
raise Exception('Expected value {0} is of unsupported type: {1}'.format(expected_val,
Expand All @@ -32,25 +32,30 @@ def _do_test_list_error(self, s):
def test_dict(self):
self._do_test({}, '{}')
self._do_test({ 'a': 'b' }, '{ "a": "b" }')
self._do_test({ 'a': 'b' }, "{ 'a': 'b' }")
self._do_test({ 'a': [1, 2, 3] }, '{ "a": [1, 2, 3] }')
self._do_test({ 'a': [1, 2, 3, 4] }, '{ "a": [1, 2] + [3, 4] }')
self._do_test({}, {})
self._do_test({ 'a': 'b' }, { 'a': 'b' })
self._do_test({ 'a': [1, 2, 3] }, { 'a': [1, 2, 3] })
self._do_test_dict_error('[]')
self._do_test_dict_error('[1, 2, 3]')
self._do_test_dict_error('1')
self._do_test_dict_error('"a"')
self._do_test_dict_error('null')

def test_list(self):
self._do_test([], '[]')
self._do_test([1, 2, 3], '[1, 2, 3]')
self._do_test((1, 2, 3), '1,2,3')
self._do_test([1, 2, 3, 4], '[1, 2] + [3, 4]')
self._do_test((1, 2, 3, 4), '(1, 2) + (3, 4)')
self._do_test(['a', 'b', 'c'], '["a", "b", "c"]')
self._do_test(['a', 'b', 'c'], "['a', 'b', 'c']")
self._do_test([], [])
self._do_test([1, 2, 3], [1, 2, 3])
self._do_test((1, 2, 3), (1, 2, 3))
self._do_test(['a', 'b', 'c'], ['a', 'b', 'c'])
self._do_test_list_error('{}')
self._do_test_list_error('{"a": "b"}')
self._do_test_list_error('1')
self._do_test_list_error('"a"')
self._do_test_list_error('null')

0 comments on commit 676c0e8

Please sign in to comment.