Skip to content

Commit

Permalink
Merge pull request #997 from mrksr/options_for_multiple_paths
Browse files Browse the repository at this point in the history
Options for multiple paths
  • Loading branch information
jlstevens authored Dec 1, 2016
2 parents fd6f388 + e8764d2 commit 69406c5
Show file tree
Hide file tree
Showing 2 changed files with 159 additions and 11 deletions.
80 changes: 69 additions & 11 deletions holoviews/ipython/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -252,6 +252,59 @@ def process_normalization(cls, parse_group):
framewise=framewise)


@classmethod
def _group_paths_without_options(cls, line_parse_result):
"""
Given a parsed options specification as a list of groups, combine
groups without options with the first subsequent group which has
options.
A line of the form
'A B C [opts] D E [opts_2]'
results in
[({A, B, C}, [opts]), ({D, E}, [opts_2])]
"""
active_pathspecs = set()
for group in line_parse_result:
active_pathspecs.add(group['pathspec'])

has_options = (
'norm_options' in group or
'plot_options' in group or
'style_options' in group
)
if has_options:
yield active_pathspecs, group
active_pathspecs = set()

if active_pathspecs:
yield active_pathspecs, {}


@classmethod
def _merge_options(cls, old_opts, new_opts):
"""
Update the old_opts option dictionary with the options defined in
new_opts. Instead of a shallow update as would be performed by calling
old_opts.update(new_opts), this updates the dictionaries of all option
types separately.
Given two dictionaries
old_opts = {'a': {'x': 'old', 'y': 'old'}}
and
new_opts = {'a': {'y': 'new', 'z': 'new'}, 'b': {'k': 'new'}}
this returns a dictionary
{'a': {'x': 'old', 'y': 'new', 'z': 'new'}, 'b': {'k': 'new'}}
"""
merged = dict(old_opts)

for option_type, options in new_opts.items():
if option_type not in merged:
merged[option_type] = {}

merged[option_type].update(options)

return merged


@classmethod
def parse(cls, line, ns={}):
Expand All @@ -268,30 +321,35 @@ def parse(cls, line, ns={}):
if (processed.strip() != line.strip()):
raise SyntaxError("Failed to parse remainder of string: %r" % line[e:])

grouped_paths = cls._group_paths_without_options(cls.opts_spec.parseString(line))
parse = {}
for group in cls.opts_spec.parseString(line):
for pathspecs, group in grouped_paths:
options = {}

normalization = cls.process_normalization(group)
if normalization is not None:
options['norm'] = Options(**normalization)
options['norm'] = normalization

if 'plot_options' in group:
plotopts = group['plot_options'][0]
opts = cls.todict(plotopts, 'brackets', ns=ns)
options['plot'] = Options(**{cls.aliases.get(k,k):v for k,v in opts.items()})
options['plot'] = {cls.aliases.get(k,k):v for k,v in opts.items()}

if 'style_options' in group:
styleopts = group['style_options'][0]
opts = cls.todict(styleopts, 'parens', ns=ns)
options['style'] = Options(**{cls.aliases.get(k,k):v for k,v in opts.items()})

if group['pathspec'] in parse:
# Update in case same pathspec accidentally repeated by the user.
parse[group['pathspec']].update(options)
else:
parse[group['pathspec']] = options
return parse
options['style'] = {cls.aliases.get(k,k):v for k,v in opts.items()}

for pathspec in pathspecs:
parse[pathspec] = cls._merge_options(parse.get(pathspec, {}), options)

return {
path: {
option_type: Options(**option_pairs)
for option_type, option_pairs in options.items()
}
for path, options in parse.items()
}



Expand Down
90 changes: 90 additions & 0 deletions tests/testparsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,32 @@ def test_plot_opts_nested_brackets(self):
expected = {'Curve': {'plot': Options(title_format='A, B')}}
self.assertEqual(OptsSpec.parse(line), expected)

