Skip to content

Instantly share code, notes, and snippets.

@danbradham
Last active August 29, 2015 14:15
Show Gist options
  • Save danbradham/e036c6f35260cc3ba4ad to your computer and use it in GitHub Desktop.
Save danbradham/e036c6f35260cc3ba4ad to your computer and use it in GitHub Desktop.
String formatters for converting standard pythong string templates to regex and glob patterns.
#!/usr/bin/env python
# -*- coding: utf-8 -*-
'''
template formatters
===================
``string.Formatter``s for converting standard pythong string templates to regex
and glob patterns. ``RegexFormatter`` formats to a regex pattern with named
groups perfect for use with ``re.search``. ``GlobFormatter`` formats to a glob
pattern that can be used with ``glob.glob``, ``fnmatch.fnmatch`` and
``fnmatch.filter``. ``format_regex`` and ``format_glob`` are instances of
``RegexFormatter`` and ``GlobFormatter`` respectively. Use these instead of
instancing them yourself.
::
template = 'assets/{asset.name}/{stage}/'
regex_pattern = format_regex(template)
glob_pattern = format_glob(template)
assert regex_pattern == 'assets/(?P<asset_name>.*?)/(?P<stage>.*?)/'
assert glob_pattern == 'assets/*/*/'
'''
import string
class BaseFormatter(string.Formatter):
'''BaseFormatter calls ``format`` when called directly. Maintains a cache
of all previously formatted templates. BaseFormatter uses the mechanics of
string formatting to find and replace all format fields with string values
returned from ``get_field``. You can think of this like a string template
converter.'''
def __init__(self):
self.cache = {}
def __call__(self, template):
return self.format(template)
def format(self, template):
'''Extends format to support caching.'''
try:
return self.cache[template]
except KeyError:
formatted = super(BaseFormatter, self).vformat(template, [], {})
self.cache[template] = formatted
return formatted
def format_field(self, value, format_spec):
'''Ignore all format_specs. Necessary to make sure get_field receives
the original field_name.'''
return value
class RegexFormatter(BaseFormatter):
'''Substitues standard Python string template fields with regex named
groups.
.. Note:: Any dots used for attribute access are converted to underscores
'''
def format(self, template):
self.groups = set()
return super(RegexFormatter, self).format(template)
def get_field(self, field_name, args, kwargs):
field_name = field_name.split('[')[0]
if field_name in self.groups:
return '(?:.*?)', field_name
self.groups.add(field_name)
return '(?P<{}>.*?)'.format(field_name.replace('.', '_')), field_name
class GlobFormatter(BaseFormatter):
'''Substitues standard Python string template fields with *.'''
def get_field(self, field_name, args, kwargs):
return '*', field_name
format_regex = RegexFormatter()
format_glob = GlobFormatter()
######################
# Test it all out... #
######################
template = ('assets/{asset.type}/{asset.name}/{stage.name}/maya/work/'
'{stage.short}_{asset.name}.v{version:0>3d}.mb')
test_path = 'assets/character/asset_a/model/maya/work/mdl_asset_a.v002.mb'
class TestRegexFormatter(unittest.TestCase):
def test_format(self):
'''Ensure Regexer is returning the correct string'''
expected = ('assets/(?P<asset_type>.*?)/(?P<asset_name>.*?)/'
'(?P<stage_name>.*?)/maya/work/(?P<stage_short>.*?)'
'_(?:.*?).v(?P<version>.*?).mb')
assert format_regex(template) == expected
def test_regex(self):
'''Ensure Regexer formatted string is a valid regex pattern'''
expected_data = {
'asset_name': 'asset_a',
'asset_type': 'character',
'stage_name': 'model',
'stage_short': 'mdl',
'version': '002'
}
pattern = re.compile(format_regex(template))
data = pattern.search(test_path).groupdict()
assert data == expected_data
class TestGlobFormatter(unittest.TestCase):
def test_format(self):
'''Ensure globber is returning the expected string'''
expected = 'assets/*/*/*/maya/work/*_*.v*.mb'
formatted = format_glob(template)
assert formatted == expected
def test_fnmatch(self):
'''Ensure Globber formatted template works with fnmatch'''
pattern = format_glob(template)
assert fnmatch(test_path, pattern)
if __name__ == '__main__':
unittest.main(verbosity=2)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment