Skip to content

Commit

Permalink
Merge 0885015 into e79a91e
Browse files Browse the repository at this point in the history
  • Loading branch information
djhoese committed Sep 17, 2018
2 parents e79a91e + 0885015 commit 2de039d
Show file tree
Hide file tree
Showing 5 changed files with 100 additions and 17 deletions.
5 changes: 2 additions & 3 deletions doc/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@
contain the root `toctree` directive.
.. meta::
description: Trollsift project, modules for formatting, parsing and filtering satellite granule file names
keywords: Python, pytroll, format, parse, filter, string
:description: Trollsift project, modules for formatting, parsing and filtering satellite granule file names
:keywords: Python, pytroll, format, parse, filter, string

Welcome to the trollsift documentation!
=========================================
Expand All @@ -26,7 +26,6 @@ Contents

installation
usage
examples
api

Indices and tables
Expand Down
14 changes: 8 additions & 6 deletions doc/source/usage.rst
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@

.. .. sectnum::
.. :depth: 4
.. :start: 2
.. :suffix: .
.. _string-format: https://docs.python.org/2/library/string.html#format-string-syntax

Usage
Expand Down Expand Up @@ -44,6 +38,14 @@ a new file name,
>>> p.compose(data)
'/somedir/otherdir/hrpt_noaa16_20120101_0101_69022.l1b'

In addition to python's builtin string formatting functionality trollsift also
provides extra conversion options such as making all characters lowercase:

>>> my_parser = Parser("{platform_name:l}")
>>> my_parser.compose({'platform_name': 'NPP'})
'npp'

For all of the options see :class:`~trollsift.parser.StringFormatter`.

standalone parse and compose
+++++++++++++++++++++++++++++++++++++++++
Expand Down
5 changes: 4 additions & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,7 @@ requires=python python-six
release=1

[bdist_wheel]
universal=1
universal=1

[flake8]
max-line-length = 120
64 changes: 57 additions & 7 deletions trollsift/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
# along with this program. If not, see <http://www.gnu.org/licenses/>.


'''Parser class
'''
"""Parser class
"""

import re
import datetime as dt
Expand All @@ -33,9 +33,8 @@


class Parser(object):

'''Parser class
'''
"""Parser class
"""

def __init__(self, fmt):
self.fmt = fmt
Expand All @@ -55,6 +54,8 @@ def compose(self, keyvals):
'''
return compose(self.fmt, keyvals)

format = compose

def globify(self, keyvals=None):
'''Generate a string useable with glob.glob() from format string
*fmt* and *keyvals* dictionary.
Expand Down Expand Up @@ -88,6 +89,55 @@ def is_one2one(self):
return is_one2one(self.fmt)


class StringFormatter(string.Formatter):
"""Custom string formatter class for basic strings.
This formatter adds a few special conversions for assisting with common
trollsift situations like making a parameter lowercase or removing
hyphens. The added conversions are listed below and can be used in a
format string by prefixing them with an `!` like so:
>>> fstr = "{!u}_{!l}"
>>> formatter = StringFormatter()
>>> formatter.format(fstr, "to_upper", "To_LowerCase")
"TO_UPPER_to_lowercase"
- c: Make capitalized version of string (first character upper case, all lowercase after that) by executing the
parameter's `.capitalize()` method.
- h: A combination of 'R' and 'l'.
- H: A combination of 'R' and 'u'.
- l: Make all characters lowercase by executing the parameter's `.lower()` method.
- R: Remove all separators from the parameter including '-', '_', ' ', and ':'.
- t: Title case the string by executing the parameter's `.title()` method.
- u: Make all characters uppercase by executing the parameter's `.upper()` method.
"""
CONV_FUNCS = {
'c': 'capitalize',
'h': 'lower',
'H': 'upper',
'l': 'lower',
't': 'title',
'u': 'upper'
}

def convert_field(self, value, conversion):
"""Apply conversions mentioned above."""
func = self.CONV_FUNCS.get(conversion)
if func is not None:
value = getattr(value, func)()
elif conversion not in ['R']:
# default conversion ('r', 's')
return super(StringFormatter, self).convert_field(value, conversion)

if conversion in ['h', 'H', 'R']:
value = value.replace('-', '').replace('_', '').replace(':', '').replace(' ', '')
return value


formatter = StringFormatter()


def _extract_parsedef(fmt):
'''Retrieve parse definition from the format string *fmt*.
'''
Expand Down Expand Up @@ -266,7 +316,7 @@ def compose(fmt, keyvals):
with values with the corresponding keys in *keyvals* dictionary.
'''

return fmt.format(**keyvals)
return formatter.format(fmt, **keyvals)


DT_FMT = {
Expand Down Expand Up @@ -389,7 +439,7 @@ def is_one2one(fmt):
Note: This test only applies to sensible usage of the format string.
If string or numeric data is causes overflow, e.g.
if composing "abcd" into {3s}, one to one correspondence will always
be broken in such cases. This off course also applies to precision
be broken in such cases. This of course also applies to precision
losses when using datetime data.
"""
# look for some bad patterns
Expand Down
29 changes: 29 additions & 0 deletions trollsift/tests/unittests/test_parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -285,6 +285,35 @@ def test_is_one2one(self):
self.assertFalse(is_one2one(
"/somedir/{directory}/somedata_{platform:4s}_{time:%Y%d%m-%H%M}_{orbit:d}.l1b"))

def test_compose(self):
"""Test the compose method's custom conversion options."""
from trollsift import compose
key_vals = {'a': 'this Is A-Test b_test c test'}

new_str = compose("{a!c}", key_vals)
self.assertEqual(new_str, 'This is a-test b_test c test')
new_str = compose("{a!h}", key_vals)
self.assertEqual(new_str, 'thisisatestbtestctest')
new_str = compose("{a!H}", key_vals)
self.assertEqual(new_str, 'THISISATESTBTESTCTEST')
new_str = compose("{a!l}", key_vals)
self.assertEqual(new_str, 'this is a-test b_test c test')
new_str = compose("{a!R}", key_vals)
self.assertEqual(new_str, 'thisIsATestbtestctest')
new_str = compose("{a!t}", key_vals)
self.assertEqual(new_str, 'This Is A-Test B_Test C Test')
new_str = compose("{a!u}", key_vals)
self.assertEqual(new_str, 'THIS IS A-TEST B_TEST C TEST')
# builtin repr
new_str = compose("{a!r}", key_vals)
self.assertEqual(new_str, '\'this Is A-Test b_test c test\'')
# no formatting
new_str = compose("{a}", key_vals)
self.assertEqual(new_str, 'this Is A-Test b_test c test')
# bad formatter
self.assertRaises(ValueError, compose, "{a!X}", key_vals)
self.assertEqual(new_str, 'this Is A-Test b_test c test')

def assertDictEqual(self, a, b):
for key in a:
self.assertTrue(key in b)
Expand Down

0 comments on commit 2de039d

Please sign in to comment.