def test_plot_opts_multiple_paths(self):
line = "Image Curve [fig_inches=(3, 3) title_format='foo bar']"
expected = {'Image':
{'plot':
Options(title_format='foo bar', fig_inches=(3, 3))},
'Curve':
{'plot':
Options(title_format='foo bar', fig_inches=(3, 3))}}
self.assertEqual(OptsSpec.parse(line), expected)

def test_plot_opts_multiple_paths_2(self):
line = "Image Curve Layout Overlay[fig_inches=(3, 3) title_format='foo bar']"
expected = {'Image':
{'plot':
Options(title_format='foo bar', fig_inches=(3, 3))},
'Curve':
{'plot':
Options(title_format='foo bar', fig_inches=(3, 3))},
'Layout':
{'plot':
Options(title_format='foo bar', fig_inches=(3, 3))},
'Overlay':
{'plot':
Options(title_format='foo bar', fig_inches=(3, 3))}}
self.assertEqual(OptsSpec.parse(line), expected)


class OptsSpecStyleOptionsTests(ComparisonTestCase):

Expand Down Expand Up @@ -136,6 +162,16 @@ def test_style_opts_cycle_list(self):
expected = {'Curve': {'style': Options(color=Cycle(values=['r', 'g', 'b']))}}
self.assertEqual(OptsSpec.parse(line, {'Cycle': Cycle}), expected)

def test_style_opts_multiple_paths(self):
line = "Image Curve (color='beautiful')"
expected = {'Image':
{'style':
Options(color='beautiful')},
'Curve':
{'style':
Options(color='beautiful')}}
self.assertEqual(OptsSpec.parse(line), expected)



class OptsNormPlotOptionsTests(ComparisonTestCase):
Expand Down Expand Up @@ -167,6 +203,16 @@ def test_norm_opts_simple_explicit_2(self):
{'norm': Options(axiswise=True, framewise=True)}}
self.assertEqual(OptsSpec.parse(line), expected)

def test_norm_opts_multiple_paths(self):
line = "Image Curve {+axiswise +framewise}"
expected = {'Image':
{'norm':
Options(axiswise=True, framewise=True)},
'Curve':
{'norm':
Options(axiswise=True, framewise=True)}}
self.assertEqual(OptsSpec.parse(line), expected)


class OptsSpecCombinedOptionsTests(ComparisonTestCase):

Expand Down Expand Up @@ -197,3 +243,47 @@ def test_combined_two_types_2(self):
'style': Options(string='foo'),
'plot': Options(foo='bar baz')}}
self.assertEqual(OptsSpec.parse(line), expected)

def test_combined_multiple_paths(self):
line = "Image Curve {+framewise} [fig_inches=(3, 3) title_format='foo bar'] (c='b') Layout [string='foo'] Overlay"
expected = {'Image':
{'norm':
Options(framewise=True, axiswise=False),
'plot':
Options(title_format='foo bar', fig_inches=(3, 3)),
'style':
Options(c='b')},
'Curve':
{'norm':
Options(framewise=True, axiswise=False),
'plot':
Options(title_format='foo bar', fig_inches=(3, 3)),
'style':
Options(c='b')},
'Layout':
{'plot':
Options(string='foo')},
'Overlay':
{}}
self.assertEqual(OptsSpec.parse(line), expected)

def test_combined_multiple_paths_merge(self):
line = "Image Curve [fig_inches=(3, 3)] (c='b') Image (s=3)"
expected = {'Image':
{'plot':
Options(fig_inches=(3, 3)),
'style':
Options(c='b', s=3)},
'Curve':
{'plot':
Options(fig_inches=(3, 3)),
'style':
Options(c='b')}}
self.assertEqual(OptsSpec.parse(line), expected)

def test_combined_multiple_paths_merge_precedence(self):
line = "Image (s=0, c='b') Image (s=3)"
expected = {'Image':
{'style':
Options(c='b', s=3)}}
self.assertEqual(OptsSpec.parse(line), expected)

0 comments on commit 69406c5

Please sign in to comment.