Last active
August 29, 2015 14:15
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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